@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
@@ -50,7 +50,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
50
50
  return (mod && mod.__esModule) ? mod : { "default": mod };
51
51
  };
52
52
  Object.defineProperty(exports, "__esModule", { value: true });
53
- exports.FlinkApp = exports.autoRegisteredJobs = exports.autoRegisteredRepos = exports.autoRegisteredHandlers = exports.expressFn = void 0;
53
+ exports.FlinkApp = exports.autoRegisteredServices = exports.autoRegisteredAgents = exports.autoRegisteredTools = exports.autoRegisteredJobs = exports.autoRegisteredRepos = exports.autoRegisteredHandlers = exports.expressFn = void 0;
54
54
  var ajv_1 = __importDefault(require("ajv"));
55
55
  var ajv_formats_1 = __importDefault(require("ajv-formats"));
56
56
  var body_parser_1 = __importDefault(require("body-parser"));
@@ -62,9 +62,17 @@ var ms_1 = __importDefault(require("ms"));
62
62
  var toad_scheduler_1 = require("toad-scheduler");
63
63
  var uuid_1 = require("uuid");
64
64
  var FlinkErrors_1 = require("./FlinkErrors");
65
+ var FlinkHttpHandler_1 = require("./FlinkHttpHandler");
66
+ var LeaderElection_1 = require("./LeaderElection");
65
67
  var FlinkLog_1 = require("./FlinkLog");
68
+ var FlinkLogFactory_1 = require("./FlinkLogFactory");
69
+ var FlinkRequestContext_1 = require("./FlinkRequestContext");
70
+ var StreamWriterFactory_1 = require("./handlers/StreamWriterFactory");
66
71
  var mock_data_generator_1 = __importDefault(require("./mock-data-generator"));
67
72
  var utils_1 = require("./utils");
73
+ var initLog = FlinkLogFactory_1.FlinkLogFactory.createLogger("flink.init");
74
+ var perfLog = FlinkLogFactory_1.FlinkLogFactory.createLogger("flink.perf");
75
+ var schedulerLog = FlinkLogFactory_1.FlinkLogFactory.createLogger("flink.scheduler");
68
76
  var ajv = new ajv_1.default();
69
77
  (0, ajv_formats_1.default)(ajv);
