@taujs/react 0.1.0 → 0.1.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/README.md CHANGED
@@ -11,3 +11,7 @@
11
11
  React Renderer: CSR, SSR, Streaming SSR
12
12
 
13
13
  https://taujs.dev/
14
+
15
+ A lightweight, production-ready React SSR library with streaming capabilities, built for modern TypeScript applications. Designed as part of the taujs (τjs) ecosystem but fully standalone and runtime-agnostic.
16
+
17
+ https://taujs.dev/renderers/react/
package/dist/index.d.ts CHANGED
@@ -22,10 +22,10 @@ type UILogger = {
22
22
  error: (...args: unknown[]) => void;
23
23
  };
24
24
  type ServerLogs = {
25
- info: (message: string, meta?: unknown) => void;
26
- warn: (message: string, meta?: unknown) => void;
27
- error: (message: string, meta?: unknown) => void;
28
- debug?: (category: string, message: string, meta?: unknown) => void;
25
+ info: (meta?: unknown, message?: string) => void;
26
+ warn: (meta?: unknown, message?: string) => void;
27
+ error: (meta?: unknown, message?: string) => void;
28
+ debug?: (category: string, meta?: unknown, message?: string) => void;
29
29
  child?: (ctx: Record<string, unknown>) => ServerLogs;
30
30
  isDebugEnabled?: (category: string) => boolean;
31
31
  };
@@ -56,6 +56,10 @@ type StreamOptions = {
56
56
  /** Whether to use cork/uncork for batched writes (default: true) */
57
57
  useCork?: boolean;
58
58
  };
