@flink-app/flink 0.14.3 → 2.0.0-alpha.100

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.
Files changed (280) hide show
  1. package/CHANGELOG.md +1051 -0
  2. package/SCHEMA_EXTRACTION_ANALYSIS.md +494 -0
  3. package/SIMPLE_AST_FEASIBILITY.md +570 -0
  4. package/bin/flink.ts +13 -2
  5. package/cli/build.ts +24 -44
  6. package/cli/clean.ts +13 -25
  7. package/cli/cli-utils.ts +190 -17
  8. package/cli/dev.ts +252 -0
  9. package/cli/loadEnvFiles.ts +116 -0
  10. package/cli/run.ts +45 -62
  11. package/dist/bin/flink.js +61 -2
  12. package/dist/cli/build.js +20 -25
  13. package/dist/cli/clean.js +12 -10
  14. package/dist/cli/cli-utils.d.ts +34 -3
  15. package/dist/cli/cli-utils.js +193 -12
  16. package/dist/cli/dev.d.ts +2 -0
  17. package/dist/cli/dev.js +279 -0
  18. package/dist/cli/loadEnvFiles.d.ts +30 -0
  19. package/dist/cli/loadEnvFiles.js +113 -0
  20. package/dist/cli/run.js +47 -46
  21. package/dist/src/DependencyTracker.d.ts +44 -0
  22. package/dist/src/DependencyTracker.js +239 -0
  23. package/dist/src/FlinkApp.d.ts +163 -10
  24. package/dist/src/FlinkApp.js +847 -184
  25. package/dist/src/FlinkContext.d.ts +41 -0
  26. package/dist/src/FlinkErrors.d.ts +19 -6
  27. package/dist/src/FlinkErrors.js +36 -42
  28. package/dist/src/FlinkHttpHandler.d.ts +219 -26
  29. package/dist/src/FlinkHttpHandler.js +37 -1
  30. package/dist/src/FlinkJob.d.ts +10 -0
  31. package/dist/src/FlinkLog.d.ts +82 -18
  32. package/dist/src/FlinkLog.js +165 -13
  33. package/dist/src/FlinkLogFactory.d.ts +288 -0
  34. package/dist/src/FlinkLogFactory.js +619 -0
  35. package/dist/src/FlinkRepo.d.ts +10 -2
  36. package/dist/src/FlinkRepo.js +11 -1
  37. package/dist/src/FlinkRequestContext.d.ts +63 -0
  38. package/dist/src/FlinkRequestContext.js +74 -0
  39. package/dist/src/FlinkResponse.d.ts +6 -0
  40. package/dist/src/FlinkService.d.ts +38 -0
  41. package/dist/src/FlinkService.js +46 -0
  42. package/dist/src/LeaderElection.d.ts +45 -0
  43. package/dist/src/LeaderElection.js +269 -0
  44. package/dist/src/SchemaCache.d.ts +84 -0
  45. package/dist/src/SchemaCache.js +289 -0
  46. package/dist/src/TypeScriptCompiler.d.ts +161 -51
  47. package/dist/src/TypeScriptCompiler.js +1253 -617
  48. package/dist/src/TypeScriptUtils.js +4 -0
  49. package/dist/src/ai/AgentRunner.d.ts +39 -0
  50. package/dist/src/ai/AgentRunner.js +760 -0
  51. package/dist/src/ai/ConversationAgent.d.ts +279 -0
  52. package/dist/src/ai/ConversationAgent.js +404 -0
  53. package/dist/src/ai/ConversationFlinkAgent.d.ts +278 -0
  54. package/dist/src/ai/ConversationFlinkAgent.js +404 -0
  55. package/dist/src/ai/FlinkAgent.d.ts +690 -0
  56. package/dist/src/ai/FlinkAgent.js +729 -0
  57. package/dist/src/ai/FlinkTool.d.ts +135 -0
  58. package/dist/src/ai/FlinkTool.js +2 -0
  59. package/dist/src/ai/InMemoryConversationAgent.d.ts +121 -0
  60. package/dist/src/ai/InMemoryConversationAgent.js +209 -0
  61. package/dist/src/ai/LLMAdapter.d.ts +148 -0
  62. package/dist/src/ai/LLMAdapter.js +2 -0
  63. package/dist/src/ai/PersistentFlinkAgent.d.ts +278 -0
  64. package/dist/src/ai/PersistentFlinkAgent.js +403 -0
  65. package/dist/src/ai/SubAgentExecutor.d.ts +38 -0
  66. package/dist/src/ai/SubAgentExecutor.js +223 -0
  67. package/dist/src/ai/ToolExecutor.d.ts +64 -0
  68. package/dist/src/ai/ToolExecutor.js +497 -0
  69. package/dist/src/ai/agentInstructions.d.ts +68 -0
  70. package/dist/src/ai/agentInstructions.js +286 -0
  71. package/dist/src/ai/index.d.ts +8 -0
  72. package/dist/src/ai/index.js +26 -0
  73. package/dist/src/ai/instructionFileLoader.d.ts +44 -0
  74. package/dist/src/ai/instructionFileLoader.js +179 -0
  75. package/dist/src/auth/FlinkAuthPlugin.d.ts +1 -1
  76. package/dist/src/handlers/StreamWriterFactory.d.ts +20 -0
  77. package/dist/src/handlers/StreamWriterFactory.js +83 -0
  78. package/dist/src/index.d.ts +14 -0
  79. package/dist/src/index.js +17 -0
  80. package/dist/src/loadPluginSchemas.d.ts +45 -0
  81. package/dist/src/loadPluginSchemas.js +143 -0
  82. package/dist/src/schema-extraction/ComplexTypeDetection.d.ts +40 -0
  83. package/dist/src/schema-extraction/ComplexTypeDetection.js +75 -0
  84. package/dist/src/schema-extraction/TypeScriptSourceParser.d.ts +321 -0
  85. package/dist/src/schema-extraction/TypeScriptSourceParser.js +925 -0
  86. package/dist/src/schema-extraction/TypeScriptSourceParser.spec.d.ts +1 -0
  87. package/dist/src/schema-extraction/TypeScriptSourceParser.spec.js +233 -0
  88. package/dist/src/schema-extraction/TypeScriptTokenizer.d.ts +57 -0
  89. package/dist/src/schema-extraction/TypeScriptTokenizer.js +177 -0
  90. package/dist/src/schema-extraction/index.d.ts +2 -0
  91. package/dist/src/schema-extraction/index.js +20 -0
  92. package/dist/src/schema-extraction/types.d.ts +31 -0
  93. package/dist/src/schema-extraction/types.js +2 -0
  94. package/dist/src/utils/loadFlinkConfig.d.ts +53 -0
  95. package/dist/src/utils/loadFlinkConfig.js +77 -0
  96. package/dist/src/utils.d.ts +30 -0
  97. package/dist/src/utils.js +52 -0
  98. package/dist/src/workers/SchemaGeneratorWorker.d.ts +1 -0
  99. package/dist/src/workers/SchemaGeneratorWorker.js +49 -0
  100. package/dist/src/workers/WorkerPool.d.ts +60 -0
  101. package/dist/src/workers/WorkerPool.js +306 -0
  102. package/examples/logging-hierarchical-example.ts +125 -0
  103. package/package.json +29 -4
  104. package/readme.md +499 -0
  105. package/spec/AgentDescendantDetection.spec.ts +335 -0
  106. package/spec/AgentDuplicateDetection.spec.ts +112 -0
  107. package/spec/AgentObserver.spec.ts +266 -0
  108. package/spec/AgentRunner.spec.ts +1062 -0
  109. package/spec/AsyncLocalStorageContext.spec.ts +223 -0
  110. package/spec/ConversationHooks.spec.ts +257 -0
  111. package/spec/FlinkAgent.spec.ts +681 -0
  112. package/spec/FlinkApp.htmlResponse.spec.ts +260 -0
  113. package/spec/FlinkApp.onError.invocation.spec.ts +151 -0
  114. package/spec/FlinkApp.onError.spec.ts +1 -2
  115. package/spec/FlinkApp.query.spec.ts +107 -0
  116. package/spec/FlinkApp.routeOrdering.spec.ts +61 -0
  117. package/spec/FlinkApp.undefinedResponse.spec.ts +123 -0
  118. package/spec/FlinkApp.validationMode.spec.ts +155 -0
  119. package/spec/FlinkJob.spec.ts +171 -0
  120. package/spec/FlinkLogFactory.spec.ts +337 -0
  121. package/spec/FlinkRepo.spec.ts +1 -1
  122. package/spec/LeaderElection.spec.ts +174 -0
  123. package/spec/StreamingIntegration.spec.ts +139 -0
  124. package/spec/ToolExecutor.spec.ts +465 -0
  125. package/spec/TypeScriptCompiler.spec.ts +1 -1
  126. package/spec/TypeScriptSourceParser.spec.ts +1215 -0
  127. package/spec/TypeScriptTokenizer.spec.ts +366 -0
  128. package/spec/ai/ContextCompaction.spec.ts +405 -0
  129. package/spec/ai/ConversationAgent.spec.ts +520 -0
  130. package/spec/ai/InMemoryConversationAgent.spec.ts +144 -0
  131. package/spec/ai/agentInstructions.spec.ts +358 -0
  132. package/spec/fixtures/agent-instructions/TestAgent.ts +24 -0
  133. package/spec/fixtures/agent-instructions/simple.md +3 -0
  134. package/spec/fixtures/agent-instructions/template.md +18 -0
  135. package/spec/fixtures/agent-instructions/yaml-format.yaml +9 -0
  136. package/spec/mock-project/dist/.tsbuildinfo +1 -0
  137. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar.js +56 -0
  138. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar2.js +58 -0
  139. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema.js +52 -0
  140. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema2.js +52 -0
  141. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema3.js +52 -0
  142. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema.js +54 -0
  143. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema2.js +54 -0
  144. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile.js +57 -0
  145. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile2.js +57 -0
  146. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler.js +53 -0
  147. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler2.js +55 -0
  148. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchCar.js +57 -0
  149. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOnboardingSession.js +75 -0
  150. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOrderWithComplexTypes.js +57 -0
  151. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchProductWithIntersection.js +58 -0
  152. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchUserWithUnion.js +58 -0
  153. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostCar.js +54 -0
  154. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogin.js +55 -0
  155. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogout.js +54 -0
  156. package/spec/mock-project/dist/spec/mock-project/src/handlers/PutCar.js +54 -0
  157. package/spec/mock-project/dist/spec/mock-project/src/index.js +83 -0
  158. package/spec/mock-project/dist/spec/mock-project/src/repos/CarRepo.js +26 -0
  159. package/spec/mock-project/dist/spec/mock-project/src/schemas/Car.js +2 -0
  160. package/spec/mock-project/dist/spec/mock-project/src/schemas/DefaultExportSchema.js +2 -0
  161. package/spec/mock-project/dist/spec/mock-project/src/schemas/FileWithTwoSchemas.js +2 -0
  162. package/spec/mock-project/dist/src/FlinkApp.js +1000 -0
  163. package/spec/mock-project/dist/src/FlinkContext.js +2 -0
  164. package/spec/mock-project/dist/src/FlinkErrors.js +143 -0
  165. package/spec/mock-project/dist/src/FlinkHttpHandler.js +47 -0
  166. package/spec/mock-project/dist/src/FlinkJob.js +2 -0
  167. package/spec/mock-project/dist/src/FlinkLog.js +119 -0
  168. package/spec/mock-project/dist/src/FlinkLogFactory.js +617 -0
  169. package/spec/mock-project/dist/src/FlinkPlugin.js +2 -0
  170. package/spec/mock-project/dist/src/FlinkRepo.js +224 -0
  171. package/spec/mock-project/dist/src/FlinkRequestContext.js +74 -0
  172. package/spec/mock-project/dist/src/FlinkResponse.js +2 -0
  173. package/spec/mock-project/dist/src/ai/AgentExecutor.js +279 -0
  174. package/spec/mock-project/dist/src/ai/AgentRunner.js +632 -0
  175. package/spec/mock-project/dist/src/ai/ConversationAgent.js +402 -0
  176. package/spec/mock-project/dist/src/ai/ConversationFlinkAgent.js +422 -0
  177. package/spec/mock-project/dist/src/ai/FlinkAgent.js +699 -0
  178. package/spec/mock-project/dist/src/ai/FlinkTool.js +2 -0
  179. package/spec/mock-project/dist/src/ai/InMemoryConversationAgent.js +209 -0
  180. package/spec/mock-project/dist/src/ai/LLMAdapter.js +2 -0
  181. package/spec/mock-project/dist/src/ai/SubAgentExecutor.js +223 -0
  182. package/spec/mock-project/dist/src/ai/ToolExecutor.js +412 -0
  183. package/spec/mock-project/dist/src/ai/agentInstructions.js +246 -0
  184. package/spec/mock-project/dist/src/auth/FlinkAuthPlugin.js +2 -0
  185. package/spec/mock-project/dist/src/auth/FlinkAuthUser.js +2 -0
  186. package/spec/mock-project/dist/src/handlers/GetCar.js +26 -52
  187. package/spec/mock-project/dist/src/handlers/GetCar.js.map +1 -0
  188. package/spec/mock-project/dist/src/handlers/GetCar2.js +32 -54
  189. package/spec/mock-project/dist/src/handlers/GetCar2.js.map +1 -0
  190. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js +26 -48
  191. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js.map +1 -0
  192. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js +28 -48
  193. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js.map +1 -0
  194. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js +29 -48
  195. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js.map +1 -0
  196. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js +26 -50
  197. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js.map +1 -0
  198. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js +28 -50
  199. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js.map +1 -0
  200. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js +27 -53
  201. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js.map +1 -0
  202. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js +29 -53
  203. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js.map +1 -0
  204. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js +16 -49
  205. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js.map +1 -0
  206. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js +25 -50
  207. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js.map +1 -0
  208. package/spec/mock-project/dist/src/handlers/PatchCar.js +27 -53
  209. package/spec/mock-project/dist/src/handlers/PatchCar.js.map +1 -0
  210. package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js +44 -70
  211. package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js.map +1 -0
  212. package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js +27 -53
  213. package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js.map +1 -0
  214. package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js +28 -54
  215. package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js.map +1 -0
  216. package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js +28 -54
  217. package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js.map +1 -0
  218. package/spec/mock-project/dist/src/handlers/PostCar.js +24 -50
  219. package/spec/mock-project/dist/src/handlers/PostCar.js.map +1 -0
  220. package/spec/mock-project/dist/src/handlers/PostLogin.js +25 -51
  221. package/spec/mock-project/dist/src/handlers/PostLogin.js.map +1 -0
  222. package/spec/mock-project/dist/src/handlers/PostLogout.js +24 -50
  223. package/spec/mock-project/dist/src/handlers/PostLogout.js.map +1 -0
  224. package/spec/mock-project/dist/src/handlers/PutCar.js +24 -50
  225. package/spec/mock-project/dist/src/handlers/PutCar.js.map +1 -0
  226. package/spec/mock-project/dist/src/handlers/StreamWriterFactory.js +83 -0
  227. package/spec/mock-project/dist/src/index.js +52 -76
  228. package/spec/mock-project/dist/src/index.js.map +1 -0
  229. package/spec/mock-project/dist/src/mock-data-generator.js +9 -0
  230. package/spec/mock-project/dist/src/repos/CarRepo.js +12 -24
  231. package/spec/mock-project/dist/src/repos/CarRepo.js.map +1 -0
  232. package/spec/mock-project/dist/src/schemas/Car.js +3 -1
  233. package/spec/mock-project/dist/src/schemas/Car.js.map +1 -0
  234. package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js +3 -1
  235. package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js.map +1 -0
  236. package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js +3 -1
  237. package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js.map +1 -0
  238. package/spec/mock-project/dist/src/utils.js +290 -0
  239. package/spec/mock-project/tsconfig.json +6 -1
  240. package/spec/schema-generation-nested-objects.spec.ts +97 -0
  241. package/spec/testHelpers.ts +49 -0
  242. package/spec/utils.caseConversion.spec.ts +78 -0
  243. package/spec/utils.spec.ts +13 -13
  244. package/src/DependencyTracker.ts +166 -0
  245. package/src/FlinkApp.ts +919 -155
  246. package/src/FlinkContext.ts +43 -0
  247. package/src/FlinkErrors.ts +32 -12
  248. package/src/FlinkHttpHandler.ts +246 -28
  249. package/src/FlinkJob.ts +11 -0
  250. package/src/FlinkLog.ts +119 -12
  251. package/src/FlinkLogFactory.ts +699 -0
  252. package/src/FlinkRepo.ts +10 -3
  253. package/src/FlinkRequestContext.ts +95 -0
  254. package/src/FlinkResponse.ts +6 -0
  255. package/src/FlinkService.ts +49 -0
  256. package/src/LeaderElection.ts +203 -0
  257. package/src/SchemaCache.ts +232 -0
  258. package/src/TypeScriptCompiler.ts +1347 -610
  259. package/src/TypeScriptUtils.ts +5 -0
  260. package/src/ai/AgentRunner.ts +646 -0
  261. package/src/ai/ConversationAgent.ts +413 -0
  262. package/src/ai/FlinkAgent.ts +1069 -0
  263. package/src/ai/FlinkTool.ts +165 -0
  264. package/src/ai/InMemoryConversationAgent.ts +149 -0
  265. package/src/ai/LLMAdapter.ts +126 -0
  266. package/src/ai/ToolExecutor.ts +485 -0
  267. package/src/ai/agentInstructions.ts +245 -0
  268. package/src/ai/index.ts +8 -0
  269. package/src/ai/instructionFileLoader.ts +156 -0
  270. package/src/auth/FlinkAuthPlugin.ts +2 -1
  271. package/src/handlers/StreamWriterFactory.ts +84 -0
  272. package/src/index.ts +14 -0
  273. package/src/loadPluginSchemas.ts +141 -0
  274. package/src/schema-extraction/TypeScriptSourceParser.ts +1058 -0
  275. package/src/schema-extraction/TypeScriptTokenizer.ts +205 -0
  276. package/src/schema-extraction/index.ts +2 -0
  277. package/src/schema-extraction/types.ts +34 -0
  278. package/src/utils/loadFlinkConfig.ts +89 -0
  279. package/src/utils.ts +52 -0
  280. package/tsconfig.json +6 -1
