@stacksjs/stx 0.2.152 → 0.2.153

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/dist/app-shell.js CHANGED
@@ -114,9 +114,36 @@ ${routerScript}
114
114
  </body>
115
115
  </html>`;
116
116
  }
117
+ function startsDocument(html) {
118
+ let i = 0;
119
+ while (i < html.length) {
120
+ const rest = html.slice(i), ws = rest.match(/^\s+/);
121
+ if (ws) {
122
+ i += ws[0].length;
123
+ continue;
124
+ }
125
+ if (rest.startsWith("<!--")) {
126
+ const end = html.indexOf("-->", i);
127
+ if (end === -1)
128
+ return !1;
129
+ i = end + 3;
130
+ continue;
131
+ }
132
+ const scriptOpen = rest.match(/^<script\b[^>]*>/i);
133
+ if (scriptOpen) {
134
+ const end = html.toLowerCase().indexOf("</script>", i + scriptOpen[0].length);
135
+ if (end === -1)
136
+ return !1;
137
+ i = end + 9;
138
+ continue;
139
+ }
140
+ return /^<!DOCTYPE\b/i.test(rest) || /^<html[\s>]/i.test(rest);
141
+ }
142
+ return !1;
143
+ }
117
144
  export function stripDocumentWrapper(html, options = {}) {
118
145
  const trimmed = html.trim();
119
- if (!/<!DOCTYPE\b/i.test(trimmed) && !/^<html[\s>]/i.test(trimmed))
146
+ if (!startsDocument(trimmed))
120
147
  return html;
121
148
  const headStyles = [], headMatch = trimmed.match(/<head\b[^>]*>([\s\S]*?)<\/head>/i);
122
149
  if (headMatch) {
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Reactive localStorage with automatic serialization.
3
3
  *
4
+ * @deprecated Returns `{ value, remove }` — a third shape for this name, and
5
+ * one the signals runtime cannot see. Use `useLocalStorage` from
6
+ * `@stacksjs/stx`, which returns a Signal (`s()` reads, `s.set(v)` writes) on
7
+ * every import path (stacksjs/stx#1797).
8
+ *
4
9
  * @example
5
10
  * ```typescript
6
11
  * const theme = useLocalStorage('theme', 'dark')
@@ -12,6 +17,9 @@ export declare function useLocalStorage<T>(key: string, defaultValue: T): { valu
12
17
  /**
13
18
  * Reactive sessionStorage with automatic serialization.
14
19
  *
20
+ * @deprecated See {@link useLocalStorage} above — use the Signal-returning
21
+ * version from `@stacksjs/stx` (stacksjs/stx#1797).
22
+ *
15
23
  * @example
16
24
  * ```typescript
17
25
  * const token = useSessionStorage('auth_token', null)
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The current process's build id.
3
+ *
4
+ * Stable for the lifetime of the process, which is the unit that matters: a
5
+ * `bun --watch` restart produces a new process and therefore a new id, while a
6
+ * production build stamps every page it renders with one value.
7
+ *
8
+ * `STX_BUILD_ID` overrides it, for deployments that render across several
9
+ * processes and need them to agree.
10
+ */
11
+ export declare function getBuildId(): string;
12
+ /** @internal test-only — force the id, or with `null` restore generation. */
13
+ export declare function __setBuildIdForTest(id: string | null): void;
14
+ /**
15
+ * Add `<meta name="stx-build">` to `<head>` if it isn't already there.
16
+ *
17
+ * Idempotent, and a no-op for output with no `<head>` — SPA fragments carry the
18
+ * id in a response header instead, since they have nowhere to put a meta.
19
+ */
20
+ export declare function injectBuildId(html: string, buildId?: string): string;
21
+ /**
22
+ * Per-process build identity, used to detect runtime/fragment skew
23
+ * (stacksjs/stx#1772).
24
+ *
25
+ * Under `bun --watch`, a loaded page holds the client runtime from build N. A
26
+ * file save restarts the server, and the next SPA navigation fetches a fragment
27
+ * rendered by build N+1. Fragments legitimately carry preserved `{{ }}`
28
+ * moustaches for client-side binding, so any drift in the scoped-script or
29
+ * binding format between the two builds leaves the OLD runtime unable to
30
+ * hydrate the NEW fragment: literal moustaches on screen, dead `:show`, stale
31
+ * canvas. It is sporadic and never reproduces from a clean boot, which is
32
+ * exactly its signature.
33
+ *
34
+ * Stamping the identity on both sides lets the router notice the mismatch and
35
+ * fall back to a full page navigation — the same thing Next and Vite's HMR
36
+ * clients do on a version mismatch. It does not fix format drift; it makes the
37
+ * whole class of skew symptoms impossible to observe.
38
+ *
39
+ * The comparison is deliberately conservative: the router only acts when BOTH
40
+ * sides are known. A missing id means "no information", never "mismatch".
41
+ * That keeps statically hosted output (no headers to send) and any consumer on
42
+ * an older server working exactly as before.
43
+ *
44
+ * @module build-id
45
+ */
46
+ /** `<meta name="stx-build">` on every rendered page. */
47
+ export declare const BUILD_ID_META: 'stx-build';
48
+ /** Response header carrying the id that rendered an SPA fragment. */
49
+ export declare const BUILD_ID_HEADER: 'X-STX-Build';
@@ -0,0 +1,20 @@
1
+ export const BUILD_ID_META = "stx-build", BUILD_ID_HEADER = "X-STX-Build";
2
+ let cached = null;
3
+ export function getBuildId() {
4
+ if (cached === null)
5
+ cached = process.env.STX_BUILD_ID || `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
6
+ return cached;
7
+ }
8
+ export function __setBuildIdForTest(id) {
9
+ cached = id;
10
+ }
11
+ export function injectBuildId(html, buildId = getBuildId()) {
12
+ if (html.includes(`name="${BUILD_ID_META}"`))
13
+ return html;
14
+ const headOpen = /<head\b[^>]*>/i.exec(html);
15
+ if (!headOpen)
16
+ return html;
17
+ const insertAt = headOpen.index + headOpen[0].length;
18
+ return `${html.slice(0, insertAt)}
19
+ <meta name="${BUILD_ID_META}" content="${buildId}">${html.slice(insertAt)}`;
20
+ }