@taujs/server 0.4.2 → 0.4.4

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.
@@ -6,7 +6,6 @@ type DebugCategory = (typeof DEBUG_CATEGORIES)[number];
6
6
  type DebugConfig = boolean | DebugCategory[] | ({
7
7
  all?: boolean;
8
8
  } & Partial<Record<DebugCategory, boolean>>);
9
- type LogLevel = 'debug' | 'info' | 'warn' | 'error';
10
9
  interface BaseLogger {
11
10
  debug?(meta?: Record<string, unknown>, message?: string): void;
12
11
  info?(meta?: Record<string, unknown>, message?: string): void;
@@ -23,41 +22,8 @@ interface Logs extends BaseLogger {
23
22
  child(context: Record<string, unknown>): Logs;
24
23
  isDebugEnabled(category: DebugCategory): boolean;
25
24
  }
26
- declare class Logger implements Logs {
27
- private config;
28
- private debugEnabled;
29
- private context;
30
- constructor(config?: {
31
- custom?: BaseLogger;
32
- context?: Record<string, unknown>;
33
- minLevel?: LogLevel;
34
- includeStack?: boolean | ((level: LogLevel) => boolean);
35
- includeContext?: boolean | ((level: LogLevel) => boolean);
36
- singleLine?: boolean;
37
- });
38
- child(context: Record<string, unknown>): Logger;
39
- configure(debug?: DebugConfig): void;
40
- isDebugEnabled(category: DebugCategory): boolean;
41
- private shouldEmit;
42
- private shouldIncludeStack;
43
- private stripStacks;
44
- private formatTimestamp;
45
- private emit;
46
- info(meta?: unknown, message?: string): void;
47
- warn(meta?: unknown, message?: string): void;
48
- error(meta?: unknown, message?: string): void;
49
- debug(category: DebugCategory, meta?: unknown, message?: string): void;
50
- }
51
- declare function createLogger(opts?: {
52
- debug?: DebugConfig | string | boolean;
53
- custom?: BaseLogger;
54
- context?: Record<string, unknown>;
55
- minLevel?: LogLevel;
56
- includeStack?: boolean | ((level: LogLevel) => boolean);
57
- includeContext?: boolean | ((level: LogLevel) => boolean);
58
- singleLine?: boolean;
59
- }): Logger;
60
25
 
26
+ type RegistryCaller<R extends ServiceRegistry = ServiceRegistry> = (serviceName: keyof R & string, methodName: string, args?: JsonObject) => Promise<JsonObject>;
61
27
  type JsonPrimitive = string | number | boolean | null;
62
28
  type JsonValue = JsonPrimitive | JsonValue[] | {
63
29
  [k: string]: JsonValue;
@@ -77,7 +43,9 @@ type ServiceContext = {
77
43
  id: string;
78
44
  roles: string[];
79
45
  } | null;
46
+ call?: (service: string, method: string, args?: JsonObject) => Promise<JsonObject>;
80
47
  };
48
+ declare function withDeadline(signal: AbortSignal | undefined, ms?: number): AbortSignal | undefined;
81
49
  type ServiceMethod<P, R extends JsonObject = JsonObject> = (params: P, ctx: ServiceContext) => Promise<R>;
82
50
  type ServiceDefinition = Readonly<Record<string, ServiceMethod<any, JsonObject>>>;
83
51
  type ServiceRegistry = Readonly<Record<string, ServiceDefinition>>;
@@ -94,6 +62,7 @@ declare function defineService<T extends Record<string, ServiceMethod<any, JsonO
94
62
  handler: ServiceMethod<infer P, infer R_1>;
95
63
  } ? ServiceMethod<P, R_1> : never; };
96
64
  declare const defineServiceRegistry: <R extends ServiceRegistry>(registry: R) => R;
65
+ declare function callServiceMethod(registry: ServiceRegistry, serviceName: string, methodName: string, params: JsonObject | undefined, ctx: ServiceContext): Promise<JsonObject>;
97
66
 
