@taujs/server 0.0.7 → 0.0.8

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
@@ -38,122 +38,27 @@ Integrated ViteDevServer HMR + Vite Runtime API run alongside tsx (TS eXecute) p
38
38
 
39
39
  ### Fastify
40
40
 
41
- ```
42
- import { SSRServer } from '@taujs/server;
43
-
44
- void (await fastify.register(SSRServer, {
45
- clientEntryClient: 'entry-client',
46
- clientEntryServer: 'entry-server',
47
- clientHtmlTemplate: 'index.html',
48
- clientRoot: path.resolve(__dirname, '../client'),
49
- routes,
50
- serviceRegistry,
51
- }));
52
- ```
41
+ https://github.com/aoede3/taujs/blob/main/src/server/index.ts
53
42
 
54
43
  Not utilising taujs [ τjs ] template? Add in your own `alias` object for your own particular setup e.g. `alias: { object }`
55
44
 
56
45
  ### React 'entry-client.tsx'
57
46
 
58
- ```
59
- import React from 'react';
60
- import { hydrateRoot } from 'react-dom/client';
61
- import { createSSRStore, SSRStoreProvider } from '@taujs/server/data-store';
62
-
63
- import AppBootstrap from './AppBootstrap';
64
-
65
- const bootstrap = () => {
66
- const initialDataPromise = Promise.resolve(window.__INITIAL_DATA__);
67
- const store = createSSRStore(initialDataPromise);
68
-
69
- hydrateRoot(
70
- document.getElementById('root') as HTMLElement,
71
- <SSRStoreProvider store={store}>
72
- <AppBootstrap />
73
- </SSRStoreProvider>,
74
- );
75
- };
76
-
77
- if (document.readyState !== 'loading') {
78
- bootstrap();
79
- } else {
80
- document.addEventListener('DOMContentLoaded', () => {
81
- bootstrap();
82
- });
83
- }
84
-
85
- ```
47
+ https://github.com/aoede3/taujs/blob/main/src/client/entry-client.tsx
86
48
 
87
49
  ### React 'entry-server.tsx'
88
50
 
89
51
  Extended pipe object with callbacks to @taujs/server enabling additional manipulation of HEAD content from client code
90
52
 
91
- ```
92
- import { ServerResponse } from 'node:http';
93
-
94
- import React from 'react';
95
- import { createSSRStore, SSRStoreProvider } from '@taujs/server/data-store';
96
- import { createStreamRenderer } from '@taujs/server/render';
97
-
98
- import AppBootstrap from '@client/AppBootstrap';
99
-
100
- import type { RenderCallbacks } from '@taujs/server';
101
-
102
- export const streamRender = (
103
- serverResponse: ServerResponse,
104
- { onHead, onFinish, onError }: RenderCallbacks,
105
- initialDataPromise: Promise<Record<string, unknown>>,
106
- bootstrapModules: string,
107
- ) => {
108
- const store = createSSRStore(initialDataPromise);
109
-
110
- const headContent = `
111
- <meta name="description" content="taujs [ τjs ]">
112
- <link rel="icon" type="image/svg+xml" href="/taujs.svg" />
113
- <title>taujs [ τjs ]</title>
114
- `;
115
-
116
- createStreamRenderer(
117
- serverResponse,
118
- { onHead, onFinish, onError },
119
- {
120
- appElement: (
121
- <SSRStoreProvider store={store}>
122
- <AppBootstrap />
123
- </SSRStoreProvider>
124
- ),
125
- bootstrapModules,
126
- getStoreSnapshot: store.getSnapshot,
127
- headContent,
128
- },
129
- );
130
- };
131
-
132
- ```
53
+ https://github.com/aoede3/taujs/blob/main/src/client/entry-server.tsx
133
54
 
134
55
  ### index.html
135
56
 
136
- ```
137
- <!DOCTYPE html>
138
- <html lang="en">
139
- <head>
140
- <meta charset="UTF-8" />
141
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
142
- <!--ssr-head-->
143
- </head>
144
- <body>
145
- <main id="root"><!--ssr-html--></main>
146
- </body>
147
- </html>
148
- ```
57
+ https://github.com/aoede3/taujs/blob/main/src/client/index.html
149
58
 
