@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
@@ -1,5 +1,7 @@
1
1
  import { FlinkAuthPlugin } from "./auth/FlinkAuthPlugin";
2
2
  import { FlinkRepo } from "./FlinkRepo";
3
+ import { FlinkAgent } from "./ai/FlinkAgent";
4
+ import { FlinkService } from "./FlinkService";
3
5
 
4
6
  export interface FlinkContext<P = any> {
5
7
  repos: {
@@ -12,4 +14,45 @@ export interface FlinkContext<P = any> {
12
14
  * Type of authentication, if any.
13
15
  */
14
16
  auth?: FlinkAuthPlugin;
17
+
18
+ /**
19
+ * AI namespace containing agents
20
+ *
21
+ * Define agents directly in your context interface:
22
+ *
23
+ * @example
24
+ * interface AppCtx extends FlinkContext<PluginCtx> {
25
+ * auth: JwtAuthPlugin;
26
+ * repos: {
27
+ * carRepo: CarRepo;
28
+ * };
29
+ * agents: {
30
+ * carAgent: CarAgent;
31
+ * userAgent: UserAgent;
32
+ * };
33
+ * }
34
+ */
35
+ agents?: {
36
+ [x: string]: FlinkAgent<any>;
37
+ };
38
+
39
+ /**
40
+ * Optional services namespace for shared business logic.
41
+ *
42
+ * Define services directly in your context interface:
43
+ *
44
+ * @example
45
+ * interface AppCtx extends FlinkContext<PluginCtx> {
46
+ * repos: {
47
+ * carRepo: CarRepo;
48
+ * };
49
+ * services: {
50
+ * carService: CarService;
51
+ * orderService: OrderService;
52
+ * };
53
+ * }
54
+ */
55
+ services?: {
56
+ [x: string]: FlinkService<any>;
57
+ };
15
58
  }
@@ -10,19 +10,22 @@ export type FlinkError = undefined;
10
10
  *
11
11
  * @param detail - Optional custom error message
12
12
  * @param code - Optional custom error code
13
+ * @param meta - Optional structured payload (must be JSON-serializable)
13
14
  * @example
14
15
  * ```ts
15
16
  * if (!user) return notFound("User not found");
17
+ * if (!user) return notFound("User not found", "userNotFound", { userId });
16
18
  * ```
17
19
  */
18
- export function notFound(detail?: string, code?: string ): FlinkResponse<FlinkError> {
20
+ export function notFound(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError> {
19
21
  return {
20
22
  status: 404,
21
23
  error: {
22
24
  id: v4(),
23
25
  title: "Not Found",
24
26
  detail: detail || "The requested resource does not exist",
25
- code : code || "notFound"
27
+ code : code || "notFound",
28
+ ...(meta !== undefined && { meta }),
26
29
  },
27
30
  };
28
31
  }
@@ -33,19 +36,21 @@ export function notFound(detail?: string, code?: string ): FlinkResponse<FlinkEr
33
36
  *
34
37
  * @param detail - Optional custom error message
35
38
  * @param code - Optional custom error code
39
+ * @param meta - Optional structured payload (must be JSON-serializable)
36
40
  * @example
37
41
  * ```ts
38
42
  * if (existingUser) return conflict("Email already registered");
39
43
  * ```
40
44
  */
41
- export function conflict(detail?: string, code?: string ): FlinkResponse<FlinkError> {
45
+ export function conflict(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError> {
42
46
  return {
43
47
  status: 409,
44
48
  error: {
45
49
  id: v4(),
46
50
  title: "Conflict",
47
51
  detail: detail || "An identical entity exits",
48
- code : code || "conflict"
52
+ code : code || "conflict",
53
+ ...(meta !== undefined && { meta }),
49
54
  },
50
55
  };
51
56
  }
@@ -58,19 +63,27 @@ export function conflict(detail?: string, code?: string ): FlinkResponse<FlinkEr
58
63
  *
59
64
  * @param detail - Optional custom error message
60
65
  * @param code - Optional custom error code
66
+ * @param meta - Optional structured payload (must be JSON-serializable).
67
+ * Useful for domain-specific context like payment decline codes,
68
+ * retry hints, or structured field errors.
61
69
  * @example
62
70
  * ```ts
63
71
  * if (!email || !password) return badRequest("Email and password are required");
72
+ * return badRequest("Payment declined", "paymentDeclined", {
73
+ * gatewayCode: "insufficient_funds",
74
+ * attemptId: "att_123",
75
+ * });
64
76
  * ```
65
77
  */
66
- export function badRequest(detail?: string, code?: string): FlinkResponse<FlinkError> {
78
+ export function badRequest(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError> {
67
79
  return {
68
80
  status: 400,
69
81
  error: {
70
82
  id: v4(),
71
83
  title: "Bad Request",
72
84
  detail: detail || "Invalid request",
73
- code : code || "badRequest"
85
+ code : code || "badRequest",
86
+ ...(meta !== undefined && { meta }),
74
87
  },
75
88
  };
76
89
  }
@@ -82,12 +95,13 @@ export function badRequest(detail?: string, code?: string): FlinkResponse<FlinkE
82
95
  *
83
96
  * @param detail - Optional custom error message
84
97
  * @param code - Optional custom error code
98
+ * @param meta - Optional structured payload (must be JSON-serializable)
85
99
  * @example
86
100
  * ```ts
87
101
  * if (!ctx.auth?.user) return unauthorized("Authentication required");
88
102
  * ```
89
103
  */
90
- export function unauthorized(detail?: string, code?: string): FlinkResponse<FlinkError> {
104
+ export function unauthorized(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError> {
91
105
  return {
92
106
  status: 401,
93
107
  error: {
@@ -95,7 +109,8 @@ export function unauthorized(detail?: string, code?: string): FlinkResponse<Flin
95
109
  title: "Unauthorized",
96
110
  detail:
97
111
  detail || "Authentication required",
98
- code : code || "unauthorized"
112
+ code : code || "unauthorized",
113
+ ...(meta !== undefined && { meta }),
99
114
  },
100
115
  };
101
116
  }
@@ -107,19 +122,21 @@ export function unauthorized(detail?: string, code?: string): FlinkResponse<Flin
107
122
  *
108
123
  * @param detail - Optional custom error message
109
124
  * @param code - Optional custom error code
125
+ * @param meta - Optional structured payload (must be JSON-serializable)
110
126
  * @example
111
127
  * ```ts
112
128
  * if (ctx.auth?.user?.role !== "admin") return forbidden("Admin access required");
113
129
  * ```
114
130
  */
115
- export function forbidden(detail?: string, code?: string): FlinkResponse<FlinkError> {
131
+ export function forbidden(detail?: string, code?: string, meta?: unknown): FlinkResponse<FlinkError> {
116
132
  return {
117
133
  status: 403,
118
134
  error: {
119
135
  id: v4(),
120
136
  title: "Forbidden",
121
137
  detail: detail || "You do not have permission to access this resource",
122
- code : code || "forbidden"
138
+ code : code || "forbidden",
139
+ ...(meta !== undefined && { meta }),
123
140
  },
124
141
  };
125
142
  }
@@ -130,6 +147,7 @@ export function forbidden(detail?: string, code?: string): FlinkResponse<FlinkEr
130
147
  *
131
148
  * @param detail - Optional custom error message
132
149
  * @param code - Optional custom error code
150
+ * @param meta - Optional structured payload (must be JSON-serializable)
133
151
  * @example
134
152
  * ```ts
135
153
  * try { ... } catch (error) { return internalServerError("Failed to process request"); }
@@ -137,7 +155,8 @@ export function forbidden(detail?: string, code?: string): FlinkResponse<FlinkEr
137
155
  */
138
156
  export function internalServerError(
139
157
  detail?: string,
140
- code?: string
158
+ code?: string,
159
+ meta?: unknown
141
160
  ): FlinkResponse<FlinkError> {
142
161
  return {
143
162
  status: 500,
@@ -145,7 +164,8 @@ export function internalServerError(
145
164
  id: v4(),
146
165
  title: "Internal Server Error",
147
166
  detail: detail || "Something unexpected went wrong",
148
- code : code || "internalServerError"
167
+ code : code || "internalServerError",
168
+ ...(meta !== undefined && { meta }),
149
169
  },
150
170
  };
151
171
  }
@@ -12,24 +12,111 @@ export enum HttpMethod {
12
12
  patch = "patch",
13
13
  }
14
14
 
15
- type Params = Request["params"];
15
+ /**
16
+ * Validation mode for handler request and response schemas.
17
+ *
18
+ * Controls whether request and/or response data is validated against JSON schemas.
19
+ *
20
+ * **Security Note:** Skipping validation can introduce security risks. Only use
21
+ * SkipValidation or ValidateResponse when you have implemented custom validation
22
+ * or the endpoint is internal/trusted.
23
+ *
24
+ * - Validate: Validate both request and response (default behavior)
25
+ * - SkipValidation: Skip both request and response validation
26
+ * - ValidateRequest: Validate only request, skip response validation
27
+ * - ValidateResponse: Validate only response, skip request validation
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // Skip validation for webhook with custom signature verification
32
+ * export const Route: RouteProps = {
33
+ * path: "/webhook",
34
+ * validation: ValidationMode.SkipValidation
35
+ * };
36
+ *
37
+ * // Validate request but allow flexible response during development
38
+ * export const Route: RouteProps = {
39
+ * path: "/api/data",
40
+ * validation: ValidationMode.ValidateRequest
41
+ * };
42
+ * ```
43
+ */
44
+ export enum ValidationMode {
45
+ Validate = "Validate",
46
+ SkipValidation = "SkipValidation",
47
+ ValidateRequest = "ValidateRequest",
48
+ ValidateResponse = "ValidateResponse",
49
+ }
50
+
51
+ type Params = Record<string, string>;
16
52
 
17
53
  /**
18
54
  * Query type for request query parameters.
19
- * Does currently not allow nested objects, although
20
- * underlying express Request does allow it.
21
55
  *
22
- * Uses index signature to allow both Record types and interface types
23
- * to be assignable to Query without requiring explicit index signatures.
56
+ * All query parameter values are normalized to strings or string arrays:
57
+ * - Single values: string (e.g., ?name=John becomes { name: "John" })
58
+ * - Multiple values: string[] (e.g., ?tag=a&tag=b becomes { tag: ["a", "b"] })
59
+ *
60
+ * Does not allow nested objects, although underlying Express Request does allow it.
24
61
  */
25
- type Query = {
26
- [x: string]: string | string[] | undefined;
27
- };
62
+ type Query = Record<string, string | string[]>;
63
+
64
+ /**
65
+ * Stream format for streaming handlers.
66
+ * - sse: Server-Sent Events (text/event-stream)
67
+ * - ndjson: Newline-Delimited JSON (application/x-ndjson)
68
+ */
69
+ export type StreamFormat = "sse" | "ndjson";
70
+
71
+ /**
72
+ * Stream writer interface for SSE/NDJSON streaming.
73
+ *
74
+ * Provides methods to write data chunks, handle errors, and manage the stream lifecycle.
75
+ * Only available in handlers where streamFormat is specified in RouteProps.
76
+ */
77
+ export interface StreamWriter<T = any> {
78
+ /**
79
+ * Write data to the stream.
80
+ * Data is automatically JSON-stringified and formatted according to streamFormat.
81
+ */
82
+ write(data: T): void;
83
+
84
+ /**
85
+ * Send error to client and close the stream.
86
+ */
87
+ error(error: Error | string): void;
88
+
89
+ /**
90
+ * Close the stream gracefully.
91
+ */
92
+ end(): void;
93
+
94
+ /**
95
+ * Check if stream is still open (client connected).
96
+ * Returns false if client has disconnected.
97
+ */
98
+ isOpen(): boolean;
99
+ }
28
100
 
29
101
  /**
30
- * Flink request extends express Request but adds reqId and user object.
102
+ * Flink request extends express Request but adds reqId, user object, and userPermissions.
103
+ *
104
+ * userPermissions is populated by auth plugins during authentication and contains
105
+ * the resolved permissions array based on the plugin's configuration (roles, dynamic
106
+ * roles, custom permissions, etc.)
107
+ *
108
+ * token / rawToken are populated by auth plugins that have a token concept (e.g. JWT).
109
+ * `token` is the decoded, signature-verified payload; `rawToken` is the original token
110
+ * string that authenticated the request (Bearer header or custom tokenExtractor output).
111
+ * Both are optional and only set once authentication succeeds.
31
112
  */
32
- export type FlinkRequest<T = any, P = Params, Q = Query> = Request<P, any, T, Q> & { reqId: string; user?: any };
113
+ export type FlinkRequest<T = any, P = Params, Q = Query> = Request<P, any, T, Q> & {
114
+ reqId: string;
115
+ user?: any;
116
+ userPermissions?: string[]; // Resolved permissions from auth plugin
117
+ token?: any; // Decoded, verified token payload (set by auth plugin)
118
+ rawToken?: string; // Raw token string that authenticated the request
119
+ };
33
120
 
34
121
  /**
35
122
  * Route props to control routing.
@@ -64,6 +151,27 @@ export interface RouteProps {
64
151
 
65
152
  /**
66
153
  * Set permissions needed to access route if route requires authentication.
154
+ *
155
+ * When an array is provided, the user must have **ALL** permissions in the array (AND logic).
156
+ * To require any one of multiple permissions (OR logic), implement custom permission
157
+ * checking in the handler using the `user.permissions` array.
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * // Single permission
162
+ * permissions: "car:create"
163
+ *
164
+ * // Multiple permissions (user must have ALL)
165
+ * permissions: ["car:create", "car:premium"]
166
+ *
167
+ * // OR logic requires custom implementation
168
+ * const handler = async ({ ctx, user }) => {
169
+ * if (!user.permissions.includes("car:admin") && !user.permissions.includes("car:moderator")) {
170
+ * throw forbidden("Need admin or moderator permission");
171
+ * }
172
+ * // ... handler logic
173
+ * };
174
+ * ```
67
175
  */
68
176
  permissions?: string | string[];
69
177
 
@@ -91,16 +199,142 @@ export interface RouteProps {
91
199
  * to avoid conflicts you can set a negative order.
92
200
  */
93
201
  order?: number;
202
+
203
+ /**
204
+ * Validation mode for request and response schemas.
205
+ *
206
+ * Controls schema validation behavior for this handler. Use with caution as
207
+ * skipping validation can introduce security vulnerabilities.
208
+ *
209
+ * **Options:**
210
+ * - Validate: Validate both request and response (default)
211
+ * - SkipValidation: Skip both request and response validation
212
+ * - ValidateRequest: Validate only request, skip response validation
213
+ * - ValidateResponse: Validate only response, skip request validation
214
+ *
215
+ * **When to skip validation:**
216
+ * - Webhook handlers with custom signature verification
217
+ * - Performance-critical internal endpoints
218
+ * - Handlers using alternative validation methods (e.g., Zod, Joi)
219
+ *
220
+ * @default ValidationMode.Validate
221
+ */
222
+ validation?: ValidationMode;
223
+
224
+ /**
225
+ * Stream format for streaming handlers (SSE or NDJSON).
226
+ *
227
+ * When specified, the handler becomes a streaming handler and receives a `stream`
228
+ * parameter for writing data chunks. Response validation is automatically skipped
229
+ * for streaming handlers (chunks are progressive, not a final JSON response).
230
+ *
231
+ * **Formats:**
232
+ * - sse: Server-Sent Events (text/event-stream) - ideal for browser EventSource API
233
+ * - ndjson: Newline-Delimited JSON (application/x-ndjson) - ideal for LLM text streaming
234
+ *
235
+ * **Example:**
236
+ * ```typescript
237
+ * export const Route: RouteProps = {
238
+ * path: "/ai/stream",
239
+ * streamFormat: "sse"
240
+ * };
241
+ *
242
+ * const handler: GetHandler<{}, void> = async ({ ctx, stream }) => {
243
+ * if (!stream) throw new Error("Stream not available");
244
+ * stream.write({ message: "Hello" });
245
+ * stream.end();
246
+ * };
247
+ * ```
248
+ */
249
+ streamFormat?: StreamFormat;
250
+
251
+ /**
252
+ * When set, the handler's `data` field is sent as a raw response with the
253
+ * given content type instead of the standard JSON envelope. Response schema
254
+ * validation is automatically skipped.
255
+ *
256
+ * Accepts Express shorthand names (`"html"`, `"csv"`, `"text"`, `"xml"`)
257
+ * or any full MIME type string (`"application/pdf"`, etc.).
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * export const Route: RouteProps = {
262
+ * path: "/dashboard",
263
+ * responseType: "html",
264
+ * };
265
+ *
266
+ * const handler: GetHandler<AppContext, void> = async ({ ctx }) => {
267
+ * return { data: `<h1>Hello</h1>` };
268
+ * };
269
+ * ```
270
+ *
271
+ * @example
272
+ * ```typescript
273
+ * export const Route: RouteProps = {
274
+ * path: "/export",
275
+ * responseType: "csv",
276
+ * };
277
+ *
278
+ * const handler: GetHandler<AppContext, void> = async ({ ctx }) => {
279
+ * return { data: `id,name\n1,Alice` };
280
+ * };
281
+ * ```
282
+ *
283
+ * @example
284
+ * ```typescript
285
+ * export const Route: RouteProps = {
286
+ * path: "/logo",
287
+ * responseType: "image/png",
288
+ * };
289
+ *
290
+ * const handler: GetHandler<AppContext, Buffer> = async ({ ctx }) => {
291
+ * const imageBuffer = await fs.promises.readFile("./logo.png");
292
+ * return { data: imageBuffer };
293
+ * };
294
+ * ```
295
+ */
296
+ responseType?:
297
+ | "html"
298
+ | "csv"
299
+ | "text"
300
+ | "xml"
301
+ | "image/png"
302
+ | "image/jpeg"
303
+ | "image/gif"
304
+ | "image/svg+xml"
305
+ | "image/webp"
306
+ | "application/pdf"
307
+ | "application/octet-stream"
308
+ | (string & {});
309
+
310
+ /**
311
+ * Direct JSON Schema for request body validation.
312
+ * When set, bypasses manifest lookup for request schema.
313
+ * Useful for plugin handlers that have their own compiled schemas.
314
+ */
315
+ reqSchema?: object;
316
+
317
+ /**
318
+ * Direct JSON Schema for response body validation.
319
+ * When set, bypasses manifest lookup for response schema.
320
+ * Useful for plugin handlers that have their own compiled schemas.
321
+ */
322
+ resSchema?: object;
94
323
  }
95
324
 
96
325
  /**
97
326
  * Http handler function that handlers implements in order to
98
327
  * handle HTTP requests and return a JSON response.
328
+ *
329
+ * For streaming handlers (when streamFormat is specified in RouteProps),
330
+ * the stream parameter is available. Streaming handlers should still return
331
+ * a FlinkResponse (can be empty), but it will be ignored by the framework.
99
332
  */
100
333
  export type Handler<Ctx extends FlinkContext, ReqSchema = any, ResSchema = any, P extends Params = Params, Q extends Query = Query> = (props: {
101
334
  req: FlinkRequest<ReqSchema, P, Q>;
102
335
  ctx: Ctx;
103
336
  origin?: string;
337
+ stream?: StreamWriter;
104
338
  }) => Promise<FlinkResponse<ResSchema | FlinkError>>;
105
339
 
106
340
  /**
@@ -121,26 +355,10 @@ export type HandlerFile = {
121
355
  default: Handler<any, any, any, any, any>;
122
356
  Route?: RouteProps;
123
357
  /**
124
- * Name of schemas, is set at compile time by Flink compiler.
125
- */
126
- __schemas?: {
127
- reqSchema?: JSONSchema;
128
- resSchema?: JSONSchema;
129
- };
130
- /**
131
- * Typescript source file name, is set at compile time by Flink compiler.
358
+ * Typescript source file path (relative to project root), set at compile time by Flink compiler.
359
+ * Used to look up handler metadata from schema-manifest.json at runtime.
132
360
  */
133
361
  __file?: string;
134
-
135
- /**
136
- * Description of query params, is set at compile time by Flink compiler.
137
- */
138
- __query?: QueryParamMetadata[];
139
-
140
- /**
141
- * Description of path params, is set at compile time by Flink compiler.
142
- */
143
- __params?: QueryParamMetadata[];
144
362
  };
145
363
 
146
364
  export type QueryParamMetadata = {
package/src/FlinkJob.ts CHANGED
@@ -37,6 +37,17 @@ export type FlinkJobProps = {
37
37
  * retried after the next interval.
38
38
  */
39
39
  singleton?: boolean;
40
+
41
+ /**
42
+ * If true, this job will run on all instances regardless of leader election.
43
+ *
44
+ * By default, when leader election is enabled, jobs only run on the leader instance.
45
+ * Set this to true for jobs that should run on every instance, such as
46
+ * local cache cleanup or instance-specific health checks.
47
+ *
48
+ * Has no effect when leader election is not enabled.
49
+ */
50
+ runOnAllInstances?: boolean;
40
51
  };
41
52
 
42
53
  /**
package/src/FlinkLog.ts CHANGED
@@ -1,16 +1,123 @@
1
- import Logger from "node-color-log";
1
+ import { FlinkLogFactory } from "./FlinkLogFactory";
2
2
 
3
+ type LogLevel = "trace" | "debug" | "info" | "warn" | "error";
4
+
5
+ /**
6
+ * Lazy-loaded application logger using FlinkLogFactory
7
+ *
8
+ * This logger uses the component name "app" and respects all
9
+ * logging configuration from flink.config.js and environment variables.
10
+ *
11
+ * Configured via:
12
+ * - flink.config.js: components["app"] = "debug"
13
+ * - Environment: LOG_LEVEL=debug
14
+ * - Programmatic: FlinkLogFactory.setComponentLevel("app", "debug")
15
+ */
16
+ let appLogger: ReturnType<typeof FlinkLogFactory.createLogger> | null = null;
17
+
18
+ /**
19
+ * Get or create the app logger (lazy initialization)
20
+ */
21
+ function getAppLogger() {
22
+ if (!appLogger) {
23
+ appLogger = FlinkLogFactory.createLogger("app");
24
+ }
25
+ return appLogger;
26
+ }
27
+
28
+ /**
29
+ * Simple logging utility with proper stdout/stderr separation
30
+ *
31
+ * Following Unix/POSIX best practices:
32
+ * - trace, debug, info → stdout (normal output)
33
+ * - warn, error → stderr (diagnostic output)
34
+ *
35
+ * Benefits:
36
+ * - Respects flink.config.js logging configuration
37
+ * - Container orchestration can capture streams separately
38
+ * - Shell redirection works correctly (command > out.log 2> err.log)
39
+ * - Log aggregation tools can route differently
40
+ * - Monitoring systems can alert on stderr only
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * import { log } from "@flink-app/flink";
45
+ *
46
+ * log.trace("Detailed trace info");
47
+ * log.debug("Debug information");
48
+ * log.info("General information");
49
+ * log.warn("Warning message");
50
+ * log.error("Error message");
51
+ * ```
52
+ *
53
+ * @example Configure via flink.config.js
54
+ * ```javascript
55
+ * module.exports = {
56
+ * logging: {
57
+ * components: {
58
+ * "app": "debug" // Enable debug for default log
59
+ * }
60
+ * }
61
+ * };
62
+ * ```
63
+ */
3
64
  export const log = {
4
- debug: Logger.debug.bind(Logger),
5
- info: Logger.info.bind(Logger),
6
- warn: Logger.warn.bind(Logger),
7
- error: Logger.error.bind(Logger),
8
- json: (...args: any) => {
9
- for (const o of args) {
10
- console.log(JSON.stringify(o, null, 2));
11
- }
65
+ /**
66
+ * Trace logs (stdout)
67
+ * Use for extremely detailed diagnostic information (most verbose level)
68
+ */
69
+ trace: (...args: any[]) => getAppLogger().trace(...args),
70
+
71
+ /**
72
+ * Debug logs (stdout)
73
+ * Use for detailed diagnostic information during development
74
+ */
75
+ debug: (...args: any[]) => getAppLogger().debug(...args),
76
+
77
+ /**
78
+ * Info logs (stdout)
79
+ * Use for general informational messages about application state
80
+ */
81
+ info: (...args: any[]) => getAppLogger().info(...args),
82
+
83
+ /**
84
+ * Warning logs (stderr)
85
+ * Use for potentially harmful situations that aren't errors
86
+ * Writes to stderr following Unix/POSIX best practices
87
+ */
88
+ warn: (...args: any[]) => getAppLogger().warn(...args),
89
+
90
+ /**
91
+ * Error logs (stderr)
92
+ * Use for error events that might still allow the app to continue
93
+ * Writes to stderr following Unix/POSIX best practices
94
+ */
95
+ error: (...args: any[]) => getAppLogger().error(...args),
96
+
97
+ /**
98
+ * JSON output (stdout)
99
+ * Use for structured data output
100
+ */
101
+ json: (...args: any[]) => getAppLogger().json(...args),
102
+
103
+ /**
104
+ * Colored background log (stdout)
105
+ * Use for highlighted messages during development
106
+ */
107
+ bgColorLog: (color: string, ...args: any[]) => getAppLogger().bgColorLog(color, ...args),
108
+
109
+ /**
110
+ * Colored font log (stdout)
111
+ * Use for colored messages during development
112
+ */
113
+ fontColorLog: (color: string, ...args: any[]) => getAppLogger().fontColorLog(color, ...args),
114
+
115
+ /**
116
+ * Set log level for the default "app" logger
117
+ *
118
+ * @deprecated Use flink.config.js or FlinkLogFactory.setComponentLevel("app", level) instead
119
+ */
120
+ setLevel: (level: LogLevel) => {
121
+ getAppLogger().setLevel(level);
12
122
  },
13
- bgColorLog: Logger.bgColorLog.bind(Logger),
14
- fontColorLog: Logger.fontColorLog.bind(Logger),
15
- setLevel: (level: "debug" | "info" | "warn" | "error") => Logger.setLevel.bind(Logger)(level),
16
123
  };