@tsed/platform-express 8.19.4 → 8.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import "@tsed/platform-multer/express";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { catchAsyncError, Env, isFunction } from "@tsed/core";
5
+ import { catchAsyncError, Env, isArray, isFunction } from "@tsed/core";
6
6
  import { constant, inject, logger, runInContext } from "@tsed/di";
7
7
  import { PlatformExceptions } from "@tsed/platform-exceptions";
8
8
  import { adapter, application, createContext, PlatformAdapter, PlatformBuilder, PlatformHandler, PlatformResponse } from "@tsed/platform-http";
@@ -19,6 +19,10 @@ function callNext(next, metadata, $ctx) {
19
19
  }
20
20
  function getVersion() {
21
21
  try {
22
+ const v = constant("express.version");
23
+ if (v) {
24
+ return v;
25
+ }
22
26
  const { version } = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.resolve("express"))), "package.json"), "utf8"));
23
27
  return `v${version.split(".")[0]}`;
24
28
  }
@@ -94,6 +98,12 @@ export class PlatformExpress extends PlatformAdapter {
94
98
  if (req.params["0"] && !req.params[wildcard]) {
95
99
  req.params[wildcard] = req.params["0"];
96
100
  }
101
+ if (isArray(req.params[wildcard])) {
102
+ req.params[wildcard] = req.params[wildcard].join("/");
103
+ }
104
+ if (wildcard === "wildcard") {
105
+ req.params["*"] ||= req.params[wildcard];
106
+ }
97
107
  next();
98
108
  }));
99
109
  }
@@ -1,11 +1,17 @@
1
- import { isNumber } from "@tsed/core";
1
+ import { isArray, isNumber } from "@tsed/core";
2
2
  import { PlatformResponse } from "@tsed/platform-http";
