@c9up/aurora 0.1.3 → 0.1.5

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,28 @@
1
+ /**
2
+ * Browser DX helpers — navigation + a typed `localStorage` wrapper.
3
+ *
4
+ * All are SSR-safe: on the server (no `window` / `localStorage`) navigation is
5
+ * a no-op and storage reads return `null`, so the same code runs during SSR
6
+ * without `typeof window` guards at every call site. Node-free — part of the
7
+ * client barrel.
8
+ */
9
+ /** Navigate to `url` with a full page load. No-op during SSR. */
10
+ export declare function redirect(url: string): void;
11
+ /**
12
+ * Navigate to `url`, replacing the current history entry (no back-button trap —
13
+ * use after a login/logout so "back" doesn't return to the form). No-op on SSR.
14
+ */
15
+ export declare function replace(url: string): void;
16
+ /** Reload the current page. No-op during SSR. */
17
+ export declare function reload(): void;
18
+ /**
19
+ * Typed, SSR-safe `localStorage` wrapper. Values are JSON-serialised; reads
20
+ * return `null` on the server, on a missing key, or on malformed JSON. Writes
21
+ * swallow quota / private-mode errors so a full storage never crashes the app.
22
+ */
23
+ export declare const storage: {
24
+ get<T>(key: string): T | null;
25
+ set(key: string, value: unknown): void;
26
+ remove(key: string): void;
27
+ clear(): void;
28
+ };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Browser DX helpers — navigation + a typed `localStorage` wrapper.
3
+ *
4
+ * All are SSR-safe: on the server (no `window` / `localStorage`) navigation is
5
+ * a no-op and storage reads return `null`, so the same code runs during SSR
6
+ * without `typeof window` guards at every call site. Node-free — part of the
7
+ * client barrel.
8
+ */
9
+ /** Navigate to `url` with a full page load. No-op during SSR. */
10
+ export function redirect(url) {
11
+ if (typeof window !== "undefined") {
12
+ window.location.href = url;
13
+ }
14
+ }
15
+ /**
16
+ * Navigate to `url`, replacing the current history entry (no back-button trap —
17
+ * use after a login/logout so "back" doesn't return to the form). No-op on SSR.
18
+ */
19
+ export function replace(url) {
20
+ if (typeof window !== "undefined") {
21
+ window.location.replace(url);
22
+ }
23
+ }
24
+ /** Reload the current page. No-op during SSR. */
25
+ export function reload() {
26
+ if (typeof window !== "undefined") {
27
+ window.location.reload();
28
+ }
29
+ }
30
+ /**
31
+ * Typed, SSR-safe `localStorage` wrapper. Values are JSON-serialised; reads
32
+ * return `null` on the server, on a missing key, or on malformed JSON. Writes
33
+ * swallow quota / private-mode errors so a full storage never crashes the app.
34
+ */
35
+ export const storage = {
36
+ get(key) {
37
+ if (typeof localStorage === "undefined")
38
+ return null;
39
+ const raw = localStorage.getItem(key);
40
+ if (raw === null)
41
+ return null;
42
+ try {
43
+ return JSON.parse(raw);
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ },
49
+ set(key, value) {
50
+ if (typeof localStorage === "undefined")
51
+ return;
52
+ try {
53
+ localStorage.setItem(key, JSON.stringify(value));
54
+ }
55
+ catch {
56
+ // QuotaExceededError / Safari private mode — best-effort write.
57
+ }
58
+ },
59
+ remove(key) {
60
+ if (typeof localStorage !== "undefined") {
61
+ localStorage.removeItem(key);
62
+ }
63
+ },
64
+ clear() {
65
+ if (typeof localStorage !== "undefined") {
66
+ localStorage.clear();
67
+ }
68
+ },
69
+ };
package/dist/hydrate.js CHANGED
@@ -307,11 +307,21 @@ function hydrateTextSlot(commentMarker, value, cleanups, mountHooks, markerCurso
307
307
  }
308
308
  return;
309
309
  }
310
- const textNode = commentMarker.nodeType === 3 /* TEXT */
310
+ let textNode = commentMarker.nodeType === 3 /* TEXT */
311
311
  ? commentMarker
312
312
  : commentMarker.previousSibling?.nodeType === 3
313
313
  ? commentMarker.previousSibling
314
314
  : null;
