@taujs/server 0.4.1 → 0.4.3

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/dist/index.js CHANGED
@@ -189,8 +189,8 @@ 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";
193
- import { performance as performance2 } from "perf_hooks";
192
+ import path7 from "path";
193
+ import { performance as performance3 } from "perf_hooks";
194
194
  import fastifyStatic from "@fastify/static";
195
195
  import Fastify from "fastify";
196
196
 
@@ -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,13 +344,133 @@ 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
- // src/network/Network.ts
352
- var import_picocolors3 = __toESM(require_picocolors(), 1);
353
- import { networkInterfaces } from "os";
351
+ // src/logging/AppError.ts
352
+ var HTTP_STATUS = {
353
+ infra: 500,
354
+ upstream: 502,
355
+ domain: 404,
356
+ validation: 400,
357
+ auth: 403,
358
+ canceled: 499,
359
+ // Client Closed Request (nginx convention)
360
+ timeout: 504
361
+ };
362
+ var AppError = class _AppError extends Error {
363
+ kind;
364
+ httpStatus;
365
+ details;
366
+ safeMessage;
367
+ code;
368
+ constructor(message, kind, options = {}) {
369
+ super(message);
370
+ this.name = "AppError";
371
+ Object.setPrototypeOf(this, new.target.prototype);
372
+ if (options.cause !== void 0) {
373
+ Object.defineProperty(this, "cause", {
374
+ value: options.cause,
375
+ enumerable: false,
376
+ writable: false,
377
+ configurable: true
378
+ });
379
+ }
380
+ this.kind = kind;
381
+ this.httpStatus = options.httpStatus ?? HTTP_STATUS[kind];
382
+ this.details = options.details;
383
+ this.safeMessage = options.safeMessage ?? this.getSafeMessage(kind, message);
384
+ this.code = options.code;
385
+ if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
386
+ }
387
+ getSafeMessage(kind, message) {
388
+ return kind === "domain" || kind === "validation" || kind === "auth" ? message : "Internal Server Error";
389
+ }
390
+ serialiseValue(value, seen = /* @__PURE__ */ new WeakSet()) {
391
+ if (value === null || value === void 0) return value;
392
+ if (typeof value !== "object") return value;
393
+ if (seen.has(value)) return "[circular]";
394
+ seen.add(value);
395
+ if (value instanceof Error) {
396
+ return {
397
+ name: value.name,
398
+ message: value.message,
399
+ stack: value.stack,
400
+ ...value instanceof _AppError && {
401
+ kind: value.kind,
402
+ httpStatus: value.httpStatus,
403
+ code: value.code
404
+ }
405
+ };
406
+ }
407
+ if (Array.isArray(value)) return value.map((item) => this.serialiseValue(item, seen));
408
+ const result = {};
409
+ for (const [key, val] of Object.entries(value)) {
410
+ result[key] = this.serialiseValue(val, seen);
411
+ }
412
+ return result;
413
+ }
414
+ toJSON() {
415
+ return {
416
+ name: this.name,
417
+ kind: this.kind,
418
+ message: this.message,
419
+ safeMessage: this.safeMessage,
420
+ httpStatus: this.httpStatus,
421
+ ...this.code && { code: this.code },
422
+ details: this.serialiseValue(this.details),
423
+ stack: this.stack,
424
+ ...this.cause && {
425
+ cause: this.serialiseValue(this.cause)
426
+ }
427
+ };
428
+ }
429
+ static notFound(message, details, code) {
430
+ return new _AppError(message, "domain", { httpStatus: 404, details, code });
431
+ }
432
+ static forbidden(message, details, code) {
433
+ return new _AppError(message, "auth", { httpStatus: 403, details, code });
434
+ }
435
+ static badRequest(message, details, code) {
436
+ return new _AppError(message, "validation", { httpStatus: 400, details, code });
437
+ }
438
+ static unprocessable(message, details, code) {
439
+ return new _AppError(message, "validation", { httpStatus: 422, details, code });
440
+ }
441
+ static timeout(message, details, code) {
442
+ return new _AppError(message, "timeout", { details, code });
443
+ }
444
+ static canceled(message, details, code) {
445
+ return new _AppError(message, "canceled", { details, code });
446
+ }
447
+ static internal(message, cause, details, code) {
448
+ return new _AppError(message, "infra", { cause, details, code });
449
+ }
450
+ static upstream(message, cause, details, code) {
451
+ return new _AppError(message, "upstream", { cause, details, code });
452
+ }
453
+ static serviceUnavailable(message, cause, details, code) {
454
+ return new _AppError(message, "infra", { httpStatus: 503, cause, details, code });
455
+ }
456
+ static from(err, fallback = "Internal error") {
457
+ return err instanceof _AppError ? err : _AppError.internal(err?.message ?? fallback, err);
458
+ }
459
+ };
460
+ function normaliseError(e) {
461
+ if (e instanceof Error) return { name: e.name, message: e.message, stack: e.stack };
462
+ const hasMessageProp = e != null && typeof e.message !== "undefined";
463
+ const msg = hasMessageProp ? String(e.message) : String(e);
464
+ return { name: "Error", message: msg };
465
+ }
466
+ function toReason(e) {
467
+ if (e instanceof Error) return e;
468
+ if (e === null) return new Error("null");
469
+ if (typeof e === "undefined") return new Error("Unknown render error");
470
+ const maybeMsg = e?.message;
471
+ if (typeof maybeMsg !== "undefined") return new Error(String(maybeMsg));
472
+ return new Error(String(e));
473
+ }
354
474
 
355
475
  // src/logging/Logger.ts
356
476
  var import_picocolors2 = __toESM(require_picocolors(), 1);
@@ -489,9 +609,11 @@ var Logger = class _Logger {
489
609
  if (!this.shouldEmit(level)) return;
490
610
  const timestamp = this.formatTimestamp();
491
611
  const wantCtx = this.config.includeContext === void 0 ? false : typeof this.config.includeContext === "function" ? this.config.includeContext(level) : this.config.includeContext;
492
- 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;
493
615
  const consoleFallback = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
494
- const sink = customSink ?? consoleFallback;
616
+ const hasCustom = !!boundSink;
495
617
  const merged = meta ?? {};
496
618
  const withCtx = wantCtx && Object.keys(this.context).length > 0 ? { context: this.context, ...merged } : merged;
497
619
  const finalMeta = this.shouldIncludeStack(level) ? withCtx : this.stripStacks(withCtx);
@@ -512,16 +634,20 @@ var Logger = class _Logger {
512
634
  return plainTag;
513
635
  }
514
636
  })();
515
- const tagForOutput = customSink ? plainTag : coloredTag;
637
+ const tagForOutput = hasCustom ? plainTag : coloredTag;
516
638
  const formatted = `${timestamp} ${tagForOutput} ${message}`;