59
+ type HeadContext<T extends Record<string, unknown> = Record<string, unknown>> = {
60
+ data: T;
61
+ meta: Record<string, unknown>;
62
+ };
59
63
  type SSRResult = {
60
64
  headContent: string;
61
65
  appHtml: string;
@@ -68,10 +72,7 @@ declare function createRenderer<T extends Record<string, unknown>>({ appComponen
68
72
  appComponent: (props: {
69
73
  location: string;
70
74
  }) => React.ReactElement;
71
- headContent: (ctx: {
72
- data: T;
73
- meta: Record<string, unknown>;
74
- }) => string;
75
+ headContent: (ctx: HeadContext<T>) => string;
75
76
  enableDebug?: boolean;
76
77
  logger?: LoggerLike;
77
78
  streamOptions?: StreamOptions;
@@ -85,4 +86,70 @@ declare function createRenderer<T extends Record<string, unknown>>({ appComponen
85
86
  };
86
87
  };
87
88
 
88
- export { type HydrateAppOptions, type RenderCallbacks, type SSRStore, SSRStoreProvider, type ServerLogs, type StreamOptions, createRenderer, createSSRStore, hydrateApp, useSSRStore };
89
+ /**
90
+ * τjs Client Data Bridge
91
+ *
92
+ * Provides framework-agnostic primitives for accessing route data:
93
+ * - SSR hydration (window.__INITIAL_DATA__)
94
+ * - Client-side fetch (/__taujs/data endpoint)
95
+ *
96
+ * This is a transport layer only. For data orchestration (caching, refetch, etc.),
97
+ * use TanStack Query or similar.
98
+ */
99
+ type RouteData = Record<string, unknown>;
100
+ /**
101
+ * Error thrown when fetchRouteData receives a non-2xx response.
102
+ * Contains structured error information from the server.
103
+ */
104
+ declare class RouteDataError extends Error {
105
+ readonly status: number;
106
+ readonly statusText: string;
107
+ readonly code?: string;
108
+ readonly body?: unknown;
109
+ constructor(message: string, opts: {
110
+ status: number;
111
+ statusText: string;
112
+ code?: string;
113
+ body?: unknown;
114
+ });
115
+ }
116
+ /**
117
+ * Read SSR boot data from window.__INITIAL_DATA__ exactly once.
118
+ * Subsequent calls return null (forces client-side fetch).
119
+ *
120
+ * Returns null on server (typeof window === 'undefined').
121
+ */
122
+ declare function readInitialDataOnce<T extends RouteData = RouteData>(): T | null;
123
+ /**
124
+ * Fetch route data from the τjs data endpoint.
125
+ *
126
+ * Calls: GET /__taujs/data?url=<pathname>
127
+ * Returns: { data: T }
128
+ *
129
+ * Throws RouteDataError on non-2xx responses with structured error info.
130
+ *
131
+ * @example
132
+ * const data = await fetchRouteData('/app/dashboard');
133
+ *
134
+ * @example
135
+ * try {
136
+ * const data = await fetchRouteData('/app/dashboard');
137
+ * } catch (err) {
138
+ * if (err instanceof RouteDataError && err.status === 404) {
139
+ * // Handle not found
140
+ * }
141
+ * }
142
+ */
143
+ declare function fetchRouteData<T extends RouteData = RouteData>(pathname: string, init?: RequestInit): Promise<T>;
144
+ /**
145
+ * Get the current browser path (pathname + search).
146
+ * Does not include hash.
147
+ *
148
+ * Returns null on server (typeof window === 'undefined').
149
+ *
150
+ * @example
151
+ * const path = getCurrentPath(); // "/app/dashboard?tab=overview"
152
+ */
153
+ declare function getCurrentPath(): string | null;
154
+
155
+ export { type HeadContext, type HydrateAppOptions, type RenderCallbacks, type RouteData, RouteDataError, type SSRStore, SSRStoreProvider, type ServerLogs, type StreamOptions, createRenderer, createSSRStore, fetchRouteData, getCurrentPath, hydrateApp, readInitialDataOnce, useSSRStore };
package/dist/index.js CHANGED
@@ -90,10 +90,16 @@ var splitMsgAndMeta = (args) => {
90
90
  };
91
91
  function createUILogger(logger, opts = {}) {
92
92
  const { debugCategory = "ssr", context, preferDebug = false, enableDebug = false } = opts;
93
- if (!enableDebug) return { log: () => {
94
- }, warn: () => {
95
- }, error: () => {
96
- } };
93
+ if (!enableDebug) {
94
+ return {
95
+ log: () => {
96
+ },
97
+ warn: () => {
98
+ },
99
+ error: () => {
100
+ }
101
+ };
102
+ }
97
103
  const looksServer = !!logger && ("info" in logger || "debug" in logger || "child" in logger || "isDebugEnabled" in logger);
98
104
  if (looksServer) {
99
105
  let s = logger;
@@ -103,11 +109,11 @@ function createUILogger(logger, opts = {}) {
103
109
  } catch {
104
110
  }
105
111
  }
106
- const info = s.info ? s.info.bind(s) : (m, meta) => meta ? console.log(m, meta) : console.log(m);
107
- const warn = s.warn ? s.warn.bind(s) : (m, meta) => meta ? console.warn(m, meta) : console.warn(m);
108
- const error = s.error ? s.error.bind(s) : (m, meta) => meta ? console.error(m, meta) : console.error(m);
109
- const debug = s.debug ? s.debug.bind(s) : void 0;
110
- const isDebugEnabled = s.isDebugEnabled ? s.isDebugEnabled.bind(s) : void 0;
112
+ const info = s.info ? (msg, meta) => s.info(meta, msg) : (msg, meta) => meta ? console.log(msg, meta) : console.log(msg);
113
+ const warn = s.warn ? (msg, meta) => s.warn(meta, msg) : (msg, meta) => meta ? console.warn(msg, meta) : console.warn(msg);
114
+ const error = s.error ? (msg, meta) => s.error(meta, msg) : (msg, meta) => meta ? console.error(msg, meta) : console.error(msg);
115
+ const debug = s.debug ? (category, msg, meta) => s.debug(category, meta, msg) : void 0;
116
+ const isDebugEnabled = s.isDebugEnabled ? (category) => s.isDebugEnabled(category) : void 0;
111
117
  return {
112
118
  log: (...args) => {
113
119
  const { msg, meta } = splitMsgAndMeta(args);
@@ -345,6 +351,8 @@ function createStreamController(writable, logger) {
345
351
 
346
352
  // src/SSRRender.tsx
347
353
  import { jsx as jsx3 } from "react/jsx-runtime";
354
+ var NOOP = () => {
355
+ };
348
356
  function createRenderer({
349
357
  appComponent,
350
358
  headContent,
@@ -385,7 +393,13 @@ function createRenderer({
385
393
  }
386
394
  };
387
395
  const renderStream = (writable, callbacks = {}, initialData, location, bootstrapModules, meta = {}, cspNonce, signal, opts) => {
388
- const { onAllReady, onError, onHead, onShellReady, onFinish } = callbacks;
396
+ const cb = {
397
+ onHead: callbacks.onHead ?? NOOP,
398
+ onShellReady: callbacks.onShellReady ?? NOOP,
399
+ onAllReady: callbacks.onAllReady ?? NOOP,
400
+ onFinish: callbacks.onFinish ?? NOOP,
401
+ onError: callbacks.onError ?? NOOP
402
+ };
389
403
  const { log, warn, error } = createUILogger(opts?.logger ?? logger, {
390
404
  debugCategory: "ssr",
391
405
  context: { scope: "react-streaming" },
@@ -412,17 +426,17 @@ function createRenderer({
412
426
  const { cleanup: guardsCleanup } = wireWritableGuards(writable, {
413
427
  benignAbort: (why) => controller.benignAbort(why),
414
428
  fatalAbort: (err) => {
415
- onError?.(err);
429
+ cb.onError(err);
416
430
  controller.fatalAbort(err);
417
431
  },
418
- onError,
432
+ onError: cb.onError,
419
433
  onFinish: () => controller.complete("Stream finished (normal completion)")
420
434
  });
421
435
  controller.setGuardsCleanup(guardsCleanup);
422
436
  const stopShellTimer = startShellTimer(effectiveShellTimeout, () => {
423
437
  if (controller.isAborted) return;
424
438
  const timeoutErr = new Error(`Shell not ready after ${effectiveShellTimeout}ms`);
425
- onError?.(timeoutErr);
439
+ cb.onError(timeoutErr);
426
440
  controller.fatalAbort(timeoutErr);
427
441
  });
428
442
  controller.setStopShellTimer(stopShellTimer);
@@ -451,16 +465,15 @@ function createRenderer({
451
465
  }
452
466
  }
453
467
  const head = headContent({ data: headData ?? {}, meta });
454
- const canCork = effectiveUseCork && typeof writable?.cork === "function" && typeof writable?.uncork === "function";
455
- if (canCork) {
468
+ const canCork = effectiveUseCork && typeof writable.cork === "function" && typeof writable.uncork === "function";
469
+ if (canCork)
456
470
  try {
457
471
  writable.cork();
458
472
  } catch {
459
473
  }
460
- }
461
474
  let wroteOk = true;
462
475
  try {
463
- const res = writable?.write ? writable.write(head) : true;
476
+ const res = typeof writable.write === "function" ? writable.write(head) : true;
464
477
  wroteOk = res !== false;
465
478
  } finally {
466
479
  if (canCork) {
@@ -472,21 +485,25 @@ function createRenderer({
472
485
  }
473
486
  let forceWait = false;
474
487
  try {
475
- const ret = onHead?.(head);
476
- forceWait = ret === false;
488
+ forceWait = cb.onHead(head) === false;
477
489
  } catch (cbErr) {
478
490
  warn("onHead callback threw:", cbErr);
479
491
  }
480
492
  const startPipe = () => stream.pipe(writable);
481
- if (forceWait || !wroteOk) writable?.once?.("drain", startPipe);
482
- else startPipe();
493
+ if (forceWait || !wroteOk) {
494
+ if (typeof writable.once === "function") {
495
+ writable.once("drain", startPipe);
496
+ } else {
497
+ startPipe();
498
+ }
499
+ } else startPipe();
483
500
  try {
484
- onShellReady?.();
501
+ cb.onShellReady();
485
502
  } catch (cbErr) {
486
503
  warn("onShellReady callback threw:", cbErr);
487
504
  }
488
505
  } catch (err) {
489
- onError?.(err);
506
+ cb.onError(err);
490
507
  controller.fatalAbort(err);
491
508
  }
492
509
  },
@@ -496,18 +513,18 @@ function createRenderer({
496
513
  const deliver = () => {
497
514
  try {
498
515
  const data = store.getSnapshot();
499
- onAllReady?.(data);
500
- onFinish?.(data);
516
+ cb.onAllReady(data);
517
+ cb.onFinish(data);
501
518
  } catch (thrown) {
502
519
  if (thrown && typeof thrown.then === "function") {
503
520
  thrown.then(deliver).catch((e) => {
504
521
  error("Data promise rejected:", e);
505
- onError?.(e);
522
+ cb.onError(e);
506
523
  controller.fatalAbort(e);
507
524
  });
508
525
  } else {
509
526
  error("Unexpected throw from getSnapshot:", thrown);
510
- onError?.(thrown);
527
+ cb.onError(thrown);
511
528
  controller.fatalAbort(thrown);
512
529
  }
513
530
  }
@@ -520,24 +537,24 @@ function createRenderer({
520
537
  stopShellTimer();
521
538
  } catch {
522
539
  }
523
- onError?.(err);
540
+ cb.onError(err);
524
541
  controller.fatalAbort(err);
525
542
  },
526
543
  onError(err) {
527
544
  if (controller.isAborted) return;
528
545
  const msg = String(err?.message ?? "");
529
- warn?.("React stream error:", msg);
546
+ warn("React stream error:", msg);
530
547
  if (isBenignStreamErr(err)) {
531
548
  controller.benignAbort("Client disconnected before stream finished");
532
549
  return;
533
550
  }
534
- onError?.(err);
551
+ cb.onError(err);
535
552
  controller.fatalAbort(err);
536
553
  }
537
554
  });
538
555
  controller.setStreamAbort(() => stream.abort());
539
556
  } catch (err) {
540
- onError?.(err);
557
+ cb.onError(err);
541
558
  controller.fatalAbort(err);
542
559
  }
543
560
  return {
@@ -548,10 +565,73 @@ function createRenderer({
548
565
  };
549
566
  return { renderSSR, renderStream };
550
567
  }
568
+
569
+ // src/RouteData.ts
570
+ var RouteDataError = class _RouteDataError extends Error {
571
+ status;
572
+ statusText;
573
+ code;
574
+ body;
575
+ constructor(message, opts) {
576
+ super(message);
577
+ this.name = "RouteDataError";
578
+ this.status = opts.status;
579
+ this.statusText = opts.statusText;
580
+ this.code = opts.code;
581
+ this.body = opts.body;
582
+ Object.setPrototypeOf(this, _RouteDataError.prototype);
583
+ }
584
+ };
585
+ var INITIAL_DATA_KEY = "__INITIAL_DATA__";
586
+ function readInitialDataOnce() {
587
+ if (typeof window === "undefined") return null;
588
+ const w = window;
589
+ const data = w[INITIAL_DATA_KEY];
590
+ if (!data) return null;
591
+ delete w[INITIAL_DATA_KEY];
592
+ return data;
593
+ }
594
+ async function fetchRouteData(pathname, init) {
595
+ if (!pathname) {
596
+ throw new Error("fetchRouteData: pathname is required");
597
+ }
598
+ const url = `/__taujs/data?url=${encodeURIComponent(pathname)}`;
599
+ const res = await fetch(url, {
600
+ credentials: "include",
601
+ ...init
602
+ });
603
+ if (!res.ok) {
604
+ let body2;
605
+ try {
606
+ body2 = await res.json();
607
+ } catch {
608
+ const text = await res.text().catch(() => "");
609
+ body2 = { error: text };
610
+ }
611
+ const json = body2;
612
+ throw new RouteDataError(json.error ?? `Request failed: ${res.status}`, {
613
+ status: res.status,
614
+ statusText: json.statusText ?? res.statusText,
615
+ code: json.code,
616
+ body: body2
617
+ });
618
+ }
619
+ const body = await res.json();
620
+ return body.data ?? {};
621
+ }
622
+ function getCurrentPath() {
623
+ if (typeof window === "undefined") return null;
624
+ const { pathname, search } = window.location;
625
+ return `${pathname}${search}`;
626
+ }
551
627
  export {
628
+ RouteDataError,
552
629
  SSRStoreProvider,
553
630
  createRenderer,
554
631
  createSSRStore,
632
+ fetchRouteData,
633
+ getCurrentPath,
555
634
  hydrateApp,
635
+ readInitialDataOnce,
556
636
  useSSRStore
557
637
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taujs/react",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "taujs | τjs",
5
5
  "author": "Aoede <taujs@aoede.uk.net> (https://www.aoede.uk.net)",
6
6
  "license": "MIT",