315
+ if (!textNode &&
316
+ commentMarker.nodeType === 8 /* Comment */ &&
317
+ commentMarker.parentNode) {
318
+ // Empty SSR text slot: a `<!---->` placeholder holds the position
319
+ // (see ssr.ts). Materialize the reactive text node there — node
320
+ // count stays 1, so sibling slot paths remain aligned.
321
+ const fresh = (commentMarker.ownerDocument ?? document).createTextNode("");
322
+ commentMarker.parentNode.replaceChild(fresh, commentMarker);
323
+ textNode = fresh;
324
+ }
315
325
  if (!textNode)
316
326
  return;
317
327
  const dispose = effect(() => {
package/dist/index.d.ts CHANGED
@@ -1,12 +1,9 @@
1
- export { AuroraManager, type AuroraManagerConfig } from "./AuroraManager.js";
1
+ export { redirect, reload, replace, storage } from "./browser.js";
2
2
  export { component, onMount, onUnmount } from "./component.js";
3
3
  export { html, isTemplateResult } from "./html.js";
4
4
  export { hydrate } from "./hydrate.js";
5
- export { type PageFactory, Pages, type PagesConfig, } from "./Pages.js";
6
5
  export { batch, effect, isSignal, memo, onCleanup, type ReadSignal, type Signal, signal, untrack, } from "./reactive.js";
7
6
  export { type Disposer, render } from "./render.js";
8
7
  export { type AuroraHttpContext, type AuroraResponse, type AuroraRouteConfig, auroraRoute, } from "./route.js";
9
- export { type RenderHttpContext, type RenderPageOptions, type RenderResponse, renderPage, } from "./server/renderPage.js";
10
- export { type AssetsHttpContext, type AssetsRequest, type AssetsResponse, type ServeAssetsOptions, serveAssets, } from "./server/serveAssets.js";
11
8
  export { renderToString } from "./ssr.js";
12
9
  export type { TemplateResult } from "./types.js";
package/dist/index.js CHANGED
@@ -1,12 +1,14 @@
1
- // ─── Inertia-shape server surface ─────────────────────────────────
2
- export { AuroraManager } from "./AuroraManager.js";
1
+ // ─── Client surface (node-free safe to bundle for the browser) ──────
2
+ //
3
+ // Server-only exports (AuroraManager, Pages, renderPage, serveAssets) that pull
4
+ // node:fs / node:path / node:url live in `@c9up/aurora/server`. Keeping them off
5
+ // this barrel is what lets a browser bundle import the client primitives without
6
+ // the bundler dragging Node built-ins through the import graph.
7
+ export { redirect, reload, replace, storage } from "./browser.js";
3
8
  export { component, onMount, onUnmount } from "./component.js";
4
9
  export { html, isTemplateResult } from "./html.js";
5
10
  export { hydrate } from "./hydrate.js";
6
- export { Pages, } from "./Pages.js";
7
11
  export { batch, effect, isSignal, memo, onCleanup, signal, untrack, } from "./reactive.js";
8
12
  export { render } from "./render.js";
9
13
  export { auroraRoute, } from "./route.js";
10
- export { renderPage, } from "./server/renderPage.js";
11
- export { serveAssets, } from "./server/serveAssets.js";
12
14
  export { renderToString } from "./ssr.js";
@@ -0,0 +1,4 @@
1
+ export { AuroraManager, type AuroraManagerConfig } from "./AuroraManager.js";
2
+ export { type PageFactory, Pages, type PagesConfig } from "./Pages.js";
3
+ export { type RenderHttpContext, type RenderPageOptions, type RenderResponse, renderPage, } from "./server/renderPage.js";
4
+ export { type AssetsHttpContext, type AssetsRequest, type AssetsResponse, type ServeAssetsOptions, serveAssets, } from "./server/serveAssets.js";
package/dist/server.js ADDED
@@ -0,0 +1,10 @@
1
+ // ─── Server-only surface (imports node:fs / node:path / node:url) ─────
2
+ //
3
+ // Kept OUT of the package's main barrel (`@c9up/aurora`) so a browser bundle
4
+ // importing client primitives (component/html/hydrate/render) never pulls the
5
+ // Node built-ins through the import graph. Server code imports from
6
+ // `@c9up/aurora/server`; the client `.` entry stays node-free.
7
+ export { AuroraManager } from "./AuroraManager.js";
8
+ export { Pages } from "./Pages.js";
9
+ export { renderPage, } from "./server/renderPage.js";
10
+ export { serveAssets, } from "./server/serveAssets.js";
package/dist/ssr.js CHANGED
@@ -75,8 +75,18 @@ function stringifyTemplateResult(result) {
75
75
  out += stringifyValue(value, false);
76
76
  out += `<!--${SLOT_END}-->`;
77
77
  }
78
+ else if (inAttr) {
79
+ out += stringifyValue(value, true);
80
+ }
78
81
  else {
79
- out += stringifyValue(value, inAttr);
82
+ // Text-region scalar slot. An empty result (e.g. `cond ? x : ''`)
83
+ // would emit NO node and desync the path-based hydration of the
84
+ // following sibling slots (their @input/@submit bindings break).
85
+ // Emit an empty-comment placeholder so the position is preserved
86
+ // — lit-html / Solid do the same; hydration materializes the text
87
+ // node there.
88
+ const text = stringifyValue(value, false);
89
+ out += text === "" ? "<!---->" : text;
80
90
  }
81
91
  }
82
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/aurora",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Aurora — reactive UI runtime for the Ream framework. Tagged-template DOM, signal-based state, isomorphic SSR + hydration, zero build step.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,6 +30,10 @@
30
30
  "./hydrate": {
31
31
  "types": "./dist/hydrate.d.ts",
32
32
  "import": "./dist/hydrate.js"
33
+ },
34
+ "./server": {
35
+ "types": "./dist/server.d.ts",
36
+ "import": "./dist/server.js"
33
37
  }
34
38
  },