150
59
  ### client.d.ts
151
60
 
152
- ```
153
- interface Window {
154
- __INITIAL_DATA__: Record<string, unknown>;
155
- }
156
- ```
61
+ https://github.com/aoede3/taujs/blob/main/src/client/client.d.ts
157
62
 
158
63
  ### Routes
159
64
 
@@ -172,73 +77,12 @@ In supporting Option 2. there is a registry of services. More detail in 'Service
172
77
 
173
78
  Each routes 'path' is a simple URL regex as per below examples.
174
79
 
175
- ```
176
- import type { Route, RouteParams } from '@taujs/server';
177
-
178
- export const routes: Route<RouteParams>[] = [
179
- {
180
- path: '/',
181
- attributes: {
182
- fetch: async () => {
183
- return {
184
- url: 'http://localhost:5173/api/initial',
185
- options: {
186
- method: 'GET',
187
- },
188
- };
189
- },
190
- },
191
- },
192
- {
193
- path: '/:id',
194
- attributes: {
195
- fetch: async (params: RouteParams) => {
196
- return {
197
- url: `http://localhost:5173/api/initial/${params.id}`,
198
- options: {
199
- method: 'GET',
200
- },
201
- };
202
- },
203
- },
204
- },
205
- {
206
- path: '/:id/:another',
207
- attributes: {
208
- fetch: async (params: RouteParams) => {
209
- return {
210
- options: { params },
211
- serviceMethod: 'exampleMethod',
212
- serviceName: 'ServiceExample',
213
- };
214
- },
215
- },
216
- },
217
- ];
218
- ```
80
+ https://github.com/aoede3/taujs/blob/main/src/shared/routes/Routes.ts
219
81
 
220
82
  ### Service Registry
221
83
 
222
84
  In supporting internal calls via τjs a registry of available services and methods provides the linkage to your own architectural setup and developmental patterns
223
85
 
224
- ```
225
- import { ServiceExample } from './ServiceExample';
226
-
227
- import type { ServiceRegistry } from '@taujs/server';
228
-
229
- export const serviceRegistry: ServiceRegistry = {
230
- ServiceExample,
231
- };
232
- ```
233
-
234
- ```
235
- export const ServiceExample = {
236
- async exampleMethod(params: Record<string, unknown>): Promise<Record<string, unknown>> {
237
- return new Promise((resolve) => {
238
- setTimeout(() => {
239
- resolve({ hello: `world internal service call response with id: ${params.id} and another: ${params.another}` });
240
- }, 5500);
241
- });
242
- },
243
- };
244
- ```
86
+ https://github.com/aoede3/taujs/blob/main/src/server/services/ServiceRegistry.ts
87
+
88
+ https://github.com/aoede3/taujs/blob/main/src/server/services/ServiceExample.ts
package/dist/data.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import { ServerResponse } from 'node:http';
3
+
4
+ type SSRStore<T> = {
5
+ getSnapshot: () => T;
6
+ getServerSnapshot: () => T;
7
+ setData: (newData: T) => void;
8
+ subscribe: (callback: () => void) => () => void;
9
+ };
10
+ declare const createSSRStore: <T>(initialDataPromise: Promise<T>) => SSRStore<T>;
11
+ declare const SSRStoreProvider: React.FC<React.PropsWithChildren<{
12
+ store: SSRStore<Record<string, unknown>>;
13
+ }>>;
14
+ declare const useSSRStore: <T>() => SSRStore<T>;
15
+
16
+ type HydrateAppOptions = {
17
+ appComponent: React.ReactElement;
18
+ initialDataKey?: keyof Window;
19
+ rootElementId?: string;
20
+ debug?: boolean;
21
+ };
22
+ declare const hydrateApp: ({ appComponent, initialDataKey, rootElementId, debug }: HydrateAppOptions) => void;
23
+
24
+ type StreamRenderOptions = {
25
+ appComponent: React.ReactElement;
26
+ initialDataPromise: Promise<Record<string, unknown>>;
27
+ bootstrapModules: string;
28
+ headContent: string;
29
+ };
30
+ type RenderCallbacks = {
31
+ onHead: (headContent: string) => void;
32
+ onFinish: (initialDataResolved: unknown) => void;
33
+ onError: (error: unknown) => void;
34
+ };
35
+ declare const createStreamRenderer: (serverResponse: ServerResponse, { onHead, onFinish, onError }: RenderCallbacks, { appComponent, initialDataPromise, bootstrapModules, headContent }: StreamRenderOptions) => void;
36
+
37
+ export { type SSRStore, SSRStoreProvider, createSSRStore, createStreamRenderer, hydrateApp, useSSRStore };
package/dist/data.js ADDED
@@ -0,0 +1,142 @@
1
+ // src/SSRDataStore.tsx
2
+ import { createContext, useContext, useSyncExternalStore } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var createSSRStore = (initialDataPromise) => {
5
+ let currentData;
6
+ let status = "pending";
7
+ const subscribers = /* @__PURE__ */ new Set();
8
+ let resolvePromise = null;
9
+ const serverDataPromise = new Promise((resolve) => resolvePromise = resolve);
10
+ initialDataPromise.then((data) => {
11
+ currentData = data;
12
+ status = "success";
13
+ subscribers.forEach((callback) => callback());
14
+ if (resolvePromise) resolvePromise();
15
+ }).catch((error) => {
16
+ console.error("Failed to load initial data:", error);
17
+ status = "error";
18
+ });
19
+ const setData = (newData) => {
20
+ currentData = newData;
21
+ status = "success";
22
+ subscribers.forEach((callback) => callback());
23
+ if (resolvePromise) resolvePromise();
24
+ };
25
+ const subscribe = (callback) => {
26
+ subscribers.add(callback);
27
+ return () => subscribers.delete(callback);
28
+ };
29
+ const getSnapshot = () => {
30
+ if (status === "pending") {
31
+ throw initialDataPromise;
32
+ } else if (status === "error") {
33
+ throw new Error("An error occurred while fetching the data.");
34
+ }
35
+ return currentData;
36
+ };
37
+ const getServerSnapshot = () => {
38
+ if (status === "pending") {
39
+ throw serverDataPromise;
40
+ } else if (status === "error") {
41
+ throw new Error("Data is not available on the server.");
42
+ }
43
+ return currentData;
44
+ };
45
+ return { getSnapshot, getServerSnapshot, setData, subscribe };
46
+ };
47
+ var SSRStoreContext = createContext(null);
48
+ var SSRStoreProvider = ({ store, children }) => /* @__PURE__ */ jsx(SSRStoreContext.Provider, { value: store, children });
49
+ var useSSRStore = () => {
50
+ const store = useContext(SSRStoreContext);
51
+ if (!store) throw new Error("useSSRStore must be used within a SSRStoreProvider");
52
+ return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot);
53
+ };
54
+
55
+ // src/SSRHydration.tsx
56
+ import React2 from "react";
57
+ import { hydrateRoot } from "react-dom/client";
58
+
59
+ // src/utils/Logger.ts
60
+ var createLogger = (debug) => ({
61
+ log: (...args) => {
62
+ if (debug) console.log(...args);
63
+ },
64
+ warn: (...args) => {
65
+ if (debug) console.warn(...args);
66
+ },
67
+ error: (...args) => {
68
+ if (debug) console.error(...args);
69
+ }
70
+ });
71
+
72
+ // src/SSRHydration.tsx
73
+ import { jsx as jsx2 } from "react/jsx-runtime";
74
+ var hydrateApp = ({ appComponent, initialDataKey = "__INITIAL_DATA__", rootElementId = "root", debug = false }) => {
75
+ const { log, warn, error } = createLogger(debug);
76
+ const bootstrap = () => {
77
+ log("Hydration started");
78
+ const rootElement = document.getElementById(rootElementId);
79
+ if (!rootElement) {
80
+ error(`Root element with id "${rootElementId}" not found.`);
81
+ return;
82
+ }
83
+ const initialData = window[initialDataKey];
84
+ if (!initialData) {
85
+ warn(`Initial data key "${initialDataKey}" is undefined on window.`);
86
+ } else {
87
+ log("Initial data loaded:", initialData);
88
+ }
89
+ const initialDataPromise = Promise.resolve(initialData);
90
+ const store = createSSRStore(initialDataPromise);
91
+ log("Store created:", store);
92
+ hydrateRoot(
93
+ rootElement,
94
+ /* @__PURE__ */ jsx2(React2.StrictMode, { children: /* @__PURE__ */ jsx2(SSRStoreProvider, { store, children: appComponent }) })
95
+ );
96
+ log("Hydration completed");
97
+ };
98
+ if (document.readyState !== "loading") {
99
+ bootstrap();
100
+ } else {
101
+ document.addEventListener("DOMContentLoaded", bootstrap);
102
+ }
103
+ };
104
+
105
+ // src/SSRRender.tsx
106
+ import { Writable } from "node:stream";
107
+ import "react";
108
+ import { renderToPipeableStream } from "react-dom/server";
109
+ import { jsx as jsx3 } from "react/jsx-runtime";
110
+ var createStreamRenderer = (serverResponse, { onHead, onFinish, onError }, { appComponent, initialDataPromise, bootstrapModules, headContent }) => {
111
+ const store = createSSRStore(initialDataPromise);
112
+ const appElement = /* @__PURE__ */ jsx3(SSRStoreProvider, { store, children: appComponent });
113
+ const { pipe } = renderToPipeableStream(appElement, {
114
+ bootstrapModules: [bootstrapModules],
115
+ onShellReady() {
116
+ onHead(headContent);
117
+ pipe(
118
+ new Writable({
119
+ write(chunk, _encoding, callback) {
120
+ serverResponse.write(chunk, callback);
121
+ },
122
+ final(callback) {
123
+ onFinish(store.getSnapshot());
124
+ callback();
125
+ }
126
+ })
127
+ );
128
+ },
129
+ onAllReady() {
130
+ },
131
+ onError(error) {
132
+ onError(error);
133
+ }
134
+ });
135
+ };
136
+ export {
137
+ SSRStoreProvider,
138
+ createSSRStore,
139
+ createStreamRenderer,
140
+ hydrateApp,
141
+ useSSRStore
142
+ };
@@ -40,8 +40,9 @@ type Manifest = {
40
40
  assets?: string[];
41
41
  };
42
42
  };
43
+ type StreamRender = (serverResponse: ServerResponse, callbacks: RenderCallbacks, initialDataPromise: Promise<Record<string, unknown>>, bootstrapModules: string) => void;
43
44
  type RenderModule = {
44
- streamRender: (serverResponse: ServerResponse, callbacks: RenderCallbacks, initialDataPromise: Promise<Record<string, unknown>>, bootstrapModules: string) => void;
45
+ streamRender: StreamRender;
45
46
  };
46
47
  type RouteAttributes<Params = {}> = {
47
48
  fetch: (params?: Params, options?: RequestInit & {
@@ -66,4 +67,4 @@ interface InitialRouteParams extends Record<string, unknown> {
66
67
  type RouteParams = InitialRouteParams & Record<string, unknown>;
67
68
  type RoutePathsAndAttributes<Params = {}> = Omit<Route<Params>, 'element'>;
68
69
 
69
- export { type FetchConfig, type InitialRouteParams, type Manifest, type RenderCallbacks, type RenderModule, type Route, type RouteAttributes, type RouteParams, type RoutePathsAndAttributes, SSRServer, type SSRServerOptions, type ServiceRegistry };
70
+ export { type FetchConfig, type InitialRouteParams, type Manifest, type RenderCallbacks, type RenderModule, type Route, type RouteAttributes, type RouteParams, type RoutePathsAndAttributes, SSRServer, type SSRServerOptions, type ServiceRegistry, type StreamRender };
@@ -121,7 +121,7 @@ import { readFile } from "node:fs/promises";
121
121
  import path from "node:path";
122
122
  import { createViteRuntime } from "vite";
123
123
 
124
- // src/utils/index.ts
124
+ // src/utils/Utils.ts
125
125
  import { fileURLToPath } from "node:url";
126
126
  import { dirname, join } from "node:path";
127
127
  import { match } from "path-to-regexp";
package/package.json CHANGED
@@ -1,10 +1,17 @@
1
1
  {
2
+ "name": "@taujs/server",
3
+ "version": "0.0.8",
4
+ "description": "taujs | τjs",
2
5
  "author": "Aoede <taujs@aoede.uk.net> (https://www.aoede.uk.net)",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/aoede3/taujs-server",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/aoede3/taujs-server.git"
11
+ },
3
12
  "bugs": {
4
13
  "url": "https://github.com/aoede3/taujs-server/issues"
5
14
  },
6
- "description": "taujs | τjs",
7
- "homepage": "https://github.com/aoede3/taujs-server",
8
15
  "keywords": [
9
16
  "fastify",
10
17
  "typescript",
@@ -14,44 +21,23 @@
14
21
  "react",
15
22
  "ssr"
16
23
  ],
17
- "license": "MIT",
18
- "name": "@taujs/server",
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/aoede3/taujs-server.git"
22
- },
23
- "version": "0.0.7",
24
+ "type": "module",
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
24
27
  "exports": {
25
28
  ".": {
26
- "import": "./dist/SSRServer.js",
27
- "types": "./dist/SSRServer.d.ts"
28
- },
29
- "./data-store": {
30
- "import": "./dist/SSRDataStore.js",
31
- "types": "./dist/SSRDataStore.d.ts"
29
+ "import": "./dist/index.js",
30
+ "types": "./dist/index.d.ts"
32
31
  },
33
- "./render": {
34
- "import": "./dist/SSRRender.js",
35
- "types": "./dist/SSRRender.d.ts"
32
+ "./data": {
33
+ "import": "./dist/data.js",
34
+ "types": "./dist/data.d.ts"
36
35
  },
37
36
  "./package.json": "./package.json"
38
37
  },
39
38
  "files": [
40
39
  "dist"
41
40
  ],
42
- "main": "./dist/SSRServer.js",
43
- "types": "./dist/SSRServer.d.ts",
44
- "typesVersions": {
45
- "*": {
46
- "data-store": [
47
- "./dist/SSRDataStore.d.ts"
48
- ],
49
- "render": [
50
- "./dist/SSRRender.d.ts"
51
- ]
52
- }
53
- },
54
- "type": "module",
55
41
  "dependencies": {
56
42
  "@fastify/static": "^7.0.4",
57
43
  "path-to-regexp": "^8.1.0"
@@ -76,7 +62,6 @@
76
62
  "vitest": "^2.0.5"
77
63
  },
78
64
  "peerDependencies": {
79
- "@vitejs/plugin-react": "^4.3.1",
80
65
  "fastify": "^5.0.0",
81
66
  "react": "^18.3.1",
82
67
  "react-dom": "^18.3.1",
@@ -85,7 +70,7 @@
85
70
  },
86
71
  "scripts": {
87
72
  "build": "tsup",
88
- "ci": "npm run build && npm run check-format && npm run check-exports && npm run lint",
73
+ "ci": "npm run build && npm run check-format && npm run lint",
89
74
  "lint": "tsc",
90
75
  "test": "vitest run",
91
76
  "coverage": "vitest run --coverage",
@@ -1,15 +0,0 @@
1
- import React from 'react';
2
-
3
- type SSRStore<T> = {
4
- getSnapshot: () => T;
5
- getServerSnapshot: () => T;
6
- setData: (newData: T) => void;
7
- subscribe: (callback: () => void) => () => void;
8
- };
9
- declare const createSSRStore: <T>(initialDataPromise: Promise<T>) => SSRStore<T>;
10
- declare const SSRStoreProvider: React.FC<React.PropsWithChildren<{
11
- store: SSRStore<Record<string, unknown>>;
12
- }>>;
13
- declare const useSSRStore: <T>() => SSRStore<T>;
14
-
15
- export { type SSRStore, SSRStoreProvider, createSSRStore, useSSRStore };
@@ -1,58 +0,0 @@
1
- // src/SSRDataStore.tsx
2
- import { createContext, useContext, useSyncExternalStore } from "react";
3
- import { jsx } from "react/jsx-runtime";
4
- var createSSRStore = (initialDataPromise) => {
5
- let currentData;
6
- let status = "pending";
7
- const subscribers = /* @__PURE__ */ new Set();
8
- let resolvePromise = null;
9
- const serverDataPromise = new Promise((resolve) => resolvePromise = resolve);
10
- initialDataPromise.then((data) => {
11
- currentData = data;
12
- status = "success";
13
- subscribers.forEach((callback) => callback());
14
- if (resolvePromise) resolvePromise();
15
- }).catch((error) => {
16
- console.error("Failed to load initial data:", error);
17
- status = "error";
18
- });
19
- const setData = (newData) => {
20
- currentData = newData;
21
- status = "success";
22
- subscribers.forEach((callback) => callback());
23
- if (resolvePromise) resolvePromise();
24
- };
25
- const subscribe = (callback) => {
26
- subscribers.add(callback);
27
- return () => subscribers.delete(callback);
28
- };
29
- const getSnapshot = () => {
30
- if (status === "pending") {
31
- throw initialDataPromise;
32
- } else if (status === "error") {
33
- throw new Error("An error occurred while fetching the data.");
34
- }
35
- return currentData;
36
- };
37
- const getServerSnapshot = () => {
38
- if (status === "pending") {
39
- throw serverDataPromise;
40
- } else if (status === "error") {
41
- throw new Error("Data is not available on the server.");
42
- }
43
- return currentData;
44
- };
45
- return { getSnapshot, getServerSnapshot, setData, subscribe };
46
- };
47
- var SSRStoreContext = createContext(null);
48
- var SSRStoreProvider = ({ store, children }) => /* @__PURE__ */ jsx(SSRStoreContext.Provider, { value: store, children });
49
- var useSSRStore = () => {
50
- const store = useContext(SSRStoreContext);
51
- if (!store) throw new Error("useSSRStore must be used within a SSRStoreProvider");
52
- return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot);
53
- };
54
- export {
55
- SSRStoreProvider,
56
- createSSRStore,
57
- useSSRStore
58
- };
@@ -1,18 +0,0 @@
1
- import { ServerResponse } from 'node:http';
2
- import React from 'react';
3
-
4
- type RenderCallbacks = {
5
- onHead: (headContent: string) => void;
6
- onFinish: (initialDataResolved: unknown) => void;
7
- onError: (error: unknown) => void;
8
- };
9
-
10
- type StreamRender = {
11
- appElement: React.JSX.Element;
12
- bootstrapModules: string;
13
- headContent: string;
14
- getStoreSnapshot: () => unknown;
15
- };
16
- declare const createStreamRenderer: (serverResponse: ServerResponse, { onHead, onFinish, onError }: RenderCallbacks, { appElement, bootstrapModules, headContent, getStoreSnapshot }: StreamRender) => void;
17
-
18
- export { createStreamRenderer };
package/dist/SSRRender.js DELETED
@@ -1,31 +0,0 @@
1
- // src/SSRRender.ts
2
- import { Writable } from "node:stream";
3
- import "react";
4
- import { renderToPipeableStream } from "react-dom/server";
5
- var createStreamRenderer = (serverResponse, { onHead, onFinish, onError }, { appElement, bootstrapModules, headContent, getStoreSnapshot }) => {
6
- const { pipe } = renderToPipeableStream(appElement, {
7
- bootstrapModules: [bootstrapModules],
8
- onShellReady() {
9
- onHead(headContent);
10
- pipe(
11
- new Writable({
12
- write(chunk, _encoding, callback) {
13
- serverResponse.write(chunk, callback);
14
- },
15
- final(callback) {
16
- onFinish(getStoreSnapshot());
17
- callback();
18
- }
19
- })
20
- );
21
- },
22
- onAllReady() {
23
- },
24
- onError(error) {
25
- onError(error);
26
- }
27
- });
28
- };
29
- export {
30
- createStreamRenderer
31
- };