@@ -0,0 +1,260 @@
1
+ import { FlinkApp } from "../src/FlinkApp";
2
+ import { FlinkContext } from "../src/FlinkContext";
3
+ import { GetHandler, Handler, HttpMethod } from "../src/FlinkHttpHandler";
4
+
5
+ const request = require("supertest");
6
+
7
+ interface TestContext extends FlinkContext {}
8
+
9
+ describe("HTML response handler (html: true in RouteProps)", () => {
10
+ let app: FlinkApp<TestContext>;
11
+
12
+ afterEach(async () => {
13
+ if (app && app.started) {
14
+ await app.stop();
15
+ }
16
+ });
17
+
18
+ it("should return text/html content type", async () => {
19
+ const handler: GetHandler<TestContext, string> = async () => {
20
+ return { data: "<h1>Hello</h1>" };
21
+ };
22
+
23
+ app = new FlinkApp<TestContext>({ name: "test-html-ct", port: 4100 });
24
+ await app.start();
25
+
26
+ app.addHandler({
27
+ default: handler,
28
+ Route: { method: HttpMethod.get, path: "/html", responseType: "html" },
29
+ });
30
+
31
+ const response = await request(app.expressApp).get("/html");
32
+
33
+ expect(response.status).toBe(200);
34
+ expect(response.headers["content-type"]).toMatch(/text\/html/);
35
+ });
36
+
37
+ it("should return the raw HTML string as the body, not a JSON envelope", async () => {
38
+ const html = "<!DOCTYPE html><html><body><h1>Hello</h1></body></html>";
39
+
40
+ const handler: GetHandler<TestContext, string> = async () => {
41
+ return { data: html };
42
+ };
43
+
44
+ app = new FlinkApp<TestContext>({ name: "test-html-body", port: 4101 });
45
+ await app.start();
46
+
47
+ app.addHandler({
48
+ default: handler,
49
+ Route: { method: HttpMethod.get, path: "/html", responseType: "html" },
50
+ });
51
+
52
+ const response = await request(app.expressApp).get("/html");
53
+
54
+ expect(response.text).toBe(html);
55
+ // Must NOT be a JSON envelope
56
+ expect(response.body).not.toEqual(jasmine.objectContaining({ data: jasmine.anything() }));
57
+ });
58
+
59
+ it("should respect a custom status code", async () => {
60
+ const handler: GetHandler<TestContext, string> = async () => {
61
+ return { status: 404, data: "<h1>Not Found</h1>" };
62
+ };
63
+
64
+ app = new FlinkApp<TestContext>({ name: "test-html-status", port: 4102 });
65
+ await app.start();
66
+
67
+ app.addHandler({
68
+ default: handler,
69
+ Route: { method: HttpMethod.get, path: "/html", responseType: "html" },
70
+ });
71
+
72
+ const response = await request(app.expressApp).get("/html");
73
+
74
+ expect(response.status).toBe(404);
75
+ expect(response.text).toBe("<h1>Not Found</h1>");
76
+ expect(response.headers["content-type"]).toMatch(/text\/html/);
77
+ });
78
+
79
+ it("should not affect regular JSON handlers on the same app", async () => {
80
+ const jsonHandler: GetHandler<TestContext, any> = async () => {
81
+ return { status: 200, data: { ok: true } };
82
+ };
83
+
84
+ const htmlHandler: GetHandler<TestContext, string> = async () => {
85
+ return { data: "<p>HTML</p>" };
86
+ };
87
+
88
+ app = new FlinkApp<TestContext>({ name: "test-html-coexist", port: 4103 });
89
+ await app.start();
90
+
91
+ app.addHandler({
92
+ default: jsonHandler,
93
+ Route: { method: HttpMethod.get, path: "/json" },
94
+ });
95
+
96
+ app.addHandler({
97
+ default: htmlHandler,
98
+ Route: { method: HttpMethod.get, path: "/html", responseType: "html" },
99
+ });
100
+
101
+ const jsonRes = await request(app.expressApp).get("/json");
102
+ expect(jsonRes.status).toBe(200);
103
+ expect(jsonRes.headers["content-type"]).toMatch(/application\/json/);
104
+ expect(jsonRes.body.data).toEqual({ ok: true });
105
+
106
+ const htmlRes = await request(app.expressApp).get("/html");
107
+ expect(htmlRes.status).toBe(200);
108
+ expect(htmlRes.headers["content-type"]).toMatch(/text\/html/);
109
+ expect(htmlRes.text).toBe("<p>HTML</p>");
110
+ });
111
+
112
+ it("should handle POST html handlers", async () => {
113
+ const handler: Handler<TestContext, { name: string }, string> = async ({ req }) => {
114
+ return { data: `<h1>Hello ${req.body.name}</h1>` };
115
+ };
116
+
117
+ app = new FlinkApp<TestContext>({ name: "test-html-post", port: 4104 });
118
+ await app.start();
119
+
120
+ app.addHandler({
121
+ default: handler,
122
+ Route: { method: HttpMethod.post, path: "/html", responseType: "html" },
123
+ });
124
+
125
+ const response = await request(app.expressApp).post("/html").send({ name: "World" });
126
+
127
+ expect(response.status).toBe(200);
128
+ expect(response.headers["content-type"]).toMatch(/text\/html/);
129
+ expect(response.text).toBe("<h1>Hello World</h1>");
130
+ });
131
+ });
132
+
133
+ describe("Binary Buffer response handler (responseType: image/png)", () => {
134
+ let app: FlinkApp<TestContext>;
135
+
136
+ afterEach(async () => {
137
+ if (app && app.started) {
138
+ await app.stop();
139
+ }
140
+ });
141
+
142
+ it("should return image/png content type for Buffer response", async () => {
143
+ // Minimal valid 1x1 red PNG (67 bytes)
144
+ const pngBuffer = Buffer.from(
145
+ "89504e470d0a1a0a0000000d49484452000000010000000108020000009001" +
146
+ "2e00000000c4944415478016360f8cfc00000000200016934e360000000049454e44ae426082",
147
+ "hex"
148
+ );
149
+
150
+ const handler: GetHandler<TestContext, Buffer> = async () => {
151
+ return { data: pngBuffer };
152
+ };
153
+
154
+ app = new FlinkApp<TestContext>({ name: "test-png-ct", port: 4110 });
155
+ await app.start();
156
+
157
+ app.addHandler({
158
+ default: handler,
159
+ Route: { method: HttpMethod.get, path: "/image", responseType: "image/png" },
160
+ });
161
+
162
+ const response = await request(app.expressApp).get("/image");
163
+
164
+ expect(response.status).toBe(200);
165
+ expect(response.headers["content-type"]).toMatch(/image\/png/);
166
+ });
167
+
168
+ it("should return the exact buffer bytes as the response body", async () => {
169
+ const pngBuffer = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
170
+
171
+ const handler: GetHandler<TestContext, Buffer> = async () => {
172
+ return { data: pngBuffer };
173
+ };
174
+
175
+ app = new FlinkApp<TestContext>({ name: "test-png-body", port: 4111 });
176
+ await app.start();
177
+
178
+ app.addHandler({
179
+ default: handler,
180
+ Route: { method: HttpMethod.get, path: "/image", responseType: "image/png" },
181
+ });
182
+
183
+ const response = await request(app.expressApp)
184
+ .get("/image")
185
+ .buffer(true)
186
+ .parse((res: any, callback: any) => {
187
+ const chunks: Buffer[] = [];
188
+ res.on("data", (chunk: Buffer) => chunks.push(chunk));
189
+ res.on("end", () => callback(null, Buffer.concat(chunks)));
190
+ });
191
+
192
+ expect(response.status).toBe(200);
193
+ expect(Buffer.compare(response.body, pngBuffer)).toBe(0);
194
+ });
195
+
196
+ it("should respect a custom status code for binary responses", async () => {
197
+ const handler: GetHandler<TestContext, Buffer> = async () => {
198
+ return { status: 404, data: Buffer.from("not found") };
199
+ };
200
+
201
+ app = new FlinkApp<TestContext>({ name: "test-png-status", port: 4112 });
202
+ await app.start();
203
+
204
+ app.addHandler({
205
+ default: handler,
206
+ Route: { method: HttpMethod.get, path: "/image", responseType: "image/png" },
207
+ });
208
+
209
+ const response = await request(app.expressApp).get("/image");
210
+
211
+ expect(response.status).toBe(404);
212
+ expect(response.headers["content-type"]).toMatch(/image\/png/);
213
+ });
214
+
215
+ it("should support image/jpeg content type", async () => {
216
+ const jpegBuffer = Buffer.from([0xff, 0xd8, 0xff, 0xe0]);
217
+
218
+ const handler: GetHandler<TestContext, Buffer> = async () => {
219
+ return { data: jpegBuffer };
220
+ };
221
+
222
+ app = new FlinkApp<TestContext>({ name: "test-jpeg-ct", port: 4113 });
223
+ await app.start();
224
+
225
+ app.addHandler({
226
+ default: handler,
227
+ Route: { method: HttpMethod.get, path: "/photo", responseType: "image/jpeg" },
228
+ });
229
+
230
+ const response = await request(app.expressApp).get("/photo");
231
+
232
+ expect(response.status).toBe(200);
233
+ expect(response.headers["content-type"]).toMatch(/image\/jpeg/);
234
+ });
235
+
236
+ it("should support application/octet-stream for generic binary data", async () => {
237
+ const binaryData = Buffer.from([0x00, 0x01, 0x02, 0x03, 0xff]);
238
+
239
+ const handler: GetHandler<TestContext, Buffer> = async () => {
240
+ return { data: binaryData };
241
+ };
242
+
243
+ app = new FlinkApp<TestContext>({ name: "test-octet", port: 4114 });
244
+ await app.start();
245
+
246
+ app.addHandler({
247
+ default: handler,
248
+ Route: {
249
+ method: HttpMethod.get,
250
+ path: "/binary",
251
+ responseType: "application/octet-stream",
252
+ },
253
+ });
254
+
255
+ const response = await request(app.expressApp).get("/binary");
256
+
257
+ expect(response.status).toBe(200);
258
+ expect(response.headers["content-type"]).toMatch(/application\/octet-stream/);
259
+ });
260
+ });
@@ -0,0 +1,151 @@
1
+ import { FlinkApp } from "../src/FlinkApp";
2
+ import { FlinkContext } from "../src/FlinkContext";
3
+ import { FlinkError } from "../src/FlinkErrors";
4
+ import { GetHandler, Handler, HttpMethod } from "../src/FlinkHttpHandler";
5
+ import { FlinkResponse } from "../src/FlinkResponse";
6
+
7
+ const request = require("supertest");
8
+
9
+ interface TestContext extends FlinkContext {}
10
+
11
+ const reqSchema = {
12
+ type: "object",
13
+ properties: {
14
+ name: { type: "string" },
15
+ },
16
+ required: ["name"],
17
+ additionalProperties: false,
18
+ };
19
+
20
+ const resSchema = {
21
+ type: "object",
22
+ properties: {
23
+ id: { type: "string" },
24
+ },
25
+ required: ["id"],
26
+ };
27
+
28
+ describe("FlinkApp onError invocation", () => {
29
+ let app: FlinkApp<TestContext>;
30
+ let calls: { error: FlinkResponse<FlinkError>; context: any }[];
31
+
32
+ const onError = (error: FlinkResponse<FlinkError>, context: any) => {
33
+ calls.push({ error, context });
34
+ };
35
+
36
+ beforeEach(() => {
37
+ calls = [];
38
+ });
39
+
40
+ afterEach(async () => {
41
+ if (app && app.started) {
42
+ await app.stop();
43
+ }
44
+ });
45
+
46
+ it("should invoke onError for request validation 400s", async () => {
47
+ const handler: Handler<TestContext, any, any> = async () => {
48
+ return { data: { id: "ok" } };
49
+ };
50
+
51
+ app = new FlinkApp<TestContext>({ name: "test-onerror-req", port: 4210, onError });
52
+ await app.start();
53
+
54
+ app.addHandler({
55
+ default: handler,
56
+ Route: { method: HttpMethod.post, path: "/test", reqSchema },
57
+ });
58
+
59
+ const response = await request(app.expressApp).post("/test").send({ wrong: "field" });
60
+
61
+ expect(response.status).toBe(400);
62
+ expect(calls.length).toBe(1);
63
+ expect(calls[0].error.status).toBe(400);
64
+ expect(calls[0].error.error?.title).toBe("Bad request");
65
+ expect(calls[0].context.method).toBe(HttpMethod.post);
66
+ expect(calls[0].context.path).toBe("/test");
67
+ expect(calls[0].context.reqId).toBeDefined();
68
+ });
69
+
70
+ it("should invoke onError for response validation 500s (invalid shape)", async () => {
71
+ const handler: GetHandler<TestContext, any> = async () => {
72
+ return { data: { wrongField: 123 } }; // missing required "id"
73
+ };
74
+
75
+ app = new FlinkApp<TestContext>({ name: "test-onerror-res", port: 4211, onError });
76
+ await app.start();
77
+
78
+ app.addHandler({
79
+ default: handler,
80
+ Route: { method: HttpMethod.get, path: "/test", resSchema },
81
+ });
82
+
83
+ const response = await request(app.expressApp).get("/test");
84
+
85
+ expect(response.status).toBe(500);
86
+ expect(calls.length).toBe(1);
87
+ expect(calls[0].error.status).toBe(500);
88
+ expect(calls[0].error.error?.title).toBe("Bad response");
89
+ });
90
+
91
+ it("should invoke onError for response validation 500s (no data)", async () => {
92
+ const handler: GetHandler<TestContext, any> = async () => {
93
+ return { status: 200 } as any; // no data
94
+ };
95
+
96
+ app = new FlinkApp<TestContext>({ name: "test-onerror-nodata", port: 4212, onError });
97
+ await app.start();
98
+
99
+ app.addHandler({
100
+ default: handler,
101
+ Route: { method: HttpMethod.get, path: "/test", resSchema },
102
+ });
103
+
104
+ const response = await request(app.expressApp).get("/test");
105
+
106
+ expect(response.status).toBe(500);
107
+ expect(calls.length).toBe(1);
108
+ expect(calls[0].error.status).toBe(500);
109
+ expect(calls[0].error.error?.title).toBe("Bad response");
110
+ });
111
+
112
+ it("should still invoke onError for handler-thrown errors", async () => {
113
+ const handler: GetHandler<TestContext, any> = async () => {
114
+ throw new Error("boom");
115
+ };
116
+
117
+ app = new FlinkApp<TestContext>({ name: "test-onerror-throw", port: 4213, onError });
118
+ await app.start();
119
+
120
+ app.addHandler({
121
+ default: handler,
122
+ Route: { method: HttpMethod.get, path: "/test" },
123
+ });
124
+
125
+ const response = await request(app.expressApp).get("/test");
126
+
127
+ expect(response.status).toBe(500);
128
+ expect(calls.length).toBe(1);
129
+ expect(calls[0].error.status).toBe(500);
130
+ });
131
+
132
+ it("should pass the app context (ctx) to the callback", async () => {
133
+ const handler: GetHandler<TestContext, any> = async () => {
134
+ throw new Error("boom");
135
+ };
136
+
137
+ app = new FlinkApp<TestContext>({ name: "test-onerror-ctx", port: 4214, onError });
138
+ await app.start();
139
+
140
+ app.addHandler({
141
+ default: handler,
142
+ Route: { method: HttpMethod.get, path: "/test" },
143
+ });
144
+
145
+ await request(app.expressApp).get("/test");
146
+
147
+ expect(calls.length).toBe(1);
148
+ expect(calls[0].context.ctx).toBeDefined();
149
+ expect(calls[0].context.ctx).toBe(app.ctx);
150
+ });
151
+ });
@@ -1,9 +1,8 @@
1
1
  import { FlinkApp } from "../src/FlinkApp";
