@flink-app/flink 2.0.0-alpha.71 → 2.0.0-alpha.73
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/CHANGELOG.md +12 -0
- package/dist/src/FlinkApp.js +11 -1
- package/package.json +2 -2
- package/spec/FlinkApp.routeOrdering.spec.ts +61 -0
- package/src/FlinkApp.ts +9 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @flink-app/flink
|
|
2
2
|
|
|
3
|
+
## 2.0.0-alpha.73
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fix(flink): register static routes before parameterized routes to prevent e.g. GET /jobs/by-tags being matched by GET /jobs/:id
|
|
8
|
+
|
|
9
|
+
## 2.0.0-alpha.72
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fix(test-utils): accept typed FlinkApp instances in init() by casting internally
|
|
14
|
+
|
|
3
15
|
## 2.0.0-alpha.71
|
|
4
16
|
|
|
5
17
|
## 2.0.0-alpha.70
|
package/dist/src/FlinkApp.js
CHANGED
|
@@ -797,7 +797,17 @@ var FlinkApp = /** @class */ (function () {
|
|
|
797
797
|
schemaManifest = this.loadSchemaManifest();
|
|
798
798
|
schemaCount = schemaManifest.version === "2.0" ? Object.keys(schemaManifest.schemas || {}).length : Object.keys(schemaManifest.definitions || {}).length;
|
|
799
799
|
FlinkLog_1.log.debug("Registering ".concat(schemaCount, " schemas with AJV (manifest version: ").concat(schemaManifest.version || "1.0", ")"));
|
|
800
|
-
for (_i = 0, _a = exports.autoRegisteredHandlers.sort(function (a, b) {
|
|
800
|
+
for (_i = 0, _a = exports.autoRegisteredHandlers.sort(function (a, b) {
|
|
801
|
+
var _a, _b, _c, _d, _e, _f;
|
|
802
|
+
var orderDiff = (((_a = a.handler.Route) === null || _a === void 0 ? void 0 : _a.order) || 0) - (((_b = b.handler.Route) === null || _b === void 0 ? void 0 : _b.order) || 0);
|
|
803
|
+
if (orderDiff !== 0)
|
|
804
|
+
return orderDiff;
|
|
805
|
+
// Static segments must be registered before parameterized ones to avoid
|
|
806
|
+
// Express matching e.g. GET /jobs/by-tags with the /jobs/:id route.
|
|
807
|
+
var aHasParam = ((_d = (_c = a.handler.Route) === null || _c === void 0 ? void 0 : _c.path) === null || _d === void 0 ? void 0 : _d.includes("/:")) ? 1 : 0;
|
|
808
|
+
var bHasParam = ((_f = (_e = b.handler.Route) === null || _e === void 0 ? void 0 : _e.path) === null || _f === void 0 ? void 0 : _f.includes("/:")) ? 1 : 0;
|
|
809
|
+
return aHasParam - bHasParam;
|
|
810
|
+
}); _i < _a.length; _i++) {
|
|
801
811
|
_b = _a[_i], handler = _b.handler, assumedHttpMethod = _b.assumedHttpMethod, __file = _b.__file;
|
|
802
812
|
if (!handler.Route) {
|
|
803
813
|
FlinkLog_1.log.error("Missing Props in handler ".concat(__file));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flink-app/flink",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.73",
|
|
4
4
|
"description": "Typescript only framework for creating REST-like APIs on top of Express and mongodb",
|
|
5
5
|
"types": "dist/src/index.d.ts",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"ts-morph": "24.0.0",
|
|
57
57
|
"uuid": "^8.3.2",
|
|
58
58
|
"zod": "^4.3.6",
|
|
59
|
-
"@flink-app/ts-source-to-json-schema": "^0.1.
|
|
59
|
+
"@flink-app/ts-source-to-json-schema": "^0.1.7"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@swc/core": "^1.15.17",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { FlinkApp, autoRegisteredHandlers } from "../src/FlinkApp";
|
|
2
|
+
import { FlinkContext } from "../src/FlinkContext";
|
|
3
|
+
import { GetHandler, HttpMethod } from "../src/FlinkHttpHandler";
|
|
4
|
+
|
|
5
|
+
const request = require("supertest");
|
|
6
|
+
|
|
7
|
+
interface TestContext extends FlinkContext {}
|
|
8
|
+
|
|
9
|
+
describe("Route ordering", () => {
|
|
10
|
+
let app: FlinkApp<TestContext>;
|
|
11
|
+
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
// Clean up auto-registered handlers between tests
|
|
14
|
+
autoRegisteredHandlers.length = 0;
|
|
15
|
+
|
|
16
|
+
if (app && app.started) {
|
|
17
|
+
await app.stop();
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should match static segment before parameterized segment when parameterized handler is registered first", async () => {
|
|
22
|
+
const byTagsHandler: GetHandler<TestContext, any> = async () => {
|
|
23
|
+
return { status: 200, data: { route: "by-tags" } };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const byIdHandler: GetHandler<TestContext, any> = async ({ req }) => {
|
|
27
|
+
return { status: 200, data: { route: "by-id", id: req.params.id } };
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Register parameterized route first (simulates bad file discovery order)
|
|
31
|
+
autoRegisteredHandlers.push({
|
|
32
|
+
handler: {
|
|
33
|
+
default: byIdHandler,
|
|
34
|
+
Route: { method: HttpMethod.get, path: "/jobs/:id" },
|
|
35
|
+
},
|
|
36
|
+
assumedHttpMethod: HttpMethod.get,
|
|
37
|
+
__file: "GetJobById.ts",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
autoRegisteredHandlers.push({
|
|
41
|
+
handler: {
|
|
42
|
+
default: byTagsHandler,
|
|
43
|
+
Route: { method: HttpMethod.get, path: "/jobs/by-tags" },
|
|
44
|
+
},
|
|
45
|
+
assumedHttpMethod: HttpMethod.get,
|
|
46
|
+
__file: "GetJobsByTags.ts",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
app = new FlinkApp<TestContext>({ name: "test-route-order", port: 4050 });
|
|
50
|
+
await app.start();
|
|
51
|
+
|
|
52
|
+
const byTagsRes = await request(app.expressApp).get("/jobs/by-tags");
|
|
53
|
+
expect(byTagsRes.status).toBe(200);
|
|
54
|
+
expect(byTagsRes.body.data.route).toBe("by-tags");
|
|
55
|
+
|
|
56
|
+
const byIdRes = await request(app.expressApp).get("/jobs/abc123");
|
|
57
|
+
expect(byIdRes.status).toBe(200);
|
|
58
|
+
expect(byIdRes.body.data.route).toBe("by-id");
|
|
59
|
+
expect(byIdRes.body.data.id).toBe("abc123");
|
|
60
|
+
});
|
|
61
|
+
});
|
package/src/FlinkApp.ts
CHANGED
|
@@ -1068,9 +1068,15 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
1068
1068
|
|
|
1069
1069
|
log.debug(`Registering ${schemaCount} schemas with AJV (manifest version: ${schemaManifest.version || "1.0"})`);
|
|
1070
1070
|
|
|
1071
|
-
for (const { handler, assumedHttpMethod, __file } of autoRegisteredHandlers.sort(
|
|
1072
|
-
|
|
1073
|
-
|
|
1071
|
+
for (const { handler, assumedHttpMethod, __file } of autoRegisteredHandlers.sort((a, b) => {
|
|
1072
|
+
const orderDiff = (a.handler.Route?.order || 0) - (b.handler.Route?.order || 0);
|
|
1073
|
+
if (orderDiff !== 0) return orderDiff;
|
|
1074
|
+
// Static segments must be registered before parameterized ones to avoid
|
|
1075
|
+
// Express matching e.g. GET /jobs/by-tags with the /jobs/:id route.
|
|
1076
|
+
const aHasParam = a.handler.Route?.path?.includes("/:") ? 1 : 0;
|
|
1077
|
+
const bHasParam = b.handler.Route?.path?.includes("/:") ? 1 : 0;
|
|
1078
|
+
return aHasParam - bHasParam;
|
|
1079
|
+
})) {
|
|
1074
1080
|
if (!handler.Route) {
|
|
1075
1081
|
log.error(`Missing Props in handler ${__file}`);
|
|
1076
1082
|
continue;
|