98
67
  type RequestContext<L extends Logs = Logs> = {
99
68
  traceId: string;
@@ -122,7 +91,11 @@ type BaseMiddleware = {
122
91
  csp?: RouteCSPConfig | false;
123
92
  };
124
93
  type DataResult = Record<string, unknown> | ServiceDescriptor;
125
- type DataHandler<Params extends PathToRegExpParams, L extends Logs = Logs> = (params: Params, ctx: RequestContext<L> & {
94
+ type RequestServiceContext<L extends Logs = Logs> = RequestContext<L> & {
95
+ call: RegistryCaller<ServiceRegistry>;
96
+ headers?: Record<string, string>;
97
+ };
98
+ type DataHandler<Params extends PathToRegExpParams, L extends Logs = Logs> = (params: Params, ctx: RequestServiceContext<L> & {
126
99
  [key: string]: unknown;
127
100
  }) => Promise<DataResult>;
128
101
  type PathToRegExpParams = Partial<Record<string, string | string[]>>;
@@ -163,6 +136,35 @@ type CSPViolationReport = {
163
136
  disposition?: 'enforce' | 'report';
164
137
  };
165
138
 
139
+ type ErrorKind = 'infra' | 'upstream' | 'domain' | 'validation' | 'auth' | 'canceled' | 'timeout';
140
+ declare class AppError extends Error {
141
+ readonly kind: ErrorKind;
142
+ readonly httpStatus: number;
143
+ readonly details?: unknown;
144
+ readonly safeMessage: string;
145
+ readonly code?: string;
146
+ constructor(message: string, kind: ErrorKind, options?: {
147
+ httpStatus?: number;
148
+ details?: unknown;
149
+ cause?: unknown;
150
+ safeMessage?: string;
151
+ code?: string;
152
+ });
153
+ private getSafeMessage;
154
+ private serialiseValue;
155
+ toJSON(): any;
156
+ static notFound(message: string, details?: unknown, code?: string): AppError;
157
+ static forbidden(message: string, details?: unknown, code?: string): AppError;
158
+ static badRequest(message: string, details?: unknown, code?: string): AppError;
159
+ static unprocessable(message: string, details?: unknown, code?: string): AppError;
160
+ static timeout(message: string, details?: unknown, code?: string): AppError;
161
+ static canceled(message: string, details?: unknown, code?: string): AppError;
162
+ static internal(message: string, cause?: unknown, details?: unknown, code?: string): AppError;
163
+ static upstream(message: string, cause?: unknown, details?: unknown, code?: string): AppError;
164
+ static serviceUnavailable(message: string, cause?: unknown, details?: unknown, code?: string): AppError;
165
+ static from(err: unknown, fallback?: string): AppError;
166
+ }
167
+
166
168
  /**
167
169
  * τjs [ taujs ] Orchestration System
168
170
  * (c) 2024-present Aoede Ltd
@@ -195,15 +197,15 @@ type AppConfig = {
195
197
  routes?: AppRoute[];
196
198
  };
197
199
  type TaujsConfig = {
200
+ apps: AppConfig[];
201
+ security?: SecurityConfig;
198
202
  server?: {
199
203
  host?: string;
200
204
  port?: number;
201
205
  hmrPort?: number;
202
206
  };
203
- security?: SecurityConfig;
204
- apps: AppConfig[];
205
207
  };
206
208
 
207
209
  declare function defineConfig<T extends TaujsConfig>(config: T): T;
208
210
 
209
- export { type AppConfig as A, type BaseLogger as B, type DebugConfig as D, type InitialRouteParams as I, type ServiceRegistry as S, type TaujsConfig as T, type SecurityConfig as a, type AppRoute as b, createLogger as c, defineConfig as d, defineServiceRegistry as e, defineService as f, type ServiceContext as g };
211
+ export { type AppConfig as A, type BaseLogger as B, type DebugConfig as D, type InitialRouteParams as I, type RegistryCaller as R, type ServiceRegistry as S, type TaujsConfig as T, type SecurityConfig as a, type AppRoute as b, callServiceMethod as c, defineConfig as d, defineService as e, defineServiceRegistry as f, type ServiceContext as g, AppError as h, withDeadline as w };
package/dist/Config.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import 'fastify';
2
2
  import 'vite';
3
- export { A as AppConfig, b as AppRoute, a as SecurityConfig, g as ServiceContext, T as TaujsConfig, d as defineConfig, f as defineService, e as defineServiceRegistry } from './Config-BuUuMjmO.js';
3
+ export { A as AppConfig, h as AppError, b as AppRoute, R as RegistryCaller, a as SecurityConfig, g as ServiceContext, T as TaujsConfig, c as callServiceMethod, d as defineConfig, e as defineService, f as defineServiceRegistry, w as withDeadline } from './Config-CEhfq8Mm.js';
package/dist/Config.js CHANGED
@@ -1,9 +1,137 @@
1
1
  // src/utils/DataServices.ts
2
2
  import { performance } from "perf_hooks";
3
+
4
+ // src/logging/AppError.ts
5
+ var HTTP_STATUS = {
6
+ infra: 500,
7
+ upstream: 502,
8
+ domain: 404,
9
+ validation: 400,
10
+ auth: 403,
11
+ canceled: 499,
12
+ // Client Closed Request (nginx convention)
13
+ timeout: 504
14
+ };
15
+ var AppError = class _AppError extends Error {
16
+ kind;
17
+ httpStatus;
18
+ details;
19
+ safeMessage;
20
+ code;
21
+ constructor(message, kind, options = {}) {
22
+ super(message);
23
+ this.name = "AppError";
24
+ Object.setPrototypeOf(this, new.target.prototype);
25
+ if (options.cause !== void 0) {
26
+ Object.defineProperty(this, "cause", {
27
+ value: options.cause,
28
+ enumerable: false,
29
+ writable: false,
30
+ configurable: true
31
+ });
32
+ }
33
+ this.kind = kind;
34
+ this.httpStatus = options.httpStatus ?? HTTP_STATUS[kind];
35
+ this.details = options.details;
36
+ this.safeMessage = options.safeMessage ?? this.getSafeMessage(kind, message);
37
+ this.code = options.code;
38
+ if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
39
+ }
40
+ getSafeMessage(kind, message) {
41
+ return kind === "domain" || kind === "validation" || kind === "auth" ? message : "Internal Server Error";
42
+ }
43
+ serialiseValue(value, seen = /* @__PURE__ */ new WeakSet()) {
44
+ if (value === null || value === void 0) return value;
45
+ if (typeof value !== "object") return value;
46
+ if (seen.has(value)) return "[circular]";
47
+ seen.add(value);
48
+ if (value instanceof Error) {
49
+ return {
50
+ name: value.name,
51
+ message: value.message,
52
+ stack: value.stack,
53
+ ...value instanceof _AppError && {
54
+ kind: value.kind,
55
+ httpStatus: value.httpStatus,
56
+ code: value.code
57
+ }
58
+ };
59
+ }
60
+ if (Array.isArray(value)) return value.map((item) => this.serialiseValue(item, seen));
61
+ const result = {};
62
+ for (const [key, val] of Object.entries(value)) {
63
+ result[key] = this.serialiseValue(val, seen);
64
+ }
65
+ return result;
66
+ }
67
+ toJSON() {
68
+ return {
69
+ name: this.name,
70
+ kind: this.kind,
71
+ message: this.message,
72
+ safeMessage: this.safeMessage,
73
+ httpStatus: this.httpStatus,
74
+ ...this.code && { code: this.code },
75
+ details: this.serialiseValue(this.details),
76
+ stack: this.stack,
77
+ ...this.cause && {
78
+ cause: this.serialiseValue(this.cause)
79
+ }
80
+ };
81
+ }
82
+ static notFound(message, details, code) {
83
+ return new _AppError(message, "domain", { httpStatus: 404, details, code });
84
+ }
85
+ static forbidden(message, details, code) {
86
+ return new _AppError(message, "auth", { httpStatus: 403, details, code });
87
+ }
88
+ static badRequest(message, details, code) {
89
+ return new _AppError(message, "validation", { httpStatus: 400, details, code });
90
+ }
91
+ static unprocessable(message, details, code) {
92
+ return new _AppError(message, "validation", { httpStatus: 422, details, code });
93
+ }
94
+ static timeout(message, details, code) {
95
+ return new _AppError(message, "timeout", { details, code });
96
+ }
97
+ static canceled(message, details, code) {
98
+ return new _AppError(message, "canceled", { details, code });
99
+ }
100
+ static internal(message, cause, details, code) {
101
+ return new _AppError(message, "infra", { cause, details, code });
102
+ }
103
+ static upstream(message, cause, details, code) {
104
+ return new _AppError(message, "upstream", { cause, details, code });
105
+ }
106
+ static serviceUnavailable(message, cause, details, code) {
107
+ return new _AppError(message, "infra", { httpStatus: 503, cause, details, code });
108
+ }
109
+ static from(err, fallback = "Internal error") {
110
+ return err instanceof _AppError ? err : _AppError.internal(err?.message ?? fallback, err);
111
+ }
112
+ };
113
+
114
+ // src/utils/DataServices.ts
3
115
  var runSchema = (schema, input) => {
4
116
  if (!schema) return input;
5
117
  return typeof schema.parse === "function" ? schema.parse(input) : schema(input);
6
118
  };
119
+ function withDeadline(signal, ms) {
120
+ if (!ms) return signal;
121
+ const ctrl = new AbortController();
122
+ const onAbort = () => ctrl.abort(signal?.reason ?? new Error("Aborted"));
123
+ signal?.addEventListener("abort", onAbort, { once: true });
124
+ const t = setTimeout(() => ctrl.abort(new Error("DeadlineExceeded")), ms);
125
+ ctrl.signal.addEventListener(
126
+ "abort",
127
+ () => {
128
+ clearTimeout(t);
129
+ signal?.removeEventListener("abort", onAbort);
130
+ },
131
+ { once: true }
132
+ );
133
+ return ctrl.signal;
134
+ }
7
135
  function defineService(spec) {
8
136
  const out = {};
9
137
  for (const [name, v] of Object.entries(spec)) {
@@ -21,6 +149,38 @@ function defineService(spec) {
21
149
  return Object.freeze(out);
22
150
  }
23
151
  var defineServiceRegistry = (registry) => Object.freeze(Object.fromEntries(Object.entries(registry).map(([k, v]) => [k, Object.freeze(v)])));
152
+ async function callServiceMethod(registry, serviceName, methodName, params, ctx) {
153
+ if (ctx.signal?.aborted) throw AppError.timeout("Request canceled");
154
+ const service = registry[serviceName];
155
+ if (!service) throw AppError.notFound(`Unknown service: ${serviceName}`);
156
+ const method = service[methodName];
157
+ if (!method) throw AppError.notFound(`Unknown method: ${serviceName}.${methodName}`);
158
+ const logger = ctx.logger?.child?.({
159
+ component: "service-call",
160
+ service: serviceName,
161
+ method: methodName,
162
+ traceId: ctx.traceId
163
+ });
164
+ const t0 = performance.now();
165
+ try {
166
+ const result = await method(params ?? {}, ctx);
167
+ if (typeof result !== "object" || result === null) {
168
+ throw AppError.internal(`Non-object result from ${serviceName}.${methodName}`);
169
+ }
170
+ logger?.debug?.({ ms: +(performance.now() - t0).toFixed(1) }, "Service method ok");
171
+ return result;
172
+ } catch (err) {
173
+ logger?.error?.(
174
+ {
175
+ params,
176
+ error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : String(err),
177
+ ms: +(performance.now() - t0).toFixed(1)
178
+ },
179
+ "Service method failed"
180
+ );
181
+ throw err instanceof AppError ? err : err instanceof Error ? AppError.internal(err.message, { cause: err }) : AppError.internal("Unknown error", { details: { err } });
182
+ }
183
+ }
24
184
 
25
185
  // src/Config.ts
26
186
  function defineConfig(config) {
@@ -28,7 +188,10 @@ function defineConfig(config) {
28
188
  return config;
29
189
  }
30
190
  export {
191
+ AppError,
192
+ callServiceMethod,
31
193
  defineConfig,
32
194
  defineService,
33
- defineServiceRegistry
195
+ defineServiceRegistry,
196
+ withDeadline
34
197
  };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { FastifyPluginCallback, FastifyPluginAsync, FastifyInstance } from 'fastify';
2
- import { T as TaujsConfig, S as ServiceRegistry, D as DebugConfig, B as BaseLogger, A as AppConfig } from './Config-BuUuMjmO.js';
3
- export { I as InitialRouteParams, c as createLogger } from './Config-BuUuMjmO.js';
4
- import 'vite';
2
+ import { T as TaujsConfig, S as ServiceRegistry, D as DebugConfig, B as BaseLogger, A as AppConfig } from './Config-CEhfq8Mm.js';
3
+ export { I as InitialRouteParams } from './Config-CEhfq8Mm.js';
4
+ import { InlineConfig } from 'vite';
5
5
 
6
6
  type StaticMountEntry = {
7
7
  plugin: FastifyPluginCallback<any> | FastifyPluginAsync<any>;
@@ -42,13 +42,84 @@ declare const createServer: (opts: CreateServerOptions) => Promise<CreateServerR
42
42
  * including CSR, SSR, streaming, and middleware composition.
43
43
  */
44
44
 
45
- declare function taujsBuild({ config, projectRoot, clientBaseDir, isSSRBuild, }: {
45
+ type ViteBuildContext = {
46
+ appId: string;
47
+ entryPoint: string;
48
+ isSSRBuild: boolean;
49
+ clientRoot: string;
50
+ };
51
+ /**
52
+ * User-supplied vite config override.
53
+ * Can be a static config object or a function that receives build context.
54
+ *
55
+ * **Plugin order**: Framework applies plugins in this sequence:
56
+ * 1. `appConfig.plugins` (from taujs.config.ts)
57
+ * 2. `nodePolyfills({ include: ['stream'] })` (client builds only)
58
+ * 3. `userViteConfig.plugins` (from this option)
59
+ *
60
+ * If you need plugins before nodePolyfills, add them to `appConfig.plugins` instead.
61
+ *
62
+ * **Allowed customisations:**
63
+ * - `plugins`: Appended to framework plugin list
64
+ * - `define`: Shallow-merged with framework defines
65
+ * - `css.preprocessorOptions`: Deep-merged by preprocessor engine (scss, less, etc.)
66
+ * - `build.sourcemap`, `minify`, `terserOptions`: Direct overrides
67
+ * - `build.rollupOptions.external`: Direct override
68
+ * - `build.rollupOptions.output.manualChunks`: Merged into output config
69
+ * - `resolve.*` (except `alias`): Merged with framework resolve config
70
+ * - `esbuild`, `logLevel`, `optimizeDeps`: Direct overrides
71
+ *
72
+ * **Protected fields (cannot override):**
73
+ * - `root`, `base`, `publicDir`: Framework-controlled per-app paths
74
+ * - `build.outDir`: Framework manages `dist/client` vs `dist/ssr` separation
75
+ * - `build.ssr`, `ssrManifest`, `format`, `target`: Framework-controlled for SSR integrity
76
+ * - `build.rollupOptions.input`: Framework manages entry points
77
+ * - `resolve.alias`: Use top-level `alias` option in taujsBuild() instead
78
+ * - `server.*`: Ignored in builds (dev-mode only; configure in DevServer.ts)
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * // Static config
83
+ * vite: {
84
+ * plugins: [visualizer()],
85
+ * build: { sourcemap: 'inline' }
86
+ * }
87
+ *
88
+ * // Function-based (conditional per app/mode)
89
+ * vite: ({ isSSRBuild, entryPoint }) => ({
90
+ * plugins: isSSRBuild ? [] : [visualizer()],
91
+ * logLevel: entryPoint === 'admin' ? 'info' : 'warn'
92
+ * })
93
+ * ```
94
+ */
95
+ type ViteConfigOverride = Partial<InlineConfig> | ((ctx: ViteBuildContext) => Partial<InlineConfig>);
96
+ declare function taujsBuild({ config, projectRoot, clientBaseDir, isSSRBuild, alias: userAlias, vite: userViteConfig, }: {
46
97
  config: {
47
98
  apps: AppConfig[];
48
99
  };
49
100
  projectRoot: string;
50
101
  clientBaseDir: string;
51
102
  isSSRBuild?: boolean;
103
+ /**
104
+ * Top-level alias overrides. Use this instead of `vite.resolve.alias`.
105
+ * User aliases are merged with framework defaults; user values win on conflicts.
106
+ *
107
+ * Framework provides:
108
+ * - `@client`: Resolves to current app's root
109
+ * - `@server`: Resolves to `src/server`
110
+ * - `@shared`: Resolves to `src/shared`
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * alias: {
115
+ * '@utils': './src/utils',
116
+ * '@server': './custom-server', // overrides framework default
117
+ * }
118
+ * ```
119
+ */
120
+ alias?: Record<string, string>;
121
+ /** User-supplied Vite config overrides (plugins, tuning, etc.) */
122
+ vite?: ViteConfigOverride;
52
123
  }): Promise<void>;
53
124
 
54
125
  interface MessageMetaLogger {
@@ -60,4 +131,4 @@ interface MessageMetaLogger {
60
131
  }
61
132
  declare function winstonAdapter(winston: MessageMetaLogger): BaseLogger;
62
133
 
63
- export { createServer, taujsBuild, winstonAdapter };
134
+ export { BaseLogger, createServer, taujsBuild, winstonAdapter };
package/dist/index.js CHANGED
@@ -189,7 +189,7 @@ var require_plugin = __commonJS({
189
189
 
190
190
  // src/CreateServer.ts
191
191
  var import_picocolors4 = __toESM(require_picocolors(), 1);
192
- import path5 from "path";
192
+ import path7 from "path";
193
193
  import { performance as performance3 } from "perf_hooks";
194
194
  import fastifyStatic from "@fastify/static";
195
195
  import Fastify from "fastify";
@@ -259,8 +259,8 @@ var extractRoutes = (taujsConfig) => {
259
259
  apps.push({ appId: app.appId, routeCount: appRoutes.length });
260
260
  allRoutes.push(...appRoutes);
261
261
  }
262
- for (const [path7, appIds] of pathTracker.entries()) {
263
- if (appIds.length > 1) warnings.push(`Route path "${path7}" is declared in multiple apps: ${appIds.join(", ")}`);
262
+ for (const [path9, appIds] of pathTracker.entries()) {
263
+ if (appIds.length > 1) warnings.push(`Route path "${path9}" is declared in multiple apps: ${appIds.join(", ")}`);
264
264
  }
265
265
  const sortedRoutes = allRoutes.sort((a, b) => computeScore(b.path) - computeScore(a.path));
266
266
  const durationMs = performance.now() - t0;
@@ -344,8 +344,8 @@ function printContractReport(logger, report) {
344
344
  }
345
345
  }
346
346
  }
347
- var computeScore = (path7) => {
348
- return path7.split("/").filter(Boolean).reduce((score, segment) => score + (segment.startsWith(":") ? 1 : 10), 0);
347
+ var computeScore = (path9) => {
348
+ return path9.split("/").filter(Boolean).reduce((score, segment) => score + (segment.startsWith(":") ? 1 : 10), 0);
349
349
  };
350
350
 
351
351
  // src/logging/AppError.ts
@@ -609,11 +609,20 @@ var Logger = class _Logger {
609
609
  if (!this.shouldEmit(level)) return;
610
610
  const timestamp = this.formatTimestamp();
611
611
  const wantCtx = this.config.includeContext === void 0 ? false : typeof this.config.includeContext === "function" ? this.config.includeContext(level) : this.config.includeContext;
612
- const customSink = this.config.custom?.[level];
612
+ const owner = this.config.custom;
613
+ const rawSink = owner && typeof owner[level] === "function" ? owner[level] : void 0;
614
+ const boundSink = rawSink ? rawSink.bind(owner) : void 0;
613
615
  const consoleFallback = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
614
- const sink = customSink ?? consoleFallback;
615
- const merged = meta ?? {};
616
- const withCtx = wantCtx && Object.keys(this.context).length > 0 ? { context: this.context, ...merged } : merged;
616
+ const hasCustom = !!boundSink;
617
+ let baseMeta;
618
+ if (meta && typeof meta === "object" && !Array.isArray(meta)) {
619
+ baseMeta = meta;
620
+ } else if (meta === void 0) {
621
+ baseMeta = {};
622
+ } else {
623
+ baseMeta = { value: meta };
624
+ }
625
+ const withCtx = wantCtx && Object.keys(this.context).length > 0 ? { context: this.context, ...baseMeta } : baseMeta;
617
626
  const finalMeta = this.shouldIncludeStack(level) ? withCtx : this.stripStacks(withCtx);
618
627
  const hasMeta = finalMeta && typeof finalMeta === "object" ? Object.keys(finalMeta).length > 0 : false;
619
628
  const levelText = level.toLowerCase() + (category ? `:${category.toLowerCase()}` : "");
@@ -632,16 +641,20 @@ var Logger = class _Logger {
632
641
  return plainTag;
633
642
  }
634
643
  })();
635
- const tagForOutput = customSink ? plainTag : coloredTag;
644
+ const tagForOutput = hasCustom ? plainTag : coloredTag;
636
645
  const formatted = `${timestamp} ${tagForOutput} ${message}`;
637
- if (this.config.singleLine && hasMeta && !customSink) {
646
+ if (this.config.singleLine && hasMeta && !hasCustom) {
638
647
  const metaStr = JSON.stringify(finalMeta).replace(/\n/g, "\\n");
639
648
  consoleFallback(`${formatted} ${metaStr}`);
640
649
  return;
641
650
  }
642
- if (customSink) {
651
+ if (hasCustom) {
643
652
  const obj = hasMeta ? finalMeta : {};
644
- customSink(obj, formatted);
653
+ try {
654
+ boundSink(obj, formatted);
655
+ } catch {
656
+ hasMeta ? consoleFallback(formatted, finalMeta) : consoleFallback(formatted);
657
+ }
645
658
  } else {
646
659
  hasMeta ? consoleFallback(formatted, finalMeta) : consoleFallback(formatted);
647
660
  }
@@ -839,6 +852,7 @@ var verifyContracts = (app, routes, contracts, security) => {
839
852
 
840
853
  // src/SSRServer.ts
841
854
  var import_fastify_plugin3 = __toESM(require_plugin(), 1);
855
+ import path6 from "path";
842
856
 
843
857
  // src/logging/utils/index.ts
844
858
  var httpStatusFrom = (err, fallback = 500) => err instanceof AppError ? err.httpStatus : fallback;
@@ -879,6 +893,12 @@ import { match } from "path-to-regexp";
879
893
 
880
894
  // src/utils/DataServices.ts
881
895
  import { performance as performance2 } from "perf_hooks";
896
+ function createCaller(registry, ctx) {
897
+ return (serviceName, methodName, args) => callServiceMethod(registry, serviceName, methodName, args ?? {}, ctx);
898
+ }
899
+ function ensureServiceCaller(registry, ctx) {
900
+ if (!ctx.call) ctx.call = createCaller(registry, ctx);
901
+ }
882
902
  async function callServiceMethod(registry, serviceName, methodName, params, ctx) {
883
903
  if (ctx.signal?.aborted) throw AppError.timeout("Request canceled");
884
904
  const service = registry[serviceName];
@@ -929,15 +949,15 @@ var safeDecode = (value) => {
929
949
  return value;
930
950
  }
931
951
  };
932
- var cleanPath = (path7) => {
933
- if (!path7) return "/";
934
- const basePart = path7.split("?")[0];
952
+ var cleanPath = (path9) => {
953
+ if (!path9) return "/";
954
+ const basePart = path9.split("?")[0];
935
955
  const base = basePart ? basePart.split("#")[0] : "/";
936
956
  return base || "/";
937
957
  };
938
- var calculateSpecificity = (path7) => {
958
+ var calculateSpecificity = (path9) => {
939
959
  let score = 0;
940
- const segments = path7.split("/").filter(Boolean);
960
+ const segments = path9.split("/").filter(Boolean);
941
961
  for (const segment of segments) {
942
962
  if (segment.startsWith(":")) {
943
963
  score += 1;
@@ -962,9 +982,9 @@ var createRouteMatchers = (routes) => {
962
982
  });
963
983
  };
964
984
  var matchRoute = (url, routeMatchers) => {
965
- const path7 = cleanPath(url);
985
+ const path9 = cleanPath(url);
966
986
  for (const { route, matcher, keys } of routeMatchers) {
967
- const match2 = matcher(path7);
987
+ const match2 = matcher(path9);
968
988
  if (match2) {
969
989
  return {
970
990
  route,
@@ -978,14 +998,16 @@ var matchRoute = (url, routeMatchers) => {
978
998
  var fetchInitialData = async (attr, params, serviceRegistry, ctx, callServiceMethodImpl = callServiceMethod) => {
979
999
  const dataHandler = attr?.data;
980
1000
  if (!dataHandler || typeof dataHandler !== "function") return {};
1001
+ const ctxForData = {
1002
+ ...ctx,
1003
+ headers: ctx.headers ?? {}
1004
+ };
1005
+ ensureServiceCaller(serviceRegistry, ctxForData);
981
1006
  try {
982
- const result = await dataHandler(params, {
983
- ...ctx,
984
- headers: ctx.headers ?? {}
985
- });
1007
+ const result = await dataHandler(params, ctxForData);
986
1008
  if (isServiceDescriptor(result)) {
987
1009
  const { serviceName, serviceMethod, args } = result;
988
- return callServiceMethodImpl(serviceRegistry, serviceName, serviceMethod, args ?? {}, ctx);
1010
+ return callServiceMethodImpl(serviceRegistry, serviceName, serviceMethod, args ?? {}, ctxForData);
989
1011
  }
990
1012
  if (isPlainObject(result)) return result;
991
1013
  throw AppError.badRequest("attr.data must return a plain object or a ServiceDescriptor");
@@ -1102,9 +1124,9 @@ var mergeDirectives = (base, override) => {
1102
1124
  }
1103
1125
  return merged;
1104
1126
  };
1105
- var findMatchingRoute = (routeMatchers, path7) => {
1127
+ var findMatchingRoute = (routeMatchers, path9) => {
1106
1128
  if (!routeMatchers) return null;
1107
- const match2 = matchRoute(path7, routeMatchers);
1129
+ const match2 = matchRoute(path9, routeMatchers);
1108
1130
  return match2 ? { route: match2.route, params: match2.params } : null;
1109
1131
  };
1110
1132
  var cspPlugin = (0, import_fastify_plugin.default)(
@@ -1434,8 +1456,9 @@ var loadAssets = async (processedConfigs, baseClientRoot, bootstrapModules, cssL
1434
1456
  debug: opts.debug,
1435
1457
  includeContext: true
1436
1458
  });
1459
+ const projectRoot = opts.projectRoot ?? path2.resolve(process.cwd());
1437
1460
  for (const config of processedConfigs) {
1438
- const { clientRoot, entryClient, entryServer, htmlTemplate } = config;
1461
+ const { clientRoot, entryClient, entryServer, htmlTemplate, entryPoint } = config;
1439
1462
  try {
1440
1463
  const templateHtmlPath = path2.join(clientRoot, htmlTemplate);
1441
1464
  const templateHtml = await readFile(templateHtmlPath, "utf-8");
@@ -1444,11 +1467,13 @@ var loadAssets = async (processedConfigs, baseClientRoot, bootstrapModules, cssL
1444
1467
  const adjustedRelativePath = relativeBasePath ? `/${relativeBasePath}` : "";
1445
1468
  if (!isDevelopment) {
1446
1469
  try {
1447
- const manifestPath = path2.join(clientRoot, ".vite/manifest.json");
1470
+ const clientDistPath = path2.resolve(projectRoot, "client", entryPoint);
1471
+ const manifestPath = path2.join(clientDistPath, ".vite/manifest.json");
1448
1472
  const manifestContent = await readFile(manifestPath, "utf-8");
1449
1473
  const manifest = JSON.parse(manifestContent);
1450
1474
  manifests.set(clientRoot, manifest);
1451
- const ssrManifestPath = path2.join(clientRoot, ".vite/ssr-manifest.json");
1475
+ const ssrDistPath = path2.resolve(projectRoot, "ssr", entryPoint);
1476
+ const ssrManifestPath = path2.join(ssrDistPath, ".vite/ssr-manifest.json");
1452
1477
  const ssrManifestContent = await readFile(ssrManifestPath, "utf-8");
1453
1478
  const ssrManifest = JSON.parse(ssrManifestContent);
1454
1479
  ssrManifests.set(clientRoot, ssrManifest);
@@ -1468,7 +1493,7 @@ var loadAssets = async (processedConfigs, baseClientRoot, bootstrapModules, cssL
1468
1493
  preloadLinks.set(clientRoot, preloadLink);
1469
1494
  const cssLink = getCssLinks(manifest, adjustedRelativePath);
1470
1495
  cssLinks.set(clientRoot, cssLink);
1471
- const renderModulePath = path2.join(clientRoot, `${entryServer}.js`);
1496
+ const renderModulePath = path2.join(ssrDistPath, `${entryServer}.js`);
1472
1497
  const moduleUrl = pathToFileURL(renderModulePath).href;
1473
1498
  try {
1474
1499
  const importedModule = await import(moduleUrl);
@@ -1476,7 +1501,7 @@ var loadAssets = async (processedConfigs, baseClientRoot, bootstrapModules, cssL
1476
1501
  } catch (err) {
1477
1502
  throw AppError.internal(`Failed to load render module ${renderModulePath}`, {
1478
1503
  cause: err,
1479
- details: { moduleUrl, clientRoot, entryServer }
1504
+ details: { moduleUrl, clientRoot, entryServer, ssrDistPath }
1480
1505
  });
1481
1506
  }
1482
1507
  } catch (err) {
@@ -1687,10 +1712,15 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1687
1712
  if (renderType === RENDERTYPE.ssr) {
1688
1713
  const { renderSSR } = renderModule;
1689
1714
  if (!renderSSR) {
1690
- throw AppError.internal("renderSSR function not found in module", {
1691
- details: { clientRoot, availableFunctions: Object.keys(renderModule) }
1692
- });
1715
+ throw AppError.internal(
1716
+ "ssr",
1717
+ {
1718
+ details: { clientRoot, availableFunctions: Object.keys(renderModule) }
1719
+ },
1720
+ "renderSSR function not found in module"
1721
+ );
1693
1722
  }
1723
+ logger.debug?.("ssr", {}, "ssr requested");
1694
1724
  const ac = new AbortController();
1695
1725
  const onAborted = () => ac.abort("client_aborted");
1696
1726
  req.raw.on("aborted", onAborted);
@@ -1699,7 +1729,7 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1699
1729
  });
1700
1730
  reply.raw.on("finish", () => req.raw.off("aborted", onAborted));
1701
1731
  if (ac.signal.aborted) {
1702
- logger.warn("SSR skipped; already aborted", { url: req.url });
1732
+ logger.warn({ url: req.url }, "SSR skipped; already aborted");
1703
1733
  return;
1704
1734
  }
1705
1735
  const initialDataResolved = await initialDataInput();
@@ -1709,14 +1739,31 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1709
1739
  const res = await renderSSR(initialDataResolved, req.url, attr?.meta, ac.signal, { logger: reqLogger });
1710
1740
  headContent = res.headContent;
1711
1741
  appHtml = res.appHtml;
1742
+ logger.debug?.("ssr", {}, "ssr data resolved");
1743
+ if (ac.signal.aborted) {
1744
+ logger.warn({}, "SSR completed but client disconnected");
1745
+ return;
1746
+ }
1712
1747
  } catch (err) {
1713
1748
  const msg = String(err?.message ?? err ?? "");
1714
1749
  const benign = REGEX.BENIGN_NET_ERR.test(msg);
1715
1750
  if (ac.signal.aborted || benign) {
1716
- logger.warn("SSR aborted mid-render (benign)", { url: req.url, reason: msg });
1751
+ logger.warn(
1752
+ {
1753
+ url: req.url,
1754
+ reason: msg
1755
+ },
1756
+ "SSR aborted mid-render (benign)"
1757
+ );
1717
1758
  return;
1718
1759
  }
1719
- logger.error("SSR render failed", { url: req.url, error: normaliseError(err) });
1760
+ logger.error(
1761
+ {
1762
+ url: req.url,
1763
+ error: normaliseError(err)
1764
+ },
1765
+ "SSR render failed"
1766
+ );
1720
1767
  throw err;
1721
1768
  }
1722
1769
  let aggregateHeadContent = headContent;
@@ -1728,13 +1775,14 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1728
1775
  const bootstrapScriptTag = shouldHydrate && bootstrapModule ? `<script${nonceAttr} type="module" src="${bootstrapModule}" defer></script>` : "";
1729
1776
  const safeAppHtml = appHtml.trim();
1730
1777
  const fullHtml = rebuildTemplate(templateParts, aggregateHeadContent, `${safeAppHtml}${initialDataScript}${bootstrapScriptTag}`);
1778
+ logger.debug?.("ssr", {}, "ssr template rebuilt and sending response");
1731
1779
  try {
1732
1780
  return reply.status(200).header("Content-Type", "text/html").send(fullHtml);
1733
1781
  } catch (err) {
1734
1782
  const msg = String(err?.message ?? err ?? "");
1735
1783
  const benign = REGEX.BENIGN_NET_ERR.test(msg);
1736
- if (!benign) logger.error("SSR send failed", { url: req.url, error: normaliseError(err) });
1737
- else logger.warn("SSR send aborted (benign)", { url: req.url, reason: msg });
1784
+ if (!benign) logger.error({ url: req.url, error: normaliseError(err) }, "SSR send failed");
1785
+ else logger.warn({ url: req.url, reason: msg }, "SSR send aborted (benign)");
1738
1786
  return;
1739
1787
  }
1740
1788
  } else {
@@ -1744,30 +1792,44 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1744
1792
  details: { clientRoot, availableFunctions: Object.keys(renderModule) }
1745
1793
  });
1746
1794
  }
1795
+ const headers2 = reply.getHeaders();
1796
+ headers2["Content-Type"] = "text/html; charset=utf-8";
1747
1797
  const cspHeader = reply.getHeader("Content-Security-Policy");
1748
- reply.raw.writeHead(200, {
1749
- "Content-Security-Policy": cspHeader,
1750
- "Content-Type": "text/html; charset=utf-8"
1751
- });
1798
+ if (cspHeader) headers2["Content-Security-Policy"] = cspHeader;
1799
+ reply.raw.writeHead(200, headers2);
1800
+ const abortedState = { aborted: false };
1752
1801
  const ac = new AbortController();
1753
- const onAborted = () => ac.abort();
1802
+ const onAborted = () => {
1803
+ if (!abortedState.aborted) {
1804
+ logger.warn({}, "Client disconnected before stream finished");
1805
+ abortedState.aborted = true;
1806
+ }
1807
+ ac.abort();
1808
+ };
1754
1809
  req.raw.on("aborted", onAborted);
1755
1810
  reply.raw.on("close", () => {
1756
- if (!reply.raw.writableEnded) ac.abort();
1811
+ if (!reply.raw.writableEnded) {
1812
+ if (!abortedState.aborted) {
1813
+ logger.warn({}, "Client disconnected before stream finished");
1814
+ abortedState.aborted = true;
1815
+ }
1816
+ ac.abort();
1817
+ }
1818
+ });
1819
+ reply.raw.on("finish", () => {
1820
+ req.raw.off("aborted", onAborted);
1757
1821
  });
1758
- reply.raw.on("finish", () => req.raw.off("aborted", onAborted));
1759
1822
  const shouldHydrate = attr?.hydrate !== false;
1760
- const abortedState = { aborted: false };
1761
1823
  const isBenignSocketAbort = (e) => {
1762
1824
  const msg = String(e?.message ?? e ?? "");
1763
1825
  return REGEX.BENIGN_NET_ERR.test(msg);
1764
1826
  };
1765
1827
  const writable = new PassThrough();
1766
1828
  writable.on("error", (err) => {
1767
- if (!isBenignSocketAbort(err)) logger.error("PassThrough error:", { error: err });
1829
+ if (!isBenignSocketAbort(err)) logger.error({ error: err }, "PassThrough error:");
1768
1830
  });
1769
1831
  reply.raw.on("error", (err) => {
1770
- if (!isBenignSocketAbort(err)) logger.error("HTTP socket error:", { error: err });
1832
+ if (!isBenignSocketAbort(err)) logger.error({ error: err }, "HTTP socket error:");
1771
1833
  });
1772
1834
  writable.pipe(reply.raw, { end: false });
1773
1835
  let finalData = void 0;
@@ -1787,30 +1849,33 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1787
1849
  },
1788
1850
  onError: (err) => {
1789
1851
  if (abortedState.aborted || isBenignSocketAbort(err)) {
1790
- logger.warn("Client disconnected before stream finished");
1852
+ logger.warn({}, "Client disconnected before stream finished");
1791
1853
  try {
1792
1854
  if (!reply.raw.writableEnded && !reply.raw.destroyed) reply.raw.destroy();
1793
1855
  } catch (e) {
1794
- logger.debug?.("stream teardown: destroy() failed", { error: normaliseError(e) });
1856
+ logger.debug?.("ssr", { error: normaliseError(e) }, "stream teardown: destroy() failed");
1795
1857
  }
1796
1858
  return;
1797
1859
  }
1798
1860
  abortedState.aborted = true;
1799
- logger.error("Critical rendering error during stream", {
1800
- error: normaliseError(err),
1801
- clientRoot,
1802
- url: req.url
1803
- });
1861
+ logger.error(
1862
+ {
1863
+ error: normaliseError(err),
1864
+ clientRoot,
1865
+ url: req.url
1866
+ },
1867
+ "Critical rendering error during stream"
1868
+ );
1804
1869
  try {
1805
1870
  ac?.abort?.();
1806
1871
  } catch (e) {
1807
- logger.debug?.("stream teardown: abort() failed", { error: normaliseError(e) });
1872
+ logger.debug?.("ssr", { error: normaliseError(e) }, "stream teardown: abort() failed");
1808
1873
  }
1809
1874
  const reason = toReason(err);
1810
1875
  try {
1811
1876
  if (!reply.raw.writableEnded && !reply.raw.destroyed) reply.raw.destroy(reason);
1812
1877
  } catch (e) {
1813
- logger.debug?.("stream teardown: destroy() failed", { error: normaliseError(e) });
1878
+ logger.debug?.("ssr", { error: normaliseError(e) }, "stream teardown: destroy() failed");
1814
1879
  }
1815
1880
  }
1816
1881
  },
@@ -1897,8 +1962,33 @@ var handleNotFound = async (req, reply, processedConfigs, maps, opts = {}) => {
1897
1962
  }
1898
1963
  };
1899
1964
 
1965
+ // src/utils/ResolveRouteData.ts
1966
+ async function resolveRouteData(url, opts) {
1967
+ const { req, reply, routeMatchers, serviceRegistry, logger } = opts;
1968
+ const match2 = matchRoute(url, routeMatchers);
1969
+ if (!match2) {
1970
+ throw AppError.notFound("route_not_found", {
1971
+ details: { url }
1972
+ });
1973
+ }
1974
+ const { route, params } = match2;
1975
+ const attr = route.attr;
1976
+ if (!attr?.data) {
1977
+ throw AppError.notFound("no_data_handler", {
1978
+ details: {
1979
+ url,
1980
+ path: route.path,
1981
+ appId: route.appId
1982
+ }
1983
+ });
1984
+ }
1985
+ const ctx = createRequestContext(req, reply, logger);
1986
+ return fetchInitialData(attr, params, serviceRegistry, ctx);
1987
+ }
1988
+
1900
1989
  // src/utils/StaticAssets.ts
1901
- function normalizeStaticAssets(reg) {
1990
+ import path5 from "path";
1991
+ function normaliseStaticAssets(reg) {
1902
1992
  if (!reg) return [];
1903
1993
  return Array.isArray(reg) ? reg : [reg];
1904
1994
  }
@@ -1906,11 +1996,14 @@ function prefixWeight(prefix) {
1906
1996
  if (typeof prefix !== "string" || prefix === "/" || prefix.length === 0) return 0;
1907
1997
  return prefix.split("/").filter(Boolean).length;
1908
1998
  }
1909
- async function registerStaticAssets(app, baseClientRoot, reg, defaults) {
1910
- const entries = normalizeStaticAssets(reg).map(({ plugin, options }) => ({
1999
+ async function registerStaticAssets(app, baseClientRoot, reg, defaults, projectRoot) {
2000
+ const isDevelopment2 = process.env.NODE_ENV === "development";
2001
+ const effectiveProjectRoot = projectRoot ?? path5.resolve(process.cwd());
2002
+ const staticRoot = isDevelopment2 ? baseClientRoot : path5.resolve(effectiveProjectRoot, "client");
2003
+ const entries = normaliseStaticAssets(reg).map(({ plugin, options }) => ({
1911
2004
  plugin,
1912
2005
  options: {
1913
- root: baseClientRoot,
2006
+ root: staticRoot,
1914
2007
  prefix: "/",
1915
2008
  index: false,
1916
2009
  wildcard: false,
@@ -1939,6 +2032,7 @@ var SSRServer = (0, import_fastify_plugin3.default)(
1939
2032
  const processedConfigs = processConfigs(configs, baseClientRoot, TEMPLATE);
1940
2033
  const routeMatchers = createRouteMatchers(routes);
1941
2034
  let viteDevServer;
2035
+ const projectRoot = path6.resolve(baseClientRoot, "..");
1942
2036
  await loadAssets(
1943
2037
  processedConfigs,
1944
2038
  baseClientRoot,
@@ -1951,10 +2045,11 @@ var SSRServer = (0, import_fastify_plugin3.default)(
1951
2045
  maps.templates,
1952
2046
  {
1953
2047
  debug: opts.debug,
1954
- logger
2048
+ logger,
2049
+ projectRoot
1955
2050
  }
1956
2051
  );
1957
- if (opts.staticAssets) await registerStaticAssets(app, baseClientRoot, opts.staticAssets);
2052
+ if (opts.staticAssets) await registerStaticAssets(app, baseClientRoot, opts.staticAssets, void 0, projectRoot);
1958
2053
  if (security?.csp?.reporting) {
1959
2054
  app.register(cspReportPlugin, {
1960
2055
  path: security.csp.reporting.endpoint,
@@ -1971,6 +2066,23 @@ var SSRServer = (0, import_fastify_plugin3.default)(
1971
2066
  });
1972
2067
  if (isDevelopment) viteDevServer = await setupDevServer(app, baseClientRoot, alias, opts.debug, opts.devNet);
1973
2068
  app.addHook("onRequest", createAuthHook(routeMatchers, logger));
2069
+ app.get("/__taujs/data", async (req, reply) => {
2070
+ const query = req.query;
2071
+ const url = typeof query.url === "string" ? query.url : "";
2072
+ if (!url) {
2073
+ throw AppError.badRequest("url query param required", {
2074
+ details: { query }
2075
+ });
2076
+ }
2077
+ const data = await resolveRouteData(url, {
2078
+ req,
2079
+ reply,
2080
+ routeMatchers,
2081
+ serviceRegistry,
2082
+ logger
2083
+ });
2084
+ return reply.status(200).send({ data });
2085
+ });
1974
2086
  app.get("/*", async (req, reply) => {
1975
2087
  await handleRender(req, reply, routeMatchers, processedConfigs, serviceRegistry, maps, {
1976
2088
  debug: opts.debug,
@@ -2026,9 +2138,9 @@ var SSRServer = (0, import_fastify_plugin3.default)(
2026
2138
  // src/CreateServer.ts
2027
2139
  var createServer = async (opts) => {
2028
2140
  const t0 = performance3.now();
2029
- const clientRoot = opts.clientRoot ?? path5.resolve(process.cwd(), "client");
2141
+ const clientRoot = opts.clientRoot ?? path7.resolve(process.cwd(), "client");
2030
2142
  const app = opts.fastify ?? Fastify({ logger: false });
2031
- const fastifyLogger = app.log && app.log.level !== "silent" ? app.log : void 0;
2143
+ const fastifyLogger = app.log && app.log.level && app.log.level !== "silent" ? app.log : void 0;
2032
2144
  const logger = createLogger({
2033
2145
  debug: opts.debug,
2034
2146
  custom: opts.logger ?? fastifyLogger,
@@ -2095,18 +2207,149 @@ ${import_picocolors4.default.bgGreen(import_picocolors4.default.black(` ${CONTEN
2095
2207
  };
2096
2208
 
2097
2209
  // src/Build.ts
2098
- import path6 from "path";
2210
+ import { existsSync } from "fs";
2211
+ import path8 from "path";
2099
2212
  import { build } from "vite";
2100
- import { nodePolyfills } from "vite-plugin-node-polyfills";
2213
+ function resolveInputs(isSSRBuild, mainExists, paths) {
2214
+ if (isSSRBuild) return { server: paths.server };
2215
+ if (mainExists) return { client: paths.client, main: paths.main };
2216
+ return { client: paths.client };
2217
+ }
2218
+ function getFrameworkInvariants(config) {
2219
+ return {
2220
+ root: config.root || "",
2221
+ base: config.base || "/",
2222
+ publicDir: config.publicDir === void 0 ? "public" : config.publicDir,
2223
+ build: {
2224
+ outDir: config.build?.outDir || "",
2225
+ manifest: config.build?.manifest ?? false,
2226
+ ssr: config.build?.ssr ?? void 0,
2227
+ // Preserve exact type
2228
+ ssrManifest: config.build?.ssrManifest ?? false,
2229
+ format: config.build?.format,
2230
+ target: config.build?.target,
2231
+ rollupOptions: {
2232
+ input: config.build?.rollupOptions?.input || {}
2233
+ }
2234
+ }
2235
+ };
2236
+ }
2237
+ function mergeViteConfig(framework, userOverride, context) {
2238
+ if (!userOverride) return framework;
2239
+ const userConfig = typeof userOverride === "function" && context ? userOverride(context) : userOverride;
2240
+ const invariants = getFrameworkInvariants(framework);
2241
+ const merged = {
2242
+ ...framework,
2243
+ build: { ...framework.build ?? {} },
2244
+ css: { ...framework.css ?? {} },
2245
+ resolve: { ...framework.resolve ?? {} },
2246
+ plugins: [...framework.plugins ?? []],
2247
+ define: { ...framework.define ?? {} }
2248
+ };
2249
+ const ignoredKeys = [];
2250
+ if (userConfig.plugins) {
2251
+ const frameworkPlugins = merged.plugins;
2252
+ merged.plugins = [...frameworkPlugins, ...userConfig.plugins];
2253
+ }
2254
+ if (userConfig.define && typeof userConfig.define === "object") {
2255
+ merged.define = {
2256
+ ...merged.define,
2257
+ ...userConfig.define
2258
+ };
2259
+ }
2260
+ if (userConfig.css?.preprocessorOptions && typeof userConfig.css.preprocessorOptions === "object") {
2261
+ const fpp = merged.css.preprocessorOptions ?? {};
2262
+ const upp = userConfig.css.preprocessorOptions;
2263
+ merged.css.preprocessorOptions = Object.keys({ ...fpp, ...upp }).reduce((acc, engine) => {
2264
+ const fppEngine = fpp[engine];
2265
+ const uppEngine = upp[engine];
2266
+ acc[engine] = { ...fppEngine ?? {}, ...uppEngine ?? {} };
2267
+ return acc;
2268
+ }, {});
2269
+ }
2270
+ if (userConfig.build) {
2271
+ const protectedBuildFields = ["outDir", "ssr", "ssrManifest", "format", "target"];
2272
+ for (const field of protectedBuildFields) {
2273
+ if (field in userConfig.build) {
2274
+ ignoredKeys.push(`build.${field}`);
2275
+ }
2276
+ }
2277
+ if ("sourcemap" in userConfig.build) merged.build.sourcemap = userConfig.build.sourcemap;
2278
+ if ("minify" in userConfig.build) merged.build.minify = userConfig.build.minify;
2279
+ if (userConfig.build.terserOptions && typeof userConfig.build.terserOptions === "object") {
2280
+ merged.build.terserOptions = {
2281
+ ...merged.build.terserOptions ?? {},
2282
+ ...userConfig.build.terserOptions
2283
+ };
2284
+ }
2285
+ if (userConfig.build.rollupOptions) {
2286
+ if (!merged.build.rollupOptions) {
2287
+ merged.build.rollupOptions = {};
2288
+ }
2289
+ const userRollup = userConfig.build.rollupOptions;
2290
+ if ("input" in userRollup) ignoredKeys.push("build.rollupOptions.input");
2291
+ if ("external" in userRollup) merged.build.rollupOptions.external = userRollup.external;
2292
+ if (userRollup.output) {
2293
+ const mro = merged.build.rollupOptions ??= {};
2294
+ const uo = Array.isArray(userRollup.output) ? userRollup.output[0] : userRollup.output;
2295
+ const baseOut = Array.isArray(mro.output) ? mro.output[0] ?? {} : mro.output ?? {};
2296
+ mro.output = { ...baseOut, ...uo?.manualChunks ? { manualChunks: uo.manualChunks } : {} };
2297
+ }
2298
+ }
2299
+ }
2300
+ if (userConfig.resolve) {
2301
+ const userResolve = userConfig.resolve;
2302
+ const { alias: _ignore, ...resolveRest } = userResolve;
2303
+ if (_ignore) ignoredKeys.push("resolve.alias");
2304
+ merged.resolve = {
2305
+ ...merged.resolve,
2306
+ ...resolveRest
2307
+ };
2308
+ }
2309
+ if (userConfig.server) ignoredKeys.push("server (ignored in build; dev-only)");
2310
+ if ("root" in userConfig) ignoredKeys.push("root");
2311
+ if ("base" in userConfig) ignoredKeys.push("base");
2312
+ if ("publicDir" in userConfig) ignoredKeys.push("publicDir");
2313
+ const safeTopLevelKeys = /* @__PURE__ */ new Set([
2314
+ "esbuild",
2315
+ "logLevel",
2316
+ "envPrefix",
2317
+ "optimizeDeps",
2318
+ "ssr"
2319
+ // NOTE: NOT 'server' (build-time irrelevant; dev-server only)
2320
+ ]);
2321
+ for (const [key, value] of Object.entries(userConfig)) {
2322
+ if (safeTopLevelKeys.has(key)) merged[key] = value;
2323
+ }
2324
+ merged.root = invariants.root;
2325
+ merged.base = invariants.base;
2326
+ merged.publicDir = invariants.publicDir;
2327
+ merged.build.outDir = invariants.build.outDir;
2328
+ merged.build.manifest = invariants.build.manifest;
2329
+ if (invariants.build.ssr !== void 0) merged.build.ssr = invariants.build.ssr;
2330
+ merged.build.ssrManifest = invariants.build.ssrManifest;
2331
+ if (invariants.build.format) merged.build.format = invariants.build.format;
2332
+ if (invariants.build.target) merged.build.target = invariants.build.target;
2333
+ if (!merged.build.rollupOptions) merged.build.rollupOptions = {};
2334
+ merged.build.rollupOptions.input = invariants.build.rollupOptions.input;
2335
+ if (ignoredKeys.length > 0) {
2336
+ const uniqueKeys = [...new Set(ignoredKeys)];
2337
+ const prefix = context ? `[taujs:build:${context.entryPoint}]` : "[taujs:build]";
2338
+ console.warn(`${prefix} Ignored Vite config overrides: ${uniqueKeys.join(", ")}`);
2339
+ }
2340
+ return merged;
2341
+ }
2101
2342
  async function taujsBuild({
2102
2343
  config,
2103
2344
  projectRoot,
2104
2345
  clientBaseDir,
2105
- isSSRBuild = process.env.BUILD_MODE === "ssr"
2346
+ isSSRBuild = process.env.BUILD_MODE === "ssr",
2347
+ alias: userAlias,
2348
+ vite: userViteConfig
2106
2349
  }) {
2107
2350
  const deleteDist = async () => {
2108
2351
  const { rm } = await import("fs/promises");
2109
- const distPath = path6.resolve(projectRoot, "dist");
2352
+ const distPath = path8.resolve(projectRoot, "dist");
2110
2353
  try {
2111
2354
  await rm(distPath, { recursive: true, force: true });
2112
2355
  console.log("Deleted the dist directory\n");
@@ -2117,26 +2360,36 @@ async function taujsBuild({
2117
2360
  const extractedConfigs = extractBuildConfigs(config);
2118
2361
  const processedConfigs = processConfigs(extractedConfigs, clientBaseDir, TEMPLATE);
2119
2362
  if (!isSSRBuild) await deleteDist();
2120
- for (const config2 of processedConfigs) {
2121
- const { appId, entryPoint, clientRoot, entryClient, entryServer, htmlTemplate, plugins = [] } = config2;
2122
- const outDir = path6.resolve(projectRoot, `dist/client/${entryPoint}`);
2123
- const root = entryPoint ? path6.resolve(clientBaseDir, entryPoint) : clientBaseDir;
2124
- const server = path6.resolve(clientRoot, `${entryServer}.tsx`);
2125
- const client = path6.resolve(clientRoot, `${entryClient}.tsx`);
2126
- const main = path6.resolve(clientRoot, htmlTemplate);
2127
- const viteConfig = {
2363
+ for (const appConfig of processedConfigs) {
2364
+ const { appId, entryPoint, clientRoot, entryClient, entryServer, htmlTemplate, plugins = [] } = appConfig;
2365
+ const outDir = path8.resolve(projectRoot, isSSRBuild ? `dist/ssr/${entryPoint}` : `dist/client/${entryPoint}`);
2366
+ const root = entryPoint ? path8.resolve(clientBaseDir, entryPoint) : clientBaseDir;
2367
+ const defaultAlias = {
2368
+ "@client": root,
2369
+ "@server": path8.resolve(projectRoot, "src/server"),
2370
+ "@shared": path8.resolve(projectRoot, "src/shared")
2371
+ };
2372
+ const resolvedAlias = { ...defaultAlias, ...userAlias ?? {} };
2373
+ const server = path8.resolve(clientRoot, `${entryServer}.tsx`);
2374
+ const client = path8.resolve(clientRoot, `${entryClient}.tsx`);
2375
+ const main = path8.resolve(clientRoot, htmlTemplate);
2376
+ const inputs = resolveInputs(isSSRBuild, !isSSRBuild && existsSync(main), { server, client, main });
2377
+ const nodeVersion = process.versions.node.split(".")[0];
2378
+ const frameworkConfig = {
2128
2379
  base: entryPoint ? `/${entryPoint}/` : "/",
2129
2380
  build: {
2130
2381
  outDir,
2382
+ emptyOutDir: true,
2131
2383
  manifest: !isSSRBuild,
2132
2384
  rollupOptions: {
2133
- input: isSSRBuild ? { server } : { client, main }
2385
+ input: inputs
2134
2386
  },
2135
2387
  ssr: isSSRBuild ? server : void 0,
2136
2388
  ssrManifest: isSSRBuild,
2137
2389
  ...isSSRBuild && {
2138
2390
  format: "esm",
2139
- target: `node${process.versions.node.split(".").map(Number)[0]}`
2391
+ target: `node${nodeVersion}`,
2392
+ copyPublicDir: false
2140
2393
  }
2141
2394
  },
2142
2395
  css: {
@@ -2144,33 +2397,28 @@ async function taujsBuild({
2144
2397
  scss: { api: "modern-compiler" }
2145
2398
  }
2146
2399
  },
2147
- plugins: [...plugins, nodePolyfills({ include: ["fs", "stream"] })],
2148
- publicDir: "public",
2149
- resolve: {
2150
- alias: {
2151
- "@client": root,
2152
- "@server": path6.resolve(projectRoot, "src/server"),
2153
- "@shared": path6.resolve(projectRoot, "src/shared")
2154
- }
2155
- },
2156
- root,
2157
- server: {
2158
- proxy: {
2159
- "/api": {
2160
- target: "http://localhost:3000",
2161
- changeOrigin: true,
2162
- rewrite: (path7) => path7.replace(/^\/api/, "")
2163
- }
2164
- }
2165
- }
2400
+ plugins,
2401
+ publicDir: isSSRBuild ? false : "public",
2402
+ // single shared public
2403
+ // publicDir: isSSRBuild ? false : path.join(root, 'public'), // per-app. no public dir for SSR builds
2404
+ resolve: { alias: resolvedAlias },
2405
+ root
2406
+ };
2407
+ const buildContext = {
2408
+ appId,
2409
+ entryPoint,
2410
+ isSSRBuild,
2411
+ clientRoot
2166
2412
  };
2413
+ const finalConfig = mergeViteConfig(frameworkConfig, userViteConfig, buildContext);
2167
2414
  try {
2168
- console.log(`Building for entryPoint: "${entryPoint}" (${appId})`);
2169
- await build(viteConfig);
2170
- console.log(`Build complete for entryPoint: "${entryPoint}"
2415
+ const mode = isSSRBuild ? "SSR" : "Client";
2416
+ console.log(`[taujs:build:${entryPoint}] Building \u2192 ${mode}`);
2417
+ await build(finalConfig);
2418
+ console.log(`[taujs:build:${entryPoint}] \u2713 Complete
2171
2419
  `);
2172
2420
  } catch (error) {
2173
- console.error(`Error building for entryPoint: "${entryPoint}"
2421
+ console.error(`[taujs:build:${entryPoint}] \u2717 Failed
2174
2422
  `, error);
2175
2423
  process.exit(1);
2176
2424
  }
@@ -2192,7 +2440,6 @@ function winstonAdapter(winston) {
2192
2440
  return messageMetaAdapter(winston);
2193
2441
  }
2194
2442
  export {
2195
- createLogger,
2196
2443
  createServer,
2197
2444
  taujsBuild,
2198
2445
  winstonAdapter
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taujs/server",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "τjs [ taujs ]",
5
5
  "author": "John Smith | Aoede <taujs@aoede.uk.net> (https://www.aoede.uk.net)",
6
6
  "license": "MIT",