bun-plugin-stx 0.2.736 → 0.2.740

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.
@@ -0,0 +1,47 @@
1
+ /**
2
+ * A digest of everything an entry's signature does not cover. Files are
3
+ * described by path, mtime and size; a missing file is part of the answer
4
+ * too, since creating one (a new `.env.local`) can change what renders.
5
+ */
6
+ export declare function renderInputsFingerprint(inputs: RenderInputs): Promise<string>;
7
+ /** Put this process's build id where the rendering process put its own. */
8
+ export declare function restampBuildId(html: string, renderedWith: string, current: string): string;
9
+ /**
10
+ * One file per entry, read on demand. A dashboard's worth of pages is tens of
11
+ * megabytes of HTML, so nothing is loaded up front and a page nobody opens
12
+ * costs nothing.
13
+ */
14
+ export declare function createRenderCacheStore(options: {
15
+ dir: string
16
+ inputs: () => RenderInputs
17
+ buildId: () => string
18
+ }): RenderCacheStore;
19
+ export declare interface PersistableEntry {
20
+ html: string
21
+ signature: Map<string, number>
22
+ status: number
23
+ headers?: Record<string, string>
24
+ }
25
+ export declare interface RenderInputs {
26
+ roots: readonly string[]
27
+ files: readonly string[]
28
+ extra: unknown
29
+ }
30
+ /**
31
+ * Taken before a render reads anything, and handed back to `write`.
32
+ *
33
+ * The fingerprint is settled first, so it can only be older than what the
34
+ * render read, never newer. An older one no longer matches after the change
35
+ * that made it older, which costs a miss; a newer one would vouch for HTML
36
+ * rendered from files it never described.
37
+ */
38
+ export declare interface RenderTicket {
39
+ generation: number
40
+ fingerprint: string
41
+ }
42
+ export declare interface RenderCacheStore {
43
+ begin: () => Promise<RenderTicket>
44
+ read: (key: string) => Promise<PersistableEntry | undefined>
45
+ write: (key: string, entry: PersistableEntry, ticket: RenderTicket) => Promise<void>
46
+ invalidate: () => void
47
+ }
package/dist/serve.d.ts CHANGED
@@ -361,6 +361,7 @@ export declare interface ServeOptions {
361
361
  watchDirs?: string[]
362
362
  renderCache?: boolean
363
363
  renderCacheVary?: 'request' | 'source'
364
+ renderCachePersist?: boolean
364
365
  renderCacheLimit?: number
365
366
  clientAddress?: (req: Request, server: ServeServer) => string | null | undefined
366
367
  cssCacheLimit?: number
package/dist/serve.js CHANGED
@@ -9,14 +9,14 @@ import {
9
9
  // src/serve.ts
10
10
  var {serve: bunServe, Glob } = globalThis.Bun;
11
11
  import { existsSync as existsSync2, watch as fsWatch, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
12
- import nodeFs from "fs/promises";
13
- import nodePath from "path";
12
+ import nodeFs2 from "fs/promises";
13
+ import nodePath2 from "path";
14
14
  import process20 from "process";
15
15
 
16
16
  // ../../node_modules/bunfig/dist/index.js
17
17
  import { existsSync, statSync } from "fs";
18
18
  import { resolve } from "path";
19
- import process from "process";
19
+ import process2 from "process";
20
20
  import { existsSync as existsSync12, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync7 } from "fs";
21
21
  import { homedir as homedir3 } from "os";
22
22
  import { dirname as dirname4, resolve as resolve12 } from "path";
@@ -33,7 +33,7 @@ import { homedir } from "os";
33
33
  import { dirname as dirname2, resolve as resolve3 } from "path";
34
34
  import process6 from "process";
35
35
  import { join, relative, resolve as resolve2 } from "path";
36
- import process2 from "process";
36
+ import process22 from "process";
37
37
  import { existsSync as existsSync22, mkdirSync, readdirSync, writeFileSync } from "fs";
38
38
  import { dirname, resolve as resolve5 } from "path";
39
39
  import process3 from "process";
@@ -352,7 +352,7 @@ function getConfigPaths(options) {
352
352
  return patterns.flatMap((pattern) => CONFIG_FILE_EXTENSIONS.map((extension) => resolve(options.directory, `${pattern}${extension}`)));
353
353
  }
354
354
  function getLocalConfigDirectories(options = {}) {
355
- const cwd = resolve(options.cwd || process.cwd());
355
+ const cwd = resolve(options.cwd || process2.cwd());
356
356
  return Array.from(new Set([
357
357
  cwd,
358
358
  resolve(cwd, "config"),
@@ -851,12 +851,12 @@ async function loadConfig({
851
851
  return defaultConfig;
852
852
  }
853
853
  function getProjectRoot(filePath, options = {}) {
854
- let path = process2.cwd();
854
+ let path = process22.cwd();
855
855
  while (path.includes("storage"))
856
856
  path = resolve2(path, "..");
857
857
  const finalPath = resolve2(path, filePath || "");
858
858
  if (options?.relative)
859
- return relative(process2.cwd(), finalPath);
859
+ return relative(process22.cwd(), finalPath);
860
860
  return finalPath;
861
861
  }
862
862
  async function loadConfig2() {
@@ -864,7 +864,7 @@ async function loadConfig2() {
864
864
  const loadedConfig = await loadConfig({
865
865
  name: "clarity",
866
866
  defaultConfig,
867
- cwd: process2.cwd(),
867
+ cwd: process22.cwd(),
868
868
  endpoint: "",
869
869
  headers: {}
870
870
  });
@@ -5389,7 +5389,7 @@ var init_dist = __esm(async () => {
5389
5389
  };
5390
5390
  defaultConfigDir = resolve5(process3.cwd(), "config");
5391
5391
  defaultGeneratedDir = resolve5(process3.cwd(), "src/generated");
5392
- defaultLogDirectory = process2.env.CLARITY_LOG_DIR || join(getProjectRoot(), "logs");
5392
+ defaultLogDirectory = process22.env.CLARITY_LOG_DIR || join(getProjectRoot(), "logs");
5393
5393
  defaultConfig = {
5394
5394
  level: "info",
5395
5395
  defaultName: "clarity",
@@ -8881,10 +8881,39 @@ async function servedTemplatesUseImageBuiltins(root, scope) {
8881
8881
  options.additionalComponentsDirs = options.additionalComponentsDirs.map(absolute);
8882
8882
  const directories = new Map;
8883
8883
  const sources = new Map;
8884
+ const listings = new Map;
8885
+ const isFileCache = new Map;
8886
+ function listing(dir) {
8887
+ let entries = listings.get(dir);
8888
+ if (!entries) {
8889
+ entries = readdir4(dir, { withFileTypes: true }).then((list) => ({ exact: new Map(list.map((entry) => [entry.name, entry])), folded: new Set(list.map((entry) => entry.name.toLowerCase())) }), () => null);
8890
+ listings.set(dir, entries);
8891
+ }
8892
+ return entries;
8893
+ }
8894
+ function isFile(file) {
8895
+ let known = isFileCache.get(file);
8896
+ if (!known) {
8897
+ known = (async () => {
8898
+ const entries = await listing(path.dirname(file));
8899
+ if (!entries)
8900
+ return false;
8901
+ const name = path.basename(file);
8902
+ const entry = entries.exact.get(name);
8903
+ if (entry?.isFile())
8904
+ return true;
8905
+ if (entry?.isSymbolicLink() || !entry && entries.folded.has(name.toLowerCase()))
8906
+ return stat4(file).then((result) => result.isFile(), () => false);
8907
+ return false;
8908
+ })();
8909
+ isFileCache.set(file, known);
8910
+ }
8911
+ return known;
8912
+ }
8884
8913
  async function firstFile(candidates) {
8885
8914
  for (const candidate of candidates) {
8886
8915
  const file = absolute(candidate);
8887
- if (await stat4(file).then((result) => result.isFile(), () => false))
8916
+ if (await isFile(file))
8888
8917
  return file;
8889
8918
  }
8890
8919
  }
@@ -8914,12 +8943,22 @@ async function servedTemplatesUseImageBuiltins(root, scope) {
8914
8943
  }
8915
8944
  return files;
8916
8945
  }
8917
- async function component(name, file, page, imported) {
8946
+ const resolutions = new Map;
8947
+ function component(name, file, page, imported) {
8918
8948
  const pascal = name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
8919
8949
  for (const alias of [name, name.toLowerCase(), pascal, pascalToKebab(name)]) {
8920
8950
  if (imported.has(alias))
8921
- return imported.get(alias);
8951
+ return Promise.resolve(imported.get(alias));
8922
8952
  }
8953
+ const key = `${name}\x00${path.dirname(page)}\x00${path.dirname(file)}`;
8954
+ let resolving = resolutions.get(key);
8955
+ if (!resolving) {
8956
+ resolving = resolveComponent(name, pascal, file, page);
8957
+ resolutions.set(key, resolving);
8958
+ }
8959
+ return resolving;
8960
+ }
8961
+ async function resolveComponent(name, pascal, file, page) {
8923
8962
  if (name.startsWith("./") || name.startsWith("../"))
8924
8963
  return firstFile([path.resolve(path.dirname(file), name.endsWith(".stx") ? name : `${name}.stx`)]);
8925
8964
  const names = [...new Set([name, pascal, pascalToKebab(name)].map((value) => `${value.replace(/\.stx$/, "")}.stx`))];
@@ -9023,6 +9062,94 @@ async function servedTemplatesUseImageBuiltins(root, scope) {
9023
9062
  }
9024
9063
  return false;
9025
9064
  }
9065
+
9066
+ // src/render-cache-store.ts
9067
+ import nodeFs from "fs/promises";
9068
+ import nodePath from "path";
9069
+ import { createHash } from "crypto";
9070
+ var WATCHED_EXTENSIONS = new Set([".stx", ".md", ".html", ".js", ".ts", ".tsx", ".mts", ".cts", ".json"]);
9071
+ async function renderInputsFingerprint(inputs) {
9072
+ const lines = [];
9073
+ for (const root of new Set(inputs.roots)) {
9074
+ const absolute = nodePath.resolve(root);
9075
+ const entries = await nodeFs.readdir(absolute, { recursive: true, withFileTypes: true }).catch(() => null);
9076
+ if (!entries) {
9077
+ lines.push(`root ${absolute} missing`);
9078
+ continue;
9079
+ }
9080
+ const files = entries.filter((entry) => entry.isFile() && WATCHED_EXTENSIONS.has(nodePath.extname(entry.name))).map((entry) => nodePath.join(entry.parentPath, entry.name)).filter((file) => !nodePath.relative(absolute, nodePath.dirname(file)).split(nodePath.sep).some((part) => part === "node_modules" || part.startsWith(".")));
9081
+ const stats = await Promise.all(files.map((file) => nodeFs.stat(file).catch(() => null)));
9082
+ files.forEach((file, index) => lines.push(`file ${file} ${stats[index]?.mtimeMs ?? "gone"} ${stats[index]?.size ?? 0}`));
9083
+ }
9084
+ for (const file of new Set(inputs.files)) {
9085
+ const stat = await nodeFs.stat(file).catch(() => null);
9086
+ lines.push(`input ${nodePath.resolve(file)} ${stat ? `${stat.mtimeMs} ${stat.size}` : "missing"}`);
9087
+ }
9088
+ lines.sort();
9089
+ lines.push(`extra ${JSON.stringify(inputs.extra)}`);
9090
+ return createHash("sha256").update(lines.join(`
9091
+ `)).digest("hex");
9092
+ }
9093
+ function restampBuildId(html, renderedWith, current) {
9094
+ if (renderedWith === current)
9095
+ return html;
9096
+ const before = `<meta name="stx-build" content="${renderedWith}">`;
9097
+ const at = html.indexOf(before);
9098
+ if (at === -1)
9099
+ return html;
9100
+ return `${html.slice(0, at)}<meta name="stx-build" content="${current}">${html.slice(at + before.length)}`;
9101
+ }
9102
+ function createRenderCacheStore(options) {
9103
+ let generation = 0;
9104
+ let fingerprint = null;
9105
+ const current = () => fingerprint ??= renderInputsFingerprint(options.inputs());
9106
+ const fileFor = (key) => nodePath.join(options.dir, `${createHash("sha256").update(key).digest("hex")}.json`);
9107
+ return {
9108
+ async begin() {
9109
+ const at = generation;
9110
+ return { generation: at, fingerprint: await current() };
9111
+ },
9112
+ async read(key) {
9113
+ const file = fileFor(key);
9114
+ const stored = await Bun.file(file).json().catch(() => null);
9115
+ if (!stored)
9116
+ return;
9117
+ if (stored.v !== 1 || stored.key !== key || stored.fingerprint !== await current()) {
9118
+ await nodeFs.rm(file, { force: true }).catch(() => {});
9119
+ return;
9120
+ }
9121
+ return {
9122
+ html: restampBuildId(stored.html, stored.buildId, options.buildId()),
9123
+ signature: new Map(stored.signature),
9124
+ status: stored.status,
9125
+ headers: stored.headers
9126
+ };
9127
+ },
9128
+ async write(key, entry, ticket) {
9129
+ if (ticket.generation !== generation)
9130
+ return;
9131
+ const stored = {
9132
+ v: 1,
9133
+ key,
9134
+ fingerprint: ticket.fingerprint,
9135
+ buildId: options.buildId(),
9136
+ html: entry.html,
9137
+ signature: [...entry.signature],
9138
+ status: entry.status,
9139
+ headers: entry.headers
9140
+ };
9141
+ await nodeFs.mkdir(options.dir, { recursive: true });
9142
+ const file = fileFor(key);
9143
+ const temporary = `${file}.${process.pid}.tmp`;
9144
+ await Bun.write(temporary, JSON.stringify(stored));
9145
+ await nodeFs.rename(temporary, file);
9146
+ },
9147
+ invalidate() {
9148
+ generation++;
9149
+ fingerprint = null;
9150
+ }
9151
+ };
9152
+ }
9026
9153
  // src/page-middleware.ts
9027
9154
  var DEFAULT_PRIORITY = 10;
9028
9155
  function middlewarePriority(middleware) {
@@ -9262,9 +9389,9 @@ function publicFileExists(requestPath, publicDir) {
9262
9389
  }
9263
9390
  if (decoded.includes("\x00"))
9264
9391
  return false;
9265
- const publicRoot = nodePath.resolve(process20.cwd(), publicDir);
9266
- const resolved = nodePath.resolve(publicRoot, `.${decoded}`);
9267
- const inside = resolved === publicRoot || resolved.startsWith(`${publicRoot}${nodePath.sep}`);
9392
+ const publicRoot = nodePath2.resolve(process20.cwd(), publicDir);
9393
+ const resolved = nodePath2.resolve(publicRoot, `.${decoded}`);
9394
+ const inside = resolved === publicRoot || resolved.startsWith(`${publicRoot}${nodePath2.sep}`);
9268
9395
  if (!inside)
9269
9396
  return false;
9270
9397
  try {
@@ -9418,7 +9545,7 @@ function boundedCache(limit) {
9418
9545
  }
9419
9546
  function pageExcluder(exclude, cwd = process20.cwd()) {
9420
9547
  const canonical = (entry) => {
9421
- const resolved = nodePath.resolve(cwd, entry);
9548
+ const resolved = nodePath2.resolve(cwd, entry);
9422
9549
  try {
9423
9550
  return realpathSync2(resolved);
9424
9551
  } catch {
@@ -9430,7 +9557,7 @@ function pageExcluder(exclude, cwd = process20.cwd()) {
9430
9557
  return () => false;
9431
9558
  return (file) => {
9432
9559
  const resolved = canonical(file);
9433
- return roots.some((root) => resolved === root || resolved.startsWith(`${root}${nodePath.sep}`));
9560
+ return roots.some((root) => resolved === root || resolved.startsWith(`${root}${nodePath2.sep}`));
9434
9561
  };
9435
9562
  }
9436
9563
  function resolveServeOpenUrl(port, openPath = "/") {
@@ -9665,9 +9792,9 @@ async function serve(options) {
9665
9792
  }
9666
9793
  const stxModule = options.stxModule ? Promise.resolve(options.stxModule) : defaultStxModule;
9667
9794
  const imageCacheDir = process20.env.STX_IMAGE_CACHE_DIR?.trim();
9668
- const imageDeliveryOutputDir = imageCacheDir ? nodePath.resolve(imageCacheDir, "image-delivery") : stateDir(process20.cwd(), "image-delivery");
9669
- const imagePlaceholderCachePath = imageCacheDir ? nodePath.resolve(imageCacheDir, "image-placeholders.json") : stateDir(process20.cwd(), "image-placeholders.json");
9670
- const imageDeliveryRoot = nodePath.join(imageDeliveryOutputDir, "_stx", "images");
9795
+ const imageDeliveryOutputDir = imageCacheDir ? nodePath2.resolve(imageCacheDir, "image-delivery") : stateDir(process20.cwd(), "image-delivery");
9796
+ const imagePlaceholderCachePath = imageCacheDir ? nodePath2.resolve(imageCacheDir, "image-placeholders.json") : stateDir(process20.cwd(), "image-placeholders.json");
9797
+ const imageDeliveryRoot = nodePath2.join(imageDeliveryOutputDir, "_stx", "images");
9671
9798
  let resolvedStxModule = null;
9672
9799
  Promise.resolve(stxModule).then((m) => {
9673
9800
  resolvedStxModule = m;
@@ -9694,6 +9821,28 @@ After installing: bun add bun-plugin-stx`);
9694
9821
  const pendingShellRenders = new Map;
9695
9822
  const ENABLE_HTML_CACHE = options.renderCache === true;
9696
9823
  const RENDER_CACHE_VARY = options.renderCacheVary ?? "request";
9824
+ const renderCacheStore = ENABLE_HTML_CACHE && RENDER_CACHE_VARY === "source" && options.renderCachePersist === true ? createRenderCacheStore({ dir: stateDir(process20.cwd(), "render-cache"), inputs: () => renderInputs(), buildId: getBuildId }) : null;
9825
+ async function renderTicket(reqCtx) {
9826
+ if (!renderCacheStore || reqCtx && reqCtx.method !== "GET" && reqCtx.method !== "HEAD")
9827
+ return;
9828
+ return renderCacheStore.begin().catch(() => {
9829
+ return;
9830
+ });
9831
+ }
9832
+ async function readRenderCache(key) {
9833
+ const hit = htmlCache.read(key);
9834
+ if (hit || !renderCacheStore)
9835
+ return hit;
9836
+ const kept = await renderCacheStore.read(key).catch(() => {
9837
+ return;
9838
+ });
9839
+ return kept ? htmlCache.remember(key, kept) : undefined;
9840
+ }
9841
+ function rememberRender(key, entry, ticket) {
9842
+ htmlCache.remember(key, entry);
9843
+ if (renderCacheStore && ticket)
9844
+ renderCacheStore.write(key, entry, ticket).catch(() => {});
9845
+ }
9697
9846
  const cssCssCache = boundedCache(options.cssCacheLimit ?? 512);
9698
9847
  const hmrClients = new Set;
9699
9848
  const hmrEncoder = new TextEncoder;
@@ -9794,6 +9943,7 @@ function __stxOverlay(errs){
9794
9943
  const watchersStarted = new Map;
9795
9944
  const clearRenderCaches = () => {
9796
9945
  htmlCache.clear();
9946
+ renderCacheStore?.invalidate();
9797
9947
  cssCssCache.clear();
9798
9948
  resolvedStxModule?.clearDevCaches?.();
9799
9949
  resolvedStxModule?.clearStoreCache?.();
@@ -9821,12 +9971,12 @@ function __stxOverlay(errs){
9821
9971
  if (isStxLike || topologyChanged)
9822
9972
  sourceFiles = null;
9823
9973
  clearRenderCaches();
9824
- if (layoutsDir && f.endsWith(".stx") && nodePath.resolve(f).startsWith(nodePath.resolve(layoutsDir)))
9974
+ if (layoutsDir && f.endsWith(".stx") && nodePath2.resolve(f).startsWith(nodePath2.resolve(layoutsDir)))
9825
9975
  writeLayoutTypes();
9826
9976
  }
9827
- const resolved = nodePath.resolve(dir, f);
9828
- const isStore = resolved.startsWith(nodePath.resolve(storesDir)) || composableDirs.some((d) => resolved.startsWith(nodePath.resolve(d)));
9829
- const isLayout = !!layoutsDir && resolved.startsWith(nodePath.resolve(layoutsDir));
9977
+ const resolved = nodePath2.resolve(dir, f);
9978
+ const isStore = resolved.startsWith(nodePath2.resolve(storesDir)) || composableDirs.some((d) => resolved.startsWith(nodePath2.resolve(d)));
9979
+ const isLayout = !!layoutsDir && resolved.startsWith(nodePath2.resolve(layoutsDir));
9830
9980
  if (isCss && !isStxLike)
9831
9981
  broadcastHmr({ type: "css", file: f });
9832
9982
  else if (isStore)
@@ -9844,13 +9994,13 @@ function __stxOverlay(errs){
9844
9994
  const watchPageRoot = (dir) => {
9845
9995
  if (production || pageRootWatchersStarted.has(dir))
9846
9996
  return;
9847
- const absolute = nodePath.resolve(dir);
9848
- const parent = nodePath.dirname(absolute);
9997
+ const absolute = nodePath2.resolve(dir);
9998
+ const parent = nodePath2.dirname(absolute);
9849
9999
  if (parent === absolute)
9850
10000
  return;
9851
10001
  try {
9852
10002
  const watcher = fsWatch(parent, { recursive: false }, (event, filename) => {
9853
- if (event !== "rename" || String(filename) !== nodePath.basename(absolute))
10003
+ if (event !== "rename" || String(filename) !== nodePath2.basename(absolute))
9854
10004
  return;
9855
10005
  watchersStarted.get(dir)?.close();
9856
10006
  watchersStarted.delete(dir);
@@ -9870,9 +10020,9 @@ function __stxOverlay(errs){
9870
10020
  } catch {}
9871
10021
  };
9872
10022
  await writeLayoutTypes();
9873
- const storesDir = nodePath.resolve(stxConfig.root || ".", stxConfig.storesDir || "stores");
9874
- const composableDirs = stxConfig.composablesDir ? [nodePath.resolve(stxConfig.root || ".", stxConfig.composablesDir)] : ["composables", "functions"].map((d) => nodePath.resolve(stxConfig.root || ".", d));
9875
- for (const dir of [
10023
+ const storesDir = nodePath2.resolve(stxConfig.root || ".", stxConfig.storesDir || "stores");
10024
+ const composableDirs = stxConfig.composablesDir ? [nodePath2.resolve(stxConfig.root || ".", stxConfig.composablesDir)] : ["composables", "functions"].map((d) => nodePath2.resolve(stxConfig.root || ".", d));
10025
+ const watchedRoots = [
9876
10026
  componentsDir,
9877
10027
  ...additionalComponentsDirs ?? [],
9878
10028
  layoutsDir,
@@ -9882,10 +10032,46 @@ function __stxOverlay(errs){
9882
10032
  storesDir,
9883
10033
  ...composableDirs,
9884
10034
  ...options.watchDirs ?? []
9885
- ]) {
9886
- if (!dir)
9887
- continue;
10035
+ ].filter((dir) => !!dir);
10036
+ for (const dir of watchedRoots)
9888
10037
  startWatcher(dir);
10038
+ function renderInputs() {
10039
+ const cwd = process20.cwd();
10040
+ const configFiles = ["stx", "css"].flatMap((name) => [
10041
+ `${name}.config.ts`,
10042
+ `${name}.config.js`,
10043
+ `.config/${name}.ts`,
10044
+ `config/${name}.ts`
10045
+ ]);
10046
+ let stxEntry;
10047
+ try {
10048
+ stxEntry = Bun.resolveSync("@stacksjs/stx", import.meta.dir);
10049
+ } catch {}
10050
+ return {
10051
+ roots: [...watchedRoots, ...patterns.filter((pattern) => !pattern.includes("*"))],
10052
+ files: [
10053
+ ...["package.json", "bun.lock", "bun.lockb", "pantry.lock", ".env", ".env.local", ".env.development", ".env.production", ...configFiles].map((file) => nodePath2.join(cwd, file)),
10054
+ import.meta.path,
10055
+ ...stxEntry ? [stxEntry] : []
10056
+ ],
10057
+ extra: {
10058
+ bun: Bun.version,
10059
+ production,
10060
+ options: {
10061
+ patterns,
10062
+ root: cwd,
10063
+ componentsDir,
10064
+ additionalComponentsDirs,
10065
+ layoutsDir,
10066
+ fallbackLayoutsDir: options.fallbackLayoutsDir,
10067
+ fallbackComponentsDir: options.fallbackComponentsDir,
10068
+ partialsDir,
10069
+ publicDir,
10070
+ watchDirs: options.watchDirs,
10071
+ domains: options.domains
10072
+ }
10073
+ }
10074
+ };
9889
10075
  }
9890
10076
  const pageMiddlewareByPath = new Map;
9891
10077
  const pageMiddlewarePatterns = [];
@@ -9924,7 +10110,7 @@ function __stxOverlay(errs){
9924
10110
  }
9925
10111
  async function detectPageMiddleware(file) {
9926
10112
  try {
9927
- const src = await nodeFs.readFile(file, "utf-8");
10113
+ const src = await nodeFs2.readFile(file, "utf-8");
9928
10114
  const meta = src.match(/definePageMeta\s*\(\s*\{[\s\S]*?\}\s*\)/);
9929
10115
  if (!meta)
9930
10116
  return [];
@@ -10164,7 +10350,7 @@ function __stxOverlay(errs){
10164
10350
  const supportedExtensions = [".stx", ".md", ".html"];
10165
10351
  for (const pattern of patterns) {
10166
10352
  try {
10167
- const stat = await nodeFs.stat(pattern).catch(() => null);
10353
+ const stat = await nodeFs2.stat(pattern).catch(() => null);
10168
10354
  if (stat?.isDirectory() || !stat && !pattern.includes("*") && !supportedExtensions.some((ext) => pattern.endsWith(ext)))
10169
10355
  watchPageRoot(pattern);
10170
10356
  if (stat?.isDirectory()) {
@@ -10202,7 +10388,7 @@ function __stxOverlay(errs){
10202
10388
  const pagesDirs = patterns.map((p) => p.replace(/\/$/, "")).filter(Boolean);
10203
10389
  const router = new Router(process20.cwd(), { pagesDirs, stateDir: stateDirName(), exclude: options.exclude });
10204
10390
  if (!options.quiet)
10205
- console.log(`[stx] Generated ${router.routes.length} routes \u2192 ${nodePath.join(stateDirName(), "routes.ts")}`);
10391
+ console.log(`[stx] Generated ${router.routes.length} routes \u2192 ${nodePath2.join(stateDirName(), "routes.ts")}`);
10206
10392
  } catch (e) {}
10207
10393
  return kept;
10208
10394
  }
@@ -10213,16 +10399,16 @@ function __stxOverlay(errs){
10213
10399
  const assetsDir = "./resources/assets";
10214
10400
  const targetAssetsDir = stateDir(process20.cwd(), "assets");
10215
10401
  try {
10216
- const assetsExist = await nodeFs.stat(assetsDir).then(() => true).catch(() => false);
10402
+ const assetsExist = await nodeFs2.stat(assetsDir).then(() => true).catch(() => false);
10217
10403
  if (assetsExist) {
10218
- await nodeFs.rm(targetAssetsDir, { recursive: true, force: true });
10219
- await nodeFs.cp(assetsDir, targetAssetsDir, { recursive: true });
10404
+ await nodeFs2.rm(targetAssetsDir, { recursive: true, force: true });
10405
+ await nodeFs2.cp(assetsDir, targetAssetsDir, { recursive: true });
10220
10406
  }
10221
10407
  } catch {}
10222
10408
  }
10223
10409
  async function templateSignatureFresh(sig) {
10224
10410
  for (const [p, expected] of sig) {
10225
- const stat = await nodeFs.stat(p).catch(() => null);
10411
+ const stat = await nodeFs2.stat(p).catch(() => null);
10226
10412
  if (!stat || stat.mtimeMs !== expected)
10227
10413
  return false;
10228
10414
  }
@@ -10231,7 +10417,7 @@ function __stxOverlay(errs){
10231
10417
  async function buildTemplateSignature(filePath, deps) {
10232
10418
  const sig = new Map;
10233
10419
  const allPaths = [filePath, ...deps];
10234
- const stats = await Promise.all(allPaths.map((p) => nodeFs.stat(p).catch(() => null)));
10420
+ const stats = await Promise.all(allPaths.map((p) => nodeFs2.stat(p).catch(() => null)));
10235
10421
  for (let i = 0;i < allPaths.length; i++) {
10236
10422
  const stat = stats[i];
10237
10423
  if (stat)
@@ -10247,10 +10433,10 @@ function __stxOverlay(errs){
10247
10433
  return cssModule;
10248
10434
  cssLoadAttempted = true;
10249
10435
  let dir = process20.cwd();
10250
- while (dir !== nodePath.dirname(dir)) {
10436
+ while (dir !== nodePath2.dirname(dir)) {
10251
10437
  for (const store of ["node_modules", "pantry"]) {
10252
10438
  for (const entry of ENGINE_PACKAGE_ENTRIES) {
10253
- const candidate = nodePath.join(dir, store, ...entry);
10439
+ const candidate = nodePath2.join(dir, store, ...entry);
10254
10440
  try {
10255
10441
  if (!await Bun.file(candidate).exists())
10256
10442
  continue;
@@ -10262,7 +10448,7 @@ function __stxOverlay(errs){
10262
10448
  } catch {}
10263
10449
  }
10264
10450
  }
10265
- dir = nodePath.dirname(dir);
10451
+ dir = nodePath2.dirname(dir);
10266
10452
  }
10267
10453
  for (const specifier of ENGINE_SPECIFIERS) {
10268
10454
  try {
@@ -10274,7 +10460,7 @@ function __stxOverlay(errs){
10274
10460
  } catch {}
10275
10461
  }
10276
10462
  try {
10277
- const localPath = nodePath.join(process20.env.HOME || "", "Code/Tools/crosswind/packages/toolkit/src/engine/index.ts");
10463
+ const localPath = nodePath2.join(process20.env.HOME || "", "Code/Tools/crosswind/packages/toolkit/src/engine/index.ts");
10278
10464
  const mod = await import(localPath);
10279
10465
  cssModule = { CSSGenerator: mod.CSSGenerator, config: mod.config };
10280
10466
  return cssModule;
@@ -10404,6 +10590,7 @@ function __stxOverlay(errs){
10404
10590
  reqCtx.responseHeaders = { ...reqCtx.responseHeaders, ...headers };
10405
10591
  }
10406
10592
  async function processTemplate(filePath, reqCtx) {
10593
+ const ticket = await renderTicket(reqCtx);
10407
10594
  const content = await Bun.file(filePath).text();
10408
10595
  if (reqCtx)
10409
10596
  reqCtx.responseStatus = extractPageResponseStatus(content) ?? 200;
@@ -10411,7 +10598,7 @@ function __stxOverlay(errs){
10411
10598
  const isMutating = !!reqCtx && reqCtx.method !== "GET" && reqCtx.method !== "HEAD";
10412
10599
  if (ENABLE_HTML_CACHE && !skipCacheHint && !isMutating) {
10413
10600
  const cacheKey = htmlCacheKey(filePath, reqCtx);
10414
- const cachedEntry = htmlCache.read(cacheKey);
10601
+ const cachedEntry = await readRenderCache(cacheKey);
10415
10602
  if (cachedEntry && await templateSignatureFresh(cachedEntry.signature)) {
10416
10603
  if (reqCtx) {
10417
10604
  reqCtx.responseStatus = cachedEntry.status;
@@ -10423,7 +10610,7 @@ function __stxOverlay(errs){
10423
10610
  const { serverScripts, templateContent } = extractServerScriptsFromTemplate(content);
10424
10611
  const context = {
10425
10612
  __filename: filePath,
10426
- __dirname: nodePath.dirname(filePath),
10613
+ __dirname: nodePath2.dirname(filePath),
10427
10614
  __stx_options: stxConfig,
10428
10615
  __stx_runtime_head: {}
10429
10616
  };
@@ -10490,7 +10677,7 @@ function __stxOverlay(errs){
10490
10677
  }
10491
10678
  if (ENABLE_HTML_CACHE && !context.__stx_skip_cache && !skipCacheHint && !isMutating && isRenderableCacheCandidate(output) && placeholdersAreReady) {
10492
10679
  const signature = await buildTemplateSignature(filePath, dependencies);
10493
- htmlCache.remember(htmlCacheKey(filePath, reqCtx), { html: output, signature, status: reqCtx?.responseStatus ?? 200, headers: reqCtx?.responseHeaders });
10680
+ rememberRender(htmlCacheKey(filePath, reqCtx), { html: output, signature, status: reqCtx?.responseStatus ?? 200, headers: reqCtx?.responseHeaders }, ticket);
10494
10681
  }
10495
10682
  return output;
10496
10683
  }
@@ -10512,7 +10699,7 @@ function __stxOverlay(errs){
10512
10699
  possibleFiles.push(`${normalizedPath}/index.md`);
10513
10700
  possibleFiles.push(`${normalizedPath}/index.html`);
10514
10701
  }
10515
- const filename = nodePath.basename(normalizedPath, nodePath.extname(normalizedPath));
10702
+ const filename = nodePath2.basename(normalizedPath, nodePath2.extname(normalizedPath));
10516
10703
  if (filename && !normalizedPath.includes("/")) {
10517
10704
  possibleFiles.push(`${filename}.stx`);
10518
10705
  possibleFiles.push(`${filename}.md`);
@@ -10594,6 +10781,7 @@ function __stxOverlay(errs){
10594
10781
  return null;
10595
10782
  }
10596
10783
  async function processTemplateDynamic(filePath, paramNames, paramValues, routePath, reqCtx) {
10784
+ const ticket = await renderTicket(reqCtx);
10597
10785
  const content = await Bun.file(filePath).text();
10598
10786
  if (reqCtx)
10599
10787
  reqCtx.responseStatus = extractPageResponseStatus(content) ?? 200;
@@ -10612,7 +10800,7 @@ function __stxOverlay(errs){
10612
10800
  if (reqCtx)
10613
10801
  reqCtx.params = paramsObj;
10614
10802
  if (cacheShell) {
10615
- const cachedEntry = htmlCache.read(filePath);
10803
+ const cachedEntry = await readRenderCache(filePath);
10616
10804
  if (cachedEntry && await templateSignatureFresh(cachedEntry.signature)) {
10617
10805
  if (reqCtx) {
10618
10806
  reqCtx.responseStatus = cachedEntry.status;
@@ -10635,7 +10823,7 @@ function __stxOverlay(errs){
10635
10823
  try {
10636
10824
  const context = {
10637
10825
  __filename: filePath,
10638
- __dirname: nodePath.dirname(filePath),
10826
+ __dirname: nodePath2.dirname(filePath),
10639
10827
  __route: routePath,
10640
10828
  __stx_runtime_head: {}
10641
10829
  };
@@ -10707,7 +10895,7 @@ ${output}`;
10707
10895
  const recordedResponse = readResponseStatus(context) !== undefined || !!readResponseHeaders(context);
10708
10896
  if (!context.__stx_skip_cache && !recordedResponse && isRenderableCacheCandidate(output) && placeholdersAreReady) {
10709
10897
  const signature = await buildTemplateSignature(filePath, dependencies);
10710
- htmlCache.remember(filePath, { html: output, signature, status: reqCtx?.responseStatus ?? 200, headers: reqCtx?.responseHeaders });
10898
+ rememberRender(filePath, { html: output, signature, status: reqCtx?.responseStatus ?? 200, headers: reqCtx?.responseHeaders }, ticket);
10711
10899
  }
10712
10900
  return fillRouteParams(output, paramsObj);
10713
10901
  } finally {
@@ -10871,7 +11059,7 @@ ${block}`;
10871
11059
  }
10872
11060
  try {
10873
11061
  const stx = await stxModule;
10874
- const publicRoot = nodePath.resolve(process20.cwd(), publicDir);
11062
+ const publicRoot = nodePath2.resolve(process20.cwd(), publicDir);
10875
11063
  const [placeholderResult, deliveryResult] = await Promise.allSettled([
10876
11064
  stx.warmImagePlaceholders(publicRoot, {
10877
11065
  cachePath: imagePlaceholderCachePath,
@@ -11106,8 +11294,8 @@ ${block}`;
11106
11294
  }
11107
11295
  if (!decodedPath || decodedPath.includes("\x00"))
11108
11296
  return new Response("Invalid image path", { status: 400 });
11109
- const resolvedPath = nodePath.resolve(imageDeliveryRoot, decodedPath);
11110
- if (!resolvedPath.startsWith(`${imageDeliveryRoot}${nodePath.sep}`))
11297
+ const resolvedPath = nodePath2.resolve(imageDeliveryRoot, decodedPath);
11298
+ if (!resolvedPath.startsWith(`${imageDeliveryRoot}${nodePath2.sep}`))
11111
11299
  return new Response("Invalid image path", { status: 400 });
11112
11300
  try {
11113
11301
  const file = Bun.file(resolvedPath);
@@ -11251,7 +11439,7 @@ ${block}`;
11251
11439
  }
11252
11440
  const body = await req.json();
11253
11441
  const { Database } = await import("bun:sqlite");
11254
- const dbPath = nodePath.resolve(process20.cwd(), "database/stacks.sqlite");
11442
+ const dbPath = nodePath2.resolve(process20.cwd(), "database/stacks.sqlite");
11255
11443
  const db = new Database(dbPath);
11256
11444
  try {
11257
11445
  const tableInfo = db.query(`PRAGMA table_info("${tableName}")`).all();
@@ -11544,7 +11732,7 @@ ${trailingPageScripts.join(`
11544
11732
  return new Response("Invalid asset path", { status: 400 });
11545
11733
  for (const assetPath of assetRequestPaths(assetPathname)) {
11546
11734
  try {
11547
- const filePath = nodePath.resolve(process20.cwd(), `.${assetPath}`);
11735
+ const filePath = nodePath2.resolve(process20.cwd(), `.${assetPath}`);
11548
11736
  const file = Bun.file(filePath);
11549
11737
  if (await file.exists()) {
11550
11738
  const ext = getAssetExtension(assetPath);
@@ -11563,7 +11751,7 @@ ${trailingPageScripts.join(`
11563
11751
  }
11564
11752
  }
11565
11753
  if ((req.method === "GET" || req.method === "HEAD") && path !== "/") {
11566
- const publicRoot = nodePath.resolve(process20.cwd(), publicDir);
11754
+ const publicRoot = nodePath2.resolve(process20.cwd(), publicDir);
11567
11755
  let safePathname;
11568
11756
  try {
11569
11757
  safePathname = decodeURIComponent(path);
@@ -11571,8 +11759,8 @@ ${trailingPageScripts.join(`
11571
11759
  safePathname = path;
11572
11760
  }
11573
11761
  if (!safePathname.includes("\x00")) {
11574
- const resolvedPath = nodePath.resolve(publicRoot, `.${safePathname}`);
11575
- const isInsidePublicRoot = resolvedPath === publicRoot || resolvedPath.startsWith(`${publicRoot}${nodePath.sep}`);
11762
+ const resolvedPath = nodePath2.resolve(publicRoot, `.${safePathname}`);
11763
+ const isInsidePublicRoot = resolvedPath === publicRoot || resolvedPath.startsWith(`${publicRoot}${nodePath2.sep}`);
11576
11764
  if (isInsidePublicRoot) {
11577
11765
  try {
11578
11766
  const file = Bun.file(resolvedPath);
@@ -11853,7 +12041,7 @@ async function projectUsesImageBuiltins(root, scope) {
11853
12041
  for (const entry of entries) {
11854
12042
  if (entry.name.startsWith("."))
11855
12043
  continue;
11856
- const full = nodePath.join(dir, entry.name);
12044
+ const full = nodePath2.join(dir, entry.name);
11857
12045
  if (entry.isDirectory()) {
11858
12046
  if (!TEMPLATE_SCAN_SKIP.has(entry.name))
11859
12047
  pending.push(full);
@@ -11867,7 +12055,7 @@ async function projectUsesImageBuiltins(root, scope) {
11867
12055
  } catch {
11868
12056
  continue;
11869
12057
  }
11870
- const inComponents = nodePath.relative(root, dir).split(nodePath.sep).includes("components");
12058
+ const inComponents = nodePath2.relative(root, dir).split(nodePath2.sep).includes("components");
11871
12059
  const component = inComponents ? entry.name.slice(0, -".stx".length) : undefined;
11872
12060
  if (!component && IMAGE_BUILTIN_PATTERN.test(source))
11873
12061
  return true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-plugin-stx",
3
3
  "type": "module",
4
- "version": "0.2.736",
4
+ "version": "0.2.740",
5
5
  "description": "A Bun plugin that allows for using Laravel Blade-like syntax.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -74,7 +74,7 @@
74
74
  "typecheck": "bun tsc --noEmit"
75
75
  },
76
76
  "dependencies": {
77
- "@stacksjs/stx": "0.2.736",
78
- "stx-router": "0.2.736"
77
+ "@stacksjs/stx": "0.2.740",
78
+ "stx-router": "0.2.740"
79
79
  }
80
80
  }