35
39
  "peerDependencies": {
package/src/browser.ts ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Browser DX helpers — navigation + a typed `localStorage` wrapper.
3
+ *
4
+ * All are SSR-safe: on the server (no `window` / `localStorage`) navigation is
5
+ * a no-op and storage reads return `null`, so the same code runs during SSR
6
+ * without `typeof window` guards at every call site. Node-free — part of the
7
+ * client barrel.
8
+ */
9
+
10
+ /** Navigate to `url` with a full page load. No-op during SSR. */
11
+ export function redirect(url: string): void {
12
+ if (typeof window !== "undefined") {
13
+ window.location.href = url;
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Navigate to `url`, replacing the current history entry (no back-button trap —
19
+ * use after a login/logout so "back" doesn't return to the form). No-op on SSR.
20
+ */
21
+ export function replace(url: string): void {
22
+ if (typeof window !== "undefined") {
23
+ window.location.replace(url);
24
+ }
25
+ }
26
+
27
+ /** Reload the current page. No-op during SSR. */
28
+ export function reload(): void {
29
+ if (typeof window !== "undefined") {
30
+ window.location.reload();
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Typed, SSR-safe `localStorage` wrapper. Values are JSON-serialised; reads
36
+ * return `null` on the server, on a missing key, or on malformed JSON. Writes
37
+ * swallow quota / private-mode errors so a full storage never crashes the app.
38
+ */
39
+ export const storage = {
40
+ get<T>(key: string): T | null {
41
+ if (typeof localStorage === "undefined") return null;
42
+ const raw = localStorage.getItem(key);
43
+ if (raw === null) return null;
44
+ try {
45
+ return JSON.parse(raw);
46
+ } catch {
47
+ return null;
48
+ }
49
+ },
50
+ set(key: string, value: unknown): void {
51
+ if (typeof localStorage === "undefined") return;
52
+ try {
53
+ localStorage.setItem(key, JSON.stringify(value));
54
+ } catch {
55
+ // QuotaExceededError / Safari private mode — best-effort write.
56
+ }
57
+ },
58
+ remove(key: string): void {
59
+ if (typeof localStorage !== "undefined") {
60
+ localStorage.removeItem(key);
61
+ }
62
+ },
63
+ clear(): void {
64
+ if (typeof localStorage !== "undefined") {
65
+ localStorage.clear();
66
+ }
67
+ },
68
+ };
package/src/hydrate.ts CHANGED
@@ -422,12 +422,26 @@ function hydrateTextSlot(
422
422
  }
423
423
  return;
424
424
  }
425
- const textNode =
425
+ let textNode =
426
426
  commentMarker.nodeType === 3 /* TEXT */
427
427
  ? (commentMarker as Text)
428
428
  : commentMarker.previousSibling?.nodeType === 3
429
429
  ? (commentMarker.previousSibling as Text)
430
430
  : null;
431
+ if (
432
+ !textNode &&
433
+ commentMarker.nodeType === 8 /* Comment */ &&
434
+ commentMarker.parentNode
435
+ ) {
436
+ // Empty SSR text slot: a `<!---->` placeholder holds the position
437
+ // (see ssr.ts). Materialize the reactive text node there — node
438
+ // count stays 1, so sibling slot paths remain aligned.
439
+ const fresh = (commentMarker.ownerDocument ?? document).createTextNode(
440
+ "",
441
+ );
442
+ commentMarker.parentNode.replaceChild(fresh, commentMarker);
443
+ textNode = fresh;
444
+ }
431
445
  if (!textNode) return;
432
446
  const dispose = effect(() => {
433
447
  const v = fn();
package/src/index.ts CHANGED
@@ -1,13 +1,13 @@
1
- // ─── Inertia-shape server surface ─────────────────────────────────
2
- export { AuroraManager, type AuroraManagerConfig } from "./AuroraManager.js";
1
+ // ─── Client surface (node-free safe to bundle for the browser) ──────
2
+ //
3
+ // Server-only exports (AuroraManager, Pages, renderPage, serveAssets) that pull
4
+ // node:fs / node:path / node:url live in `@c9up/aurora/server`. Keeping them off
5
+ // this barrel is what lets a browser bundle import the client primitives without
6
+ // the bundler dragging Node built-ins through the import graph.
7
+ export { redirect, reload, replace, storage } from "./browser.js";
3
8
  export { component, onMount, onUnmount } from "./component.js";
4
9
  export { html, isTemplateResult } from "./html.js";
5
10
  export { hydrate } from "./hydrate.js";
6
- export {
7
- type PageFactory,
8
- Pages,
9
- type PagesConfig,
10
- } from "./Pages.js";
11
11
  export {
12
12
  batch,
13
13
  effect,
@@ -26,18 +26,5 @@ export {
26
26
  type AuroraRouteConfig,
27
27
  auroraRoute,
28
28
  } from "./route.js";
29
- export {
30
- type RenderHttpContext,
31
- type RenderPageOptions,
32
- type RenderResponse,
33
- renderPage,
34
- } from "./server/renderPage.js";
35
- export {
36
- type AssetsHttpContext,
37
- type AssetsRequest,
38
- type AssetsResponse,
39
- type ServeAssetsOptions,
40
- serveAssets,
41
- } from "./server/serveAssets.js";
42
29
  export { renderToString } from "./ssr.js";
43
30
  export type { TemplateResult } from "./types.js";
package/src/server.ts ADDED
@@ -0,0 +1,22 @@
1
+ // ─── Server-only surface (imports node:fs / node:path / node:url) ─────
2
+ //
3
+ // Kept OUT of the package's main barrel (`@c9up/aurora`) so a browser bundle
4
+ // importing client primitives (component/html/hydrate/render) never pulls the
5
+ // Node built-ins through the import graph. Server code imports from
6
+ // `@c9up/aurora/server`; the client `.` entry stays node-free.
7
+
8
+ export { AuroraManager, type AuroraManagerConfig } from "./AuroraManager.js";
9
+ export { type PageFactory, Pages, type PagesConfig } from "./Pages.js";
10
+ export {
11
+ type RenderHttpContext,
12
+ type RenderPageOptions,
13
+ type RenderResponse,
14
+ renderPage,
15
+ } from "./server/renderPage.js";
16
+ export {
17
+ type AssetsHttpContext,
18
+ type AssetsRequest,
19
+ type AssetsResponse,
20
+ type ServeAssetsOptions,
21
+ serveAssets,
22
+ } from "./server/serveAssets.js";
package/src/ssr.ts CHANGED
@@ -78,8 +78,17 @@ function stringifyTemplateResult(result: TemplateResult): string {
78
78
  out += `<!--${SLOT_START}-->`;
79
79
  out += stringifyValue(value, false);
80
80
  out += `<!--${SLOT_END}-->`;
81
+ } else if (inAttr) {
82
+ out += stringifyValue(value, true);
81
83
  } else {
82
- out += stringifyValue(value, inAttr);
84
+ // Text-region scalar slot. An empty result (e.g. `cond ? x : ''`)
85
+ // would emit NO node and desync the path-based hydration of the
86
+ // following sibling slots (their @input/@submit bindings break).
87
+ // Emit an empty-comment placeholder so the position is preserved
88
+ // — lit-html / Solid do the same; hydration materializes the text
89
+ // node there.
90
+ const text = stringifyValue(value, false);
91
+ out += text === "" ? "<!---->" : text;
83
92
  }
84
93
  }
85
94
  }