2
2
  import { FlinkContext } from "../src/FlinkContext";
3
- import { FlinkResponse } from "../src/FlinkResponse";
4
3
  import { FlinkError } from "../src/FlinkErrors";
5
- import { badRequest, internalServerError, notFound } from "../src/FlinkErrors";
6
4
  import { HttpMethod } from "../src/FlinkHttpHandler";
5
+ import { FlinkResponse } from "../src/FlinkResponse";
7
6
 
8
7
  interface TestContext extends FlinkContext {}
9
8
 
@@ -0,0 +1,107 @@
1
+ import { FlinkApp } 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("Query parameter normalization", () => {
10
+ let app: FlinkApp<TestContext>;
11
+
12
+ afterEach(async () => {
13
+ if (app && app.started) {
14
+ await app.stop();
15
+ }
16
+ });
17
+
18
+ it("should normalize query params to strings", async () => {
19
+ const handler: GetHandler<TestContext, any> = async ({ req }) => {
20
+ return { status: 200, data: { query: req.query } };
21
+ };
22
+
23
+ app = new FlinkApp<TestContext>({ name: "test-query-norm", port: 4000 });
24
+ await app.start();
25
+
26
+ app.addHandler({
27
+ default: handler,
28
+ Route: { method: HttpMethod.get, path: "/test" },
29
+ });
30
+
31
+ const response = await request(app.expressApp).get("/test?name=John&age=25");
32
+
33
+ expect(response.status).toBe(200);
34
+ expect(response.body.data.query.name).toBe("John");
35
+ expect(response.body.data.query.age).toBe("25");
36
+ expect(typeof response.body.data.query.name).toBe("string");
37
+ expect(typeof response.body.data.query.age).toBe("string");
38
+ });
39
+
40
+ it("should normalize array query params to string arrays", async () => {
41
+ const handler: GetHandler<TestContext, any> = async ({ req }) => {
42
+ return { status: 200, data: { query: req.query } };
43
+ };
44
+
45
+ app = new FlinkApp<TestContext>({ name: "test-query-array", port: 4001 });
46
+ await app.start();
47
+
48
+ app.addHandler({
49
+ default: handler,
50
+ Route: { method: HttpMethod.get, path: "/test-array" },
51
+ });
52
+
53
+ const response = await request(app.expressApp).get("/test-array?tag=a&tag=b&tag=c");
54
+
55
+ expect(response.status).toBe(200);
56
+ expect(Array.isArray(response.body.data.query.tag)).toBe(true);
57
+ expect(response.body.data.query.tag).toEqual(["a", "b", "c"]);
58
+ response.body.data.query.tag.forEach((tag: string) => {
59
+ expect(typeof tag).toBe("string");
60
+ });
61
+ });
62
+
63
+ it("should normalize numeric values to strings", async () => {
64
+ const handler: GetHandler<TestContext, any> = async ({ req }) => {
65
+ return { status: 200, data: { query: req.query } };
66
+ };
67
+
68
+ app = new FlinkApp<TestContext>({ name: "test-query-numbers", port: 4002 });
69
+ await app.start();
70
+
71
+ app.addHandler({
72
+ default: handler,
73
+ Route: { method: HttpMethod.get, path: "/test-numbers" },
74
+ });
75
+
76
+ const response = await request(app.expressApp).get("/test-numbers?count=100&price=99.99");
77
+
78
+ expect(response.status).toBe(200);
79
+ expect(response.body.data.query.count).toBe("100");
80
+ expect(response.body.data.query.price).toBe("99.99");
81
+ expect(typeof response.body.data.query.count).toBe("string");
82
+ expect(typeof response.body.data.query.price).toBe("string");
83
+ });
84
+
85
+ it("should allow handlers to parse strings as needed", async () => {
86
+ const handler: GetHandler<TestContext, any> = async ({ req }) => {
87
+ const page = Number(req.query.page) || 1;
88
+ const active = req.query.active === "true";
89
+
90
+ return { status: 200, data: { page, active } };
91
+ };
92
+
93
+ app = new FlinkApp<TestContext>({ name: "test-query-parsing", port: 4003 });
94
+ await app.start();
95
+
96
+ app.addHandler({
97
+ default: handler,
98
+ Route: { method: HttpMethod.get, path: "/test-parsing" },
99
+ });
100
+
101
+ const response = await request(app.expressApp).get("/test-parsing?page=2&active=true");
102
+
103
+ expect(response.status).toBe(200);
104
+ expect(response.body.data.page).toBe(2);
105
+ expect(response.body.data.active).toBe(true);
106
+ });
107
+ });
@@ -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
+ });