3
3
  export class PlatformExpressResponse extends PlatformResponse {
4
+ cookie(name, value, opts) {
5
+ super.cookie(name, value, opts);
6
+ const cookie = this.get("set-cookie");
7
+ if (cookie && !isArray(value)) {
8
+ this.setHeader("set-cookie", [].concat(cookie));
9
+ }
10
+ return this;
11
+ }
4
12
  end(data) {
5
13
  if (isNumber(data)) {
6
- // TODO remove this when Express 5 will be the default version of Ts.ED
7
- // @ts-ignore
8
- this.raw.send(this.statusCode, data);
14
+ this.raw.send(String(data));
9
15
  }
10
16
  else {
11
17
  this.raw.send(data);
@@ -3,77 +3,109 @@ import { isString } from "@tsed/core";
3
3
  * Converts a path to v4 format
4
4
  */
5
5
  function convertPathToV4(path) {
6
- const segments = path.split("/");
7
- const parsed = segments.reduce((options, segment, index) => {
8
- const isLastSegment = index === segments.length - 1;
9
- if (isLastSegment && (segment === "*" || segment === "(.*)")) {
10
- options.wildcard = "*";
11
- options.path.push("*");
12
- return options;
13
- }
14
- if (isLastSegment && segment.startsWith("*")) {
15
- options.wildcard = segment.substring(1);
16
- options.path.push("*");
17
- return options;
18
- }
19
- if (segment.startsWith(":") && segment.endsWith("*")) {
20
- options.wildcard = segment.substring(1, segment.length - 1);
21
- options.path.push("*");
22
- return options;
23
- }
24
- if (segment.startsWith("{")) {
25
- // Handle v5 style parameters like /{param}
26
- const paramName = segment.substring(1, segment.length - 1);
27
- options.path.push(`${paramName}?`);
28
- return options;
6
+ // Preserve unsupported/complex patterns
7
+ // Keep paths with bracket choices or middle wildcards intact
8
+ if (path.includes("[") || (path.includes("(.*)") && !path.endsWith("/(.*)") && path !== "/(.*)")) {
9
+ return { path };
10
+ }
11
+ let wildcard;
12
+ let result = path;
13
+ // v5 optional segment syntax -> v4 optional param
14
+ // e.g. "/test{/:foo}{/:param}" -> "/test/:foo?/:param?"
15
+ result = result.replace(/\{\/:([A-Za-z0-9_]+)\}/g, "/:$1?");
16
+ // e.g. "/test/:foo/{:param}" -> "/test/:foo/:param"
17
+ result = result.replace(/\{:([A-Za-z0-9_]+)\}/g, ":$1");
18
+ // Trailing v5 wildcard segment -> v4
19
+ result = result.replace(/\/{\*([A-Za-z0-9_]+)\}$/g, (_m, name) => {
20
+ wildcard = name;
21
+ return "/*";
22
+ });
23
+ const segments = result.split("/");
24
+ const last = segments[segments.length - 1];
25
+ if (last === "*" || last === "(.*)") {
26
+ wildcard = "*";
27
+ if (last === "(.*)") {
28
+ segments[segments.length - 1] = "*";
29
+ result = segments.join("/");
29
30
  }
30
- options.path.push(segment);
31
- return options;
32
- }, { path: [], wildcard: undefined });
33
- return {
34
- path: parsed.path.join("/"),
35
- wildcard: parsed.wildcard
36
- };
31
+ return { path: result, wildcard };
32
+ }
33
+ // Trailing *name -> wildcard name for v4
34
+ if (last && last.startsWith("*") && last.length > 1) {
35
+ wildcard = last.substring(1);
36
+ segments[segments.length - 1] = "*";
37
+ result = segments.join("/");
38
+ return { path: result, wildcard };
39
+ }
40
+ // Ts.ED syntax :param* -> v4 "*"
41
+ if (last && last.startsWith(":") && last.endsWith("*")) {
42
+ wildcard = last.substring(1, last.length - 1);
43
+ segments[segments.length - 1] = "*";
44
+ result = segments.join("/");
45
+ return { path: result, wildcard };
46
+ }
47
+ return { path: result, wildcard };
37
48
  }
38
49
  /**
39
50
  * Converts a path to v5 format
40
51
  */
41
52
  function convertPathToV5(path) {
53
+ // Preserve unsupported/complex patterns
54
+ if (path.includes("[") || (path.includes("(.*)") && !path.endsWith("/(.*)") && path !== "/(.*)")) {
55
+ return { path };
56
+ }
57
+ // Already v5-style specific constructs should be preserved
58
+ if (/\/{\*[^}]+\}/.test(path) || /\{:[^}]+\}/.test(path) || /\{\/:[^}]+\}/.test(path)) {
59
+ const m = path.match(/\/{\*([^}]+)\}$/);
60
+ return { path, wildcard: m ? m[1] : undefined };
61
+ }
42
62
  const segments = path.split("/");
43
- const parsed = segments.reduce((options, segment, index) => {
44
- const isLastSegment = index === segments.length - 1;
45
- if (isLastSegment && (segment === "*" || segment === "(.*)")) {
46
- options.wildcard = "*";
47
- options.path.push("{*wildcard}");
48
- return options;
63
+ const base = [];
64
+ const optionals = [];
65
+ let wildcard;
66
+ segments.forEach((seg, idx) => {
67
+ if (idx === 0)
68
+ return; // first split is empty because path starts with '/'
69
+ const isLast = idx === segments.length - 1;
70
+ // Trailing raw wildcard
71
+ if (isLast && (seg === "*" || seg === "(.*)")) {
72
+ wildcard = "wildcard";
73
+ optionals.push("/{*wildcard}");
74
+ return;
49
75
  }
50
- if (isLastSegment && segment.startsWith("*")) {
51
- options.wildcard = segment.substring(1);
52
- options.path.push(segment);
53
- return options;
76
+ // Trailing named wildcard forms
77
+ if (isLast && seg.startsWith(":") && seg.endsWith("*")) {
78
+ wildcard = seg.substring(1, seg.length - 1);
79
+ optionals.push(`/{*${wildcard}}`);
80
+ return;
54
81
  }
55
- if (segment.startsWith(":")) {
56
- if (segment.endsWith("?")) {
57
- options.path.push(`{:${segment.substring(1, segment.length - 1)}}`);
58
- return options;
59
- }
60
- if (isLastSegment && segment.endsWith("*")) {
61
- options.wildcard = segment.substring(1, segment.length - 1);
62
- options.path.push(`{*${options.wildcard}}`);
63
- return options;
64
- }
82
+ if (isLast && seg.startsWith("*") && seg.length > 1) {
83
+ wildcard = seg.substring(1);
84
+ base.push(seg); // keep as-is according to spec for v5
85
+ return;
65
86
  }
66
- if (isLastSegment && segment.startsWith("{*")) {
67
- // Handle v5 style parameters like /{param}
68
- options.wildcard = segment.substring(2, segment.length - 1);
87
+ // Optional parameter -> v5 optional segment
88
+ if (seg.startsWith(":") && seg.endsWith("?")) {
89
+ const name = seg.substring(1, seg.length - 1);
90
+ optionals.push(`{/:${name}}`);
91
+ return;
69
92
  }
70
- options.path.push(segment);
71
- return options;
72
- }, { path: [], wildcard: undefined });
73
- return {
74
- path: parsed.path.join("/"),
75
- wildcard: parsed.wildcard
76
- };
93
+ // Keep everything else (including non-optional params)
94
+ base.push(seg);
95
+ });
96
+ let result = "/" + base.join("/");
97
+ // Collapse double slash when base is empty
98
+ if (result === "/") {
99
+ result = "";
100
+ }
101
+ result += optionals.join("");
102
+ // If named wildcard kept as segment like '/*splat', leave as-is (handled above)
103
+ if (!result) {
104
+ // Edge case when path was only optional param like "/:param?"
105
+ // base is empty -> result becomes optionals only
106
+ result = optionals.join("");
107
+ }
108
+ return { path: result || "/", wildcard };
77
109
  }
78
110
  /**
79
111
  * Converts a path between v4 and v5 formats
@@ -2,6 +2,10 @@ import type { Options, OptionsJson, OptionsText, OptionsUrlencoded } from "body-
2
2
  import type { NextHandleFunction } from "connect";
3
3
  import type { RouterOptions } from "express";
4
4
  export interface PlatformExpressSettings {
5
+ /**
6
+ * Force Express version detection. Use when Ts.ED doesn't detect the version automatically.
7
+ */
8
+ version?: "v4" | "v5";
5
9
  /**
6
10
  * Global configuration for the Express.Router. See express [documentation](http://expressjs.com/en/api.html#express.router).
7
11
  */
@@ -1,4 +1,5 @@
1
1
  import { PlatformResponse } from "@tsed/platform-http";
2
2
  export declare class PlatformExpressResponse extends PlatformResponse<TsED.Response> {
3
- protected end(data?: string | Buffer): void;
3
+ cookie(name: string, value: string | null, opts?: TsED.SetCookieOpts): this;
4
+ protected end(data?: number | string | Buffer): void;
4
5
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@tsed/platform-express",
3
3
  "description": "A TypeScript Framework on top of Express",
4
4
  "type": "module",
5
- "version": "8.19.4",
5
+ "version": "8.20.0",
6
6
  "source": "./src/index.ts",
7
7
  "main": "./lib/esm/index.js",
8
8
  "module": "./lib/esm/index.js",
@@ -58,17 +58,17 @@
58
58
  "tslib": "2.7.0"
59
59
  },
60
60
  "devDependencies": {
61
- "@tsed/barrels": "8.19.4",
62
- "@tsed/core": "8.19.4",
63
- "@tsed/di": "8.19.4",
64
- "@tsed/platform-http": "8.19.4",
65
- "@tsed/platform-test-sdk": "8.19.4",
66
- "@tsed/platform-views": "8.19.4",
67
- "@tsed/typescript": "8.19.4",
61
+ "@tsed/barrels": "8.20.0",
62
+ "@tsed/core": "8.20.0",
63
+ "@tsed/di": "8.20.0",
64
+ "@tsed/platform-http": "8.20.0",
65
+ "@tsed/platform-test-sdk": "8.20.0",
66
+ "@tsed/platform-views": "8.20.0",
67
+ "@tsed/typescript": "8.20.0",
68
68
  "@types/body-parser": "1.19.5",
69
69
  "@types/compression": "1.7.5",
70
70
  "@types/cookie-parser": "1.4.7",
71
- "@types/express": "^4.17.21",
71
+ "@types/express": "^5.0.6",
72
72
  "@types/express-session": "1.18.1",
73
73
  "@types/method-override": "0.0.35",
74
74
  "body-parser": "2.2.0",
@@ -77,7 +77,7 @@
77
77
  "cors": "2.8.5",
78
78
  "cross-env": "7.0.3",
79
79
  "eslint": "9.12.0",
80
- "express": "^4.21.0",
80
+ "express": "5.2.1",
81
81
  "express-session": "1.18.1",
82
82
  "method-override": "3.0.0",
83
83
  "typescript": "5.9.3",