@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 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
@@ -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) { var _a, _b; return (((_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); }); _i < _a.length; _i++) {
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.71",
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.6"
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
- (a, b) => (a.handler.Route?.order || 0) - (b.handler.Route?.order || 0)
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;