517
- if (this.config.singleLine && hasMeta && !customSink) {
639
+ if (this.config.singleLine && hasMeta && !hasCustom) {
518
640
  const metaStr = JSON.stringify(finalMeta).replace(/\n/g, "\\n");
519
641
  consoleFallback(`${formatted} ${metaStr}`);
520
642
  return;
521
643
  }
522
- if (customSink) {
644
+ if (hasCustom) {
523
645
  const obj = hasMeta ? finalMeta : {};
524
- customSink(obj, formatted);
646
+ try {
647
+ const result = boundSink(obj, formatted);
648
+ } catch (err) {
649
+ hasMeta ? consoleFallback(formatted, finalMeta) : consoleFallback(formatted);
650
+ }
525
651
  } else {
526
652
  hasMeta ? consoleFallback(formatted, finalMeta) : consoleFallback(formatted);
527
653
  }
@@ -554,7 +680,54 @@ function createLogger(opts) {
554
680
  return logger;
555
681
  }
556
682
 
683
+ // src/network/CLI.ts
684
+ function readFlag(argv, keys, bareValue) {
685
+ const end = argv.indexOf("--");
686
+ const limit = end === -1 ? argv.length : end;
687
+ for (let i = 0; i < limit; i++) {
688
+ const arg = argv[i];
689
+ for (const key of keys) {
690
+ if (arg === key) {
691
+ const next = argv[i + 1];
692
+ if (!next || next.startsWith("-")) return bareValue;
693
+ return next.trim();
694
+ }
695
+ const pref = `${key}=`;
696
+ if (arg && arg.startsWith(pref)) {
697
+ const v = arg.slice(pref.length).trim();
698
+ return v || bareValue;
699
+ }
700
+ }
701
+ }
702
+ return void 0;
703
+ }
704
+ function resolveNet(input) {
705
+ const env = process.env;
706
+ const argv = process.argv;
707
+ let host = "localhost";
708
+ let port = 5173;
709
+ let hmrPort = 5174;
710
+ if (input?.host) host = input.host;
711
+ if (Number.isFinite(input?.port)) port = Number(input.port);
712
+ if (Number.isFinite(input?.hmrPort)) hmrPort = Number(input.hmrPort);
713
+ if (env.HOST?.trim()) host = env.HOST.trim();
714
+ else if (env.FASTIFY_ADDRESS?.trim()) host = env.FASTIFY_ADDRESS.trim();
715
+ if (env.PORT) port = Number(env.PORT) || port;
716
+ if (env.FASTIFY_PORT) port = Number(env.FASTIFY_PORT) || port;
717
+ if (env.HMR_PORT) hmrPort = Number(env.HMR_PORT) || hmrPort;
718
+ const cliHost = readFlag(argv, ["--host", "--hostname", "-H"], "0.0.0.0");
719
+ const cliPort = readFlag(argv, ["--port", "-p"]);
720
+ const cliHMR = readFlag(argv, ["--hmr-port"]);
721
+ if (cliHost) host = cliHost;
722
+ if (cliPort) port = Number(cliPort) || port;
723
+ if (cliHMR) hmrPort = Number(cliHMR) || hmrPort;
724
+ if (host === "true" || host === "") host = "0.0.0.0";
725
+ return { host, port, hmrPort };
726
+ }
727
+
557
728
  // src/network/Network.ts
729
+ var import_picocolors3 = __toESM(require_picocolors(), 1);
730
+ import { networkInterfaces } from "os";
558
731
  var isPrivateIPv4 = (addr) => {
559
732
  if (!/^\d+\.\d+\.\d+\.\d+$/.test(addr)) return false;
560
733
  const [a, b, _c, _d] = addr.split(".").map(Number);
@@ -606,51 +779,6 @@ var bannerPlugin = async (fastify, options) => {
606
779
  });
607
780
  };
608
781
 
609
- // src/network/CLI.ts
610
- function readFlag(argv, keys, bareValue) {
611
- const end = argv.indexOf("--");
612
- const limit = end === -1 ? argv.length : end;
613
- for (let i = 0; i < limit; i++) {
614
- const arg = argv[i];
615
- for (const key of keys) {
616
- if (arg === key) {
617
- const next = argv[i + 1];
618
- if (!next || next.startsWith("-")) return bareValue;
619
- return next.trim();
620
- }
621
- const pref = `${key}=`;
622
- if (arg && arg.startsWith(pref)) {
623
- const v = arg.slice(pref.length).trim();
624
- return v || bareValue;
625
- }
626
- }
627
- }
628
- return void 0;
629
- }
630
- function resolveNet(input) {
631
- const env = process.env;
632
- const argv = process.argv;
633
- let host = "localhost";
634
- let port = 5173;
635
- let hmrPort = 5174;
636
- if (input?.host) host = input.host;
637
- if (Number.isFinite(input?.port)) port = Number(input.port);
638
- if (Number.isFinite(input?.hmrPort)) hmrPort = Number(input.hmrPort);
639
- if (env.HOST?.trim()) host = env.HOST.trim();
640
- else if (env.FASTIFY_ADDRESS?.trim()) host = env.FASTIFY_ADDRESS.trim();
641
- if (env.PORT) port = Number(env.PORT) || port;
642
- if (env.FASTIFY_PORT) port = Number(env.FASTIFY_PORT) || port;
643
- if (env.HMR_PORT) hmrPort = Number(env.HMR_PORT) || hmrPort;
644
- const cliHost = readFlag(argv, ["--host", "--hostname", "-H"], "0.0.0.0");
645
- const cliPort = readFlag(argv, ["--port", "-p"]);
646
- const cliHMR = readFlag(argv, ["--hmr-port"]);
647
- if (cliHost) host = cliHost;
648
- if (cliPort) port = Number(cliPort) || port;
649
- if (cliHMR) hmrPort = Number(cliHMR) || hmrPort;
650
- if (host === "true" || host === "") host = "0.0.0.0";
651
- return { host, port, hmrPort };
652
- }
653
-
654
782
  // src/security/VerifyMiddleware.ts
655
783
  var isAuthRequired = (route) => Boolean(route.attr?.middleware?.auth);
656
784
  var hasAuthenticate = (app) => typeof app.authenticate === "function";
@@ -717,130 +845,7 @@ var verifyContracts = (app, routes, contracts, security) => {
717
845
 
718
846
  // src/SSRServer.ts
719
847
  var import_fastify_plugin3 = __toESM(require_plugin(), 1);
720
-
721
- // src/logging/AppError.ts
722
- var HTTP_STATUS = {
723
- infra: 500,
724
- upstream: 502,
725
- domain: 404,
726
- validation: 400,
727
- auth: 403,
728
- canceled: 499,
729
- // Client Closed Request (nginx convention)
730
- timeout: 504
731
- };
732
- var AppError = class _AppError extends Error {
733
- kind;
734
- httpStatus;
735
- details;
736
- safeMessage;
737
- code;
738
- constructor(message, kind, options = {}) {
739
- super(message);
740
- this.name = "AppError";
741
- Object.setPrototypeOf(this, new.target.prototype);
742
- if (options.cause !== void 0) {
743
- Object.defineProperty(this, "cause", {
744
- value: options.cause,
745
- enumerable: false,
746
- writable: false,
747
- configurable: true
748
- });
749
- }
750
- this.kind = kind;
751
- this.httpStatus = options.httpStatus ?? HTTP_STATUS[kind];
752
- this.details = options.details;
753
- this.safeMessage = options.safeMessage ?? this.getSafeMessage(kind, message);
754
- this.code = options.code;
755
- if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
756
- }
757
- getSafeMessage(kind, message) {
758
- return kind === "domain" || kind === "validation" || kind === "auth" ? message : "Internal Server Error";
759
- }
760
- serialiseValue(value, seen = /* @__PURE__ */ new WeakSet()) {
761
- if (value === null || value === void 0) return value;
762
- if (typeof value !== "object") return value;
763
- if (seen.has(value)) return "[circular]";
764
- seen.add(value);
765
- if (value instanceof Error) {
766
- return {
767
- name: value.name,
768
- message: value.message,
769
- stack: value.stack,
770
- ...value instanceof _AppError && {
771
- kind: value.kind,
772
- httpStatus: value.httpStatus,
773
- code: value.code
774
- }
775
- };
776
- }
777
- if (Array.isArray(value)) return value.map((item) => this.serialiseValue(item, seen));
778
- const result = {};
779
- for (const [key, val] of Object.entries(value)) {
780
- result[key] = this.serialiseValue(val, seen);
781
- }
782
- return result;
783
- }
784
- toJSON() {
785
- return {
786
- name: this.name,
787
- kind: this.kind,
788
- message: this.message,
789
- safeMessage: this.safeMessage,
790
- httpStatus: this.httpStatus,
791
- ...this.code && { code: this.code },
792
- details: this.serialiseValue(this.details),
793
- stack: this.stack,
794
- ...this.cause && {
795
- cause: this.serialiseValue(this.cause)
796
- }
797
- };
798
- }
799
- static notFound(message, details, code) {
800
- return new _AppError(message, "domain", { httpStatus: 404, details, code });
801
- }
802
- static forbidden(message, details, code) {
803
- return new _AppError(message, "auth", { httpStatus: 403, details, code });
804
- }
805
- static badRequest(message, details, code) {
806
- return new _AppError(message, "validation", { httpStatus: 400, details, code });
807
- }
808
- static unprocessable(message, details, code) {
809
- return new _AppError(message, "validation", { httpStatus: 422, details, code });
810
- }
811
- static timeout(message, details, code) {
812
- return new _AppError(message, "timeout", { details, code });
813
- }
814
- static canceled(message, details, code) {
815
- return new _AppError(message, "canceled", { details, code });
816
- }
817
- static internal(message, cause, details, code) {
818
- return new _AppError(message, "infra", { cause, details, code });
819
- }
820
- static upstream(message, cause, details, code) {
821
- return new _AppError(message, "upstream", { cause, details, code });
822
- }
823
- static serviceUnavailable(message, cause, details, code) {
824
- return new _AppError(message, "infra", { httpStatus: 503, cause, details, code });
825
- }
826
- static from(err, fallback = "Internal error") {
827
- return err instanceof _AppError ? err : _AppError.internal(err?.message ?? fallback, err);
828
- }
829
- };
830
- function normaliseError(e) {
831
- if (e instanceof Error) return { name: e.name, message: e.message, stack: e.stack };
832
- const hasMessageProp = e != null && typeof e.message !== "undefined";
833
- const msg = hasMessageProp ? String(e.message) : String(e);
834
- return { name: "Error", message: msg };
835
- }
836
- function toReason(e) {
837
- if (e instanceof Error) return e;
838
- if (e === null) return new Error("null");
839
- if (typeof e === "undefined") return new Error("Unknown render error");
840
- const maybeMsg = e?.message;
841
- if (typeof maybeMsg !== "undefined") return new Error(String(maybeMsg));
842
- return new Error(String(e));
843
- }
848
+ import path6 from "path";
844
849
 
845
850
  // src/logging/utils/index.ts
846
851
  var httpStatusFrom = (err, fallback = 500) => err instanceof AppError ? err.httpStatus : fallback;
@@ -880,39 +885,53 @@ var statusText = (status) => {
880
885
  import { match } from "path-to-regexp";
881
886
 
882
887
  // src/utils/DataServices.ts
888
+ import { performance as performance2 } from "perf_hooks";
889
+ function createCaller(registry, ctx) {
890
+ return (serviceName, methodName, args) => callServiceMethod(registry, serviceName, methodName, args ?? {}, ctx);
891
+ }
892
+ function ensureServiceCaller(registry, ctx) {
893
+ if (!ctx.call) ctx.call = createCaller(registry, ctx);
894
+ }
883
895
  async function callServiceMethod(registry, serviceName, methodName, params, ctx) {
884
896
  if (ctx.signal?.aborted) throw AppError.timeout("Request canceled");
885
897
  const service = registry[serviceName];
886
898
  if (!service) throw AppError.notFound(`Unknown service: ${serviceName}`);
887
- const desc = service[methodName];
888
- if (!desc) throw AppError.notFound(`Unknown method: ${serviceName}.${methodName}`);
889
- const logger = ctx.logger?.child({
899
+ const method = service[methodName];
900
+ if (!method) throw AppError.notFound(`Unknown method: ${serviceName}.${methodName}`);
901
+ const logger = ctx.logger?.child?.({
890
902
  component: "service-call",
891
903
  service: serviceName,
892
904
  method: methodName,
893
905
  traceId: ctx.traceId
894
906
  });
907
+ const t0 = performance2.now();
895
908
  try {
896
- const p = desc.parsers?.params ? desc.parsers.params(params) : params;
897
- const data = await desc.handler(p, ctx);
898
- const out = desc.parsers?.result ? desc.parsers.result(data) : data;
899
- if (typeof out !== "object" || out === null) throw AppError.internal(`Non-object result from ${serviceName}.${methodName}`);
900
- return out;
909
+ const result = await method(params ?? {}, ctx);
910
+ if (typeof result !== "object" || result === null) {
911
+ throw AppError.internal(`Non-object result from ${serviceName}.${methodName}`);
912
+ }
913
+ logger?.debug?.({ ms: +(performance2.now() - t0).toFixed(1) }, "Service method ok");
914
+ return result;
901
915
  } catch (err) {
902
- logger?.error(
916
+ logger?.error?.(
903
917
  {
904
918
  params,
905
- error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : String(err)
919
+ error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : String(err),
920
+ ms: +(performance2.now() - t0).toFixed(1)
906
921
  },
907
922
  "Service method failed"
908
923
  );
909
- throw err;
924
+ throw err instanceof AppError ? err : err instanceof Error ? AppError.internal(err.message, { cause: err }) : AppError.internal("Unknown error", { details: { err } });
910
925
  }
911
926
  }
912
927
  var isServiceDescriptor = (obj) => {
913
- if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return false;
914
- const maybe = obj;
915
- return typeof maybe.serviceName === "string" && typeof maybe.serviceMethod === "string";
928
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) return false;
929
+ const o = obj;
930
+ if (typeof o.serviceName !== "string" || typeof o.serviceMethod !== "string") return false;
931
+ if ("args" in o) {
932
+ if (o.args === null || typeof o.args !== "object" || Array.isArray(o.args)) return false;
933
+ }
934
+ return true;
916
935
  };
917
936
 
918
937
  // src/utils/DataRoutes.ts
@@ -923,15 +942,15 @@ var safeDecode = (value) => {
923
942
  return value;
924
943
  }
925
944
  };
926
- var cleanPath = (path7) => {
927
- if (!path7) return "/";
928
- const basePart = path7.split("?")[0];
945
+ var cleanPath = (path9) => {
946
+ if (!path9) return "/";
947
+ const basePart = path9.split("?")[0];
929
948
  const base = basePart ? basePart.split("#")[0] : "/";
930
949
  return base || "/";
931
950
  };
932
- var calculateSpecificity = (path7) => {
951
+ var calculateSpecificity = (path9) => {
933
952
  let score = 0;
934
- const segments = path7.split("/").filter(Boolean);
953
+ const segments = path9.split("/").filter(Boolean);
935
954
  for (const segment of segments) {
936
955
  if (segment.startsWith(":")) {
937
956
  score += 1;
@@ -956,9 +975,9 @@ var createRouteMatchers = (routes) => {
956
975
  });
957
976
  };
958
977
  var matchRoute = (url, routeMatchers) => {
959
- const path7 = cleanPath(url);
978
+ const path9 = cleanPath(url);
960
979
  for (const { route, matcher, keys } of routeMatchers) {
961
- const match2 = matcher(path7);
980
+ const match2 = matcher(path9);
962
981
  if (match2) {
963
982
  return {
964
983
  route,
@@ -972,14 +991,16 @@ var matchRoute = (url, routeMatchers) => {
972
991
  var fetchInitialData = async (attr, params, serviceRegistry, ctx, callServiceMethodImpl = callServiceMethod) => {
973
992
  const dataHandler = attr?.data;
974
993
  if (!dataHandler || typeof dataHandler !== "function") return {};
994
+ const ctxForData = {
995
+ ...ctx,
996
+ headers: ctx.headers ?? {}
997
+ };
998
+ ensureServiceCaller(serviceRegistry, ctxForData);
975
999
  try {
976
- const result = await dataHandler(params, {
977
- ...ctx,
978
- headers: ctx.headers ?? {}
979
- });
1000
+ const result = await dataHandler(params, ctxForData);
980
1001
  if (isServiceDescriptor(result)) {
981
1002
  const { serviceName, serviceMethod, args } = result;
982
- return callServiceMethodImpl(serviceRegistry, serviceName, serviceMethod, args ?? {}, ctx);
1003
+ return callServiceMethodImpl(serviceRegistry, serviceName, serviceMethod, args ?? {}, ctxForData);
983
1004
  }
984
1005
  if (isPlainObject(result)) return result;
985
1006
  throw AppError.badRequest("attr.data must return a plain object or a ServiceDescriptor");
@@ -1019,6 +1040,16 @@ var createAuthHook = (routeMatchers, logger) => {
1019
1040
  if (!match2) return;
1020
1041
  const { route } = match2;
1021
1042
  const authConfig = route.attr?.middleware?.auth;
1043
+ req.routeMeta = {
1044
+ path: route.path,
1045
+ appId: route.appId,
1046
+ attr: {
1047
+ middleware: {
1048
+ auth: route.attr?.middleware?.auth
1049
+ },
1050
+ render: route.attr?.render
1051
+ }
1052
+ };
1022
1053
  if (!authConfig) {
1023
1054
  logger.debug("auth", { method: req.method, url: req.url }, "(none)");
1024
1055
  return;
@@ -1086,9 +1117,9 @@ var mergeDirectives = (base, override) => {
1086
1117
  }
1087
1118
  return merged;
1088
1119
  };
1089
- var findMatchingRoute = (routeMatchers, path7) => {
1120
+ var findMatchingRoute = (routeMatchers, path9) => {
1090
1121
  if (!routeMatchers) return null;
1091
- const match2 = matchRoute(path7, routeMatchers);
1122
+ const match2 = matchRoute(path9, routeMatchers);
1092
1123
  return match2 ? { route: match2.route, params: match2.params } : null;
1093
1124
  };
1094
1125
  var cspPlugin = (0, import_fastify_plugin.default)(
@@ -1103,42 +1134,27 @@ var cspPlugin = (0, import_fastify_plugin.default)(
1103
1134
  fastify.addHook("onRequest", (req, reply, done) => {
1104
1135
  const nonce = generateNonce();
1105
1136
  req.cspNonce = nonce;
1137
+ const headerNameFor = (routeCSP2) => routeCSP2 && typeof routeCSP2 === "object" && routeCSP2.reportOnly || opts.reporting?.reportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
1138
+ let routeCSP;
1106
1139
  try {
1107
1140
  const routeMatch = findMatchingRoute(matchers, req.url);
1108
- const routeCSP = routeMatch?.route.attr?.middleware?.csp;
1141
+ routeCSP = routeMatch?.route.attr?.middleware?.csp;
1109
1142
  if (routeCSP === false) {
1110
1143
  done();
1111
1144
  return;
1112
1145
  }
1113
1146
  let finalDirectives = globalDirectives;
1114
- if (routeCSP && typeof routeCSP === "object") {
1115
- if (!routeCSP.disabled) {
1116
- let routeDirectives;
1117
- if (typeof routeCSP.directives === "function") {
1118
- const params = routeMatch?.params || {};
1119
- routeDirectives = routeCSP.directives({
1120
- url: req.url,
1121
- params,
1122
- headers: req.headers,
1123
- req
1124
- });
1125
- } else {
1126
- routeDirectives = routeCSP.directives || {};
1127
- }
1128
- if (routeCSP.mode === "replace") {
1129
- finalDirectives = routeDirectives;
1130
- } else {
1131
- finalDirectives = mergeDirectives(globalDirectives, routeDirectives);
1132
- }
1133
- }
1134
- }
1135
- let cspHeader;
1136
- if (routeCSP?.generateCSP) {
1137
- cspHeader = routeCSP.generateCSP(finalDirectives, nonce, req);
1138
- } else {
1139
- cspHeader = generateCSP(finalDirectives, nonce, req);
1147
+ if (routeCSP && typeof routeCSP === "object" && !routeCSP.disabled) {
1148
+ const routeDirectives = typeof routeCSP.directives === "function" ? routeCSP.directives({
1149
+ url: req.url,
1150
+ params: routeMatch?.params || {},
1151
+ headers: req.headers,
1152
+ req
1153
+ }) : routeCSP.directives ?? {};
1154
+ finalDirectives = routeCSP.mode === "replace" ? routeDirectives : mergeDirectives(globalDirectives, routeDirectives);
1140
1155
  }
1141
- reply.header("Content-Security-Policy", cspHeader);
1156
+ const cspHeader = routeCSP?.generateCSP ? routeCSP.generateCSP(finalDirectives, nonce, req) : generateCSP(finalDirectives, nonce, req);
1157
+ reply.header(headerNameFor(routeCSP), cspHeader);
1142
1158
  } catch (error) {
1143
1159
  logger.error(
1144
1160
  {
@@ -1148,7 +1164,7 @@ var cspPlugin = (0, import_fastify_plugin.default)(
1148
1164
  "CSP plugin error"
1149
1165
  );
1150
1166
  const fallbackHeader = generateCSP(globalDirectives, nonce, req);
1151
- reply.header("Content-Security-Policy", fallbackHeader);
1167
+ reply.header(headerNameFor(routeCSP), fallbackHeader);
1152
1168
  }
1153
1169
  done();
1154
1170
  });
@@ -1433,8 +1449,9 @@ var loadAssets = async (processedConfigs, baseClientRoot, bootstrapModules, cssL
1433
1449
  debug: opts.debug,
1434
1450
  includeContext: true
1435
1451
  });
1452
+ const projectRoot = opts.projectRoot ?? path2.resolve(process.cwd());
1436
1453
  for (const config of processedConfigs) {
1437
- const { clientRoot, entryClient, entryServer, htmlTemplate } = config;
1454
+ const { clientRoot, entryClient, entryServer, htmlTemplate, entryPoint } = config;
1438
1455
  try {
1439
1456
  const templateHtmlPath = path2.join(clientRoot, htmlTemplate);
1440
1457
  const templateHtml = await readFile(templateHtmlPath, "utf-8");
@@ -1443,11 +1460,13 @@ var loadAssets = async (processedConfigs, baseClientRoot, bootstrapModules, cssL
1443
1460
  const adjustedRelativePath = relativeBasePath ? `/${relativeBasePath}` : "";
1444
1461
  if (!isDevelopment) {
1445
1462
  try {
1446
- const manifestPath = path2.join(clientRoot, ".vite/manifest.json");
1463
+ const clientDistPath = path2.resolve(projectRoot, "client", entryPoint);
1464
+ const manifestPath = path2.join(clientDistPath, ".vite/manifest.json");
1447
1465
  const manifestContent = await readFile(manifestPath, "utf-8");
1448
1466
  const manifest = JSON.parse(manifestContent);
1449
1467
  manifests.set(clientRoot, manifest);
1450
- const ssrManifestPath = path2.join(clientRoot, ".vite/ssr-manifest.json");
1468
+ const ssrDistPath = path2.resolve(projectRoot, "ssr", entryPoint);
1469
+ const ssrManifestPath = path2.join(ssrDistPath, ".vite/ssr-manifest.json");
1451
1470
  const ssrManifestContent = await readFile(ssrManifestPath, "utf-8");
1452
1471
  const ssrManifest = JSON.parse(ssrManifestContent);
1453
1472
  ssrManifests.set(clientRoot, ssrManifest);
@@ -1467,7 +1486,7 @@ var loadAssets = async (processedConfigs, baseClientRoot, bootstrapModules, cssL
1467
1486
  preloadLinks.set(clientRoot, preloadLink);
1468
1487
  const cssLink = getCssLinks(manifest, adjustedRelativePath);
1469
1488
  cssLinks.set(clientRoot, cssLink);
1470
- const renderModulePath = path2.join(clientRoot, `${entryServer}.js`);
1489
+ const renderModulePath = path2.join(ssrDistPath, `${entryServer}.js`);
1471
1490
  const moduleUrl = pathToFileURL(renderModulePath).href;
1472
1491
  try {
1473
1492
  const importedModule = await import(moduleUrl);
@@ -1475,7 +1494,7 @@ var loadAssets = async (processedConfigs, baseClientRoot, bootstrapModules, cssL
1475
1494
  } catch (err) {
1476
1495
  throw AppError.internal(`Failed to load render module ${renderModulePath}`, {
1477
1496
  cause: err,
1478
- details: { moduleUrl, clientRoot, entryServer }
1497
+ details: { moduleUrl, clientRoot, entryServer, ssrDistPath }
1479
1498
  });
1480
1499
  }
1481
1500
  } catch (err) {
@@ -1686,10 +1705,15 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1686
1705
  if (renderType === RENDERTYPE.ssr) {
1687
1706
  const { renderSSR } = renderModule;
1688
1707
  if (!renderSSR) {
1689
- throw AppError.internal("renderSSR function not found in module", {
1690
- details: { clientRoot, availableFunctions: Object.keys(renderModule) }
1691
- });
1708
+ throw AppError.internal(
1709
+ "ssr",
1710
+ {
1711
+ details: { clientRoot, availableFunctions: Object.keys(renderModule) }
1712
+ },
1713
+ "renderSSR function not found in module"
1714
+ );
1692
1715
  }
1716
+ logger.debug?.("ssr", {}, "ssr requested");
1693
1717
  const ac = new AbortController();
1694
1718
  const onAborted = () => ac.abort("client_aborted");
1695
1719
  req.raw.on("aborted", onAborted);
@@ -1698,7 +1722,7 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1698
1722
  });
1699
1723
  reply.raw.on("finish", () => req.raw.off("aborted", onAborted));
1700
1724
  if (ac.signal.aborted) {
1701
- logger.warn("SSR skipped; already aborted", { url: req.url });
1725
+ logger.warn({ url: req.url }, "SSR skipped; already aborted");
1702
1726
  return;
1703
1727
  }
1704
1728
  const initialDataResolved = await initialDataInput();
@@ -1708,14 +1732,31 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1708
1732
  const res = await renderSSR(initialDataResolved, req.url, attr?.meta, ac.signal, { logger: reqLogger });
1709
1733
  headContent = res.headContent;
1710
1734
  appHtml = res.appHtml;
1735
+ logger.debug?.("ssr", {}, "ssr data resolved");
1736
+ if (ac.signal.aborted) {
1737
+ logger.warn({}, "SSR completed but client disconnected");
1738
+ return;
1739
+ }
1711
1740
  } catch (err) {
1712
1741
  const msg = String(err?.message ?? err ?? "");
1713
1742
  const benign = REGEX.BENIGN_NET_ERR.test(msg);
1714
1743
  if (ac.signal.aborted || benign) {
1715
- logger.warn("SSR aborted mid-render (benign)", { url: req.url, reason: msg });
1744
+ logger.warn(
1745
+ {
1746
+ url: req.url,
1747
+ reason: msg
1748
+ },
1749
+ "SSR aborted mid-render (benign)"
1750
+ );
1716
1751
  return;
1717
1752
  }
1718
- logger.error("SSR render failed", { url: req.url, error: normaliseError(err) });
1753
+ logger.error(
1754
+ {
1755
+ url: req.url,
1756
+ error: normaliseError(err)
1757
+ },
1758
+ "SSR render failed"
1759
+ );
1719
1760
  throw err;
1720
1761
  }
1721
1762
  let aggregateHeadContent = headContent;
@@ -1727,13 +1768,14 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1727
1768
  const bootstrapScriptTag = shouldHydrate && bootstrapModule ? `<script${nonceAttr} type="module" src="${bootstrapModule}" defer></script>` : "";
1728
1769
  const safeAppHtml = appHtml.trim();
1729
1770
  const fullHtml = rebuildTemplate(templateParts, aggregateHeadContent, `${safeAppHtml}${initialDataScript}${bootstrapScriptTag}`);
1771
+ logger.debug?.("ssr", {}, "ssr template rebuilt and sending response");
1730
1772
  try {
1731
1773
  return reply.status(200).header("Content-Type", "text/html").send(fullHtml);
1732
1774
  } catch (err) {
1733
1775
  const msg = String(err?.message ?? err ?? "");
1734
1776
  const benign = REGEX.BENIGN_NET_ERR.test(msg);
1735
- if (!benign) logger.error("SSR send failed", { url: req.url, error: normaliseError(err) });
1736
- else logger.warn("SSR send aborted (benign)", { url: req.url, reason: msg });
1777
+ if (!benign) logger.error({ url: req.url, error: normaliseError(err) }, "SSR send failed");
1778
+ else logger.warn({ url: req.url, reason: msg }, "SSR send aborted (benign)");
1737
1779
  return;
1738
1780
  }
1739
1781
  } else {
@@ -1743,30 +1785,44 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1743
1785
  details: { clientRoot, availableFunctions: Object.keys(renderModule) }
1744
1786
  });
1745
1787
  }
1788
+ const headers2 = reply.getHeaders();
1789
+ headers2["Content-Type"] = "text/html; charset=utf-8";
1746
1790
  const cspHeader = reply.getHeader("Content-Security-Policy");
1747
- reply.raw.writeHead(200, {
1748
- "Content-Security-Policy": cspHeader,
1749
- "Content-Type": "text/html; charset=utf-8"
1750
- });
1791
+ if (cspHeader) headers2["Content-Security-Policy"] = cspHeader;
1792
+ reply.raw.writeHead(200, headers2);
1793
+ const abortedState = { aborted: false };
1751
1794
  const ac = new AbortController();
1752
- const onAborted = () => ac.abort();
1795
+ const onAborted = () => {
1796
+ if (!abortedState.aborted) {
1797
+ logger.warn({}, "Client disconnected before stream finished");
1798
+ abortedState.aborted = true;
1799
+ }
1800
+ ac.abort();
1801
+ };
1753
1802
  req.raw.on("aborted", onAborted);
1754
1803
  reply.raw.on("close", () => {
1755
- if (!reply.raw.writableEnded) ac.abort();
1804
+ if (!reply.raw.writableEnded) {
1805
+ if (!abortedState.aborted) {
1806
+ logger.warn({}, "Client disconnected before stream finished");
1807
+ abortedState.aborted = true;
1808
+ }
1809
+ ac.abort();
1810
+ }
1811
+ });
1812
+ reply.raw.on("finish", () => {
1813
+ req.raw.off("aborted", onAborted);
1756
1814
  });
1757
- reply.raw.on("finish", () => req.raw.off("aborted", onAborted));
1758
1815
  const shouldHydrate = attr?.hydrate !== false;
1759
- const abortedState = { aborted: false };
1760
1816
  const isBenignSocketAbort = (e) => {
1761
1817
  const msg = String(e?.message ?? e ?? "");
1762
1818
  return REGEX.BENIGN_NET_ERR.test(msg);
1763
1819
  };
1764
1820
  const writable = new PassThrough();
1765
1821
  writable.on("error", (err) => {
1766
- if (!isBenignSocketAbort(err)) logger.error("PassThrough error:", { error: err });
1822
+ if (!isBenignSocketAbort(err)) logger.error({ error: err }, "PassThrough error:");
1767
1823
  });
1768
1824
  reply.raw.on("error", (err) => {
1769
- if (!isBenignSocketAbort(err)) logger.error("HTTP socket error:", { error: err });
1825
+ if (!isBenignSocketAbort(err)) logger.error({ error: err }, "HTTP socket error:");
1770
1826
  });
1771
1827
  writable.pipe(reply.raw, { end: false });
1772
1828
  let finalData = void 0;
@@ -1786,30 +1842,33 @@ var handleRender = async (req, reply, routeMatchers, processedConfigs, serviceRe
1786
1842
  },
1787
1843
  onError: (err) => {
1788
1844
  if (abortedState.aborted || isBenignSocketAbort(err)) {
1789
- logger.warn("Client disconnected before stream finished");
1845
+ logger.warn({}, "Client disconnected before stream finished");
1790
1846
  try {
1791
1847
  if (!reply.raw.writableEnded && !reply.raw.destroyed) reply.raw.destroy();
1792
1848
  } catch (e) {
1793
- logger.debug?.("stream teardown: destroy() failed", { error: normaliseError(e) });
1849
+ logger.debug?.("ssr", { error: normaliseError(e) }, "stream teardown: destroy() failed");
1794
1850
  }
1795
1851
  return;
1796
1852
  }
1797
1853
  abortedState.aborted = true;
1798
- logger.error("Critical rendering error during stream", {
1799
- error: normaliseError(err),
1800
- clientRoot,
1801
- url: req.url
1802
- });
1854
+ logger.error(
1855
+ {
1856
+ error: normaliseError(err),
1857
+ clientRoot,
1858
+ url: req.url
1859
+ },
1860
+ "Critical rendering error during stream"
1861
+ );
1803
1862
  try {
1804
1863
  ac?.abort?.();
1805
1864
  } catch (e) {
1806
- logger.debug?.("stream teardown: abort() failed", { error: normaliseError(e) });
1865
+ logger.debug?.("ssr", { error: normaliseError(e) }, "stream teardown: abort() failed");
1807
1866
  }
1808
1867
  const reason = toReason(err);
1809
1868
  try {
1810
1869
  if (!reply.raw.writableEnded && !reply.raw.destroyed) reply.raw.destroy(reason);
1811
1870
  } catch (e) {
1812
- logger.debug?.("stream teardown: destroy() failed", { error: normaliseError(e) });
1871
+ logger.debug?.("ssr", { error: normaliseError(e) }, "stream teardown: destroy() failed");
1813
1872
  }
1814
1873
  }
1815
1874
  },
@@ -1896,8 +1955,33 @@ var handleNotFound = async (req, reply, processedConfigs, maps, opts = {}) => {
1896
1955
  }
1897
1956
  };
1898
1957
 
1958
+ // src/utils/ResolveRouteData.ts
1959
+ async function resolveRouteData(url, opts) {
1960
+ const { req, reply, routeMatchers, serviceRegistry, logger } = opts;
1961
+ const match2 = matchRoute(url, routeMatchers);
1962
+ if (!match2) {
1963
+ throw AppError.notFound("route_not_found", {
1964
+ details: { url }
1965
+ });
1966
+ }
1967
+ const { route, params } = match2;
1968
+ const attr = route.attr;
1969
+ if (!attr?.data) {
1970
+ throw AppError.notFound("no_data_handler", {
1971
+ details: {
1972
+ url,
1973
+ path: route.path,
1974
+ appId: route.appId
1975
+ }
1976
+ });
1977
+ }
1978
+ const ctx = createRequestContext(req, reply, logger);
1979
+ return fetchInitialData(attr, params, serviceRegistry, ctx);
1980
+ }
1981
+
1899
1982
  // src/utils/StaticAssets.ts
1900
- function normalizeStaticAssets(reg) {
1983
+ import path5 from "path";
1984
+ function normaliseStaticAssets(reg) {
1901
1985
  if (!reg) return [];
1902
1986
  return Array.isArray(reg) ? reg : [reg];
1903
1987
  }
@@ -1905,11 +1989,14 @@ function prefixWeight(prefix) {
1905
1989
  if (typeof prefix !== "string" || prefix === "/" || prefix.length === 0) return 0;
1906
1990
  return prefix.split("/").filter(Boolean).length;
1907
1991
  }
1908
- async function registerStaticAssets(app, baseClientRoot, reg, defaults) {
1909
- const entries = normalizeStaticAssets(reg).map(({ plugin, options }) => ({
1992
+ async function registerStaticAssets(app, baseClientRoot, reg, defaults, projectRoot) {
1993
+ const isDevelopment2 = process.env.NODE_ENV === "development";
1994
+ const effectiveProjectRoot = projectRoot ?? path5.resolve(process.cwd());
1995
+ const staticRoot = isDevelopment2 ? baseClientRoot : path5.resolve(effectiveProjectRoot, "client");
1996
+ const entries = normaliseStaticAssets(reg).map(({ plugin, options }) => ({
1910
1997
  plugin,
1911
1998
  options: {
1912
- root: baseClientRoot,
1999
+ root: staticRoot,
1913
2000
  prefix: "/",
1914
2001
  index: false,
1915
2002
  wildcard: false,
@@ -1938,6 +2025,7 @@ var SSRServer = (0, import_fastify_plugin3.default)(
1938
2025
  const processedConfigs = processConfigs(configs, baseClientRoot, TEMPLATE);
1939
2026
  const routeMatchers = createRouteMatchers(routes);
1940
2027
  let viteDevServer;
2028
+ const projectRoot = path6.resolve(baseClientRoot, "..");
1941
2029
  await loadAssets(
1942
2030
  processedConfigs,
1943
2031
  baseClientRoot,
@@ -1950,10 +2038,11 @@ var SSRServer = (0, import_fastify_plugin3.default)(
1950
2038
  maps.templates,
1951
2039
  {
1952
2040
  debug: opts.debug,
1953
- logger
2041
+ logger,
2042
+ projectRoot
1954
2043
  }
1955
2044
  );
1956
- if (opts.staticAssets) await registerStaticAssets(app, baseClientRoot, opts.staticAssets);
2045
+ if (opts.staticAssets) await registerStaticAssets(app, baseClientRoot, opts.staticAssets, void 0, projectRoot);
1957
2046
  if (security?.csp?.reporting) {
1958
2047
  app.register(cspReportPlugin, {
1959
2048
  path: security.csp.reporting.endpoint,
@@ -1970,6 +2059,23 @@ var SSRServer = (0, import_fastify_plugin3.default)(
1970
2059
  });
1971
2060
  if (isDevelopment) viteDevServer = await setupDevServer(app, baseClientRoot, alias, opts.debug, opts.devNet);
1972
2061
  app.addHook("onRequest", createAuthHook(routeMatchers, logger));
2062
+ app.get("/__taujs/data", async (req, reply) => {
2063
+ const query = req.query;
2064
+ const url = typeof query.url === "string" ? query.url : "";
2065
+ if (!url) {
2066
+ throw AppError.badRequest("url query param required", {
2067
+ details: { query }
2068
+ });
2069
+ }
2070
+ const data = await resolveRouteData(url, {
2071
+ req,
2072
+ reply,
2073
+ routeMatchers,
2074
+ serviceRegistry,
2075
+ logger
2076
+ });
2077
+ return reply.status(200).send({ data });
2078
+ });
1973
2079
  app.get("/*", async (req, reply) => {
1974
2080
  await handleRender(req, reply, routeMatchers, processedConfigs, serviceRegistry, maps, {
1975
2081
  debug: opts.debug,
@@ -2024,20 +2130,21 @@ var SSRServer = (0, import_fastify_plugin3.default)(
2024
2130
 
2025
2131
  // src/CreateServer.ts
2026
2132
  var createServer = async (opts) => {
2027
- const t0 = performance2.now();
2028
- const clientRoot = opts.clientRoot ?? path5.resolve(process.cwd(), "client");
2133
+ const t0 = performance3.now();
2134
+ const clientRoot = opts.clientRoot ?? path7.resolve(process.cwd(), "client");
2029
2135
  const app = opts.fastify ?? Fastify({ logger: false });
2030
- const net = resolveNet(opts.config.server);
2031
- await app.register(bannerPlugin, {
2032
- debug: opts.debug,
2033
- hmr: { host: net.host, port: net.hmrPort }
2034
- });
2136
+ const fastifyLogger = app.log && app.log.level && app.log.level !== "silent" ? app.log : void 0;
2035
2137
  const logger = createLogger({
2036
2138
  debug: opts.debug,
2037
- custom: opts.logger,
2139
+ custom: opts.logger ?? fastifyLogger,
2038
2140
  minLevel: process.env.NODE_ENV === "production" ? "info" : "debug",
2039
2141
  includeContext: true
2040
2142
  });
2143
+ const net = resolveNet(opts.config.server);
2144
+ await app.register(bannerPlugin, {
2145
+ debug: opts.debug,
2146
+ hmr: { host: net.host, port: net.hmrPort }
2147
+ });
2041
2148
  const configs = extractBuildConfigs(opts.config);
2042
2149
  const { routes, apps, totalRoutes, durationMs, warnings } = extractRoutes(opts.config);
2043
2150
  const { security, durationMs: securityDuration, hasExplicitCSP } = extractSecurity(opts.config);
@@ -2084,7 +2191,7 @@ var createServer = async (opts) => {
2084
2191
  "Failed to register SSRServer"
2085
2192
  );
2086
2193
  }
2087
- const t1 = performance2.now();
2194
+ const t1 = performance3.now();
2088
2195
  console.log(`
2089
2196
  ${import_picocolors4.default.bgGreen(import_picocolors4.default.black(` ${CONTENT.TAG} `))} configured in ${(t1 - t0).toFixed(0)}ms
2090
2197
  `);
@@ -2093,18 +2200,149 @@ ${import_picocolors4.default.bgGreen(import_picocolors4.default.black(` ${CONTEN
2093
2200
  };
2094
2201
 
2095
2202
  // src/Build.ts
2096
- import path6 from "path";
2203
+ import { existsSync } from "fs";
2204
+ import path8 from "path";
2097
2205
  import { build } from "vite";
2098
- import { nodePolyfills } from "vite-plugin-node-polyfills";
2206
+ function resolveInputs(isSSRBuild, mainExists, paths) {
2207
+ if (isSSRBuild) return { server: paths.server };
2208
+ if (mainExists) return { client: paths.client, main: paths.main };
2209
+ return { client: paths.client };
2210
+ }
2211
+ function getFrameworkInvariants(config) {
2212
+ return {
2213
+ root: config.root || "",
2214
+ base: config.base || "/",
2215
+ publicDir: config.publicDir === void 0 ? "public" : config.publicDir,
2216
+ build: {
2217
+ outDir: config.build?.outDir || "",
2218
+ manifest: config.build?.manifest ?? false,
2219
+ ssr: config.build?.ssr ?? void 0,
2220
+ // Preserve exact type
2221
+ ssrManifest: config.build?.ssrManifest ?? false,
2222
+ format: config.build?.format,
2223
+ target: config.build?.target,
2224
+ rollupOptions: {
2225
+ input: config.build?.rollupOptions?.input || {}
2226
+ }
2227
+ }
2228
+ };
2229
+ }
2230
+ function mergeViteConfig(framework, userOverride, context) {
2231
+ if (!userOverride) return framework;
2232
+ const userConfig = typeof userOverride === "function" && context ? userOverride(context) : userOverride;
2233
+ const invariants = getFrameworkInvariants(framework);
2234
+ const merged = {
2235
+ ...framework,
2236
+ build: { ...framework.build ?? {} },
2237
+ css: { ...framework.css ?? {} },
2238
+ resolve: { ...framework.resolve ?? {} },
2239
+ plugins: [...framework.plugins ?? []],
2240
+ define: { ...framework.define ?? {} }
2241
+ };
2242
+ const ignoredKeys = [];
2243
+ if (userConfig.plugins) {
2244
+ const frameworkPlugins = merged.plugins;
2245
+ merged.plugins = [...frameworkPlugins, ...userConfig.plugins];
2246
+ }
2247
+ if (userConfig.define && typeof userConfig.define === "object") {
2248
+ merged.define = {
2249
+ ...merged.define,
2250
+ ...userConfig.define
2251
+ };
2252
+ }
2253
+ if (userConfig.css?.preprocessorOptions && typeof userConfig.css.preprocessorOptions === "object") {
2254
+ const fpp = merged.css.preprocessorOptions ?? {};
2255
+ const upp = userConfig.css.preprocessorOptions;
2256
+ merged.css.preprocessorOptions = Object.keys({ ...fpp, ...upp }).reduce((acc, engine) => {
2257
+ const fppEngine = fpp[engine];
2258
+ const uppEngine = upp[engine];
2259
+ acc[engine] = { ...fppEngine ?? {}, ...uppEngine ?? {} };
2260
+ return acc;
2261
+ }, {});
2262
+ }
2263
+ if (userConfig.build) {
2264
+ const protectedBuildFields = ["outDir", "ssr", "ssrManifest", "format", "target"];
2265
+ for (const field of protectedBuildFields) {
2266
+ if (field in userConfig.build) {
2267
+ ignoredKeys.push(`build.${field}`);
2268
+ }
2269
+ }
2270
+ if ("sourcemap" in userConfig.build) merged.build.sourcemap = userConfig.build.sourcemap;
2271
+ if ("minify" in userConfig.build) merged.build.minify = userConfig.build.minify;
2272
+ if (userConfig.build.terserOptions && typeof userConfig.build.terserOptions === "object") {
2273
+ merged.build.terserOptions = {
2274
+ ...merged.build.terserOptions ?? {},
2275
+ ...userConfig.build.terserOptions
2276
+ };
2277
+ }
2278
+ if (userConfig.build.rollupOptions) {
2279
+ if (!merged.build.rollupOptions) {
2280
+ merged.build.rollupOptions = {};
2281
+ }
2282
+ const userRollup = userConfig.build.rollupOptions;
2283
+ if ("input" in userRollup) ignoredKeys.push("build.rollupOptions.input");
2284
+ if ("external" in userRollup) merged.build.rollupOptions.external = userRollup.external;
2285
+ if (userRollup.output) {
2286
+ const mro = merged.build.rollupOptions ??= {};
2287
+ const uo = Array.isArray(userRollup.output) ? userRollup.output[0] : userRollup.output;
2288
+ const baseOut = Array.isArray(mro.output) ? mro.output[0] ?? {} : mro.output ?? {};
2289
+ mro.output = { ...baseOut, ...uo?.manualChunks ? { manualChunks: uo.manualChunks } : {} };
2290
+ }
2291
+ }
2292
+ }
2293
+ if (userConfig.resolve) {
2294
+ const userResolve = userConfig.resolve;
2295
+ const { alias: _ignore, ...resolveRest } = userResolve;
2296
+ if (_ignore) ignoredKeys.push("resolve.alias");
2297
+ merged.resolve = {
2298
+ ...merged.resolve,
2299
+ ...resolveRest
2300
+ };
2301
+ }
2302
+ if (userConfig.server) ignoredKeys.push("server (ignored in build; dev-only)");
2303
+ if ("root" in userConfig) ignoredKeys.push("root");
2304
+ if ("base" in userConfig) ignoredKeys.push("base");
2305
+ if ("publicDir" in userConfig) ignoredKeys.push("publicDir");
2306
+ const safeTopLevelKeys = /* @__PURE__ */ new Set([
2307
+ "esbuild",
2308
+ "logLevel",
2309
+ "envPrefix",
2310
+ "optimizeDeps",
2311
+ "ssr"
2312
+ // NOTE: NOT 'server' (build-time irrelevant; dev-server only)
2313
+ ]);
2314
+ for (const [key, value] of Object.entries(userConfig)) {
2315
+ if (safeTopLevelKeys.has(key)) merged[key] = value;
2316
+ }
2317
+ merged.root = invariants.root;
2318
+ merged.base = invariants.base;
2319
+ merged.publicDir = invariants.publicDir;
2320
+ merged.build.outDir = invariants.build.outDir;
2321
+ merged.build.manifest = invariants.build.manifest;
2322
+ if (invariants.build.ssr !== void 0) merged.build.ssr = invariants.build.ssr;
2323
+ merged.build.ssrManifest = invariants.build.ssrManifest;
2324
+ if (invariants.build.format) merged.build.format = invariants.build.format;
2325
+ if (invariants.build.target) merged.build.target = invariants.build.target;
2326
+ if (!merged.build.rollupOptions) merged.build.rollupOptions = {};
2327
+ merged.build.rollupOptions.input = invariants.build.rollupOptions.input;
2328
+ if (ignoredKeys.length > 0) {
2329
+ const uniqueKeys = [...new Set(ignoredKeys)];
2330
+ const prefix = context ? `[taujs:build:${context.entryPoint}]` : "[taujs:build]";
2331
+ console.warn(`${prefix} Ignored Vite config overrides: ${uniqueKeys.join(", ")}`);
2332
+ }
2333
+ return merged;
2334
+ }
2099
2335
  async function taujsBuild({
2100
2336
  config,
2101
2337
  projectRoot,
2102
2338
  clientBaseDir,
2103
- isSSRBuild = process.env.BUILD_MODE === "ssr"
2339
+ isSSRBuild = process.env.BUILD_MODE === "ssr",
2340
+ alias: userAlias,
2341
+ vite: userViteConfig
2104
2342
  }) {
2105
2343
  const deleteDist = async () => {
2106
2344
  const { rm } = await import("fs/promises");
2107
- const distPath = path6.resolve(projectRoot, "dist");
2345
+ const distPath = path8.resolve(projectRoot, "dist");
2108
2346
  try {
2109
2347
  await rm(distPath, { recursive: true, force: true });
2110
2348
  console.log("Deleted the dist directory\n");
@@ -2115,26 +2353,36 @@ async function taujsBuild({
2115
2353
  const extractedConfigs = extractBuildConfigs(config);
2116
2354
  const processedConfigs = processConfigs(extractedConfigs, clientBaseDir, TEMPLATE);
2117
2355
  if (!isSSRBuild) await deleteDist();
2118
- for (const config2 of processedConfigs) {
2119
- const { appId, entryPoint, clientRoot, entryClient, entryServer, htmlTemplate, plugins = [] } = config2;
2120
- const outDir = path6.resolve(projectRoot, `dist/client/${entryPoint}`);
2121
- const root = entryPoint ? path6.resolve(clientBaseDir, entryPoint) : clientBaseDir;
2122
- const server = path6.resolve(clientRoot, `${entryServer}.tsx`);
2123
- const client = path6.resolve(clientRoot, `${entryClient}.tsx`);
2124
- const main = path6.resolve(clientRoot, htmlTemplate);
2125
- const viteConfig = {
2356
+ for (const appConfig of processedConfigs) {
2357
+ const { appId, entryPoint, clientRoot, entryClient, entryServer, htmlTemplate, plugins = [] } = appConfig;
2358
+ const outDir = path8.resolve(projectRoot, isSSRBuild ? `dist/ssr/${entryPoint}` : `dist/client/${entryPoint}`);
2359
+ const root = entryPoint ? path8.resolve(clientBaseDir, entryPoint) : clientBaseDir;
2360
+ const defaultAlias = {
2361
+ "@client": root,
2362
+ "@server": path8.resolve(projectRoot, "src/server"),
2363
+ "@shared": path8.resolve(projectRoot, "src/shared")
2364
+ };
2365
+ const resolvedAlias = { ...defaultAlias, ...userAlias ?? {} };
2366
+ const server = path8.resolve(clientRoot, `${entryServer}.tsx`);
2367
+ const client = path8.resolve(clientRoot, `${entryClient}.tsx`);
2368
+ const main = path8.resolve(clientRoot, htmlTemplate);
2369
+ const inputs = resolveInputs(isSSRBuild, !isSSRBuild && existsSync(main), { server, client, main });
2370
+ const nodeVersion = process.versions.node.split(".")[0];
2371
+ const frameworkConfig = {
2126
2372
  base: entryPoint ? `/${entryPoint}/` : "/",
2127
2373
  build: {
2128
2374
  outDir,
2375
+ emptyOutDir: true,
2129
2376
  manifest: !isSSRBuild,
2130
2377
  rollupOptions: {
2131
- input: isSSRBuild ? { server } : { client, main }
2378
+ input: inputs
2132
2379
  },
2133
2380
  ssr: isSSRBuild ? server : void 0,
2134
2381
  ssrManifest: isSSRBuild,
2135
2382
  ...isSSRBuild && {
2136
2383
  format: "esm",
2137
- target: `node${process.versions.node.split(".").map(Number)[0]}`
2384
+ target: `node${nodeVersion}`,
2385
+ copyPublicDir: false
2138
2386
  }
2139
2387
  },
2140
2388
  css: {
@@ -2142,39 +2390,50 @@ async function taujsBuild({
2142
2390
  scss: { api: "modern-compiler" }
2143
2391
  }
2144
2392
  },
2145
- plugins: [...plugins, nodePolyfills({ include: ["fs", "stream"] })],
2146
- publicDir: "public",
2147
- resolve: {
2148
- alias: {
2149
- "@client": root,
2150
- "@server": path6.resolve(projectRoot, "src/server"),
2151
- "@shared": path6.resolve(projectRoot, "src/shared")
2152
- }
2153
- },
2154
- root,
2155
- server: {
2156
- proxy: {
2157
- "/api": {
2158
- target: "http://localhost:3000",
2159
- changeOrigin: true,
2160
- rewrite: (path7) => path7.replace(/^\/api/, "")
2161
- }
2162
- }
2163
- }
2393
+ plugins,
2394
+ publicDir: isSSRBuild ? false : "public",
2395
+ // single shared public
2396
+ // publicDir: isSSRBuild ? false : path.join(root, 'public'), // per-app. no public dir for SSR builds
2397
+ resolve: { alias: resolvedAlias },
2398
+ root
2399
+ };
2400
+ const buildContext = {
2401
+ appId,
2402
+ entryPoint,
2403
+ isSSRBuild,
2404
+ clientRoot
2164
2405
  };
2406
+ const finalConfig = mergeViteConfig(frameworkConfig, userViteConfig, buildContext);
2165
2407
  try {
2166
- console.log(`Building for entryPoint: "${entryPoint}" (${appId})`);
2167
- await build(viteConfig);
2168
- console.log(`Build complete for entryPoint: "${entryPoint}"
2408
+ const mode = isSSRBuild ? "SSR" : "Client";
2409
+ console.log(`[taujs:build:${entryPoint}] Building \u2192 ${mode}`);
2410
+ await build(finalConfig);
2411
+ console.log(`[taujs:build:${entryPoint}] \u2713 Complete
2169
2412
  `);
2170
2413
  } catch (error) {
2171
- console.error(`Error building for entryPoint: "${entryPoint}"
2414
+ console.error(`[taujs:build:${entryPoint}] \u2717 Failed
2172
2415
  `, error);
2173
2416
  process.exit(1);
2174
2417
  }
2175
2418
  }
2176
2419
  }
2420
+
2421
+ // src/logging/Adapters.ts
2422
+ var cleanMeta = (m) => m && Object.keys(m).length === 0 ? void 0 : m;
2423
+ function messageMetaAdapter(sink) {
2424
+ return {
2425
+ debug: (meta, message) => sink.debug?.(message, cleanMeta(meta)),
2426
+ info: (meta, message) => sink.info?.(message, cleanMeta(meta)),
2427
+ warn: (meta, message) => sink.warn?.(message, cleanMeta(meta)),
2428
+ error: (meta, message) => sink.error?.(message, cleanMeta(meta)),
2429
+ child: (ctx) => messageMetaAdapter(sink.child?.(ctx) ?? sink)
2430
+ };
2431
+ }
2432
+ function winstonAdapter(winston) {
2433
+ return messageMetaAdapter(winston);
2434
+ }
2177
2435
  export {
2178
2436
  createServer,
2179
- taujsBuild
2437
+ taujsBuild,
2438
+ winstonAdapter
2180
2439
  };