70
78
  var defaultCorsOptions = {
@@ -92,8 +100,24 @@ exports.autoRegisteredRepos = [];
92
100
  * are picked up by TypeScript compiler
93
101
  */
94
102
  exports.autoRegisteredJobs = [];
103
+ /**
104
+ * This will be populated at compile time when the apps tools
105
+ * are picked up by TypeScript compiler
106
+ */
107
+ exports.autoRegisteredTools = [];
108
+ /**
109
+ * This will be populated at compile time when the apps agents
110
+ * are picked up by TypeScript compiler
111
+ */
112
+ exports.autoRegisteredAgents = [];
113
+ /**
114
+ * This will be populated at compile time when the apps services
115
+ * are picked up by TypeScript compiler
116
+ */
117
+ exports.autoRegisteredServices = [];
95
118
  var FlinkApp = /** @class */ (function () {
96
119
  function FlinkApp(opts) {
120
+ var _a, _b;
97
121
  this.handlers = [];
98
122
  this.started = false;
99
123
  this.debug = false;
@@ -101,10 +125,18 @@ var FlinkApp = /** @class */ (function () {
101
125
  this.routingConfigured = false;
102
126
  this.disableHttpServer = false;
103
127
  this.repos = {};
128
+ this.services = {};
129
+ this.llmAdapters = new Map();
130
+ this.tools = {};
131
+ this.agents = {}; // FlinkAgent<C> instances
104
132
  /**
105
133
  * Internal cache used to track registered handlers and potentially any overlapping routes
106
134
  */
107
135
  this.handlerRouteCache = new Map();
136
+ // Load config file and initialize logging
137
+ var loadFlinkConfig = require("./utils/loadFlinkConfig").loadFlinkConfig;
138
+ var flinkConfig = loadFlinkConfig();
139
+ FlinkLogFactory_1.FlinkLogFactory.configure(flinkConfig === null || flinkConfig === void 0 ? void 0 : flinkConfig.logging);
108
140
  this.name = opts.name;
109
141
  this.port = opts.port || 3333;
110
142
  this.dbOpts = opts.db;
@@ -123,6 +155,13 @@ var FlinkApp = /** @class */ (function () {
123
155
  this.disableHttpServer = !!opts.disableHttpServer;
124
156
  this.accessLog = __assign({ enabled: true, format: "dev" }, opts.accessLog);
125
157
  this.onError = opts.onError;
158
+ // Register LLM adapters if configured
159
+ if ((_a = opts.ai) === null || _a === void 0 ? void 0 : _a.llms) {
160
+ // Convert plain object to Map for internal use
161
+ this.llmAdapters = new Map(Object.entries(opts.ai.llms));
162
+ }
163
+ // Register global agent observer if configured
164
+ this.agentObserver = (_b = opts.ai) === null || _b === void 0 ? void 0 : _b.observer;
126
165
  }
127
166
  Object.defineProperty(FlinkApp.prototype, "ctx", {
128
167
  get: function () {
@@ -136,33 +175,43 @@ var FlinkApp = /** @class */ (function () {
136
175
  });
137
176
  FlinkApp.prototype.start = function () {
138
177
  return __awaiter(this, void 0, void 0, function () {
139
- var startTime, offsetTime, _i, _a, type, _b, _c, plugin, db;
178
+ var startTime, dbStartTime, contextStartTime, toolsStartTime, agentsStartTime, agentInitStartTime, _i, _a, type, pluginsStartTime, _b, _c, plugin, db, handlersStartTime, jobsStartTime, totalStartTime;
140
179
  var _this = this;
141
180
  var _d;
142
181
  return __generator(this, function (_e) {
143
182
  switch (_e.label) {
144
183
  case 0:
145
184
  startTime = Date.now();
146
- offsetTime = 0;
185
+ dbStartTime = Date.now();
147
186
  return [4 /*yield*/, this.initDb()];
148
187
  case 1:
149
188
  _e.sent();
150
- if (this.debug) {
151
- offsetTime = Date.now();
152
- FlinkLog_1.log.bgColorLog("cyan", "Init db took ".concat(offsetTime - startTime, " ms"));
153
- }
189
+ perfLog.debug("Init db took ".concat(Date.now() - dbStartTime, "ms"));
190
+ contextStartTime = Date.now();
154
191
  return [4 /*yield*/, this.buildContext()];
155
192
  case 2:
156
193
  _e.sent();
157
- if (this.debug) {
158
- FlinkLog_1.log.bgColorLog("cyan", "Build context took ".concat(Date.now() - offsetTime, " ms"));
159
- offsetTime = Date.now();
160
- }
161
- if (this.isSchedulingEnabled) {
194
+ perfLog.debug("Build context took ".concat(Date.now() - contextStartTime, "ms"));
195
+ toolsStartTime = Date.now();
196
+ return [4 /*yield*/, this.registerAutoRegisterableTools()];
197
+ case 3:
198
+ _e.sent();
199
+ perfLog.debug("Register tools took ".concat(Date.now() - toolsStartTime, "ms"));
200
+ agentsStartTime = Date.now();
201
+ return [4 /*yield*/, this.registerAutoRegisterableAgents()];
202
+ case 4:
203
+ _e.sent();
204
+ perfLog.debug("Register agents took ".concat(Date.now() - agentsStartTime, "ms"));
205
+ agentInitStartTime = Date.now();
206
+ return [4 /*yield*/, this.initializeAgents()];
207
+ case 5:
208
+ _e.sent();
209
+ perfLog.debug("Initialize agents took ".concat(Date.now() - agentInitStartTime, "ms"));
210
+ if (this.isSchedulingEnabled && !this.leaderElectionConfig) {
162
211
  this.scheduler = new toad_scheduler_1.ToadScheduler();
163
212
  }
164
- else {
165
- FlinkLog_1.log.info("🚫 Scheduling is disabled");
213
+ else if (!this.isSchedulingEnabled) {
214
+ schedulerLog.info("Scheduling is disabled");
166
215
  }
167
216
  if (!this.disableHttpServer) {
168
217
  this.expressApp = (0, express_1.default)();
@@ -182,46 +231,53 @@ var FlinkApp = /** @class */ (function () {
182
231
  next();
183
232
  });
184
233
  }
234
+ pluginsStartTime = Date.now();
185
235
  _b = 0, _c = this.plugins;
186
- _e.label = 3;
187
- case 3:
188
- if (!(_b < _c.length)) return [3 /*break*/, 9];
236
+ _e.label = 6;
237
+ case 6:
238
+ if (!(_b < _c.length)) return [3 /*break*/, 12];
189
239
  plugin = _c[_b];
190
240
  db = void 0;
191
- if (!plugin.db) return [3 /*break*/, 5];
241
+ if (!plugin.db) return [3 /*break*/, 8];
192
242
  return [4 /*yield*/, this.initPluginDb(plugin)];
193
- case 4:
194
- db = _e.sent();
195
- _e.label = 5;
196
- case 5:
197
- if (!plugin.init) return [3 /*break*/, 7];
198
- return [4 /*yield*/, plugin.init(this, db)];
199
- case 6:
200
- _e.sent();
201
- _e.label = 7;
202
243
  case 7:
203
- FlinkLog_1.log.info("Initialized plugin '".concat(plugin.id, "'"));
244
+ db = _e.sent();
204
245
  _e.label = 8;
205
246
  case 8:
206
- _b++;
207
- return [3 /*break*/, 3];
208
- case 9: return [4 /*yield*/, this.registerAutoRegisterableHandlers()];
209
- case 10:
247
+ if (!plugin.init) return [3 /*break*/, 10];
248
+ return [4 /*yield*/, plugin.init(this, db)];
249
+ case 9:
210
250
  _e.sent();
211
- if (this.debug) {
212
- FlinkLog_1.log.bgColorLog("cyan", "Register handlers took ".concat(Date.now() - offsetTime, " ms"));
213
- offsetTime = Date.now();
251
+ _e.label = 10;
252
+ case 10:
253
+ initLog.info("Initialized plugin '".concat(plugin.id, "'"));
254
+ _e.label = 11;
255
+ case 11:
256
+ _b++;
257
+ return [3 /*break*/, 6];
258
+ case 12:
259
+ if (this.plugins.length > 0) {
260
+ perfLog.debug("Initialize plugins took ".concat(Date.now() - pluginsStartTime, "ms (").concat(this.plugins.length, " plugins)"));
214
261
  }
215
- if (!this.isSchedulingEnabled) return [3 /*break*/, 12];
262
+ handlersStartTime = Date.now();
263
+ return [4 /*yield*/, this.registerAutoRegisterableHandlers()];
264
+ case 13:
265
+ _e.sent();
266
+ perfLog.debug("Register handlers took ".concat(Date.now() - handlersStartTime, "ms"));
267
+ if (!this.isSchedulingEnabled) return [3 /*break*/, 17];
268
+ if (!this.leaderElectionConfig) return [3 /*break*/, 15];
269
+ return [4 /*yield*/, this.startLeaderElection()];
270
+ case 14:
271
+ _e.sent();
272
+ return [3 /*break*/, 17];
273
+ case 15:
274
+ jobsStartTime = Date.now();
216
275
  return [4 /*yield*/, this.registerAutoRegisterableJobs()];
217
- case 11:
276
+ case 16:
218
277
  _e.sent();
219
- if (this.debug) {
220
- FlinkLog_1.log.bgColorLog("cyan", "Register jobs took ".concat(Date.now() - offsetTime, " ms"));
221
- offsetTime = Date.now();
222
- }
223
- _e.label = 12;
224
- case 12:
278
+ perfLog.debug("Register jobs took ".concat(Date.now() - jobsStartTime, "ms"));
279
+ _e.label = 17;
280
+ case 17:
225
281
  // Register 404 with slight delay to allow all manually added routes to be added
226
282
  // TODO: Is there a better solution to force this handler to always run last?
227
283
  setTimeout(function () {
@@ -233,7 +289,7 @@ var FlinkApp = /** @class */ (function () {
233
289
  _this.routingConfigured = true;
234
290
  });
235
291
  if (this.disableHttpServer) {
236
- FlinkLog_1.log.info("🚧 HTTP server is disabled, but flink app is running");
292
+ initLog.info("🚧 HTTP server is disabled, but flink app is running");
237
293
  this.started = true;
238
294
  }
239
295
  else {
@@ -242,6 +298,8 @@ var FlinkApp = /** @class */ (function () {
242
298
  _this.started = true;
243
299
  });
244
300
  }
301
+ totalStartTime = Date.now() - startTime;
302
+ perfLog.info("\u2713 FlinkApp started in ".concat(totalStartTime, "ms"));
245
303
  return [2 /*return*/, this];
246
304
  }
247
305
  });
@@ -254,12 +312,24 @@ var FlinkApp = /** @class */ (function () {
254
312
  switch (_a.label) {
255
313
  case 0:
256
314
  FlinkLog_1.log.info("🛑 Stopping Flink app...");
257
- if (!this.scheduler) return [3 /*break*/, 2];
258
- return [4 /*yield*/, this.scheduler.stop()];
315
+ if (!this.leaderElection) return [3 /*break*/, 2];
316
+ return [4 /*yield*/, this.leaderElection.stop()];
259
317
  case 1:
260
318
  _a.sent();
261
319
  _a.label = 2;
262
320
  case 2:
321
+ if (!this.scheduler) return [3 /*break*/, 4];
322
+ return [4 /*yield*/, this.scheduler.stop()];
323
+ case 3:
324
+ _a.sent();
325
+ _a.label = 4;
326
+ case 4:
327
+ if (!this.allInstanceScheduler) return [3 /*break*/, 6];
328
+ return [4 /*yield*/, this.allInstanceScheduler.stop()];
329
+ case 5:
330
+ _a.sent();
331
+ _a.label = 6;
332
+ case 6:
263
333
  if (this.expressServer) {
264
334
  return [2 /*return*/, new Promise(function (resolve, reject) {
265
335
  var int = setTimeout(function () {
@@ -284,7 +354,6 @@ var FlinkApp = /** @class */ (function () {
284
354
  * which are derived from handler function type arguments.
285
355
  */
286
356
  FlinkApp.prototype.addHandler = function (handler, routePropsOverride) {
287
- var _a, _b, _c, _d, _e, _f;
288
357
  if (this.routingConfigured) {
289
358
  throw new Error("Cannot add handler after routes has been registered, make sure to invoke earlier");
290
359
  }
@@ -303,28 +372,37 @@ var FlinkApp = /** @class */ (function () {
303
372
  // TODO: Not sure if there is a case where you'd want to overwrite a route?
304
373
  FlinkLog_1.log.warn("".concat(methodAndPath, " overlaps existing route"));
305
374
  }
375
+ // Use direct schemas from routeProps if provided, otherwise fall back to manifest lookup
376
+ var reqSchema = routeProps.reqSchema;
377
+ var resSchema = routeProps.resSchema;
378
+ var queryMetadata = [];
379
+ var paramsMetadata = [];
380
+ if (!reqSchema || !resSchema) {
381
+ var schemaManifest = this.loadSchemaManifest();
382
+ var metadata = handler.__file ? schemaManifest.handlers[handler.__file] : undefined;
383
+ if (!reqSchema)
384
+ reqSchema = this.resolveSchema(metadata === null || metadata === void 0 ? void 0 : metadata.reqSchemaName);
385
+ if (!resSchema)
386
+ resSchema = this.resolveSchema(metadata === null || metadata === void 0 ? void 0 : metadata.resSchemaName);
387
+ queryMetadata = (metadata === null || metadata === void 0 ? void 0 : metadata.queryMetadata) || [];
388
+ paramsMetadata = (metadata === null || metadata === void 0 ? void 0 : metadata.paramsMetadata) || [];
389
+ }
306
390
  var handlerConfig = {
307
391
  routeProps: __assign(__assign({}, routeProps), { method: routeProps.method, path: routeProps.path }),
308
392
  schema: {
309
- reqSchema: (_a = handler.__schemas) === null || _a === void 0 ? void 0 : _a.reqSchema,
310
- resSchema: (_b = handler.__schemas) === null || _b === void 0 ? void 0 : _b.resSchema,
393
+ reqSchema: reqSchema,
394
+ resSchema: resSchema,
311
395
  },
312
- queryMetadata: handler.__query || [],
313
- paramsMetadata: handler.__params || [],
396
+ queryMetadata: queryMetadata,
397
+ paramsMetadata: paramsMetadata,
314
398
  };
315
- if (((_c = handler.__schemas) === null || _c === void 0 ? void 0 : _c.reqSchema) && !((_d = handlerConfig.schema) === null || _d === void 0 ? void 0 : _d.reqSchema)) {
316
- FlinkLog_1.log.warn("Expected request schema ".concat(handler.__schemas.reqSchema, " for handler ").concat(methodAndPath, " but no such schema was found"));
317
- }
318
- if (((_e = handler.__schemas) === null || _e === void 0 ? void 0 : _e.resSchema) && !((_f = handlerConfig.schema) === null || _f === void 0 ? void 0 : _f.resSchema)) {
319
- FlinkLog_1.log.warn("Expected response schema ".concat(handler.__schemas.resSchema, " for handler ").concat(methodAndPath, " but no such schema was found"));
320
- }
321
399
  this.registerHandler(handlerConfig, handler.default);
322
400
  };
323
401
  FlinkApp.prototype.registerHandler = function (handlerConfig, handler) {
324
402
  var _this = this;
325
403
  this.handlers.push(handlerConfig);
326
404
  var routeProps = handlerConfig.routeProps, _a = handlerConfig.schema, schema = _a === void 0 ? {} : _a;
327
- var method = routeProps.method;
405
+ var method = routeProps.method, streamFormat = routeProps.streamFormat;
328
406
  if (!method) {
329
407
  FlinkLog_1.log.error("Route ".concat(routeProps.path, " is missing http method"));
330
408
  }
@@ -335,41 +413,77 @@ var FlinkApp = /** @class */ (function () {
335
413
  }
336
414
  var validateReq_1;
337
415
  var validateRes_1;
338
- if (schema.reqSchema) {
339
- validateReq_1 = ajv.compile(schema.reqSchema);
416
+ // Select AJV instance (use schemaAjv for v2.0 manifests, fallback to global ajv)
417
+ var ajvInstance = this.schemaAjv || ajv;
418
+ // Determine validation mode (default to Validate if not specified)
419
+ var validationMode = routeProps.validation || FlinkHttpHandler_1.ValidationMode.Validate;
420
+ // Compile request schema if validation mode requires it
421
+ if (schema.reqSchema && validationMode !== FlinkHttpHandler_1.ValidationMode.SkipValidation && validationMode !== FlinkHttpHandler_1.ValidationMode.ValidateResponse) {
422
+ // For v2.0 manifests with $id, use getSchema() if available
423
+ if (schema.reqSchema.$id && this.schemaAjv) {
424
+ validateReq_1 = this.schemaAjv.getSchema(schema.reqSchema.$id);
425
+ if (!validateReq_1) {
426
+ FlinkLog_1.log.warn("Schema ".concat(schema.reqSchema.$id, " not found in AJV registry, compiling inline"));
427
+ validateReq_1 = ajvInstance.compile(schema.reqSchema);
428
+ }
429
+ }
430
+ else {
431
+ validateReq_1 = ajvInstance.compile(schema.reqSchema);
432
+ }
340
433
  }
341
- if (schema.resSchema) {
342
- validateRes_1 = ajv.compile(schema.resSchema);
434
+ // Skip response validation for streaming handlers (responses are stream chunks, not final JSON)
435
+ // Skip response validation for non-JSON response types (html, csv, etc.)
436
+ if (!streamFormat &&
437
+ !routeProps.responseType &&
438
+ schema.resSchema &&
439
+ validationMode !== FlinkHttpHandler_1.ValidationMode.SkipValidation &&
440
+ validationMode !== FlinkHttpHandler_1.ValidationMode.ValidateRequest) {
441
+ // For v2.0 manifests with $id, use getSchema() if available
442
+ if (schema.resSchema.$id && this.schemaAjv) {
443
+ validateRes_1 = this.schemaAjv.getSchema(schema.resSchema.$id);
444
+ if (!validateRes_1) {
445
+ FlinkLog_1.log.warn("Schema ".concat(schema.resSchema.$id, " not found in AJV registry, compiling inline"));
446
+ validateRes_1 = ajvInstance.compile(schema.resSchema);
447
+ }
448
+ }
449
+ else {
450
+ validateRes_1 = ajvInstance.compile(schema.resSchema);
451
+ }
343
452
  }
344
453
  this.expressApp[method](routeProps.path, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
345
- var valid, formattedErrors, data, handlerRes, err_1, errorResponse, result, valid, formattedErrors;
346
- return __generator(this, function (_a) {
347
- switch (_a.label) {
454
+ var valid, formattedErrors, errorResponse, data, normalizedQuery, _i, _a, _b, key, value, stream, handlerRes, flinkReq_1, err_1, errorResponse, detail, errorResponse, valid, formattedErrors, errorResponse;
455
+ var _this = this;
456
+ var _c;
457
+ return __generator(this, function (_d) {
458
+ switch (_d.label) {
348
459
  case 0:
349
460
  if (!routeProps.permissions) return [3 /*break*/, 2];
350
461
  return [4 /*yield*/, this.authenticate(req, routeProps.permissions)];
351
462
  case 1:
352
- if (!(_a.sent())) {
463
+ if (!(_d.sent())) {
353
464
  return [2 /*return*/, res.status(401).json((0, FlinkErrors_1.unauthorized)())];
354
465
  }
355
- _a.label = 2;
466
+ _d.label = 2;
356
467
  case 2:
357
468
  if (validateReq_1) {
358
469
  valid = validateReq_1(req.body);
359
470
  if (!valid) {
360
471
  formattedErrors = (0, utils_1.formatValidationErrors)(validateReq_1.errors, req.body);
361
472
  FlinkLog_1.log.warn("[".concat(req.reqId, "] ").concat(methodAndRoute_1, ": Bad request\n").concat(formattedErrors));
362
- return [2 /*return*/, res.status(400).json({
363
- status: 400,
364
- error: {
365
- id: (0, uuid_1.v4)(),
366
- title: "Bad request",
367
- detail: formattedErrors,
368
- },
369
- })];
473
+ errorResponse = {
474
+ status: 400,
475
+ error: {
476
+ id: (0, uuid_1.v4)(),
477
+ title: "Bad request",
478
+ detail: formattedErrors,
479
+ },
480
+ };
481
+ this.invokeOnError(errorResponse, req, method, routeProps.path);
482
+ return [2 /*return*/, res.status(400).json(errorResponse)];
370
483
  }
371
484
  }
372
- if (routeProps.mockApi && schema.resSchema) {
485
+ // Skip mock API for streaming handlers
486
+ if (routeProps.mockApi && schema.resSchema && !streamFormat) {
373
487
  FlinkLog_1.log.warn("Mock response for ".concat(req.method.toUpperCase(), " ").concat(req.path));
374
488
  data = (0, mock_data_generator_1.default)(schema.resSchema);
375
489
  res.status(200).json({
@@ -378,20 +492,66 @@ var FlinkApp = /** @class */ (function () {
378
492
  });
379
493
  return [2 /*return*/];
380
494
  }
381
- _a.label = 3;
495
+ // Normalize query parameters to predictable string or string[] types
496
+ // Express query parser can produce numbers, booleans, objects, etc.
497
+ // We normalize everything to strings or string arrays for consistency
498
+ if (req.query && typeof req.query === "object") {
499
+ normalizedQuery = {};
500
+ for (_i = 0, _a = Object.entries(req.query); _i < _a.length; _i++) {
501
+ _b = _a[_i], key = _b[0], value = _b[1];
502
+ if (Array.isArray(value)) {
503
+ // Handle array values (e.g., ?tag=a&tag=b)
504
+ normalizedQuery[key] = value.map(function (v) { return String(v); });
505
+ }
506
+ else if (value !== undefined && value !== null) {
507
+ // Convert single values to strings
508
+ normalizedQuery[key] = String(value);
509
+ }
510
+ // Skip undefined/null values - they won't appear in the normalized query
511
+ }
512
+ req.query = normalizedQuery;
513
+ }
514
+ stream = streamFormat ? StreamWriterFactory_1.StreamWriterFactory.create(res, streamFormat) : undefined;
515
+ _d.label = 3;
382
516
  case 3:
383
- _a.trys.push([3, 5, , 6]);
384
- return [4 /*yield*/, handler({
385
- req: req,
386
- ctx: this.ctx,
387
- origin: routeProps.origin,
388
- })];
517
+ _d.trys.push([3, 5, , 6]);
518
+ flinkReq_1 = req;
519
+ return [4 /*yield*/, FlinkRequestContext_1.requestContext.run({
520
+ reqId: flinkReq_1.reqId,
521
+ user: flinkReq_1.user,
522
+ userPermissions: flinkReq_1.userPermissions,
523
+ method: method,
524
+ path: routeProps.path,
525
+ timestamp: Date.now(),
526
+ isStreaming: !!streamFormat,
527
+ }, function () { return __awaiter(_this, void 0, void 0, function () {
528
+ return __generator(this, function (_a) {
529
+ switch (_a.label) {
530
+ case 0: return [4 /*yield*/, handler({
531
+ req: flinkReq_1,
532
+ ctx: this.ctx,
533
+ origin: routeProps.origin,
534
+ stream: stream,
535
+ })];
536
+ case 1: return [2 /*return*/, _a.sent()];
537
+ }
538
+ });
539
+ }); })];
389
540
  case 4:
390
- // 👇 This is where the actual handler gets invoked
391
- handlerRes = _a.sent();
541
+ handlerRes = _d.sent();
392
542
  return [3 /*break*/, 6];
393
543
  case 5:
394
- err_1 = _a.sent();
544
+ err_1 = _d.sent();
545
+ // Handle errors for streaming handlers
546
+ if (streamFormat && stream) {
547
+ FlinkLog_1.log.error("Streaming handler error on ".concat(req.method.toUpperCase(), " ").concat(req.path, ": ").concat(err_1.message), {
548
+ error: err_1,
549
+ path: req.path,
550
+ method: req.method,
551
+ });
552
+ stream.error(err_1);
553
+ return [2 /*return*/];
554
+ }
395
555
  errorResponse = void 0;
396
556
  // duck typing to check if it is a FlinkError
397
557
  if (typeof err_1.status === "number" && err_1.status >= 400 && err_1.status < 600 && err_1.error) {
@@ -410,44 +570,64 @@ var FlinkApp = /** @class */ (function () {
410
570
  console.error(err_1);
411
571
  errorResponse = (0, FlinkErrors_1.internalServerError)(err_1);
412
572
  }
413
- // Invoke onError callback if provided
414
- if (this.onError) {
415
- try {
416
- result = this.onError(errorResponse, {
417
- req: req,
418
- method: method,
419
- path: routeProps.path,
420
- reqId: req.reqId,
421
- });
422
- // Handle async callbacks - don't wait for them
423
- if (result instanceof Promise) {
424
- result.catch(function (callbackErr) {
425
- FlinkLog_1.log.error("onError callback rejected with: ".concat(callbackErr));
426
- });
427
- }
428
- }
429
- catch (callbackErr) {
430
- FlinkLog_1.log.error("onError callback threw an exception: ".concat(callbackErr));
431
- }
432
- }
573
+ this.invokeOnError(errorResponse, req, method, routeProps.path);
433
574
  return [2 /*return*/, res.status(errorResponse.status || 500).json(errorResponse)];
434
575
  case 6:
576
+ // Skip response handling for streaming handlers (stream controls response lifecycle)
577
+ if (streamFormat) {
578
+ return [2 /*return*/];
579
+ }
580
+ // Ensure handlerRes is defined for non-streaming handlers
581
+ if (!handlerRes) {
582
+ return [2 /*return*/, res.status(204).send()];
583
+ }
435
584
  if (validateRes_1 && !(0, utils_1.isError)(handlerRes)) {
436
- valid = validateRes_1(JSON.parse(JSON.stringify(handlerRes.data)));
437
- if (!valid) {
438
- formattedErrors = (0, utils_1.formatValidationErrors)(validateRes_1.errors, handlerRes.data);
439
- FlinkLog_1.log.warn("[".concat(req.reqId, "] ").concat(methodAndRoute_1, ": Bad response\n").concat(formattedErrors));
440
- return [2 /*return*/, res.status(500).json({
585
+ if (handlerRes.data === undefined) {
586
+ if (handlerRes.status !== 204) {
587
+ detail = "Response schema is defined but handler returned no data";
588
+ FlinkLog_1.log.warn("[".concat(req.reqId, "] ").concat(methodAndRoute_1, ": Bad response - ").concat(detail));
589
+ errorResponse = {
590
+ status: 500,
591
+ error: { id: (0, uuid_1.v4)(), title: "Bad response", detail: detail },
592
+ };
593
+ this.invokeOnError(errorResponse, req, method, routeProps.path);
594
+ return [2 /*return*/, res.status(500).json(errorResponse)];
595
+ }
596
+ }
597
+ else {
598
+ valid = validateRes_1(JSON.parse(JSON.stringify(handlerRes.data)));
599
+ if (!valid) {
600
+ formattedErrors = (0, utils_1.formatValidationErrors)(validateRes_1.errors, handlerRes.data);
601
+ FlinkLog_1.log.warn("[".concat(req.reqId, "] ").concat(methodAndRoute_1, ": Bad response\n").concat(formattedErrors));
602
+ errorResponse = {
441
603
  status: 500,
442
604
  error: {
443
605
  id: (0, uuid_1.v4)(),
444
606
  title: "Bad response",
445
607
  detail: formattedErrors,
446
608
  },
447
- })];
609
+ };
610
+ this.invokeOnError(errorResponse, req, method, routeProps.path);
611
+ return [2 /*return*/, res.status(500).json(errorResponse)];
612
+ }
448
613
  }
449
614
  }
450
615
  res.set(handlerRes.headers);
616
+ if (routeProps.responseType) {
617
+ return [2 /*return*/, res
618
+ .status(handlerRes.status || 200)
619
+ .type(routeProps.responseType)
620
+ .send(handlerRes.data)];
621
+ }
622
+ if (((_c = handlerRes.error) === null || _c === void 0 ? void 0 : _c.meta) !== undefined) {
623
+ try {
624
+ JSON.stringify(handlerRes.error.meta);
625
+ }
626
+ catch (e) {
627
+ FlinkLog_1.log.warn("[".concat(handlerRes.reqId, "] error.meta stripped from error ").concat(handlerRes.error.id, ": not JSON-serializable (").concat(e.message, ")"));
628
+ delete handlerRes.error.meta;
629
+ }
630
+ }
451
631
  res.status(handlerRes.status || 200).json(handlerRes);
452
632
  return [2 /*return*/];
453
633
  }
@@ -459,9 +639,174 @@ var FlinkApp = /** @class */ (function () {
459
639
  }
460
640
  else {
461
641
  this.handlerRouteCache.set(methodAndRoute_1, JSON.stringify(routeProps));
462
- FlinkLog_1.log.info("Registered route ".concat(methodAndRoute_1));
642
+ initLog.info("Registered ".concat(streamFormat ? "streaming " : "", "route ").concat(methodAndRoute_1).concat(streamFormat ? " (".concat(streamFormat, ")") : ""));
643
+ }
644
+ }
645
+ };
646
+ /**
647
+ * Load schema manifest from .flink directory.
648
+ * Returns empty structure if manifest doesn't exist (dev mode without build).
649
+ *
650
+ * The manifest contains:
651
+ * - definitions: ALL JSON Schema type definitions (supports $ref resolution)
652
+ * - handlers: Handler metadata with schema names (references to definitions)
653
+ * - tools: Tool metadata with schema names (references to definitions)
654
+ */
655
+ FlinkApp.prototype.loadSchemaManifest = function () {
656
+ // Return cached manifest if already loaded
657
+ if (this.schemaManifest) {
658
+ return this.schemaManifest;
659
+ }
660
+ var fs = require("fs");
661
+ var path = require("path");
662
+ var manifestPath = path.join(process.cwd(), "dist/.flink/schema-manifest.json");
663
+ if (!fs.existsSync(manifestPath)) {
664
+ FlinkLog_1.log.warn("Schema manifest not found at dist/.flink/schema-manifest.json - handlers/tools may not have validation schemas");
665
+ var emptyManifest = { definitions: {}, handlers: {}, tools: {} };
666
+ this.schemaManifest = emptyManifest;
667
+ return emptyManifest;
668
+ }
669
+ try {
670
+ var manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
671
+ // Check version for backward compatibility
672
+ if (manifest.version === "2.0") {
673
+ // New format with schema universe and AJV global registry
674
+ this.schemaManifest = manifest;
675
+ // Initialize AJV and register all schemas
676
+ this.schemaAjv = new ajv_1.default({
677
+ strict: false, // Allow additional properties by default
678
+ allErrors: true, // Return all validation errors, not just first
679
+ });
680
+ (0, ajv_formats_1.default)(this.schemaAjv);
681
+ // Register all schemas in the universe
682
+ var schemas = manifest.schemas || {};
683
+ for (var _i = 0, _a = Object.values(schemas); _i < _a.length; _i++) {
684
+ var schema = _a[_i];
685
+ if (schema && typeof schema === "object" && schema.$id) {
686
+ // Skip schemas with unresolved generic type parameters (T, U, V, etc.)
687
+ if (this.hasUnresolvedTypeParams(schema, Object.keys(schemas))) {
688
+ FlinkLog_1.log.debug("Skipping registration of generic schema: ".concat(schema.$id));
689
+ continue;
690
+ }
691
+ this.schemaAjv.addSchema(schema);
692
+ }
693
+ }
694
+ FlinkLog_1.log.debug("Loaded schema manifest v2.0: ".concat(Object.keys(schemas).length, " schemas, ").concat(Object.keys(manifest.handlers).length, " handlers, ").concat(Object.keys(manifest.tools).length, " tools"));
695
+ return manifest;
696
+ }
697
+ else {
698
+ // Old format (v1.0) - still supported for migration
699
+ FlinkLog_1.log.debug("Loaded schema manifest v1.0: ".concat(Object.keys(manifest.definitions || {}).length, " definitions, ").concat(Object.keys(manifest.handlers).length, " handlers, ").concat(Object.keys(manifest.tools).length, " tools"));
700
+ this.schemaManifest = manifest;
701
+ return manifest;
702
+ }
703
+ }
704
+ catch (error) {
705
+ FlinkLog_1.log.error("Failed to parse schema manifest:", error);
706
+ var errorManifest = { definitions: {}, handlers: {}, tools: {} };
707
+ this.schemaManifest = errorManifest;
708
+ return errorManifest;
709
+ }
710
+ };
711
+ /**
712
+ * Get the AJV instance for validation (v2.0 manifests).
713
+ * Returns undefined for v1.0 manifests.
714
+ */
715
+ FlinkApp.prototype.getSchemaAjv = function () {
716
+ return this.schemaAjv;
717
+ };
718
+ /**
719
+ * Register plugin schemas into the app's AJV instance.
720
+ * Schema $id values are prefixed with `pluginId::` to avoid collisions
721
+ * with the app's own schemas or other plugins.
722
+ *
723
+ * @param pluginId Unique identifier for the plugin (used as namespace prefix)
724
+ * @param schemas Record of schema name to JSON Schema object (already prefixed)
725
+ */
726
+ FlinkApp.prototype.registerSchemas = function (pluginId, schemas) {
727
+ // Ensure schema manifest and AJV are initialized
728
+ this.loadSchemaManifest();
729
+ if (!this.schemaAjv) {
730
+ // If no v2.0 manifest exists, create a fresh AJV instance
731
+ this.schemaAjv = new ajv_1.default({
732
+ strict: false,
733
+ allErrors: true,
734
+ });
735
+ (0, ajv_formats_1.default)(this.schemaAjv);
736
+ }
737
+ for (var _i = 0, _a = Object.values(schemas); _i < _a.length; _i++) {
738
+ var schema = _a[_i];
739
+ if (schema && typeof schema === "object" && schema.$id) {
740
+ // Skip if already registered
741
+ if (this.schemaAjv.getSchema(schema.$id)) {
742
+ continue;
743
+ }
744
+ try {
745
+ this.schemaAjv.addSchema(schema);
746
+ }
747
+ catch (err) {
748
+ FlinkLog_1.log.warn("Failed to register plugin schema ".concat(schema.$id, ": ").concat(err));
749
+ }
463
750
  }
464
751
  }
752
+ FlinkLog_1.log.debug("Registered ".concat(Object.keys(schemas).length, " schemas from plugin '").concat(pluginId, "'"));
753
+ };
754
+ /**
755
+ * Check if a schema has unresolved generic type parameter references.
756
+ * Generic type parameters like T, U, V are single uppercase letters.
757
+ *
758
+ * @param schema JSON schema object
759
+ * @param registeredSchemaIds List of schema IDs in the manifest
760
+ * @returns true if schema references generic type params that don't exist
761
+ */
762
+ FlinkApp.prototype.hasUnresolvedTypeParams = function (schema, registeredSchemaIds) {
763
+ var schemaIdSet = new Set(registeredSchemaIds);
764
+ var checkRefs = function (obj) {
765
+ if (!obj || typeof obj !== "object") {
766
+ return false;
767
+ }
768
+ if (Array.isArray(obj)) {
769
+ return obj.some(function (item) { return checkRefs(item); });
770
+ }
771
+ for (var _i = 0, _a = Object.entries(obj); _i < _a.length; _i++) {
772
+ var _b = _a[_i], key = _b[0], value = _b[1];
773
+ if (key === "$ref" && typeof value === "string") {
774
+ // Check if ref is a single uppercase letter (generic type param)
775
+ // and not in the registered schema IDs
776
+ if (/^[A-Z]$/.test(value) && !schemaIdSet.has(value)) {
777
+ return true;
778
+ }
779
+ }
780
+ else if (typeof value === "object") {
781
+ if (checkRefs(value)) {
782
+ return true;
783
+ }
784
+ }
785
+ }
786
+ return false;
787
+ };
788
+ return checkRefs(schema);
789
+ };
790
+ /**
791
+ * Resolve schema by name from manifest.
792
+ * Works with both v1.0 (definitions) and v2.0 (schema universe) formats.
793
+ *
794
+ * @param schemaName Schema name or $id
795
+ * @returns JSON schema object or undefined
796
+ */
797
+ FlinkApp.prototype.resolveSchema = function (schemaName) {
798
+ if (!schemaName)
799
+ return undefined;
800
+ var manifest = this.loadSchemaManifest();
801
+ // v2.0 manifest - return schema from universe
802
+ if (manifest.version === "2.0" && manifest.schemas) {
803
+ return manifest.schemas[schemaName];
804
+ }
805
+ // v1.0 manifest - return from definitions
806
+ if (manifest.definitions) {
807
+ return manifest.definitions[schemaName];
808
+ }
809
+ return undefined;
465
810
  };
466
811
  /**
467
812
  * Register handlers found within the `/src/handlers`
@@ -471,47 +816,77 @@ var FlinkApp = /** @class */ (function () {
471
816
  */
472
817
  FlinkApp.prototype.registerAutoRegisterableHandlers = function () {
473
818
  return __awaiter(this, void 0, void 0, function () {
474
- var _i, _a, _b, handler, assumedHttpMethod, pathParams, _c, _d, param;
475
- var _e, _f, _g;
476
- return __generator(this, function (_h) {
477
- 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++) {
478
- _b = _a[_i], handler = _b.handler, assumedHttpMethod = _b.assumedHttpMethod;
819
+ var schemaManifest, schemaCount, _i, _a, _b, handler, assumedHttpMethod, __file, metadata, reqSchema, resSchema, pathParams, _c, _d, param;
820
+ var _e;
821
+ return __generator(this, function (_f) {
822
+ schemaManifest = this.loadSchemaManifest();
823
+ schemaCount = schemaManifest.version === "2.0" ? Object.keys(schemaManifest.schemas || {}).length : Object.keys(schemaManifest.definitions || {}).length;
824
+ FlinkLog_1.log.debug("Registering ".concat(schemaCount, " schemas with AJV (manifest version: ").concat(schemaManifest.version || "1.0", ")"));
825
+ for (_i = 0, _a = exports.autoRegisteredHandlers.sort(function (a, b) {
826
+ var _a, _b, _c, _d, _e, _f;
827
+ 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);
828
+ if (orderDiff !== 0)
829
+ return orderDiff;
830
+ // Static segments must be registered before parameterized ones to avoid
831
+ // Express matching e.g. GET /jobs/by-tags with the /jobs/:id route.
832
+ 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;
833
+ 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;
834
+ return aHasParam - bHasParam;
835
+ }); _i < _a.length; _i++) {
836
+ _b = _a[_i], handler = _b.handler, assumedHttpMethod = _b.assumedHttpMethod, __file = _b.__file;
479
837
  if (!handler.Route) {
480
- FlinkLog_1.log.error("Missing Props in handler ".concat(handler.__file));
838
+ FlinkLog_1.log.error("Missing Props in handler ".concat(__file));
481
839
  continue;
482
840
  }
483
841
  if (!handler.default) {
484
- FlinkLog_1.log.error("Missing exported handler function in handler ".concat(handler.__file));
842
+ FlinkLog_1.log.error("Missing exported handler function in handler ".concat(__file));
485
843
  continue;
486
844
  }
487
- if (!!((_e = handler.__params) === null || _e === void 0 ? void 0 : _e.length)) {
845
+ metadata = schemaManifest.handlers[__file || ""];
846
+ reqSchema = handler.Route.reqSchema || this.resolveSchema(metadata === null || metadata === void 0 ? void 0 : metadata.reqSchemaName);
847
+ resSchema = handler.Route.resSchema || this.resolveSchema(metadata === null || metadata === void 0 ? void 0 : metadata.resSchemaName);
848
+ // Validation warnings
849
+ if (!metadata &&
850
+ (handler.Route.validation === FlinkHttpHandler_1.ValidationMode.Validate ||
851
+ handler.Route.validation === FlinkHttpHandler_1.ValidationMode.ValidateRequest ||
852
+ handler.Route.validation === FlinkHttpHandler_1.ValidationMode.ValidateResponse)) {
853
+ FlinkLog_1.log.warn("Handler ".concat(__file, " expects validation but no metadata found in manifest"));
854
+ }
855
+ // Warn if schema name doesn't resolve
856
+ if ((metadata === null || metadata === void 0 ? void 0 : metadata.reqSchemaName) && !reqSchema) {
857
+ FlinkLog_1.log.warn("Handler ".concat(__file, " references reqSchema \"").concat(metadata.reqSchemaName, "\" but not found in schema universe"));
858
+ }
859
+ if ((metadata === null || metadata === void 0 ? void 0 : metadata.resSchemaName) && !resSchema) {
860
+ FlinkLog_1.log.warn("Handler ".concat(__file, " references resSchema \"").concat(metadata.resSchemaName, "\" but not found in schema universe"));
861
+ }
862
+ if (!!((_e = metadata === null || metadata === void 0 ? void 0 : metadata.paramsMetadata) === null || _e === void 0 ? void 0 : _e.length)) {
488
863
  pathParams = (0, utils_1.getPathParams)(handler.Route.path);
489
- for (_c = 0, _d = handler.__params; _c < _d.length; _c++) {
864
+ for (_c = 0, _d = metadata.paramsMetadata; _c < _d.length; _c++) {
490
865
  param = _d[_c];
491
866
  if (!pathParams.includes(param.name)) {
492
- FlinkLog_1.log.error("Handler ".concat(handler.__file, " has param ").concat(param.name, " but it is not present in the path '").concat(handler.Route.path, "'"));
867
+ FlinkLog_1.log.error("Handler ".concat(__file, " has param ").concat(param.name, " but it is not present in the path '").concat(handler.Route.path, "'"));
493
868
  throw new Error("Invalid/missing handler path param");
494
869
  }
495
870
  }
496
- if (pathParams.length !== handler.__params.length) {
497
- FlinkLog_1.log.warn("Handler ".concat(handler.__file, " has ").concat(handler.__params.length, " typed params but the path '").concat(handler.Route.path, "' has ").concat(pathParams.length, " params"));
871
+ if (pathParams.length !== metadata.paramsMetadata.length) {
872
+ FlinkLog_1.log.warn("Handler ".concat(__file, " has ").concat(metadata.paramsMetadata.length, " typed params but the path '").concat(handler.Route.path, "' has ").concat(pathParams.length, " params"));
498
873
  }
499
874
  }
500
875
  this.registerHandler({
501
- routeProps: __assign(__assign({}, handler.Route), { method: handler.Route.method || assumedHttpMethod, origin: this.name }),
876
+ routeProps: __assign(__assign({}, handler.Route), { method: handler.Route.method || assumedHttpMethod || (metadata === null || metadata === void 0 ? void 0 : metadata.assumedMethod), origin: this.name }),
502
877
  schema: {
503
- reqSchema: (_f = handler.__schemas) === null || _f === void 0 ? void 0 : _f.reqSchema,
504
- resSchema: (_g = handler.__schemas) === null || _g === void 0 ? void 0 : _g.resSchema,
878
+ reqSchema: reqSchema,
879
+ resSchema: resSchema,
505
880
  },
506
- queryMetadata: handler.__query || [],
507
- paramsMetadata: handler.__params || [],
881
+ queryMetadata: (metadata === null || metadata === void 0 ? void 0 : metadata.queryMetadata) || [],
882
+ paramsMetadata: (metadata === null || metadata === void 0 ? void 0 : metadata.paramsMetadata) || [],
508
883
  }, handler.default);
509
884
  }
510
885
  return [2 /*return*/];
511
886
  });
512
887
  });
513
888
  };
514
- FlinkApp.prototype.registerAutoRegisterableJobs = function () {
889
+ FlinkApp.prototype.registerAutoRegisterableJobs = function (filter) {
515
890
  return __awaiter(this, void 0, void 0, function () {
516
891
  var _loop_1, this_1, _i, autoRegisteredJobs_1, _a, jobProps, jobFn, __file;
517
892
  var _this = this;
@@ -520,30 +895,33 @@ var FlinkApp = /** @class */ (function () {
520
895
  throw new Error("Scheduler not initialized"); // should never happen
521
896
  }
522
897
  _loop_1 = function (jobProps, jobFn, __file) {
898
+ if (filter && !filter(jobProps)) {
899
+ return "continue";
900
+ }
523
901
  if (jobProps.cron && jobProps.interval) {
524
- FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - both cron and interval are set in ").concat(__file));
902
+ schedulerLog.error("Cannot register job ".concat(jobProps.id, " - both cron and interval are set in ").concat(__file));
525
903
  return "continue";
526
904
  }
527
905
  if (jobProps.cron && jobProps.afterDelay) {
528
- FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - both cron and afterDelay are set in ").concat(__file));
906
+ schedulerLog.error("Cannot register job ".concat(jobProps.id, " - both cron and afterDelay are set in ").concat(__file));
529
907
  return "continue";
530
908
  }
531
909
  if (jobProps.interval && jobProps.afterDelay) {
532
- FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - both interval and afterDelay are set in ").concat(__file));
910
+ schedulerLog.error("Cannot register job ".concat(jobProps.id, " - both interval and afterDelay are set in ").concat(__file));
533
911
  return "continue";
534
912
  }
535
913
  if (this_1.scheduler.existsById(jobProps.id)) {
536
- FlinkLog_1.log.error("Job with id ".concat(jobProps.id, " is already registered, found duplicate in ").concat(__file));
914
+ schedulerLog.error("Job with id ".concat(jobProps.id, " is already registered, found duplicate in ").concat(__file));
537
915
  return "continue";
538
916
  }
539
- FlinkLog_1.log.debug("Registering job ".concat(jobProps.id, ": ").concat(JSON.stringify(jobProps), " from ").concat(__file));
917
+ schedulerLog.debug("Registering job ".concat(jobProps.id, ": ").concat(JSON.stringify(jobProps), " from ").concat(__file));
540
918
  var task = new toad_scheduler_1.AsyncTask(jobProps.id, function () { return __awaiter(_this, void 0, void 0, function () {
541
919
  return __generator(this, function (_a) {
542
920
  switch (_a.label) {
543
921
  case 0: return [4 /*yield*/, jobFn({ ctx: this.ctx })];
544
922
  case 1:
545
923
  _a.sent();
546
- FlinkLog_1.log.debug("Job ".concat(jobProps.id, " completed"));
924
+ schedulerLog.debug("Job ".concat(jobProps.id, " completed"));
547
925
  if (jobProps.afterDelay) {
548
926
  // afterDelay runs only once, so we remove the job
549
927
  this.scheduler.removeById(jobProps.id);
@@ -552,7 +930,7 @@ var FlinkApp = /** @class */ (function () {
552
930
  }
553
931
  });
554
932
  }); }, function (err) {
555
- FlinkLog_1.log.error("Job ".concat(jobProps.id, " threw unhandled exception ").concat(err));
933
+ schedulerLog.error("Job ".concat(jobProps.id, " threw unhandled exception ").concat(err));
556
934
  console.error(err);
557
935
  });
558
936
  if (jobProps.cron) {
@@ -573,17 +951,41 @@ var FlinkApp = /** @class */ (function () {
573
951
  this_1.scheduler.addSimpleIntervalJob(job);
574
952
  }
575
953
  else if (jobProps.afterDelay !== undefined) {
576
- var job = new toad_scheduler_1.SimpleIntervalJob({
577
- milliseconds: (0, ms_1.default)(jobProps.afterDelay),
578
- runImmediately: false,
579
- }, task, {
580
- id: jobProps.id,
581
- preventOverrun: jobProps.singleton,
582
- });
583
- this_1.scheduler.addSimpleIntervalJob(job);
954
+ var delayMs = (0, ms_1.default)(jobProps.afterDelay);
955
+ if (delayMs === 0) {
956
+ setImmediate(function () { return __awaiter(_this, void 0, void 0, function () {
957
+ var err_2;
958
+ return __generator(this, function (_a) {
959
+ switch (_a.label) {
960
+ case 0:
961
+ _a.trys.push([0, 2, , 3]);
962
+ return [4 /*yield*/, jobFn({ ctx: this.ctx })];
963
+ case 1:
964
+ _a.sent();
965
+ return [3 /*break*/, 3];
966
+ case 2:
967
+ err_2 = _a.sent();
968
+ schedulerLog.error("Job ".concat(jobProps.id, " threw unhandled exception ").concat(err_2));
969
+ console.error(err_2);
970
+ return [3 /*break*/, 3];
971
+ case 3: return [2 /*return*/];
972
+ }
973
+ });
974
+ }); });
975
+ }
976
+ else {
977
+ var job = new toad_scheduler_1.SimpleIntervalJob({
978
+ milliseconds: delayMs,
979
+ runImmediately: false,
980
+ }, task, {
981
+ id: jobProps.id,
982
+ preventOverrun: jobProps.singleton,
983
+ });
984
+ this_1.scheduler.addSimpleIntervalJob(job);
985
+ }
584
986
  }
585
987
  else {
586
- FlinkLog_1.log.error("Cannot register job ".concat(jobProps.id, " - no cron, interval or once set in ").concat(__file));
988
+ schedulerLog.error("Cannot register job ".concat(jobProps.id, " - no cron, interval or once set in ").concat(__file));
587
989
  return "continue";
588
990
  }
589
991
  };
@@ -601,40 +1003,201 @@ var FlinkApp = /** @class */ (function () {
601
1003
  // TODO: Find out if we need to set ctx here or wanted not to if plugin has its own context
602
1004
  // repoInstance.ctx = this.ctx;
603
1005
  };
1006
+ FlinkApp.prototype.registerAutoRegisterableTools = function () {
1007
+ return __awaiter(this, void 0, void 0, function () {
1008
+ var ToolExecutor, getRepoInstanceName, schemaManifest, _i, autoRegisteredTools_1, toolFile, toolId, toolInstanceName, metadata, schemas, allSchemas, toolExecutor;
1009
+ return __generator(this, function (_a) {
1010
+ ToolExecutor = require("./ai/ToolExecutor").ToolExecutor;
1011
+ getRepoInstanceName = require("./utils").getRepoInstanceName;
1012
+ schemaManifest = this.loadSchemaManifest();
1013
+ for (_i = 0, autoRegisteredTools_1 = exports.autoRegisteredTools; _i < autoRegisteredTools_1.length; _i++) {
1014
+ toolFile = autoRegisteredTools_1[_i];
1015
+ if (!toolFile.Tool) {
1016
+ FlinkLog_1.log.error("Missing FlinkToolProps export in tool ".concat(toolFile.__file));
1017
+ continue;
1018
+ }
1019
+ if (!toolFile.default) {
1020
+ FlinkLog_1.log.error("Missing exported tool function in tool ".concat(toolFile.__file));
1021
+ continue;
1022
+ }
1023
+ toolId = toolFile.Tool.id;
1024
+ if (!toolId) {
1025
+ FlinkLog_1.log.error("Tool ".concat(toolFile.__file, " missing 'id' property"));
1026
+ continue;
1027
+ }
1028
+ toolInstanceName = getRepoInstanceName(toolId);
1029
+ metadata = schemaManifest.tools[toolFile.__file || ""];
1030
+ schemas = metadata
1031
+ ? {
1032
+ inputSchema: this.resolveSchema(metadata.inputSchemaName),
1033
+ outputSchema: this.resolveSchema(metadata.outputSchemaName),
1034
+ inputTypeHint: metadata.inputTypeHint,
1035
+ outputTypeHint: metadata.outputTypeHint,
1036
+ }
1037
+ : undefined;
1038
+ // Warn if schema name doesn't resolve
1039
+ if ((metadata === null || metadata === void 0 ? void 0 : metadata.inputSchemaName) && !(schemas === null || schemas === void 0 ? void 0 : schemas.inputSchema)) {
1040
+ FlinkLog_1.log.warn("Tool ".concat(toolFile.__file, " references inputSchema \"").concat(metadata.inputSchemaName, "\" but not found in schema universe"));
1041
+ }
1042
+ if ((metadata === null || metadata === void 0 ? void 0 : metadata.outputSchemaName) && !(schemas === null || schemas === void 0 ? void 0 : schemas.outputSchema)) {
1043
+ FlinkLog_1.log.warn("Tool ".concat(toolFile.__file, " references outputSchema \"").concat(metadata.outputSchemaName, "\" but not found in schema universe"));
1044
+ }
1045
+ allSchemas = schemaManifest.version === "2.0" ? schemaManifest.schemas : schemaManifest.definitions;
1046
+ toolExecutor = new ToolExecutor(toolFile.Tool, toolFile.default, this.ctx, schemas, // Auto-generated schemas from manifest (resolved from definitions)
1047
+ allSchemas);
1048
+ this.tools[toolInstanceName] = toolExecutor;
1049
+ initLog.info("Registered tool ".concat(toolInstanceName, " (").concat(toolId, ")"));
1050
+ }
1051
+ return [2 /*return*/];
1052
+ });
1053
+ });
1054
+ };
1055
+ FlinkApp.prototype.registerAutoRegisterableAgents = function () {
1056
+ return __awaiter(this, void 0, void 0, function () {
1057
+ var _a, getRepoInstanceName, toKebabCase, _loop_2, this_2, _i, autoRegisteredAgents_1, agentFile;
1058
+ return __generator(this, function (_b) {
1059
+ _a = require("./utils"), getRepoInstanceName = _a.getRepoInstanceName, toKebabCase = _a.toKebabCase;
1060
+ _loop_2 = function (agentFile) {
1061
+ // agentFile now exports a class, not a config object
1062
+ var AgentClass = agentFile.default;
1063
+ if (!AgentClass) {
1064
+ FlinkLog_1.log.error("Missing default export in agent ".concat(agentFile.__file));
1065
+ return "continue";
1066
+ }
1067
+ // Instantiate agent (similar to repo instantiation)
1068
+ var agentInstance = new AgentClass();
1069
+ // Derive instance name from class name (camelCase)
1070
+ var agentInstanceName = getRepoInstanceName(AgentClass.name);
1071
+ // Get agent ID (kebab-case) - either explicit or derived
1072
+ var agentId = agentInstance.id;
1073
+ // Check for duplicate instance name
1074
+ if (this_2.agents[agentInstanceName]) {
1075
+ var existingAgent = this_2.agents[agentInstanceName];
1076
+ throw new Error("Duplicate agent instance name: \"".concat(agentInstanceName, "\". ") +
1077
+ "Agent class \"".concat(AgentClass.name, "\" conflicts with existing agent \"").concat(existingAgent.constructor.name, "\". ") +
1078
+ "Instance names are derived by lowercasing the first letter of the class name. " +
1079
+ "Rename one of the classes or use a unique explicit 'id' property.");
1080
+ }
1081
+ // Check for duplicate agent ID
1082
+ var existingAgentWithSameId = Object.values(this_2.agents).find(function (agent) { return agent.id === agentId; });
1083
+ if (existingAgentWithSameId) {
1084
+ throw new Error("Duplicate agent ID: \"".concat(agentId, "\". ") +
1085
+ "Agent class \"".concat(AgentClass.name, "\" conflicts with existing agent \"").concat(existingAgentWithSameId.constructor.name, "\". ") +
1086
+ "Change the 'id' property on one of them to resolve the conflict.");
1087
+ }
1088
+ // Validate tools exist
1089
+ if (agentInstance.tools) {
1090
+ for (var _c = 0, _d = agentInstance.tools; _c < _d.length; _c++) {
1091
+ var toolRef = _d[_c];
1092
+ // Handle string IDs, tool file references, and tool props
1093
+ var toolId = void 0;
1094
+ if (typeof toolRef === "string") {
1095
+ toolId = toolRef;
1096
+ }
1097
+ else if ("Tool" in toolRef) {
1098
+ // FlinkToolFile - extract ID from Tool property
1099
+ toolId = toolRef.Tool.id;
1100
+ }
1101
+ else {
1102
+ // FlinkToolProps - extract ID directly
1103
+ toolId = toolRef.id;
1104
+ }
1105
+ var tool = this_2.tools[toolId];
1106
+ if (!tool) {
1107
+ FlinkLog_1.log.error("Agent ".concat(AgentClass.name, " references tool ").concat(toolId, " which is not registered"));
1108
+ throw new Error("Invalid tool reference in agent ".concat(AgentClass.name));
1109
+ }
1110
+ }
1111
+ }
1112
+ // Register agent (duplicate checks already performed above)
1113
+ this_2.agents[agentInstanceName] = agentInstance;
1114
+ initLog.info("Registered agent ".concat(agentInstanceName, " (").concat(AgentClass.name, ") with ID: ").concat(agentId));
1115
+ };
1116
+ this_2 = this;
1117
+ for (_i = 0, autoRegisteredAgents_1 = exports.autoRegisteredAgents; _i < autoRegisteredAgents_1.length; _i++) {
1118
+ agentFile = autoRegisteredAgents_1[_i];
1119
+ _loop_2(agentFile);
1120
+ }
1121
+ return [2 /*return*/];
1122
+ });
1123
+ });
1124
+ };
604
1125
  /**
605
1126
  * Constructs the app context. Will inject context in all components
606
1127
  * except for handlers which are handled in later stage.
607
1128
  */
608
1129
  FlinkApp.prototype.buildContext = function () {
609
1130
  return __awaiter(this, void 0, void 0, function () {
610
- var _i, autoRegisteredRepos_1, _a, collectionName, repoInstanceName, Repo, repoInstance, pluginCtx, _b, _c, repo;
611
- return __generator(this, function (_d) {
612
- if (this.dbOpts) {
613
- for (_i = 0, autoRegisteredRepos_1 = exports.autoRegisteredRepos; _i < autoRegisteredRepos_1.length; _i++) {
614
- _a = autoRegisteredRepos_1[_i], collectionName = _a.collectionName, repoInstanceName = _a.repoInstanceName, Repo = _a.Repo;
615
- repoInstance = new Repo(collectionName, this.db, this.dbClient);
616
- this.repos[repoInstanceName] = repoInstance;
617
- FlinkLog_1.log.info("Registered repo ".concat(repoInstanceName));
618
- }
619
- }
620
- else if (exports.autoRegisteredRepos.length > 0) {
621
- FlinkLog_1.log.warn("No db configured but found repo(s)");
1131
+ var _i, autoRegisteredRepos_1, _a, collectionName, repoInstanceName, Repo, repoInstance, pluginCtx, _b, autoRegisteredServices_1, _c, serviceInstanceName, Service, serviceInstance, _d, _e, repo, _f, _g, service, servicesWithInit;
1132
+ return __generator(this, function (_h) {
1133
+ switch (_h.label) {
1134
+ case 0:
1135
+ if (this.dbOpts) {
1136
+ for (_i = 0, autoRegisteredRepos_1 = exports.autoRegisteredRepos; _i < autoRegisteredRepos_1.length; _i++) {
1137
+ _a = autoRegisteredRepos_1[_i], collectionName = _a.collectionName, repoInstanceName = _a.repoInstanceName, Repo = _a.Repo;
1138
+ repoInstance = new Repo(collectionName, this.db, this.dbClient);
1139
+ this.repos[repoInstanceName] = repoInstance;
1140
+ initLog.info("Registered repo ".concat(repoInstanceName));
1141
+ }
1142
+ }
1143
+ else if (exports.autoRegisteredRepos.length > 0) {
1144
+ FlinkLog_1.log.warn("No db configured but found repo(s)");
1145
+ }
1146
+ pluginCtx = this.plugins.reduce(function (out, plugin) {
1147
+ if (out[plugin.id]) {
1148
+ throw new Error("Plugin ".concat(plugin.id, " is already registered"));
1149
+ }
1150
+ out[plugin.id] = plugin.ctx;
1151
+ return out;
1152
+ }, {});
1153
+ // Instantiate services (ctx not yet available - constructors must not access it)
1154
+ for (_b = 0, autoRegisteredServices_1 = exports.autoRegisteredServices; _b < autoRegisteredServices_1.length; _b++) {
1155
+ _c = autoRegisteredServices_1[_b], serviceInstanceName = _c.serviceInstanceName, Service = _c.Service;
1156
+ serviceInstance = new Service();
1157
+ this.services[serviceInstanceName] = serviceInstance;
1158
+ initLog.info("Registered service ".concat(serviceInstanceName));
1159
+ }
1160
+ this._ctx = {
1161
+ repos: this.repos,
1162
+ plugins: pluginCtx,
1163
+ auth: this.auth,
1164
+ agents: this.agents,
1165
+ services: this.services,
1166
+ };
1167
+ // Inject context into repos
1168
+ for (_d = 0, _e = Object.values(this.repos); _d < _e.length; _d++) {
1169
+ repo = _e[_d];
1170
+ repo.ctx = this.ctx;
1171
+ }
1172
+ // Inject context into services, then call onInit() in parallel
1173
+ for (_f = 0, _g = Object.values(this.services); _f < _g.length; _f++) {
1174
+ service = _g[_f];
1175
+ service.ctx = this.ctx;
1176
+ }
1177
+ servicesWithInit = Object.values(this.services).filter(function (s) { return typeof s.onInit === "function"; });
1178
+ if (!(servicesWithInit.length > 0)) return [3 /*break*/, 2];
1179
+ return [4 /*yield*/, Promise.all(servicesWithInit.map(function (s) { return s.onInit(); }))];
1180
+ case 1:
1181
+ _h.sent();
1182
+ _h.label = 2;
1183
+ case 2: return [2 /*return*/];
622
1184
  }
623
- pluginCtx = this.plugins.reduce(function (out, plugin) {
624
- if (out[plugin.id]) {
625
- throw new Error("Plugin ".concat(plugin.id, " is already registered"));
626
- }
627
- out[plugin.id] = plugin.ctx;
628
- return out;
629
- }, {});
630
- this._ctx = {
631
- repos: this.repos,
632
- plugins: pluginCtx,
633
- auth: this.auth,
634
- };
635
- for (_b = 0, _c = Object.values(this.repos); _b < _c.length; _b++) {
636
- repo = _c[_b];
637
- repo.ctx = this.ctx;
1185
+ });
1186
+ });
1187
+ };
1188
+ /**
1189
+ * Initialize agents after they've been registered and context is ready
1190
+ * Must be called after registerAutoRegisterableAgents()
1191
+ */
1192
+ FlinkApp.prototype.initializeAgents = function () {
1193
+ return __awaiter(this, void 0, void 0, function () {
1194
+ var _i, _a, agent;
1195
+ return __generator(this, function (_b) {
1196
+ // Inject context and initialize agents
1197
+ for (_i = 0, _a = Object.values(this.agents); _i < _a.length; _i++) {
1198
+ agent = _a[_i];
1199
+ agent.ctx = this.ctx;
1200
+ agent.__init(this.llmAdapters, this.tools, this.agentObserver);
638
1201
  }
639
1202
  return [2 /*return*/];
640
1203
  });
@@ -645,7 +1208,7 @@ var FlinkApp = /** @class */ (function () {
645
1208
  */
646
1209
  FlinkApp.prototype.initDb = function () {
647
1210
  return __awaiter(this, void 0, void 0, function () {
648
- var client, err_2;
1211
+ var client, err_3;
649
1212
  return __generator(this, function (_a) {
650
1213
  switch (_a.label) {
651
1214
  case 0:
@@ -661,8 +1224,8 @@ var FlinkApp = /** @class */ (function () {
661
1224
  this.dbClient = client;
662
1225
  return [3 /*break*/, 4];
663
1226
  case 3:
664
- err_2 = _a.sent();
665
- FlinkLog_1.log.error("Failed to connect to db: " + err_2);
1227
+ err_3 = _a.sent();
1228
+ FlinkLog_1.log.error("Failed to connect to db: " + err_3);
666
1229
  process.exit(1);
667
1230
  return [3 /*break*/, 4];
668
1231
  case 4:
@@ -681,7 +1244,7 @@ var FlinkApp = /** @class */ (function () {
681
1244
  */
682
1245
  FlinkApp.prototype.initPluginDb = function (plugin) {
683
1246
  return __awaiter(this, void 0, void 0, function () {
684
- var client, err_3;
1247
+ var client, err_4;
685
1248
  return __generator(this, function (_a) {
686
1249
  switch (_a.label) {
687
1250
  case 0:
@@ -702,14 +1265,14 @@ var FlinkApp = /** @class */ (function () {
702
1265
  _a.label = 2;
703
1266
  case 2:
704
1267
  _a.trys.push([2, 4, , 5]);
705
- FlinkLog_1.log.debug("Connecting to '".concat(plugin.id, "' db"));
1268
+ initLog.debug("Connecting to '".concat(plugin.id, "' db"));
706
1269
  return [4 /*yield*/, mongodb_1.MongoClient.connect(plugin.db.uri, this.getMongoConnectionOptions())];
707
1270
  case 3:
708
1271
  client = _a.sent();
709
1272
  return [2 /*return*/, client.db()];
710
1273
  case 4:
711
- err_3 = _a.sent();
712
- FlinkLog_1.log.error("Failed to connect to db defined in plugin '".concat(plugin.id, "': ") + err_3);
1274
+ err_4 = _a.sent();
1275
+ FlinkLog_1.log.error("Failed to connect to db defined in plugin '".concat(plugin.id, "': ") + err_4);
713
1276
  return [3 /*break*/, 5];
714
1277
  case 5: return [2 /*return*/];
715
1278
  }
@@ -724,12 +1287,40 @@ var FlinkApp = /** @class */ (function () {
724
1287
  if (!this.auth) {
725
1288
  throw new Error("Attempting to authenticate request (".concat(req.method, " ").concat(req.path, ") but no authPlugin is set"));
726
1289
  }
727
- return [4 /*yield*/, this.auth.authenticateRequest(req, permissions)];
1290
+ return [4 /*yield*/, this.auth.authenticateRequest(req, permissions, this._ctx)];
728
1291
  case 1: return [2 /*return*/, _a.sent()];
729
1292
  }
730
1293
  });
731
1294
  });
732
1295
  };
1296
+ /**
1297
+ * Invokes the optional onError callback in a fire-and-forget manner.
1298
+ * Any error thrown or rejected by the callback is caught and logged so
1299
+ * it never affects the error response sent to the client.
1300
+ */
1301
+ FlinkApp.prototype.invokeOnError = function (errorResponse, req, method, path) {
1302
+ if (!this.onError) {
1303
+ return;
1304
+ }
1305
+ try {
1306
+ var result = this.onError(errorResponse, {
1307
+ req: req,
1308
+ method: method,
1309
+ path: path,
1310
+ reqId: req.reqId,
1311
+ ctx: this.ctx,
1312
+ });
1313
+ // Handle async callbacks - don't wait for them
1314
+ if (result instanceof Promise) {
1315
+ result.catch(function (callbackErr) {
1316
+ FlinkLog_1.log.error("onError callback rejected with: ".concat(callbackErr));
1317
+ });
1318
+ }
1319
+ }
1320
+ catch (callbackErr) {
1321
+ FlinkLog_1.log.error("onError callback threw an exception: ".concat(callbackErr));
1322
+ }
1323
+ };
733
1324
  FlinkApp.prototype.getRegisteredRoutes = function () {
734
1325
  return Array.from(this.handlerRouteCache.values());
735
1326
  };
@@ -741,19 +1332,91 @@ var FlinkApp = /** @class */ (function () {
741
1332
  enumerable: false,
742
1333
  configurable: true
743
1334
  });
1335
+ Object.defineProperty(FlinkApp.prototype, "leaderElectionConfig", {
1336
+ get: function () {
1337
+ var _a;
1338
+ var opt = (_a = this.schedulingOptions) === null || _a === void 0 ? void 0 : _a.leaderElection;
1339
+ if (!opt)
1340
+ return undefined;
1341
+ return opt === true ? {} : opt;
1342
+ },
1343
+ enumerable: false,
1344
+ configurable: true
1345
+ });
1346
+ FlinkApp.prototype.startLeaderElection = function () {
1347
+ return __awaiter(this, void 0, void 0, function () {
1348
+ var hasAllInstanceJobs, opts;
1349
+ var _this = this;
1350
+ return __generator(this, function (_a) {
1351
+ switch (_a.label) {
1352
+ case 0:
1353
+ if (!!this.db) return [3 /*break*/, 2];
1354
+ schedulerLog.warn("Leader election is enabled but no database is configured. " +
1355
+ "Leader election requires a MongoDB connection to coordinate between instances. " +
1356
+ "Either add a database connection via the `db` option, or remove `scheduling.leaderElection` from your config. " +
1357
+ "Jobs will run on ALL instances without leader election.");
1358
+ // Fall back to running jobs on all instances
1359
+ this.scheduler = new toad_scheduler_1.ToadScheduler();
1360
+ return [4 /*yield*/, this.registerAutoRegisterableJobs()];
1361
+ case 1:
1362
+ _a.sent();
1363
+ return [2 /*return*/];
1364
+ case 2:
1365
+ hasAllInstanceJobs = exports.autoRegisteredJobs.some(function (j) { return j.Job.runOnAllInstances; });
1366
+ if (!hasAllInstanceJobs) return [3 /*break*/, 4];
1367
+ this.allInstanceScheduler = new toad_scheduler_1.ToadScheduler();
1368
+ this.scheduler = this.allInstanceScheduler;
1369
+ return [4 /*yield*/, this.registerAutoRegisterableJobs(function (job) { return !!job.runOnAllInstances; })];
1370
+ case 3:
1371
+ _a.sent();
1372
+ this.scheduler = undefined;
1373
+ _a.label = 4;
1374
+ case 4:
1375
+ opts = this.leaderElectionConfig;
1376
+ this.leaderElection = new LeaderElection_1.LeaderElection(this.db, opts);
1377
+ return [4 /*yield*/, this.leaderElection.start(
1378
+ // onBecameLeader
1379
+ function () { return __awaiter(_this, void 0, void 0, function () {
1380
+ return __generator(this, function (_a) {
1381
+ switch (_a.label) {
1382
+ case 0:
1383
+ schedulerLog.info("This instance is now the leader - starting scheduled jobs");
1384
+ this.scheduler = new toad_scheduler_1.ToadScheduler();
1385
+ return [4 /*yield*/, this.registerAutoRegisterableJobs(function (job) { return !job.runOnAllInstances; })];
1386
+ case 1:
1387
+ _a.sent();
1388
+ return [2 /*return*/];
1389
+ }
1390
+ });
1391
+ }); },
1392
+ // onLostLeadership
1393
+ function () {
1394
+ schedulerLog.info("This instance lost leadership - stopping scheduled jobs");
1395
+ if (_this.scheduler) {
1396
+ _this.scheduler.stop();
1397
+ _this.scheduler = undefined;
1398
+ }
1399
+ })];
1400
+ case 5:
1401
+ _a.sent();
1402
+ return [2 /*return*/];
1403
+ }
1404
+ });
1405
+ });
1406
+ };
744
1407
  FlinkApp.prototype.getMongoConnectionOptions = function () {
745
1408
  if (!this.dbOpts) {
746
1409
  throw new Error("No db configured");
747
1410
  }
748
1411
  var driverVersion = require("mongodb/package.json").version;
749
1412
  if (driverVersion.startsWith("3")) {
750
- FlinkLog_1.log.debug("Using legacy mongodb connection options as mongo client is version ".concat(driverVersion));
1413
+ initLog.debug("Using legacy mongodb connection options as mongo client is version ".concat(driverVersion));
751
1414
  return {
752
1415
  useNewUrlParser: true,
753
1416
  useUnifiedTopology: true,
754
1417
  };
755
1418
  }
756
- FlinkLog_1.log.debug("Using modern MongoDB client options (driver version ".concat(driverVersion, ")"));
1419
+ initLog.debug("Using modern MongoDB client options (driver version ".concat(driverVersion, ")"));
757
1420
  return {
758
1421
  serverApi: {
759
1422
  version: mongodb_1.ServerApiVersion.v1,