@taujs/server 0.2.6 → 0.3.0
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/LICENSE +3 -1
- package/README.md +2 -0
- package/dist/SSRServer-CmMH3qwx.d.ts +150 -0
- package/dist/build.d.ts +17 -8
- package/dist/build.js +101 -19
- package/dist/config.d.ts +37 -0
- package/dist/config.js +146 -0
- package/dist/index.d.ts +22 -2
- package/dist/index.js +104 -19
- package/dist/security/csp.d.ts +4 -131
- package/dist/security/csp.js +2 -2
- package/package.json +7 -3
package/LICENSE
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
taujs [ τjs ] Orchestration System
|
|
4
|
+
Author: John Smith
|
|
5
|
+
Copyright (c) Aoede Ltd 2024-present
|
|
4
6
|
|
|
5
7
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
8
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { ServerResponse } from 'node:http';
|
|
2
|
+
import { FastifyRequest, FastifyReply, HookHandlerDoneFunction, FastifyPluginAsync } from 'fastify';
|
|
3
|
+
import { PluginOption } from 'vite';
|
|
4
|
+
|
|
5
|
+
type CSPDirectives = Record<string, string[]>;
|
|
6
|
+
interface CSPOptions {
|
|
7
|
+
directives?: CSPDirectives;
|
|
8
|
+
exposeNonce?: (req: FastifyRequest, nonce: string) => void;
|
|
9
|
+
generateCSP?: (directives: CSPDirectives, nonce: string) => string;
|
|
10
|
+
}
|
|
11
|
+
declare const defaultGenerateCSP: (directives: CSPDirectives, nonce: string) => string;
|
|
12
|
+
declare const generateNonce: () => string;
|
|
13
|
+
declare const createCSPHook: (options?: CSPOptions) => (req: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) => void;
|
|
14
|
+
declare const getRequestNonce: (req: FastifyRequest) => string | undefined;
|
|
15
|
+
declare const applyCSP: (security: SSRServerOptions["security"], reply: FastifyReply) => string | undefined;
|
|
16
|
+
|
|
17
|
+
declare const TEMPLATE: {
|
|
18
|
+
readonly defaultEntryClient: "entry-client";
|
|
19
|
+
readonly defaultEntryServer: "entry-server";
|
|
20
|
+
readonly defaultHtmlTemplate: "index.html";
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* taujs [ τjs ] Orchestration System
|
|
25
|
+
* (c) 2024-present Aoede Ltd
|
|
26
|
+
* Author: John Smith
|
|
27
|
+
*
|
|
28
|
+
* Licensed under the MIT License — attribution appreciated.
|
|
29
|
+
* Part of the taujs [ τjs ] system for declarative, build-time orchestration of microfrontend applications,
|
|
30
|
+
* including SSR, streaming, and middleware composition.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
declare const createMaps: () => {
|
|
34
|
+
bootstrapModules: Map<string, string>;
|
|
35
|
+
cssLinks: Map<string, string>;
|
|
36
|
+
manifests: Map<string, Manifest>;
|
|
37
|
+
preloadLinks: Map<string, string>;
|
|
38
|
+
renderModules: Map<string, RenderModule>;
|
|
39
|
+
ssrManifests: Map<string, SSRManifest>;
|
|
40
|
+
templates: Map<string, string>;
|
|
41
|
+
};
|
|
42
|
+
declare const processConfigs: (configs: Config[], baseClientRoot: string, templateDefaults: typeof TEMPLATE) => ProcessedConfig[];
|
|
43
|
+
declare const SSRServer: FastifyPluginAsync<SSRServerOptions>;
|
|
44
|
+
type Config = {
|
|
45
|
+
appId: string;
|
|
46
|
+
entryPoint: string;
|
|
47
|
+
entryClient?: string;
|
|
48
|
+
entryServer?: string;
|
|
49
|
+
htmlTemplate?: string;
|
|
50
|
+
};
|
|
51
|
+
type ProcessedConfig = {
|
|
52
|
+
appId: string;
|
|
53
|
+
clientRoot: string;
|
|
54
|
+
entryClient: string;
|
|
55
|
+
entryPoint: string;
|
|
56
|
+
entryServer: string;
|
|
57
|
+
htmlTemplate: string;
|
|
58
|
+
plugins?: PluginOption[];
|
|
59
|
+
};
|
|
60
|
+
type SSRServerOptions = {
|
|
61
|
+
alias?: Record<string, string>;
|
|
62
|
+
clientRoot: string;
|
|
63
|
+
configs: Config[];
|
|
64
|
+
routes: Route<RouteParams>[];
|
|
65
|
+
serviceRegistry: ServiceRegistry;
|
|
66
|
+
security?: {
|
|
67
|
+
csp?: {
|
|
68
|
+
directives?: CSPDirectives;
|
|
69
|
+
generateCSP?: (directives: CSPDirectives, nonce: string) => string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
isDebug?: boolean;
|
|
73
|
+
};
|
|
74
|
+
type ServiceMethod = (params: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
75
|
+
type NamedService = Record<string, ServiceMethod>;
|
|
76
|
+
type ServiceRegistry = Record<string, NamedService>;
|
|
77
|
+
type RenderCallbacks = {
|
|
78
|
+
onHead: (headContent: string) => void;
|
|
79
|
+
onFinish: (initialDataResolved: unknown) => void;
|
|
80
|
+
onError: (error: unknown) => void;
|
|
81
|
+
};
|
|
82
|
+
type FetchConfig = {
|
|
83
|
+
url?: string;
|
|
84
|
+
options: RequestInit & {
|
|
85
|
+
params?: Record<string, unknown>;
|
|
86
|
+
};
|
|
87
|
+
serviceName?: string;
|
|
88
|
+
serviceMethod?: string;
|
|
89
|
+
};
|
|
90
|
+
type SSRManifest = {
|
|
91
|
+
[key: string]: string[];
|
|
92
|
+
};
|
|
93
|
+
type ManifestEntry = {
|
|
94
|
+
file: string;
|
|
95
|
+
src?: string;
|
|
96
|
+
isDynamicEntry?: boolean;
|
|
97
|
+
imports?: string[];
|
|
98
|
+
css?: string[];
|
|
99
|
+
assets?: string[];
|
|
100
|
+
};
|
|
101
|
+
type Manifest = {
|
|
102
|
+
[key: string]: ManifestEntry;
|
|
103
|
+
};
|
|
104
|
+
type RenderSSR = (initialDataResolved: Record<string, unknown>, location: string, meta?: Record<string, unknown>) => Promise<{
|
|
105
|
+
headContent: string;
|
|
106
|
+
appHtml: string;
|
|
107
|
+
}>;
|
|
108
|
+
type RenderStream = (serverResponse: ServerResponse, callbacks: RenderCallbacks, initialDataPromise: Promise<Record<string, unknown>>, location: string, bootstrapModules?: string, meta?: Record<string, unknown>) => void;
|
|
109
|
+
type RenderModule = {
|
|
110
|
+
renderSSR: RenderSSR;
|
|
111
|
+
renderStream: RenderStream;
|
|
112
|
+
};
|
|
113
|
+
type BaseMiddleware = {
|
|
114
|
+
auth?: {
|
|
115
|
+
required: boolean;
|
|
116
|
+
redirect?: string;
|
|
117
|
+
roles?: string[];
|
|
118
|
+
strategy?: string;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
type RouteAttributes<Params = {}, Middleware = BaseMiddleware> = {
|
|
122
|
+
render: 'ssr';
|
|
123
|
+
hydrate?: boolean;
|
|
124
|
+
meta?: Record<string, unknown>;
|
|
125
|
+
middleware?: Middleware;
|
|
126
|
+
fetch?: (params?: Params, options?: RequestInit & {
|
|
127
|
+
params?: Record<string, unknown>;
|
|
128
|
+
}) => Promise<FetchConfig>;
|
|
129
|
+
} | {
|
|
130
|
+
render: 'streaming';
|
|
131
|
+
hydrate?: never;
|
|
132
|
+
meta: Record<string, unknown>;
|
|
133
|
+
middleware?: Middleware;
|
|
134
|
+
fetch?: (params?: Params, options?: RequestInit & {
|
|
135
|
+
params?: Record<string, unknown>;
|
|
136
|
+
}) => Promise<FetchConfig>;
|
|
137
|
+
};
|
|
138
|
+
type Route<Params = {}> = {
|
|
139
|
+
attr?: RouteAttributes<Params>;
|
|
140
|
+
path: string;
|
|
141
|
+
appId?: string;
|
|
142
|
+
};
|
|
143
|
+
interface InitialRouteParams extends Record<string, unknown> {
|
|
144
|
+
serviceName?: string;
|
|
145
|
+
serviceMethod?: string;
|
|
146
|
+
}
|
|
147
|
+
type RouteParams = InitialRouteParams & Record<string, unknown>;
|
|
148
|
+
type RoutePathsAndAttributes<Params = {}> = Omit<Route<Params>, 'element'>;
|
|
149
|
+
|
|
150
|
+
export { type BaseMiddleware as B, type Config as C, type FetchConfig as F, type InitialRouteParams as I, type ManifestEntry as M, type NamedService as N, type ProcessedConfig as P, type Route as R, SSRServer as S, TEMPLATE as T, type RouteParams as a, type RouteAttributes as b, createMaps as c, type SSRServerOptions as d, type ServiceMethod as e, type ServiceRegistry as f, type RenderCallbacks as g, type SSRManifest as h, type Manifest as i, type RenderSSR as j, type RenderStream as k, type RenderModule as l, type RoutePathsAndAttributes as m, type CSPDirectives as n, type CSPOptions as o, processConfigs as p, defaultGenerateCSP as q, generateNonce as r, createCSPHook as s, getRequestNonce as t, applyCSP as u };
|
package/dist/build.d.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AppConfig } from './config.js';
|
|
2
|
+
import 'vite';
|
|
3
|
+
import './SSRServer-CmMH3qwx.js';
|
|
4
|
+
import 'node:http';
|
|
5
|
+
import 'fastify';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* taujs [ τjs ] Orchestration System
|
|
9
|
+
* (c) 2024-present Aoede Ltd
|
|
10
|
+
* Author: John Smith
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the MIT License — attribution appreciated.
|
|
13
|
+
* Part of the taujs [ τjs ] system for declarative, build-time orchestration of microfrontend applications,
|
|
14
|
+
* including SSR, streaming, and middleware composition.
|
|
15
|
+
*/
|
|
2
16
|
|
|
3
|
-
type Config = {
|
|
4
|
-
appId: string;
|
|
5
|
-
entryPoint: string;
|
|
6
|
-
plugins?: PluginOption[];
|
|
7
|
-
};
|
|
8
17
|
declare function taujsBuild({ configs, projectRoot, clientBaseDir, isSSRBuild, }: {
|
|
9
|
-
configs:
|
|
18
|
+
configs: AppConfig[];
|
|
10
19
|
projectRoot: string;
|
|
11
20
|
clientBaseDir: string;
|
|
12
21
|
isSSRBuild?: boolean;
|
|
13
22
|
}): Promise<void>;
|
|
14
23
|
|
|
15
|
-
export {
|
|
24
|
+
export { taujsBuild };
|
package/dist/build.js
CHANGED
|
@@ -198,6 +198,26 @@ var import_picocolors = __toESM(require_picocolors(), 1);
|
|
|
198
198
|
import { readFile } from "fs/promises";
|
|
199
199
|
import path2 from "path";
|
|
200
200
|
|
|
201
|
+
// src/utils/Logger.ts
|
|
202
|
+
var createLogger = (debug) => ({
|
|
203
|
+
log: (...args) => {
|
|
204
|
+
if (debug) console.log(...args);
|
|
205
|
+
},
|
|
206
|
+
warn: (...args) => {
|
|
207
|
+
if (debug) console.warn(...args);
|
|
208
|
+
},
|
|
209
|
+
error: (...args) => {
|
|
210
|
+
if (debug) console.error(...args);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
var debugLog = (logger, message, req) => {
|
|
214
|
+
const prefix = "[\u03C4js]";
|
|
215
|
+
const method = req?.method ?? "";
|
|
216
|
+
const url = req?.url ?? "";
|
|
217
|
+
const tag = method && url ? `${method} ${url}` : "";
|
|
218
|
+
logger.log(`${prefix} ${tag} ${message}`);
|
|
219
|
+
};
|
|
220
|
+
|
|
201
221
|
// src/constants.ts
|
|
202
222
|
var RENDERTYPE = {
|
|
203
223
|
ssr: "ssr",
|
|
@@ -219,6 +239,32 @@ var DEV_CSP_DIRECTIVES = {
|
|
|
219
239
|
"img-src": ["'self'", "data:"]
|
|
220
240
|
};
|
|
221
241
|
|
|
242
|
+
// src/security/auth.ts
|
|
243
|
+
function createAuthHook(routes, isDebug) {
|
|
244
|
+
const logger = createLogger(Boolean(isDebug));
|
|
245
|
+
return async function authHook(req, reply) {
|
|
246
|
+
const url = new URL(req.url, `http://${req.headers.host}`).pathname;
|
|
247
|
+
const matched = routes.find((r) => r.path === url);
|
|
248
|
+
const authConfig = matched?.attr?.middleware?.auth;
|
|
249
|
+
if (!authConfig?.required) {
|
|
250
|
+
debugLog(logger, "Auth not required for route", req);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (typeof req.server.authenticate !== "function") {
|
|
254
|
+
req.log.warn('Route requires auth but no "authenticate" decorator is defined on Fastify.');
|
|
255
|
+
return reply.status(500).send("Server misconfiguration: auth decorator missing.");
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
debugLog(logger, "Invoking authenticate(...)", req);
|
|
259
|
+
await req.server.authenticate(req, reply);
|
|
260
|
+
debugLog(logger, "Authentication successful", req);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
debugLog(logger, "Authentication failed", req);
|
|
263
|
+
return reply.send(err);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
222
268
|
// src/security/csp.ts
|
|
223
269
|
import crypto from "crypto";
|
|
224
270
|
var defaultGenerateCSP = (directives, nonce) => {
|
|
@@ -237,7 +283,7 @@ var defaultGenerateCSP = (directives, nonce) => {
|
|
|
237
283
|
return Object.entries(merged).map(([key, values]) => `${key} ${values.join(" ")}`).join("; ");
|
|
238
284
|
};
|
|
239
285
|
var generateNonce = () => crypto.randomBytes(16).toString("base64");
|
|
240
|
-
var
|
|
286
|
+
var createCSPHook = (options = {}) => (req, reply, done) => {
|
|
241
287
|
const nonce = generateNonce();
|
|
242
288
|
const directives = options.directives ?? DEV_CSP_DIRECTIVES;
|
|
243
289
|
const generate = options.generateCSP ?? defaultGenerateCSP;
|
|
@@ -260,6 +306,26 @@ var applyCSP = (security, reply) => {
|
|
|
260
306
|
return nonce;
|
|
261
307
|
};
|
|
262
308
|
|
|
309
|
+
// src/security/verifyMiddleware.ts
|
|
310
|
+
var isAuthRequired = (r) => r.attr?.middleware?.auth?.required === true;
|
|
311
|
+
var hasAuthenticate = (app) => typeof app.authenticate === "function";
|
|
312
|
+
var verifyContracts = (app, routes, contracts, isDebug) => {
|
|
313
|
+
const logger = createLogger(Boolean(isDebug));
|
|
314
|
+
for (const contract of contracts) {
|
|
315
|
+
const isUsed = routes.some(contract.required);
|
|
316
|
+
if (!isUsed) {
|
|
317
|
+
debugLog(logger, `Middleware "${contract.key}" not used in any routes`);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (!contract.verify(app)) {
|
|
321
|
+
const error = new Error(`[\u03C4js] ${contract.errorMessage}`);
|
|
322
|
+
logger.error(error.message);
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
debugLog(logger, `Middleware "${contract.key}" verified \u2713`);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
263
329
|
// src/utils/Utils.ts
|
|
264
330
|
import { dirname, join } from "path";
|
|
265
331
|
import "path";
|
|
@@ -330,16 +396,15 @@ function renderPreloadLink(file) {
|
|
|
330
396
|
return "";
|
|
331
397
|
}
|
|
332
398
|
}
|
|
333
|
-
var callServiceMethod = async (
|
|
334
|
-
const service =
|
|
335
|
-
if (service
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
throw new Error(`Service method ${serviceMethod} does not exist on ${serviceName}`);
|
|
399
|
+
var callServiceMethod = async (registry, serviceName, methodName, params) => {
|
|
400
|
+
const service = registry[serviceName];
|
|
401
|
+
if (!service) throw new Error(`Service ${String(serviceName)} does not exist in the registry`);
|
|
402
|
+
const method = service[methodName];
|
|
403
|
+
if (typeof method !== "function") throw new Error(`Service method ${String(methodName)} does not exist on ${String(serviceName)}`);
|
|
404
|
+
const data = await method(params);
|
|
405
|
+
if (typeof data !== "object" || data === null)
|
|
406
|
+
throw new Error(`Expected object response from ${String(serviceName)}.${String(methodName)}, but got ${typeof data}`);
|
|
407
|
+
return data;
|
|
343
408
|
};
|
|
344
409
|
var fetchData = async ({ url, options }) => {
|
|
345
410
|
if (url) {
|
|
@@ -357,10 +422,10 @@ var fetchInitialData = async (attr, params, serviceRegistry) => {
|
|
|
357
422
|
headers: { "Content-Type": "application/json" },
|
|
358
423
|
params
|
|
359
424
|
}).then(async (data) => {
|
|
360
|
-
if (data.serviceName && data.serviceMethod)
|
|
425
|
+
if (data.serviceName && data.serviceMethod && typeof data.serviceName === "string" && typeof data.serviceMethod === "string")
|
|
361
426
|
return await callServiceMethod(serviceRegistry, data.serviceName, data.serviceMethod, data.options?.params ?? {});
|
|
362
|
-
|
|
363
|
-
|
|
427
|
+
if (data.url && typeof data.url === "string") return await fetchData(data);
|
|
428
|
+
throw new Error("Invalid fetch configuration: must have either serviceName+serviceMethod or url");
|
|
364
429
|
}).catch((error) => {
|
|
365
430
|
console.error("Error fetching initial data:", error);
|
|
366
431
|
throw error;
|
|
@@ -433,6 +498,7 @@ var processConfigs = (configs, baseClientRoot, templateDefaults) => {
|
|
|
433
498
|
};
|
|
434
499
|
var SSRServer = (0, import_fastify_plugin.default)(
|
|
435
500
|
async (app, opts) => {
|
|
501
|
+
const logger = createLogger(opts.isDebug ?? false);
|
|
436
502
|
const { alias, configs, routes, serviceRegistry, isDebug, clientRoot: baseClientRoot } = opts;
|
|
437
503
|
const { bootstrapModules, cssLinks, manifests, preloadLinks, renderModules, ssrManifests, templates } = createMaps();
|
|
438
504
|
const processedConfigs = processConfigs(configs, baseClientRoot, TEMPLATE);
|
|
@@ -466,6 +532,19 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
466
532
|
}
|
|
467
533
|
}
|
|
468
534
|
let viteDevServer;
|
|
535
|
+
verifyContracts(
|
|
536
|
+
app,
|
|
537
|
+
routes,
|
|
538
|
+
[
|
|
539
|
+
{
|
|
540
|
+
key: "auth",
|
|
541
|
+
required: isAuthRequired,
|
|
542
|
+
verify: hasAuthenticate,
|
|
543
|
+
errorMessage: "Routes require auth but Fastify instance is missing `.authenticate` decorator."
|
|
544
|
+
}
|
|
545
|
+
],
|
|
546
|
+
opts.isDebug
|
|
547
|
+
);
|
|
469
548
|
await app.register(import("@fastify/static"), {
|
|
470
549
|
index: false,
|
|
471
550
|
prefix: "/",
|
|
@@ -474,11 +553,12 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
474
553
|
});
|
|
475
554
|
app.addHook(
|
|
476
555
|
"onRequest",
|
|
477
|
-
|
|
556
|
+
createCSPHook({
|
|
478
557
|
directives: opts.security?.csp?.directives,
|
|
479
558
|
generateCSP: opts.security?.csp?.generateCSP
|
|
480
559
|
})
|
|
481
560
|
);
|
|
561
|
+
app.addHook("onRequest", createAuthHook(routes));
|
|
482
562
|
if (isDevelopment) {
|
|
483
563
|
const { createServer } = await import("vite");
|
|
484
564
|
viteDevServer = await createServer({
|
|
@@ -496,10 +576,10 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
496
576
|
{
|
|
497
577
|
name: "taujs-development-server-debug-logging",
|
|
498
578
|
configureServer(server) {
|
|
499
|
-
|
|
579
|
+
logger.log(import_picocolors.default.green("\u03C4js development server debug started."));
|
|
500
580
|
server.middlewares.use((req, res, next) => {
|
|
501
|
-
|
|
502
|
-
res.on("finish", () =>
|
|
581
|
+
logger.log(import_picocolors.default.cyan(`\u2190 rx: ${req.url}`));
|
|
582
|
+
res.on("finish", () => logger.log(import_picocolors.default.yellow(`\u2192 tx: ${req.url}`)));
|
|
503
583
|
next();
|
|
504
584
|
});
|
|
505
585
|
}
|
|
@@ -583,7 +663,9 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
583
663
|
let aggregateHeadContent = headContent;
|
|
584
664
|
if (ssrManifest && preloadLink) aggregateHeadContent += preloadLink;
|
|
585
665
|
if (manifest && cssLink) aggregateHeadContent += cssLink;
|
|
586
|
-
const
|
|
666
|
+
const shouldHydrate = attr?.hydrate !== false;
|
|
667
|
+
const bootstrapScriptTag = shouldHydrate ? `<script nonce="${nonce}" type="module" src="${bootstrapModule}" defer></script>` : "";
|
|
668
|
+
const fullHtml = template.replace(SSRTAG.ssrHead, aggregateHeadContent).replace(SSRTAG.ssrHtml, `${appHtml}${initialDataScript}${bootstrapScriptTag}`);
|
|
587
669
|
return reply.status(200).header("Content-Type", "text/html").send(fullHtml);
|
|
588
670
|
} else {
|
|
589
671
|
const { renderStream } = renderModule;
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { PluginOption } from 'vite';
|
|
2
|
+
import { R as Route, a as RouteParams, b as RouteAttributes } from './SSRServer-CmMH3qwx.js';
|
|
3
|
+
import 'node:http';
|
|
4
|
+
import 'fastify';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* taujs [ τjs ] Orchestration System
|
|
8
|
+
* (c) 2024-present Aoede Ltd
|
|
9
|
+
* Author: John Smith
|
|
10
|
+
*
|
|
11
|
+
* Licensed under the MIT License — attribution appreciated.
|
|
12
|
+
* Part of the taujs [ τjs ] system for declarative, build-time orchestration of microfrontend applications,
|
|
13
|
+
* including SSR, streaming, and middleware composition.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type AppRoute = Omit<Route<RouteParams>, 'appId'> & {
|
|
17
|
+
attr?: RouteAttributes;
|
|
18
|
+
};
|
|
19
|
+
type AppConfig = {
|
|
20
|
+
appId: string;
|
|
21
|
+
entryPoint: string;
|
|
22
|
+
plugins?: PluginOption[];
|
|
23
|
+
routes?: AppRoute[];
|
|
24
|
+
};
|
|
25
|
+
type TaujsConfig = {
|
|
26
|
+
apps: AppConfig[];
|
|
27
|
+
};
|
|
28
|
+
declare const extractBuildConfigs: (config: {
|
|
29
|
+
apps: {
|
|
30
|
+
appId: string;
|
|
31
|
+
entryPoint: string;
|
|
32
|
+
plugins?: PluginOption[];
|
|
33
|
+
}[];
|
|
34
|
+
}) => AppConfig[];
|
|
35
|
+
declare function extractRoutes(taujsConfig: TaujsConfig): Route<RouteParams>[];
|
|
36
|
+
|
|
37
|
+
export { type AppConfig, type AppRoute, type TaujsConfig, extractBuildConfigs, extractRoutes };
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
19
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
20
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
21
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
22
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
23
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
24
|
+
mod
|
|
25
|
+
));
|
|
26
|
+
|
|
27
|
+
// node_modules/picocolors/picocolors.js
|
|
28
|
+
var require_picocolors = __commonJS({
|
|
29
|
+
"node_modules/picocolors/picocolors.js"(exports, module) {
|
|
30
|
+
"use strict";
|
|
31
|
+
var p = process || {};
|
|
32
|
+
var argv = p.argv || [];
|
|
33
|
+
var env = p.env || {};
|
|
34
|
+
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
35
|
+
var formatter = (open, close, replace = open) => (input) => {
|
|
36
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
37
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
38
|
+
};
|
|
39
|
+
var replaceClose = (string, close, replace, index) => {
|
|
40
|
+
let result = "", cursor = 0;
|
|
41
|
+
do {
|
|
42
|
+
result += string.substring(cursor, index) + replace;
|
|
43
|
+
cursor = index + close.length;
|
|
44
|
+
index = string.indexOf(close, cursor);
|
|
45
|
+
} while (~index);
|
|
46
|
+
return result + string.substring(cursor);
|
|
47
|
+
};
|
|
48
|
+
var createColors = (enabled = isColorSupported) => {
|
|
49
|
+
let f = enabled ? formatter : () => String;
|
|
50
|
+
return {
|
|
51
|
+
isColorSupported: enabled,
|
|
52
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
53
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
54
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
55
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
56
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
57
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
58
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
59
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
60
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
61
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
62
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
63
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
64
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
65
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
66
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
67
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
68
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
69
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
70
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
71
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
72
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
73
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
74
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
75
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
76
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
77
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
78
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
79
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
80
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
81
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
82
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
83
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
84
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
85
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
86
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
87
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
88
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
89
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
90
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
91
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
92
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
module.exports = createColors();
|
|
96
|
+
module.exports.createColors = createColors;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// src/config.ts
|
|
101
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
102
|
+
import { performance } from "perf_hooks";
|
|
103
|
+
var extractBuildConfigs = (config) => {
|
|
104
|
+
return config.apps.map(({ appId, entryPoint, plugins }) => ({
|
|
105
|
+
appId,
|
|
106
|
+
entryPoint,
|
|
107
|
+
plugins
|
|
108
|
+
}));
|
|
109
|
+
};
|
|
110
|
+
function extractRoutes(taujsConfig) {
|
|
111
|
+
console.log(import_picocolors.default.bold("Preparing taujs [ \u03C4js ]"));
|
|
112
|
+
const t0 = performance.now();
|
|
113
|
+
try {
|
|
114
|
+
const allRoutes = [];
|
|
115
|
+
const pathTracker = /* @__PURE__ */ new Map();
|
|
116
|
+
let totalRoutes = 0;
|
|
117
|
+
for (const app of taujsConfig.apps) {
|
|
118
|
+
const appRoutes = (app.routes ?? []).map((route) => {
|
|
119
|
+
const fullRoute = { ...route, appId: app.appId };
|
|
120
|
+
if (!pathTracker.has(route.path)) pathTracker.set(route.path, []);
|
|
121
|
+
pathTracker.get(route.path).push(app.appId);
|
|
122
|
+
return fullRoute;
|
|
123
|
+
});
|
|
124
|
+
console.log(import_picocolors.default.gray(` \u2022 ${app.appId}: ${appRoutes.length} route(s)`));
|
|
125
|
+
allRoutes.push(...appRoutes);
|
|
126
|
+
totalRoutes += appRoutes.length;
|
|
127
|
+
}
|
|
128
|
+
for (const [path, appIds] of pathTracker.entries()) {
|
|
129
|
+
if (appIds.length > 1) console.warn(import_picocolors.default.yellow(`\u26A0\uFE0F Route path "${path}" is declared in multiple apps: ${appIds.join(", ")} \u2013 order may affect matching`));
|
|
130
|
+
}
|
|
131
|
+
const sortedRoutes = allRoutes.sort((a, b) => computeScore(b.path) - computeScore(a.path));
|
|
132
|
+
const t1 = performance.now();
|
|
133
|
+
console.log(import_picocolors.default.green(`Prepared ${totalRoutes} route(s) in ${(t1 - t0).toFixed(1)}ms`));
|
|
134
|
+
return sortedRoutes;
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.log(import_picocolors.default.red("Failed to prepare routes"));
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function computeScore(path) {
|
|
141
|
+
return path.split("/").filter(Boolean).length;
|
|
142
|
+
}
|
|
143
|
+
export {
|
|
144
|
+
extractBuildConfigs,
|
|
145
|
+
extractRoutes
|
|
146
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
import { FastifyReply } from 'fastify';
|
|
2
|
+
export { B as BaseMiddleware, C as Config, F as FetchConfig, I as InitialRouteParams, i as Manifest, M as ManifestEntry, N as NamedService, P as ProcessedConfig, g as RenderCallbacks, l as RenderModule, j as RenderSSR, k as RenderStream, R as Route, b as RouteAttributes, a as RouteParams, m as RoutePathsAndAttributes, h as SSRManifest, S as SSRServer, d as SSRServerOptions, e as ServiceMethod, f as ServiceRegistry, T as TEMPLATE, c as createMaps, p as processConfigs } from './SSRServer-CmMH3qwx.js';
|
|
2
3
|
import 'node:http';
|
|
3
|
-
import 'fastify';
|
|
4
4
|
import 'vite';
|
|
5
|
+
|
|
6
|
+
declare module 'fastify' {
|
|
7
|
+
interface FastifyRequest {
|
|
8
|
+
nonce?: string;
|
|
9
|
+
}
|
|
10
|
+
interface FastifyInstance {
|
|
11
|
+
/**
|
|
12
|
+
* Optional authentication hook to be used by the TauJS SSRServer.
|
|
13
|
+
* This method must be decorated by the user when using auth middleware in `taujs.config.ts`.
|
|
14
|
+
*
|
|
15
|
+
* Example usage:
|
|
16
|
+
* ```ts
|
|
17
|
+
* fastify.decorate('authenticate', async function (req, reply) {
|
|
18
|
+
* await req.jwtVerify();
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
authenticate: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -187,12 +187,35 @@ var require_picocolors = __commonJS({
|
|
|
187
187
|
}
|
|
188
188
|
});
|
|
189
189
|
|
|
190
|
+
// src/types.d.ts
|
|
191
|
+
import "fastify";
|
|
192
|
+
|
|
190
193
|
// src/SSRServer.ts
|
|
191
194
|
var import_fastify_plugin = __toESM(require_plugin(), 1);
|
|
192
195
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
193
196
|
import { readFile } from "fs/promises";
|
|
194
197
|
import path2 from "path";
|
|
195
198
|
|
|
199
|
+
// src/utils/Logger.ts
|
|
200
|
+
var createLogger = (debug) => ({
|
|
201
|
+
log: (...args) => {
|
|
202
|
+
if (debug) console.log(...args);
|
|
203
|
+
},
|
|
204
|
+
warn: (...args) => {
|
|
205
|
+
if (debug) console.warn(...args);
|
|
206
|
+
},
|
|
207
|
+
error: (...args) => {
|
|
208
|
+
if (debug) console.error(...args);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
var debugLog = (logger, message, req) => {
|
|
212
|
+
const prefix = "[\u03C4js]";
|
|
213
|
+
const method = req?.method ?? "";
|
|
214
|
+
const url = req?.url ?? "";
|
|
215
|
+
const tag = method && url ? `${method} ${url}` : "";
|
|
216
|
+
logger.log(`${prefix} ${tag} ${message}`);
|
|
217
|
+
};
|
|
218
|
+
|
|
196
219
|
// src/constants.ts
|
|
197
220
|
var RENDERTYPE = {
|
|
198
221
|
ssr: "ssr",
|
|
@@ -214,6 +237,32 @@ var DEV_CSP_DIRECTIVES = {
|
|
|
214
237
|
"img-src": ["'self'", "data:"]
|
|
215
238
|
};
|
|
216
239
|
|
|
240
|
+
// src/security/auth.ts
|
|
241
|
+
function createAuthHook(routes, isDebug) {
|
|
242
|
+
const logger = createLogger(Boolean(isDebug));
|
|
243
|
+
return async function authHook(req, reply) {
|
|
244
|
+
const url = new URL(req.url, `http://${req.headers.host}`).pathname;
|
|
245
|
+
const matched = routes.find((r) => r.path === url);
|
|
246
|
+
const authConfig = matched?.attr?.middleware?.auth;
|
|
247
|
+
if (!authConfig?.required) {
|
|
248
|
+
debugLog(logger, "Auth not required for route", req);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (typeof req.server.authenticate !== "function") {
|
|
252
|
+
req.log.warn('Route requires auth but no "authenticate" decorator is defined on Fastify.');
|
|
253
|
+
return reply.status(500).send("Server misconfiguration: auth decorator missing.");
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
debugLog(logger, "Invoking authenticate(...)", req);
|
|
257
|
+
await req.server.authenticate(req, reply);
|
|
258
|
+
debugLog(logger, "Authentication successful", req);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
debugLog(logger, "Authentication failed", req);
|
|
261
|
+
return reply.send(err);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
217
266
|
// src/security/csp.ts
|
|
218
267
|
import crypto from "crypto";
|
|
219
268
|
var defaultGenerateCSP = (directives, nonce) => {
|
|
@@ -232,7 +281,7 @@ var defaultGenerateCSP = (directives, nonce) => {
|
|
|
232
281
|
return Object.entries(merged).map(([key, values]) => `${key} ${values.join(" ")}`).join("; ");
|
|
233
282
|
};
|
|
234
283
|
var generateNonce = () => crypto.randomBytes(16).toString("base64");
|
|
235
|
-
var
|
|
284
|
+
var createCSPHook = (options = {}) => (req, reply, done) => {
|
|
236
285
|
const nonce = generateNonce();
|
|
237
286
|
const directives = options.directives ?? DEV_CSP_DIRECTIVES;
|
|
238
287
|
const generate = options.generateCSP ?? defaultGenerateCSP;
|
|
@@ -255,6 +304,26 @@ var applyCSP = (security, reply) => {
|
|
|
255
304
|
return nonce;
|
|
256
305
|
};
|
|
257
306
|
|
|
307
|
+
// src/security/verifyMiddleware.ts
|
|
308
|
+
var isAuthRequired = (r) => r.attr?.middleware?.auth?.required === true;
|
|
309
|
+
var hasAuthenticate = (app) => typeof app.authenticate === "function";
|
|
310
|
+
var verifyContracts = (app, routes, contracts, isDebug) => {
|
|
311
|
+
const logger = createLogger(Boolean(isDebug));
|
|
312
|
+
for (const contract of contracts) {
|
|
313
|
+
const isUsed = routes.some(contract.required);
|
|
314
|
+
if (!isUsed) {
|
|
315
|
+
debugLog(logger, `Middleware "${contract.key}" not used in any routes`);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (!contract.verify(app)) {
|
|
319
|
+
const error = new Error(`[\u03C4js] ${contract.errorMessage}`);
|
|
320
|
+
logger.error(error.message);
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
debugLog(logger, `Middleware "${contract.key}" verified \u2713`);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
258
327
|
// src/utils/Utils.ts
|
|
259
328
|
import { dirname, join } from "path";
|
|
260
329
|
import "path";
|
|
@@ -325,16 +394,15 @@ function renderPreloadLink(file) {
|
|
|
325
394
|
return "";
|
|
326
395
|
}
|
|
327
396
|
}
|
|
328
|
-
var callServiceMethod = async (
|
|
329
|
-
const service =
|
|
330
|
-
if (service
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
throw new Error(`Service method ${serviceMethod} does not exist on ${serviceName}`);
|
|
397
|
+
var callServiceMethod = async (registry, serviceName, methodName, params) => {
|
|
398
|
+
const service = registry[serviceName];
|
|
399
|
+
if (!service) throw new Error(`Service ${String(serviceName)} does not exist in the registry`);
|
|
400
|
+
const method = service[methodName];
|
|
401
|
+
if (typeof method !== "function") throw new Error(`Service method ${String(methodName)} does not exist on ${String(serviceName)}`);
|
|
402
|
+
const data = await method(params);
|
|
403
|
+
if (typeof data !== "object" || data === null)
|
|
404
|
+
throw new Error(`Expected object response from ${String(serviceName)}.${String(methodName)}, but got ${typeof data}`);
|
|
405
|
+
return data;
|
|
338
406
|
};
|
|
339
407
|
var fetchData = async ({ url, options }) => {
|
|
340
408
|
if (url) {
|
|
@@ -352,10 +420,10 @@ var fetchInitialData = async (attr, params, serviceRegistry) => {
|
|
|
352
420
|
headers: { "Content-Type": "application/json" },
|
|
353
421
|
params
|
|
354
422
|
}).then(async (data) => {
|
|
355
|
-
if (data.serviceName && data.serviceMethod)
|
|
423
|
+
if (data.serviceName && data.serviceMethod && typeof data.serviceName === "string" && typeof data.serviceMethod === "string")
|
|
356
424
|
return await callServiceMethod(serviceRegistry, data.serviceName, data.serviceMethod, data.options?.params ?? {});
|
|
357
|
-
|
|
358
|
-
|
|
425
|
+
if (data.url && typeof data.url === "string") return await fetchData(data);
|
|
426
|
+
throw new Error("Invalid fetch configuration: must have either serviceName+serviceMethod or url");
|
|
359
427
|
}).catch((error) => {
|
|
360
428
|
console.error("Error fetching initial data:", error);
|
|
361
429
|
throw error;
|
|
@@ -428,6 +496,7 @@ var processConfigs = (configs, baseClientRoot, templateDefaults) => {
|
|
|
428
496
|
};
|
|
429
497
|
var SSRServer = (0, import_fastify_plugin.default)(
|
|
430
498
|
async (app, opts) => {
|
|
499
|
+
const logger = createLogger(opts.isDebug ?? false);
|
|
431
500
|
const { alias, configs, routes, serviceRegistry, isDebug, clientRoot: baseClientRoot } = opts;
|
|
432
501
|
const { bootstrapModules, cssLinks, manifests, preloadLinks, renderModules, ssrManifests, templates } = createMaps();
|
|
433
502
|
const processedConfigs = processConfigs(configs, baseClientRoot, TEMPLATE);
|
|
@@ -461,6 +530,19 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
461
530
|
}
|
|
462
531
|
}
|
|
463
532
|
let viteDevServer;
|
|
533
|
+
verifyContracts(
|
|
534
|
+
app,
|
|
535
|
+
routes,
|
|
536
|
+
[
|
|
537
|
+
{
|
|
538
|
+
key: "auth",
|
|
539
|
+
required: isAuthRequired,
|
|
540
|
+
verify: hasAuthenticate,
|
|
541
|
+
errorMessage: "Routes require auth but Fastify instance is missing `.authenticate` decorator."
|
|
542
|
+
}
|
|
543
|
+
],
|
|
544
|
+
opts.isDebug
|
|
545
|
+
);
|
|
464
546
|
await app.register(import("@fastify/static"), {
|
|
465
547
|
index: false,
|
|
466
548
|
prefix: "/",
|
|
@@ -469,11 +551,12 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
469
551
|
});
|
|
470
552
|
app.addHook(
|
|
471
553
|
"onRequest",
|
|
472
|
-
|
|
554
|
+
createCSPHook({
|
|
473
555
|
directives: opts.security?.csp?.directives,
|
|
474
556
|
generateCSP: opts.security?.csp?.generateCSP
|
|
475
557
|
})
|
|
476
558
|
);
|
|
559
|
+
app.addHook("onRequest", createAuthHook(routes));
|
|
477
560
|
if (isDevelopment) {
|
|
478
561
|
const { createServer } = await import("vite");
|
|
479
562
|
viteDevServer = await createServer({
|
|
@@ -491,10 +574,10 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
491
574
|
{
|
|
492
575
|
name: "taujs-development-server-debug-logging",
|
|
493
576
|
configureServer(server) {
|
|
494
|
-
|
|
577
|
+
logger.log(import_picocolors.default.green("\u03C4js development server debug started."));
|
|
495
578
|
server.middlewares.use((req, res, next) => {
|
|
496
|
-
|
|
497
|
-
res.on("finish", () =>
|
|
579
|
+
logger.log(import_picocolors.default.cyan(`\u2190 rx: ${req.url}`));
|
|
580
|
+
res.on("finish", () => logger.log(import_picocolors.default.yellow(`\u2192 tx: ${req.url}`)));
|
|
498
581
|
next();
|
|
499
582
|
});
|
|
500
583
|
}
|
|
@@ -578,7 +661,9 @@ var SSRServer = (0, import_fastify_plugin.default)(
|
|
|
578
661
|
let aggregateHeadContent = headContent;
|
|
579
662
|
if (ssrManifest && preloadLink) aggregateHeadContent += preloadLink;
|
|
580
663
|
if (manifest && cssLink) aggregateHeadContent += cssLink;
|
|
581
|
-
const
|
|
664
|
+
const shouldHydrate = attr?.hydrate !== false;
|
|
665
|
+
const bootstrapScriptTag = shouldHydrate ? `<script nonce="${nonce}" type="module" src="${bootstrapModule}" defer></script>` : "";
|
|
666
|
+
const fullHtml = template.replace(SSRTAG.ssrHead, aggregateHeadContent).replace(SSRTAG.ssrHtml, `${appHtml}${initialDataScript}${bootstrapScriptTag}`);
|
|
582
667
|
return reply.status(200).header("Content-Type", "text/html").send(fullHtml);
|
|
583
668
|
} else {
|
|
584
669
|
const { renderStream } = renderModule;
|
package/dist/security/csp.d.ts
CHANGED
|
@@ -1,131 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
type CSPDirectives = Record<string, string[]>;
|
|
6
|
-
interface CSPOptions {
|
|
7
|
-
directives?: CSPDirectives;
|
|
8
|
-
exposeNonce?: (req: FastifyRequest, nonce: string) => void;
|
|
9
|
-
generateCSP?: (directives: CSPDirectives, nonce: string) => string;
|
|
10
|
-
}
|
|
11
|
-
declare const defaultGenerateCSP: (directives: CSPDirectives, nonce: string) => string;
|
|
12
|
-
declare const generateNonce: () => string;
|
|
13
|
-
declare const cspHook: (options?: CSPOptions) => (req: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) => void;
|
|
14
|
-
declare const getRequestNonce: (req: FastifyRequest) => string | undefined;
|
|
15
|
-
declare const applyCSP: (security: SSRServerOptions["security"], reply: FastifyReply) => string | undefined;
|
|
16
|
-
|
|
17
|
-
declare const RENDERTYPE: {
|
|
18
|
-
ssr: string;
|
|
19
|
-
streaming: string;
|
|
20
|
-
};
|
|
21
|
-
declare const TEMPLATE: {
|
|
22
|
-
defaultEntryClient: string;
|
|
23
|
-
defaultEntryServer: string;
|
|
24
|
-
defaultHtmlTemplate: string;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
declare const createMaps: () => {
|
|
28
|
-
bootstrapModules: Map<string, string>;
|
|
29
|
-
cssLinks: Map<string, string>;
|
|
30
|
-
manifests: Map<string, Manifest>;
|
|
31
|
-
preloadLinks: Map<string, string>;
|
|
32
|
-
renderModules: Map<string, RenderModule>;
|
|
33
|
-
ssrManifests: Map<string, SSRManifest>;
|
|
34
|
-
templates: Map<string, string>;
|
|
35
|
-
};
|
|
36
|
-
declare const processConfigs: (configs: Config[], baseClientRoot: string, templateDefaults: typeof TEMPLATE) => ProcessedConfig[];
|
|
37
|
-
declare const SSRServer: FastifyPluginAsync<SSRServerOptions>;
|
|
38
|
-
type Config = {
|
|
39
|
-
appId: string;
|
|
40
|
-
entryPoint: string;
|
|
41
|
-
entryClient?: string;
|
|
42
|
-
entryServer?: string;
|
|
43
|
-
htmlTemplate?: string;
|
|
44
|
-
};
|
|
45
|
-
type ProcessedConfig = {
|
|
46
|
-
appId: string;
|
|
47
|
-
clientRoot: string;
|
|
48
|
-
entryClient: string;
|
|
49
|
-
entryPoint: string;
|
|
50
|
-
entryServer: string;
|
|
51
|
-
htmlTemplate: string;
|
|
52
|
-
plugins?: PluginOption[];
|
|
53
|
-
};
|
|
54
|
-
type SSRServerOptions = {
|
|
55
|
-
alias?: Record<string, string>;
|
|
56
|
-
clientRoot: string;
|
|
57
|
-
configs: Config[];
|
|
58
|
-
routes: Route<RouteParams>[];
|
|
59
|
-
serviceRegistry: ServiceRegistry;
|
|
60
|
-
security?: {
|
|
61
|
-
csp?: {
|
|
62
|
-
directives?: CSPDirectives;
|
|
63
|
-
generateCSP?: (directives: CSPDirectives, nonce: string) => string;
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
isDebug?: boolean;
|
|
67
|
-
};
|
|
68
|
-
type ServiceMethod = (params: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
69
|
-
type NamedService = Record<string, ServiceMethod>;
|
|
70
|
-
type ServiceRegistry = Record<string, NamedService>;
|
|
71
|
-
type RenderCallbacks = {
|
|
72
|
-
onHead: (headContent: string) => void;
|
|
73
|
-
onFinish: (initialDataResolved: unknown) => void;
|
|
74
|
-
onError: (error: unknown) => void;
|
|
75
|
-
};
|
|
76
|
-
type FetchConfig = {
|
|
77
|
-
url?: string;
|
|
78
|
-
options: RequestInit & {
|
|
79
|
-
params?: Record<string, unknown>;
|
|
80
|
-
};
|
|
81
|
-
serviceName?: string;
|
|
82
|
-
serviceMethod?: string;
|
|
83
|
-
};
|
|
84
|
-
type SSRManifest = {
|
|
85
|
-
[key: string]: string[];
|
|
86
|
-
};
|
|
87
|
-
type ManifestEntry = {
|
|
88
|
-
file: string;
|
|
89
|
-
src?: string;
|
|
90
|
-
isDynamicEntry?: boolean;
|
|
91
|
-
imports?: string[];
|
|
92
|
-
css?: string[];
|
|
93
|
-
assets?: string[];
|
|
94
|
-
};
|
|
95
|
-
type Manifest = {
|
|
96
|
-
[key: string]: ManifestEntry;
|
|
97
|
-
};
|
|
98
|
-
type RenderSSR = (initialDataResolved: Record<string, unknown>, location: string, meta?: Record<string, unknown>) => Promise<{
|
|
99
|
-
headContent: string;
|
|
100
|
-
appHtml: string;
|
|
101
|
-
initialDataScript: string;
|
|
102
|
-
}>;
|
|
103
|
-
type RenderStream = (serverResponse: ServerResponse, callbacks: RenderCallbacks, initialDataPromise: Promise<Record<string, unknown>>, location: string, bootstrapModules?: string, meta?: Record<string, unknown>) => void;
|
|
104
|
-
type RenderModule = {
|
|
105
|
-
renderSSR: RenderSSR;
|
|
106
|
-
renderStream: RenderStream;
|
|
107
|
-
};
|
|
108
|
-
type RouteAttributes<Params = {}> = {
|
|
109
|
-
fetch?: (params?: Params, options?: RequestInit & {
|
|
110
|
-
params?: Record<string, unknown>;
|
|
111
|
-
}) => Promise<FetchConfig>;
|
|
112
|
-
} & ({
|
|
113
|
-
render?: typeof RENDERTYPE.ssr;
|
|
114
|
-
meta?: Record<string, unknown>;
|
|
115
|
-
} | {
|
|
116
|
-
render: typeof RENDERTYPE.streaming;
|
|
117
|
-
meta: Record<string, unknown>;
|
|
118
|
-
});
|
|
119
|
-
type Route<Params = {}> = {
|
|
120
|
-
attr?: RouteAttributes<Params>;
|
|
121
|
-
path: string;
|
|
122
|
-
appId?: string;
|
|
123
|
-
};
|
|
124
|
-
interface InitialRouteParams extends Record<string, unknown> {
|
|
125
|
-
serviceName?: string;
|
|
126
|
-
serviceMethod?: string;
|
|
127
|
-
}
|
|
128
|
-
type RouteParams = InitialRouteParams & Record<string, unknown>;
|
|
129
|
-
type RoutePathsAndAttributes<Params = {}> = Omit<Route<Params>, 'element'>;
|
|
130
|
-
|
|
131
|
-
export { type Config as C, type CSPDirectives, type CSPOptions, type FetchConfig as F, type InitialRouteParams as I, type ManifestEntry as M, type NamedService as N, type ProcessedConfig as P, type RenderCallbacks as R, SSRServer as S, TEMPLATE as T, type SSRServerOptions as a, applyCSP, type ServiceMethod as b, createMaps as c, cspHook, type ServiceRegistry as d, defaultGenerateCSP, type SSRManifest as e, type Manifest as f, type RenderSSR as g, generateNonce, getRequestNonce, type RenderStream as h, type RenderModule as i, type RouteAttributes as j, type Route as k, type RouteParams as l, type RoutePathsAndAttributes as m, processConfigs as p };
|
|
1
|
+
import 'fastify';
|
|
2
|
+
export { n as CSPDirectives, o as CSPOptions, u as applyCSP, s as createCSPHook, q as defaultGenerateCSP, r as generateNonce, t as getRequestNonce } from '../SSRServer-CmMH3qwx.js';
|
|
3
|
+
import 'node:http';
|
|
4
|
+
import 'vite';
|
package/dist/security/csp.js
CHANGED
|
@@ -26,7 +26,7 @@ var defaultGenerateCSP = (directives, nonce) => {
|
|
|
26
26
|
return Object.entries(merged).map(([key, values]) => `${key} ${values.join(" ")}`).join("; ");
|
|
27
27
|
};
|
|
28
28
|
var generateNonce = () => crypto.randomBytes(16).toString("base64");
|
|
29
|
-
var
|
|
29
|
+
var createCSPHook = (options = {}) => (req, reply, done) => {
|
|
30
30
|
const nonce = generateNonce();
|
|
31
31
|
const directives = options.directives ?? DEV_CSP_DIRECTIVES;
|
|
32
32
|
const generate = options.generateCSP ?? defaultGenerateCSP;
|
|
@@ -51,7 +51,7 @@ var applyCSP = (security, reply) => {
|
|
|
51
51
|
};
|
|
52
52
|
export {
|
|
53
53
|
applyCSP,
|
|
54
|
-
|
|
54
|
+
createCSPHook,
|
|
55
55
|
defaultGenerateCSP,
|
|
56
56
|
generateNonce,
|
|
57
57
|
getRequestNonce
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taujs/server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "taujs
|
|
5
|
-
"author": "Aoede <taujs@aoede.uk.net> (https://www.aoede.uk.net)",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "taujs [ τjs ]",
|
|
5
|
+
"author": "John Smith | Aoede <taujs@aoede.uk.net> (https://www.aoede.uk.net)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/aoede3/taujs-server",
|
|
8
8
|
"repository": {
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"import": "./dist/build.js",
|
|
34
34
|
"types": "./dist/build.d.ts"
|
|
35
35
|
},
|
|
36
|
+
"./config": {
|
|
37
|
+
"import": "./dist/config.js",
|
|
38
|
+
"types": "./dist/config.d.ts"
|
|
39
|
+
},
|
|
36
40
|
"./csp": {
|
|
37
41
|
"import": "./dist/security/csp.js",
|
|
38
42
|
"types": "./dist/security/csp.d.ts"
|