swift-rust 1.8.0 → 1.10.2
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/bin/dev-server.mjs +430 -110
- package/bin/runtime/navigator.js +12 -0
- package/dist/cache.d.ts +37 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +76 -0
- package/dist/cache.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/bin/dev-server.mjs
CHANGED
|
@@ -414,22 +414,22 @@ function detectIslandExports(src) {
|
|
|
414
414
|
return { hasDefault, named: [...named] };
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
//
|
|
417
|
+
// Build the source of an island wrapper module for a given client component
|
|
418
|
+
// file. Temp sibling files hold verbatim copies of client components, so the
|
|
418
419
|
// wrapper can import the *real* module under a distinct on-disk path (Bun
|
|
419
420
|
// dedupes modules by resolved path, so a query-string alias would collapse the
|
|
420
|
-
// real module into the wrapper).
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
function islandWrapperSource(abs) {
|
|
421
|
+
// real module into the wrapper). `temps` is per-build: a shared global list
|
|
422
|
+
// let concurrent builds delete each other's temp files mid-build, which
|
|
423
|
+
// surfaced as transient module-not-found errors on save.
|
|
424
|
+
function islandWrapperSource(abs, temps) {
|
|
425
425
|
const src = readFileSync(abs, "utf8");
|
|
426
426
|
const { hasDefault, named } = detectIslandExports(src);
|
|
427
427
|
// Write the real component to a unique sibling so imports resolve identically.
|
|
428
428
|
const rawName = `.__sr_raw_${process.pid}_${Math.random().toString(36).slice(2)}__${basename(abs)}`;
|
|
429
429
|
const rawPath = join(dirname(abs), rawName);
|
|
430
430
|
writeFileSync(rawPath, src);
|
|
431
|
-
|
|
432
|
-
const rawSpec = JSON.stringify(
|
|
431
|
+
temps.push(rawPath);
|
|
432
|
+
const rawSpec = JSON.stringify(rawPath);
|
|
433
433
|
let out = `import { createElement as __h } from "react";
|
|
434
434
|
import * as __real from ${rawSpec};
|
|
435
435
|
const __src = ${JSON.stringify(abs)};
|
|
@@ -467,43 +467,152 @@ function __wrap(Comp, name){
|
|
|
467
467
|
return out;
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
+
// Which exported names of a 'use cache' module are *async functions* (the only
|
|
471
|
+
// ones safe to wrap with cache(), since cache() always returns a Promise). We
|
|
472
|
+
// pass everything else through untouched.
|
|
473
|
+
function detectAsyncExports(src) {
|
|
474
|
+
const code = src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1");
|
|
475
|
+
const asyncNamed = new Set();
|
|
476
|
+
const allNamed = new Set();
|
|
477
|
+
let defaultAsync = false;
|
|
478
|
+
let hasDefault = false;
|
|
479
|
+
if (/\bexport\s+default\b/.test(code)) {
|
|
480
|
+
hasDefault = true;
|
|
481
|
+
if (/\bexport\s+default\s+async\b/.test(code)) defaultAsync = true;
|
|
482
|
+
}
|
|
483
|
+
for (const m of code.matchAll(/\bexport\s+(async\s+)?function\s+([A-Za-z_$][\w$]*)/g)) {
|
|
484
|
+
allNamed.add(m[2]);
|
|
485
|
+
if (m[1]) asyncNamed.add(m[2]);
|
|
486
|
+
}
|
|
487
|
+
for (const m of code.matchAll(/\bexport\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*(async\b)?/g)) {
|
|
488
|
+
allNamed.add(m[1]);
|
|
489
|
+
if (m[2]) asyncNamed.add(m[1]);
|
|
490
|
+
}
|
|
491
|
+
for (const m of code.matchAll(/\bexport\s*\{([^}]*)\}/g)) {
|
|
492
|
+
for (const part of m[1].split(",")) {
|
|
493
|
+
const name = part.trim().split(/\s+as\s+/).pop()?.trim();
|
|
494
|
+
if (name && name !== "default") allNamed.add(name);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return { asyncNamed: [...asyncNamed], allNamed: [...allNamed], defaultAsync, hasDefault };
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Source of a wrapper module that memoizes a 'use cache' module's async exports.
|
|
501
|
+
function cacheWrapperSource(abs, temps) {
|
|
502
|
+
const src = readFileSync(abs, "utf8");
|
|
503
|
+
const { asyncNamed, allNamed, defaultAsync, hasDefault } = detectAsyncExports(src);
|
|
504
|
+
const rawName = `.__sr_raw_${process.pid}_${Math.random().toString(36).slice(2)}__${basename(abs)}`;
|
|
505
|
+
const rawPath = join(dirname(abs), rawName);
|
|
506
|
+
writeFileSync(rawPath, src);
|
|
507
|
+
temps.push(rawPath);
|
|
508
|
+
const tag = relative(cwd, abs);
|
|
509
|
+
let out = `import { cache as __cache } from "swift-rust/cache";
|
|
510
|
+
import * as __real from ${JSON.stringify(rawPath)};
|
|
511
|
+
const __opts = { tags: ${JSON.stringify([tag])} };
|
|
512
|
+
`;
|
|
513
|
+
if (hasDefault) {
|
|
514
|
+
out += defaultAsync
|
|
515
|
+
? `export default __cache(__real.default, __opts);\n`
|
|
516
|
+
: `export default __real.default;\n`;
|
|
517
|
+
}
|
|
518
|
+
for (const n of allNamed) {
|
|
519
|
+
out += asyncNamed.includes(n)
|
|
520
|
+
? `export const ${n} = __cache(__real[${JSON.stringify(n)}], __opts);\n`
|
|
521
|
+
: `export const ${n} = __real[${JSON.stringify(n)}];\n`;
|
|
522
|
+
}
|
|
523
|
+
return out;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Bun plugin: auto-memoize modules that declare a leading 'use cache' directive.
|
|
527
|
+
// Built per-build so each build owns its temp-file list (see islandWrapperSource).
|
|
528
|
+
function makeUseCachePlugin(temps) {
|
|
529
|
+
return {
|
|
530
|
+
name: "sr-use-cache",
|
|
531
|
+
setup(build) {
|
|
532
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
533
|
+
const p = args.path;
|
|
534
|
+
if (!(p.startsWith(".") || p.startsWith("/") || p.startsWith("@/"))) return undefined;
|
|
535
|
+
// Our own raw temp copy: resolve it explicitly into the file namespace.
|
|
536
|
+
// (Deferring to Bun's default resolver fails from a virtual namespace.)
|
|
537
|
+
if (/\.__sr_raw_/.test(p)) return { path: p, namespace: "file" };
|
|
538
|
+
if (args.importer && hasUseDirective(args.importer, "cache")) return undefined;
|
|
539
|
+
const abs = resolveIslandSpecifier(p, args.importer);
|
|
540
|
+
if (!abs || !hasUseDirective(abs, "cache")) return undefined;
|
|
541
|
+
return { path: abs, namespace: "sr-cache" };
|
|
542
|
+
});
|
|
543
|
+
build.onLoad({ filter: /.*/, namespace: "sr-cache" }, (args) => ({
|
|
544
|
+
contents: cacheWrapperSource(args.path, temps),
|
|
545
|
+
loader: "js",
|
|
546
|
+
resolveDir: dirname(args.path),
|
|
547
|
+
}));
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
470
552
|
// Bun plugin: wrap client components imported by *server* modules as islands.
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
build
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
553
|
+
function makeClientIslandPlugin(temps) {
|
|
554
|
+
return {
|
|
555
|
+
name: "sr-client-islands",
|
|
556
|
+
setup(build) {
|
|
557
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
558
|
+
const p = args.path;
|
|
559
|
+
if (!(p.startsWith(".") || p.startsWith("/") || p.startsWith("@/"))) return undefined;
|
|
560
|
+
// Never wrap our own temp raw copies, and don't wrap when the importer is
|
|
561
|
+
// itself a client module — nested client components belong to that
|
|
562
|
+
// island's bundle, not a new boundary.
|
|
563
|
+
if (/\.__sr_raw_/.test(p)) return { path: p, namespace: "file" };
|
|
564
|
+
if (args.importer && hasUseDirective(args.importer, "client")) return undefined;
|
|
565
|
+
const abs = resolveIslandSpecifier(p, args.importer);
|
|
566
|
+
if (!abs || !hasUseDirective(abs, "client")) return undefined;
|
|
567
|
+
return { path: abs, namespace: "sr-island" };
|
|
568
|
+
});
|
|
569
|
+
build.onLoad({ filter: /.*/, namespace: "sr-island" }, (args) => ({
|
|
570
|
+
contents: islandWrapperSource(args.path, temps),
|
|
571
|
+
loader: "js",
|
|
572
|
+
resolveDir: dirname(args.path),
|
|
573
|
+
}));
|
|
574
|
+
},
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// In-flight build dedup: concurrent requests for the same entry (every open tab
|
|
579
|
+
// reloads at once on an HMR broadcast) await one build instead of racing.
|
|
580
|
+
function withInflight(map, key, gen, factory) {
|
|
581
|
+
const cur = map.get(key);
|
|
582
|
+
if (cur && cur.gen === gen) return cur.promise;
|
|
583
|
+
const entry = { gen, promise: null };
|
|
584
|
+
entry.promise = (async () => {
|
|
585
|
+
try {
|
|
586
|
+
return await factory();
|
|
587
|
+
} finally {
|
|
588
|
+
if (map.get(key) === entry) map.delete(key);
|
|
589
|
+
}
|
|
590
|
+
})();
|
|
591
|
+
map.set(key, entry);
|
|
592
|
+
return entry.promise;
|
|
593
|
+
}
|
|
493
594
|
|
|
494
595
|
// Cache for per-component browser island bundles (src -> { gen, code }).
|
|
495
596
|
// Each bundle is self-mounting: it imports the client component, finds every
|
|
496
597
|
// marker on the page for this source, and hydrates it. React is bundled in, so a
|
|
497
598
|
// single <script type="module"> per source needs no import map or shared runtime.
|
|
498
599
|
const componentIslandCache = new Map();
|
|
600
|
+
const componentIslandInflight = new Map();
|
|
499
601
|
async function buildComponentIslandBundle(srcFile) {
|
|
500
602
|
if (typeof Bun === "undefined" || typeof Bun.build !== "function") {
|
|
501
603
|
throw new Error("client islands require the Bun runtime");
|
|
502
604
|
}
|
|
503
605
|
const cached = componentIslandCache.get(srcFile);
|
|
504
606
|
if (cached && cached.gen === buildGeneration) return cached.code;
|
|
505
|
-
|
|
506
|
-
|
|
607
|
+
return withInflight(componentIslandInflight, srcFile, buildGeneration, () =>
|
|
608
|
+
buildComponentIslandBundleNow(srcFile),
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
async function buildComponentIslandBundleNow(srcFile) {
|
|
612
|
+
const entryPath = join(
|
|
613
|
+
dirname(srcFile),
|
|
614
|
+
`.__sr_cisland_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2)}.js`,
|
|
615
|
+
);
|
|
507
616
|
const entry = `import { hydrateRoot, createRoot } from "react-dom/client";
|
|
508
617
|
import { createElement } from "react";
|
|
509
618
|
import * as __mod from ${JSON.stringify(srcFile)};
|
|
@@ -551,6 +660,7 @@ function collectIslandSources(html) {
|
|
|
551
660
|
}
|
|
552
661
|
|
|
553
662
|
const ssrBundleCache = new Map(); // file -> { gen, mod }
|
|
663
|
+
const ssrInflight = new Map(); // file -> { gen, promise }
|
|
554
664
|
|
|
555
665
|
async function loadModuleFresh(filePath) {
|
|
556
666
|
if (typeof Bun === "undefined" || typeof Bun.build !== "function") {
|
|
@@ -558,28 +668,32 @@ async function loadModuleFresh(filePath) {
|
|
|
558
668
|
}
|
|
559
669
|
const cached = ssrBundleCache.get(filePath);
|
|
560
670
|
if (cached && cached.gen === buildGeneration) return cached.mod;
|
|
671
|
+
return withInflight(ssrInflight, filePath, buildGeneration, () =>
|
|
672
|
+
buildSsrModule(filePath, buildGeneration),
|
|
673
|
+
);
|
|
674
|
+
}
|
|
561
675
|
|
|
562
|
-
|
|
676
|
+
async function buildSsrModule(filePath, gen) {
|
|
677
|
+
const temps = [];
|
|
563
678
|
let result;
|
|
564
679
|
try {
|
|
565
680
|
result = await Bun.build({
|
|
566
681
|
entrypoints: [filePath],
|
|
567
682
|
target: "bun",
|
|
568
|
-
plugins: [
|
|
683
|
+
plugins: [makeUseCachePlugin(temps), makeClientIslandPlugin(temps), externalizeDepsPlugin],
|
|
569
684
|
});
|
|
570
685
|
} finally {
|
|
571
|
-
for (const t of
|
|
572
|
-
islandRawTemps.length = 0;
|
|
686
|
+
for (const t of temps) { try { unlinkSync(t); } catch {} }
|
|
573
687
|
}
|
|
574
688
|
if (!result.success) {
|
|
575
689
|
throw new Error(result.logs.map((l) => l.message).join("\n"));
|
|
576
690
|
}
|
|
577
691
|
const code = await result.outputs[0].text();
|
|
578
|
-
const tmp = join(dirname(filePath), `.__sr_ssr_${
|
|
692
|
+
const tmp = join(dirname(filePath), `.__sr_ssr_${gen}_${Math.random().toString(36).slice(2)}.mjs`);
|
|
579
693
|
writeFileSync(tmp, code);
|
|
580
694
|
try {
|
|
581
695
|
const mod = await import(pathToFileURL(tmp).href);
|
|
582
|
-
ssrBundleCache.set(filePath, { gen
|
|
696
|
+
ssrBundleCache.set(filePath, { gen, mod });
|
|
583
697
|
return mod;
|
|
584
698
|
} finally {
|
|
585
699
|
try { unlinkSync(tmp); } catch {}
|
|
@@ -617,11 +731,15 @@ async function compileRoute(urlPath) {
|
|
|
617
731
|
}
|
|
618
732
|
|
|
619
733
|
try {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
734
|
+
// Generation-keyed bundles: a save bumps the generation (fresh build), an
|
|
735
|
+
// unchanged file is a cache hit. The old `?t=` bust-imports created new
|
|
736
|
+
// module instances on every request — they leaked (ESM registry entries
|
|
737
|
+
// are permanent) and, under Bun, didn't even pick up child-module edits.
|
|
738
|
+
await loadModuleFresh(route.file);
|
|
739
|
+
for (const layout of layouts) await loadModuleFresh(layout.file);
|
|
740
|
+
if (notFoundFile) await loadModuleFresh(notFoundFile);
|
|
741
|
+
if (errorFile) await loadModuleFresh(errorFile);
|
|
742
|
+
if (loadingFile) await loadModuleFresh(loadingFile);
|
|
625
743
|
} catch (err) {
|
|
626
744
|
compileTimings.set(urlPath, performance.now() - start);
|
|
627
745
|
return { ok: false, reason: "compile_error", error: err, pageFile: route.file, layoutFile: layouts[0]?.file, params: route.params, segments };
|
|
@@ -645,14 +763,33 @@ function findAppGlobalsCss() {
|
|
|
645
763
|
return null;
|
|
646
764
|
}
|
|
647
765
|
|
|
766
|
+
// A require() rooted at the user's project, so Node module resolution walks the
|
|
767
|
+
// real node_modules tree (project → monorepo root → …) and finds packages
|
|
768
|
+
// wherever the package manager hoisted them. Critical for production builds
|
|
769
|
+
// (e.g. Vercel), where hard-coded ../../node_modules guesses miss the plugin
|
|
770
|
+
// and Tailwind silently doesn't compile — shipping an unstyled site.
|
|
771
|
+
let _projectRequire;
|
|
772
|
+
function projectRequire() {
|
|
773
|
+
if (!_projectRequire) _projectRequire = createRequire(join(cwd, "package.json"));
|
|
774
|
+
return _projectRequire;
|
|
775
|
+
}
|
|
776
|
+
|
|
648
777
|
let postcssInstance = null;
|
|
649
778
|
async function getPostcss() {
|
|
650
779
|
if (postcssInstance !== null) return postcssInstance;
|
|
780
|
+
// 1) Resolve through the project's module graph.
|
|
781
|
+
try {
|
|
782
|
+
const entry = projectRequire().resolve("postcss");
|
|
783
|
+
const mod = await import(pathToFileURL(entry).href);
|
|
784
|
+
postcssInstance = { available: true, default: mod.default ?? mod, mod };
|
|
785
|
+
return postcssInstance;
|
|
786
|
+
} catch {}
|
|
787
|
+
// 2) Hard-coded fallbacks, then a bare import.
|
|
651
788
|
try {
|
|
652
789
|
const candidates = [
|
|
653
790
|
join(cwd, "node_modules", "postcss"),
|
|
654
|
-
join(cwd, "
|
|
655
|
-
join(cwd, "
|
|
791
|
+
join(cwd, "..", "..", "node_modules", "postcss"),
|
|
792
|
+
join(cwd, "..", "..", "..", "node_modules", "postcss"),
|
|
656
793
|
];
|
|
657
794
|
for (const candidate of candidates) {
|
|
658
795
|
if (existsSync(candidate)) {
|
|
@@ -671,9 +808,19 @@ async function getPostcss() {
|
|
|
671
808
|
}
|
|
672
809
|
|
|
673
810
|
async function loadPostcssPluginByName(name) {
|
|
811
|
+
// 1) Node resolution from the project root — finds the plugin at any hoist level.
|
|
812
|
+
try {
|
|
813
|
+
const entryPath = projectRequire().resolve(name);
|
|
814
|
+
const mod = await import(pathToFileURL(entryPath).href);
|
|
815
|
+
const result = mod.default ?? mod;
|
|
816
|
+
logLine([` ${paint("dim", "css plugin loaded:")} ${paint("cyan", name)} ${paint("dim", "(resolved)")}`], 1);
|
|
817
|
+
return result;
|
|
818
|
+
} catch {}
|
|
819
|
+
// 2) Fallback: scan a few likely node_modules locations.
|
|
674
820
|
const candidates = [
|
|
675
821
|
join(cwd, "node_modules", name),
|
|
676
822
|
join(cwd, "..", "..", "node_modules", name),
|
|
823
|
+
join(cwd, "..", "..", "..", "node_modules", name),
|
|
677
824
|
];
|
|
678
825
|
for (const candidate of candidates) {
|
|
679
826
|
const pkgJsonPath = join(candidate, "package.json");
|
|
@@ -696,7 +843,7 @@ async function loadPostcssPluginByName(name) {
|
|
|
696
843
|
}
|
|
697
844
|
}
|
|
698
845
|
}
|
|
699
|
-
logLine([` ${paint("dim", "css plugin not found:")} ${paint("yellow", name)}`], 1);
|
|
846
|
+
logLine([` ${paint("dim", "css plugin not found:")} ${paint("yellow", name)} ${paint("dim", "— is it in dependencies?")}`], 1);
|
|
700
847
|
return null;
|
|
701
848
|
}
|
|
702
849
|
|
|
@@ -890,13 +1037,13 @@ async function resolveMetadata(layoutFiles, pageFile, params, segments) {
|
|
|
890
1037
|
const metas = [];
|
|
891
1038
|
for (const layoutFile of layoutFiles || []) {
|
|
892
1039
|
try {
|
|
893
|
-
const mod = await
|
|
1040
|
+
const mod = await loadModuleFresh(layoutFile);
|
|
894
1041
|
if (mod.metadata) metas.push(mod.metadata);
|
|
895
1042
|
} catch {}
|
|
896
1043
|
}
|
|
897
1044
|
if (pageFile) {
|
|
898
1045
|
try {
|
|
899
|
-
const mod = await
|
|
1046
|
+
const mod = await loadModuleFresh(pageFile);
|
|
900
1047
|
if (mod.generateMetadata) {
|
|
901
1048
|
const m = await mod.generateMetadata({ params: params || {}, searchParams: {} });
|
|
902
1049
|
if (m) metas.push(m);
|
|
@@ -1072,6 +1219,7 @@ export function hasUseDirective(file, name) {
|
|
|
1072
1219
|
return false;
|
|
1073
1220
|
}
|
|
1074
1221
|
|
|
1222
|
+
const islandBundleInflight = new Map();
|
|
1075
1223
|
async function buildIslandBundle(pageFile) {
|
|
1076
1224
|
if (typeof Bun === "undefined" || typeof Bun.build !== "function") {
|
|
1077
1225
|
throw new Error("client islands require the Bun runtime");
|
|
@@ -1079,9 +1227,16 @@ async function buildIslandBundle(pageFile) {
|
|
|
1079
1227
|
assertNoServerComponentInClientPage(pageFile);
|
|
1080
1228
|
const cached = islandBundleCache.get(pageFile);
|
|
1081
1229
|
if (cached && cached.gen === buildGeneration) return cached.code;
|
|
1082
|
-
|
|
1230
|
+
return withInflight(islandBundleInflight, pageFile, buildGeneration, () =>
|
|
1231
|
+
buildIslandBundleNow(pageFile),
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
async function buildIslandBundleNow(pageFile) {
|
|
1083
1235
|
// Temp hydration entry, written next to the page so module resolution works.
|
|
1084
|
-
const entryPath = join(
|
|
1236
|
+
const entryPath = join(
|
|
1237
|
+
dirname(pageFile),
|
|
1238
|
+
`.__sr_island_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2)}.js`,
|
|
1239
|
+
);
|
|
1085
1240
|
const entry = `import { createRoot } from "react-dom/client";
|
|
1086
1241
|
import { createElement } from "react";
|
|
1087
1242
|
import Page from ${JSON.stringify(pageFile)};
|
|
@@ -1796,7 +1951,7 @@ async function renderRoute(urlPath, req) {
|
|
|
1796
1951
|
if (errorFile) {
|
|
1797
1952
|
try {
|
|
1798
1953
|
const React = await import("react");
|
|
1799
|
-
const errorMod = await
|
|
1954
|
+
const errorMod = await loadModuleFresh(errorFile);
|
|
1800
1955
|
const ErrorBoundary = errorMod.default ?? errorMod.ErrorBoundary ?? errorMod.error;
|
|
1801
1956
|
if (ErrorBoundary) {
|
|
1802
1957
|
const html = await renderToStringCompat(React.createElement(ErrorBoundary, { error: err }));
|
|
@@ -1809,7 +1964,7 @@ async function renderRoute(urlPath, req) {
|
|
|
1809
1964
|
if (recoveryFile) {
|
|
1810
1965
|
try {
|
|
1811
1966
|
const React = await import("react");
|
|
1812
|
-
const mod = await
|
|
1967
|
+
const mod = await loadModuleFresh(recoveryFile);
|
|
1813
1968
|
const Recovery = mod.default ?? mod.ErrorRecovery;
|
|
1814
1969
|
if (Recovery) {
|
|
1815
1970
|
const html = await renderToStringCompat(
|
|
@@ -1826,7 +1981,7 @@ async function renderRoute(urlPath, req) {
|
|
|
1826
1981
|
if (globalErrorFile) {
|
|
1827
1982
|
try {
|
|
1828
1983
|
const React = await import("react");
|
|
1829
|
-
const mod = await
|
|
1984
|
+
const mod = await loadModuleFresh(globalErrorFile);
|
|
1830
1985
|
const GlobalError = mod.default ?? mod.GlobalError;
|
|
1831
1986
|
if (GlobalError) {
|
|
1832
1987
|
const inner = await renderToStringCompat(
|
|
@@ -1914,14 +2069,14 @@ async function renderNotFound(segments) {
|
|
|
1914
2069
|
try {
|
|
1915
2070
|
const React = await import("react");
|
|
1916
2071
|
const layouts = findLayoutsFor(segments || []);
|
|
1917
|
-
const mod = await
|
|
2072
|
+
const mod = await loadModuleFresh(notFoundFile);
|
|
1918
2073
|
const NotFound = mod.default ?? mod.NotFound ?? mod.notFound;
|
|
1919
2074
|
if (!NotFound) {
|
|
1920
2075
|
return { status: 404, html: null, error: null, segments };
|
|
1921
2076
|
}
|
|
1922
2077
|
let tree = React.createElement(NotFound);
|
|
1923
2078
|
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
1924
|
-
const layoutMod = await
|
|
2079
|
+
const layoutMod = await loadModuleFresh(layouts[i].file);
|
|
1925
2080
|
const Layout = layoutMod.default ?? layoutMod.Layout ?? layoutMod.layout;
|
|
1926
2081
|
if (Layout) tree = React.createElement(Layout, null, tree);
|
|
1927
2082
|
}
|
|
@@ -2019,83 +2174,219 @@ function readNavigatorClient() {
|
|
|
2019
2174
|
}
|
|
2020
2175
|
}
|
|
2021
2176
|
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2177
|
+
const WATCH_IGNORE_DIRS = new Set([
|
|
2178
|
+
"node_modules",
|
|
2179
|
+
"dist",
|
|
2180
|
+
"build",
|
|
2181
|
+
"out",
|
|
2182
|
+
"coverage",
|
|
2183
|
+
"target",
|
|
2184
|
+
]);
|
|
2185
|
+
|
|
2186
|
+
// Ignore anything inside a dep/build dir, any dot-prefixed path segment (which
|
|
2187
|
+
// also covers our own .__sr_* build temps and dirs like .git/.turbo/.next),
|
|
2188
|
+
// and editor scratch suffixes.
|
|
2189
|
+
function isIgnoredWatchPath(full) {
|
|
2190
|
+
const rel = relative(cwd, full);
|
|
2191
|
+
if (!rel || rel.startsWith("..")) return true;
|
|
2192
|
+
const parts = rel.split(sep);
|
|
2193
|
+
for (const p of parts) {
|
|
2194
|
+
if (!p || p.startsWith(".")) return true;
|
|
2195
|
+
if (WATCH_IGNORE_DIRS.has(p)) return true;
|
|
2196
|
+
}
|
|
2197
|
+
const base = parts[parts.length - 1];
|
|
2027
2198
|
if (base.endsWith(".bak") || base.endsWith(".tmp") || base.endsWith("~")) return true;
|
|
2028
|
-
if (base === "node_modules") return true;
|
|
2029
2199
|
return false;
|
|
2030
2200
|
}
|
|
2031
2201
|
|
|
2202
|
+
const SYNTAX_CHECK_LOADERS = { ".ts": "ts", ".tsx": "tsx", ".js": "js", ".jsx": "jsx", ".mjs": "js" };
|
|
2203
|
+
|
|
2204
|
+
// Quick transpile of the changed sources before telling the browser to reload.
|
|
2205
|
+
// Editors save in stages (truncate+write, format-on-save), so the first FS
|
|
2206
|
+
// event often fires mid-write; compiling that state used to navigate every tab
|
|
2207
|
+
// into a dead 500 page. A failed check retries once after a beat (to skate
|
|
2208
|
+
// over partial writes); a persistent failure is a real syntax error.
|
|
2209
|
+
async function findSyntaxError(files) {
|
|
2210
|
+
if (typeof Bun === "undefined" || typeof Bun.Transpiler !== "function") return null;
|
|
2211
|
+
for (const file of files) {
|
|
2212
|
+
const loader = SYNTAX_CHECK_LOADERS[extname(file)];
|
|
2213
|
+
if (!loader) continue;
|
|
2214
|
+
for (let attempt = 0; ; attempt++) {
|
|
2215
|
+
let src;
|
|
2216
|
+
try {
|
|
2217
|
+
src = readFileSync(file, "utf8");
|
|
2218
|
+
} catch {
|
|
2219
|
+
break; // deleted — nothing to check
|
|
2220
|
+
}
|
|
2221
|
+
try {
|
|
2222
|
+
new Bun.Transpiler({ loader }).transformSync(src);
|
|
2223
|
+
break;
|
|
2224
|
+
} catch (err) {
|
|
2225
|
+
if (attempt >= 1) {
|
|
2226
|
+
const msg = err?.message || String(err);
|
|
2227
|
+
return `${relative(cwd, file)}: ${msg}`;
|
|
2228
|
+
}
|
|
2229
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
return null;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
function broadcastHmr(data) {
|
|
2237
|
+
const payload = JSON.stringify({ event: "change", data });
|
|
2238
|
+
for (const send of hmrClients) {
|
|
2239
|
+
try {
|
|
2240
|
+
send(payload);
|
|
2241
|
+
} catch {}
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2032
2245
|
function setupWatcher() {
|
|
2033
2246
|
if (!existsSync(APP_DIR)) return;
|
|
2034
2247
|
const watchers = new Map();
|
|
2248
|
+
let recursiveMode = false;
|
|
2035
2249
|
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2250
|
+
// ── Debounced batch flush ──────────────────────────────────────────────────
|
|
2251
|
+
// All events funnel into one pending set; a flush invalidates caches once,
|
|
2252
|
+
// syntax-checks the batch, and broadcasts a single reload (or error).
|
|
2253
|
+
const pendingFiles = new Set();
|
|
2254
|
+
let pendingWildcard = false; // event with no usable filename → invalidate broadly
|
|
2255
|
+
let flushTimer = null;
|
|
2256
|
+
let flushing = false;
|
|
2257
|
+
let lastFlushErrored = false;
|
|
2258
|
+
|
|
2259
|
+
function scheduleFlush() {
|
|
2260
|
+
if (flushTimer) clearTimeout(flushTimer);
|
|
2261
|
+
flushTimer = setTimeout(() => {
|
|
2262
|
+
flushTimer = null;
|
|
2263
|
+
void flush();
|
|
2264
|
+
}, 80);
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
function noteChange(full) {
|
|
2268
|
+
pendingFiles.add(full);
|
|
2269
|
+
scheduleFlush();
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
function noteWildcard() {
|
|
2273
|
+
pendingWildcard = true;
|
|
2274
|
+
scheduleFlush();
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
async function flush() {
|
|
2278
|
+
if (flushing) {
|
|
2279
|
+
scheduleFlush();
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
flushing = true;
|
|
2283
|
+
try {
|
|
2284
|
+
const files = [...pendingFiles];
|
|
2285
|
+
pendingFiles.clear();
|
|
2286
|
+
const wildcard = pendingWildcard;
|
|
2287
|
+
pendingWildcard = false;
|
|
2288
|
+
if (!files.length && !wildcard) return;
|
|
2289
|
+
|
|
2290
|
+
buildGeneration++;
|
|
2291
|
+
for (const full of files) {
|
|
2292
|
+
logEvent("change", full);
|
|
2293
|
+
bustCache(full);
|
|
2294
|
+
if (full.endsWith(`${sep}layout.tsx`) || full.endsWith(`${sep}layout.ts`)) {
|
|
2295
|
+
fontsScannedFromLayout = false;
|
|
2296
|
+
GOOGLE_FONT_FAMILIES.clear();
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
// Any source edit can introduce new utility classes (Tailwind scans the
|
|
2300
|
+
// source tree), so re-run the CSS pipeline on every flush — not only when
|
|
2301
|
+
// globals.css itself changes. mtime caching keeps subsequent requests hot.
|
|
2302
|
+
globalsCssCache = { file: null, mtime: 0, css: "" };
|
|
2303
|
+
if (wildcard) {
|
|
2304
|
+
fontsScannedFromLayout = false;
|
|
2305
|
+
GOOGLE_FONT_FAMILIES.clear();
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
const syntaxError = await findSyntaxError(files);
|
|
2309
|
+
if (syntaxError) {
|
|
2310
|
+
// Overlay on the live page instead of reloading into a dead 500 —
|
|
2311
|
+
// page state survives, and the next clean save reloads normally.
|
|
2312
|
+
lastFlushErrored = true;
|
|
2313
|
+
logLine([` ${paint("red", "✗")} ${paint("red", syntaxError.split("\n")[0])}`]);
|
|
2314
|
+
broadcastHmr({ type: "error", message: syntaxError });
|
|
2315
|
+
return;
|
|
2316
|
+
}
|
|
2317
|
+
if (lastFlushErrored) {
|
|
2318
|
+
lastFlushErrored = false;
|
|
2319
|
+
broadcastHmr({ type: "ok" });
|
|
2320
|
+
}
|
|
2321
|
+
broadcastHmr({ type: "reload", file: relative(cwd, files[0] ?? cwd), at: Date.now() });
|
|
2322
|
+
logHmr(files[0] ?? cwd);
|
|
2323
|
+
} finally {
|
|
2324
|
+
flushing = false;
|
|
2325
|
+
if (pendingFiles.size || pendingWildcard) scheduleFlush();
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
function handleEvent(full) {
|
|
2330
|
+
try {
|
|
2331
|
+
if (statSync(full).isDirectory()) {
|
|
2332
|
+
// New directory: in fallback mode start watching it. Files may have
|
|
2333
|
+
// been created inside it before the watcher attached, so invalidate
|
|
2334
|
+
// broadly instead of silently dropping the event (the old behavior —
|
|
2335
|
+
// one dropped event meant stale renders until the server restarted).
|
|
2336
|
+
if (!recursiveMode) walk(full);
|
|
2337
|
+
noteWildcard();
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
} catch {}
|
|
2341
|
+
noteChange(full);
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
function onWatchEvent(baseDir) {
|
|
2345
|
+
return (event, filename) => {
|
|
2346
|
+
if (!filename) {
|
|
2347
|
+
// macOS/FSEvents can coalesce events and drop the filename. Treat it
|
|
2348
|
+
// as "something changed" rather than ignoring it.
|
|
2349
|
+
noteWildcard();
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
const full = join(baseDir, filename.toString());
|
|
2353
|
+
if (isIgnoredWatchPath(full)) return;
|
|
2354
|
+
handleEvent(full);
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// Fallback: one watcher per directory (non-recursive platforms).
|
|
2049
2359
|
function walk(dir) {
|
|
2050
2360
|
if (watchers.has(dir)) return;
|
|
2361
|
+
// A probe child path tells us whether the directory itself is ignored
|
|
2362
|
+
// (cwd resolves to rel "x", which passes).
|
|
2363
|
+
if (isIgnoredWatchPath(join(dir, "x"))) return;
|
|
2051
2364
|
try {
|
|
2052
2365
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
2053
2366
|
for (const e of entries) {
|
|
2054
|
-
if (e.isDirectory() && !e.name.startsWith(".") && !
|
|
2367
|
+
if (e.isDirectory() && !e.name.startsWith(".") && !WATCH_IGNORE_DIRS.has(e.name)) {
|
|
2055
2368
|
walk(join(dir, e.name));
|
|
2056
2369
|
}
|
|
2057
2370
|
}
|
|
2058
|
-
|
|
2059
|
-
const w = fsWatch(dir, (event, filename) => {
|
|
2060
|
-
if (shouldIgnoreFile(filename)) return;
|
|
2061
|
-
const full = join(dir, filename.toString());
|
|
2062
|
-
if (debounce) clearTimeout(debounce);
|
|
2063
|
-
debounce = setTimeout(() => {
|
|
2064
|
-
try {
|
|
2065
|
-
const s = statSync(full);
|
|
2066
|
-
if (s.isDirectory()) {
|
|
2067
|
-
walk(full);
|
|
2068
|
-
return;
|
|
2069
|
-
}
|
|
2070
|
-
} catch {}
|
|
2071
|
-
logEvent("change", full);
|
|
2072
|
-
buildGeneration++;
|
|
2073
|
-
bustCache(full);
|
|
2074
|
-
if (full.includes(`${sep}globals.${"css"}`)) {
|
|
2075
|
-
globalsCssCache = { file: null, mtime: 0, css: "" };
|
|
2076
|
-
}
|
|
2077
|
-
if (full.endsWith(`${sep}layout.tsx`) || full.endsWith(`${sep}layout.ts`)) {
|
|
2078
|
-
fontsScannedFromLayout = false;
|
|
2079
|
-
GOOGLE_FONT_FAMILIES.clear();
|
|
2080
|
-
}
|
|
2081
|
-
const payload = { type: "reload", file: relative(cwd, full), at: Date.now() };
|
|
2082
|
-
for (const send of hmrClients) {
|
|
2083
|
-
try {
|
|
2084
|
-
send(JSON.stringify({ event: "change", data: payload }));
|
|
2085
|
-
} catch {}
|
|
2086
|
-
}
|
|
2087
|
-
logHmr(full);
|
|
2088
|
-
}, 30);
|
|
2089
|
-
});
|
|
2371
|
+
const w = fsWatch(dir, onWatchEvent(dir));
|
|
2090
2372
|
watchers.set(dir, w);
|
|
2091
2373
|
} catch (err) {
|
|
2092
2374
|
logLine([` ${paint("dim", "watch error:")} ${paint("red", err.message)}`], 1);
|
|
2093
2375
|
}
|
|
2094
2376
|
}
|
|
2377
|
+
|
|
2095
2378
|
// Watch the WHOLE project (minus dep/build dirs) so a change to *any* source
|
|
2096
2379
|
// file — pages, components, lib, hooks, content, config, wherever — triggers
|
|
2097
|
-
// a recompile + reload.
|
|
2098
|
-
|
|
2380
|
+
// a recompile + reload. Prefer a single recursive watcher (macOS, Windows,
|
|
2381
|
+
// Linux on Node 20+/Bun): hundreds of per-directory FSEvents watchers could
|
|
2382
|
+
// hit OS limits and die silently, leaving saves unnoticed until a restart.
|
|
2383
|
+
try {
|
|
2384
|
+
const w = fsWatch(cwd, { recursive: true }, onWatchEvent(cwd));
|
|
2385
|
+
watchers.set(cwd, w);
|
|
2386
|
+
recursiveMode = true;
|
|
2387
|
+
} catch {
|
|
2388
|
+
walk(cwd);
|
|
2389
|
+
}
|
|
2099
2390
|
}
|
|
2100
2391
|
|
|
2101
2392
|
const networkUrls = [];
|
|
@@ -2479,6 +2770,35 @@ async function handleFetch(req) {
|
|
|
2479
2770
|
return new Response("// island not found", { status: 404, headers: { "Content-Type": "application/javascript" } });
|
|
2480
2771
|
}
|
|
2481
2772
|
|
|
2773
|
+
// On-demand revalidation: POST /_swift-rust/revalidate { tag?, path? }
|
|
2774
|
+
// Purges the shared data cache (globalThis.__SR_CACHE__) by tag or path so
|
|
2775
|
+
// webhooks / mutations can invalidate without a redeploy.
|
|
2776
|
+
if (pathname === "/_swift-rust/revalidate") {
|
|
2777
|
+
if (req.method !== "POST") {
|
|
2778
|
+
return new Response(JSON.stringify({ error: "use POST" }), {
|
|
2779
|
+
status: 405,
|
|
2780
|
+
headers: { "Content-Type": "application/json" },
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
let body = {};
|
|
2784
|
+
try { body = await req.json(); } catch {}
|
|
2785
|
+
const tag = body.tag ?? url.searchParams.get("tag");
|
|
2786
|
+
const path = body.path ?? url.searchParams.get("path");
|
|
2787
|
+
const store = globalThis.__SR_CACHE__;
|
|
2788
|
+
let purged = 0;
|
|
2789
|
+
if (store?.store) {
|
|
2790
|
+
for (const [k, v] of store.store) {
|
|
2791
|
+
if ((tag && v.tags?.includes(tag)) || (path && k.includes(path))) {
|
|
2792
|
+
store.store.delete(k);
|
|
2793
|
+
purged++;
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
if (tag) store.purged?.add(`tag:${tag}`);
|
|
2797
|
+
if (path) store.purged?.add(`path:${path}`);
|
|
2798
|
+
}
|
|
2799
|
+
return Response.json({ revalidated: true, tag: tag ?? null, path: path ?? null, purged });
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2482
2802
|
if (pathname.startsWith("/_swift-rust/")) {
|
|
2483
2803
|
return new Response("Not found", { status: 404 });
|
|
2484
2804
|
}
|
|
@@ -2749,7 +3069,7 @@ async function handleApiRoute(req, segments, method, reqStart) {
|
|
|
2749
3069
|
}
|
|
2750
3070
|
const handlerName = method.toUpperCase();
|
|
2751
3071
|
try {
|
|
2752
|
-
const mod = await
|
|
3072
|
+
const mod = await loadModuleFresh(route.file);
|
|
2753
3073
|
const handler = mod[handlerName] || mod[method.toLowerCase()];
|
|
2754
3074
|
if (typeof handler !== "function") {
|
|
2755
3075
|
const total = performance.now() - reqStart;
|
package/bin/runtime/navigator.js
CHANGED
|
@@ -54,10 +54,22 @@
|
|
|
54
54
|
nav.prefetch = (url) => fetchDoc(new URL(url, location.href).href).catch(() => {});
|
|
55
55
|
|
|
56
56
|
// Scripts inserted via DOM cloning don't execute; clone them into fresh nodes.
|
|
57
|
+
// Classic scripts re-run on re-insertion, but ES module scripts are keyed by
|
|
58
|
+
// URL in the module map and won't re-evaluate if the same src is inserted
|
|
59
|
+
// again — which would leave the swapped-in island markup un-hydrated (stale
|
|
60
|
+
// active states, dead event handlers) until a full reload. So we cache-bust
|
|
61
|
+
// module src scripts with a per-navigation token to force re-execution.
|
|
62
|
+
let swapToken = 0;
|
|
57
63
|
function runScripts(root) {
|
|
64
|
+
swapToken++;
|
|
58
65
|
for (const old of root.querySelectorAll("script")) {
|
|
59
66
|
const s = document.createElement("script");
|
|
60
67
|
for (const att of old.attributes) s.setAttribute(att.name, att.value);
|
|
68
|
+
if (s.type === "module" && s.src) {
|
|
69
|
+
const u = new URL(s.src, location.href);
|
|
70
|
+
u.searchParams.set("__srnav", String(swapToken));
|
|
71
|
+
s.src = u.href;
|
|
72
|
+
}
|
|
61
73
|
s.textContent = old.textContent;
|
|
62
74
|
old.replaceWith(s);
|
|
63
75
|
}
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface CacheEntry<T = unknown> {
|
|
2
|
+
value: T;
|
|
3
|
+
/** epoch ms when the entry goes stale; 0 = never expires (until invalidated). */
|
|
4
|
+
expires: number;
|
|
5
|
+
tags: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface CacheOptions {
|
|
8
|
+
/** Tags for targeted invalidation via revalidateTag(). */
|
|
9
|
+
tags?: string[];
|
|
10
|
+
/** Seconds before the entry is considered stale. Omit/0 = cache until invalidated. */
|
|
11
|
+
revalidate?: number;
|
|
12
|
+
/** Custom key derivation from the call arguments (defaults to fn name + JSON args). */
|
|
13
|
+
key?: (...args: any[]) => string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Wrap an async function so its result is cached by argument key, with optional
|
|
17
|
+
* TTL and tags. Returns a function with the same signature.
|
|
18
|
+
*
|
|
19
|
+
* const getPosts = cache(fetchPosts, { tags: ["posts"], revalidate: 3600 });
|
|
20
|
+
* await getPosts(); // miss → runs fetchPosts, caches
|
|
21
|
+
* await getPosts(); // hit
|
|
22
|
+
* revalidateTag("posts"); // purge
|
|
23
|
+
*/
|
|
24
|
+
export declare function cache<A extends unknown[], R>(fn: (...args: A) => Promise<R> | R, options?: CacheOptions): (...args: A) => Promise<R>;
|
|
25
|
+
/** Invalidate every cached entry tagged with `tag`. */
|
|
26
|
+
export declare function revalidateTag(tag: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Invalidate cached entries whose key references `path`. (Best-effort by
|
|
29
|
+
* substring — useful for "everything derived from /blog/*".) Also records the
|
|
30
|
+
* path so the CDN/platform can purge the rendered route.
|
|
31
|
+
*/
|
|
32
|
+
export declare function revalidatePath(path: string): void;
|
|
33
|
+
/** Drain the set of purge signals recorded since the last call (for endpoints/webhooks). */
|
|
34
|
+
export declare function drainPurged(): string[];
|
|
35
|
+
/** Clear the entire data cache (mainly for tests / dev HMR). */
|
|
36
|
+
export declare function clearCache(): void;
|
|
37
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,OAAO;IACrC,KAAK,EAAE,CAAC,CAAC;IACT,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAWD,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uFAAuF;IAEvF,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC;CAClC;AAED;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,CAAC,SAAS,OAAO,EAAE,EAAE,CAAC,EAC1C,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAClC,OAAO,GAAE,YAAiB,GACzB,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAY5B;AAED,uDAAuD;AACvD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAK/C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAKjD;AAED,4FAA4F;AAC5F,wBAAgB,WAAW,IAAI,MAAM,EAAE,CAItC;AAED,gEAAgE;AAChE,wBAAgB,UAAU,IAAI,IAAI,CAEjC"}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Data cache + on-demand revalidation.
|
|
2
|
+
//
|
|
3
|
+
// A single process-wide store (kept on globalThis so the app's `swift-rust`
|
|
4
|
+
// import and the dev server share ONE instance regardless of how the module was
|
|
5
|
+
// resolved). Entries carry an expiry (TTL) and tags; `revalidateTag` /
|
|
6
|
+
// `revalidatePath` purge by tag or key substring.
|
|
7
|
+
//
|
|
8
|
+
// In dev and within a warm production function instance this is a real cache.
|
|
9
|
+
// Across instances/CDN, invalidation also relies on the `x-vercel-cache-tags`
|
|
10
|
+
// response header (emitted by the render pipeline) + the platform's tag purge;
|
|
11
|
+
// `revalidateTag` records the purge so an on-demand endpoint/webhook can act on
|
|
12
|
+
// it. The in-memory store is the fast path; the CDN tags are the durable path.
|
|
13
|
+
const g = globalThis;
|
|
14
|
+
const cacheState = (g.__SR_CACHE__ ??= { store: new Map(), purged: new Set() });
|
|
15
|
+
/**
|
|
16
|
+
* Wrap an async function so its result is cached by argument key, with optional
|
|
17
|
+
* TTL and tags. Returns a function with the same signature.
|
|
18
|
+
*
|
|
19
|
+
* const getPosts = cache(fetchPosts, { tags: ["posts"], revalidate: 3600 });
|
|
20
|
+
* await getPosts(); // miss → runs fetchPosts, caches
|
|
21
|
+
* await getPosts(); // hit
|
|
22
|
+
* revalidateTag("posts"); // purge
|
|
23
|
+
*/
|
|
24
|
+
export function cache(fn, options = {}) {
|
|
25
|
+
const tags = options.tags ?? [];
|
|
26
|
+
const ttlMs = options.revalidate ? options.revalidate * 1000 : 0;
|
|
27
|
+
const name = fn.name || "anon";
|
|
28
|
+
return async (...args) => {
|
|
29
|
+
const key = options.key ? `${name}:${options.key(...args)}` : `${name}:${safeStringify(args)}`;
|
|
30
|
+
const hit = cacheState.store.get(key);
|
|
31
|
+
if (hit && (hit.expires === 0 || hit.expires > Date.now()))
|
|
32
|
+
return hit.value;
|
|
33
|
+
const value = await fn(...args);
|
|
34
|
+
cacheState.store.set(key, { value, expires: ttlMs ? Date.now() + ttlMs : 0, tags });
|
|
35
|
+
return value;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Invalidate every cached entry tagged with `tag`. */
|
|
39
|
+
export function revalidateTag(tag) {
|
|
40
|
+
for (const [k, v] of cacheState.store) {
|
|
41
|
+
if (v.tags.includes(tag))
|
|
42
|
+
cacheState.store.delete(k);
|
|
43
|
+
}
|
|
44
|
+
cacheState.purged.add(`tag:${tag}`);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Invalidate cached entries whose key references `path`. (Best-effort by
|
|
48
|
+
* substring — useful for "everything derived from /blog/*".) Also records the
|
|
49
|
+
* path so the CDN/platform can purge the rendered route.
|
|
50
|
+
*/
|
|
51
|
+
export function revalidatePath(path) {
|
|
52
|
+
for (const [k] of cacheState.store) {
|
|
53
|
+
if (k.includes(path))
|
|
54
|
+
cacheState.store.delete(k);
|
|
55
|
+
}
|
|
56
|
+
cacheState.purged.add(`path:${path}`);
|
|
57
|
+
}
|
|
58
|
+
/** Drain the set of purge signals recorded since the last call (for endpoints/webhooks). */
|
|
59
|
+
export function drainPurged() {
|
|
60
|
+
const out = [...cacheState.purged];
|
|
61
|
+
cacheState.purged.clear();
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
/** Clear the entire data cache (mainly for tests / dev HMR). */
|
|
65
|
+
export function clearCache() {
|
|
66
|
+
cacheState.store.clear();
|
|
67
|
+
}
|
|
68
|
+
function safeStringify(value) {
|
|
69
|
+
try {
|
|
70
|
+
return JSON.stringify(value);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return String(value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,EAAE;AACF,4EAA4E;AAC5E,gFAAgF;AAChF,uEAAuE;AACvE,kDAAkD;AAClD,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAe/E,MAAM,CAAC,GAAG,UAAuD,CAAC;AAClE,MAAM,UAAU,GAAgB,CAAC,CAAC,CAAC,YAAY,KAAK,EAAE,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;AAY7F;;;;;;;;GAQG;AACH,MAAM,UAAU,KAAK,CACnB,EAAkC,EAClC,UAAwB,EAAE;IAE1B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,IAAI,MAAM,CAAC;IAC/B,OAAO,KAAK,EAAE,GAAG,IAAO,EAAc,EAAE;QACtC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/F,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAA8B,CAAC;QACnE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAAE,OAAO,GAAG,CAAC,KAAK,CAAC;QAC7E,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;QAChC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACpF,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;AACJ,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACtC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC;IACD,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnD,CAAC;IACD,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,WAAW;IACzB,MAAM,GAAG,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACnC,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IAC1B,OAAO,GAAG,CAAC;AACb,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,UAAU;IACxB,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;AAC3B,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -74,6 +74,8 @@ export { Link } from "./link";
|
|
|
74
74
|
export { Head, Title, Meta, Style } from "./head";
|
|
75
75
|
export { notFound, redirect, permanentRedirect } from "./router";
|
|
76
76
|
export { NotFoundError, RedirectError } from "./router";
|
|
77
|
+
export { cache, revalidateTag, revalidatePath, clearCache } from "./cache";
|
|
78
|
+
export type { CacheOptions, CacheEntry } from "./cache";
|
|
77
79
|
export { Video, BackgroundVideo, VideoLightbox, isYouTubeUrl, isVimeoUrl, getYouTubeId, getVimeoId, detectProvider, getYouTubeEmbedUrl, getVimeoEmbedUrl, } from "./video";
|
|
78
80
|
export type { VideoProps, VideoSource, VideoCaption, VideoPreload, VideoProvider, BackgroundVideoProps, VideoLightboxProps, } from "./video";
|
|
79
81
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -8,5 +8,6 @@ export { Link } from "./link";
|
|
|
8
8
|
export { Head, Title, Meta, Style } from "./head";
|
|
9
9
|
export { notFound, redirect, permanentRedirect } from "./router";
|
|
10
10
|
export { NotFoundError, RedirectError } from "./router";
|
|
11
|
+
export { cache, revalidateTag, revalidatePath, clearCache } from "./cache";
|
|
11
12
|
export { Video, BackgroundVideo, VideoLightbox, isYouTubeUrl, isVimeoUrl, getYouTubeId, getVimeoId, detectProvider, getYouTubeEmbedUrl, getVimeoEmbedUrl, } from "./video";
|
|
12
13
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA+BA,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,OAAO,MAAM,CAAC;AAChB,CAAC;AAwBD,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAW1C,OAAO,EACL,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,MAAM,EACN,eAAe,EACf,oBAAoB,EACpB,YAAY,EACZ,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAI7D,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EACL,KAAK,EACL,eAAe,EACf,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA+BA,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,OAAO,MAAM,CAAC;AAChB,CAAC;AAwBD,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAW1C,OAAO,EACL,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,MAAM,EACN,eAAe,EACf,oBAAoB,EACpB,YAAY,EACZ,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAI7D,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE3E,OAAO,EACL,KAAK,EACL,eAAe,EACf,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,SAAS,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swift-rust",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"description": "The full-stack React framework powered with Rust + Bun. TSX-first, Rust rendering, 10x faster than Next.js, single binary deploy.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://swift-rust.dev",
|
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
"types": "./dist/router.d.ts",
|
|
56
56
|
"import": "./dist/router.js"
|
|
57
57
|
},
|
|
58
|
+
"./cache": {
|
|
59
|
+
"types": "./dist/cache.d.ts",
|
|
60
|
+
"import": "./dist/cache.js"
|
|
61
|
+
},
|
|
58
62
|
"./head": {
|
|
59
63
|
"types": "./dist/head.d.ts",
|
|
60
64
|
"import": "./dist/head.js"
|