@flink-app/flink 1.0.0 → 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 (277) hide show
  1. package/CHANGELOG.md +991 -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 +823 -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 +157 -18
  29. package/dist/src/FlinkJob.d.ts +10 -0
  30. package/dist/src/FlinkLog.d.ts +82 -18
  31. package/dist/src/FlinkLog.js +165 -13
  32. package/dist/src/FlinkLogFactory.d.ts +288 -0
  33. package/dist/src/FlinkLogFactory.js +619 -0
  34. package/dist/src/FlinkRepo.d.ts +10 -2
  35. package/dist/src/FlinkRepo.js +11 -1
  36. package/dist/src/FlinkRequestContext.d.ts +63 -0
  37. package/dist/src/FlinkRequestContext.js +74 -0
  38. package/dist/src/FlinkResponse.d.ts +6 -0
  39. package/dist/src/FlinkService.d.ts +38 -0
  40. package/dist/src/FlinkService.js +46 -0
  41. package/dist/src/LeaderElection.d.ts +45 -0
  42. package/dist/src/LeaderElection.js +269 -0
  43. package/dist/src/SchemaCache.d.ts +84 -0
  44. package/dist/src/SchemaCache.js +289 -0
  45. package/dist/src/TypeScriptCompiler.d.ts +161 -51
  46. package/dist/src/TypeScriptCompiler.js +1253 -617
  47. package/dist/src/TypeScriptUtils.js +4 -0
  48. package/dist/src/ai/AgentRunner.d.ts +39 -0
  49. package/dist/src/ai/AgentRunner.js +760 -0
  50. package/dist/src/ai/ConversationAgent.d.ts +279 -0
  51. package/dist/src/ai/ConversationAgent.js +404 -0
  52. package/dist/src/ai/ConversationFlinkAgent.d.ts +278 -0
  53. package/dist/src/ai/ConversationFlinkAgent.js +404 -0
  54. package/dist/src/ai/FlinkAgent.d.ts +690 -0
  55. package/dist/src/ai/FlinkAgent.js +729 -0
  56. package/dist/src/ai/FlinkTool.d.ts +135 -0
  57. package/dist/src/ai/FlinkTool.js +2 -0
  58. package/dist/src/ai/InMemoryConversationAgent.d.ts +121 -0
  59. package/dist/src/ai/InMemoryConversationAgent.js +209 -0
  60. package/dist/src/ai/LLMAdapter.d.ts +148 -0
  61. package/dist/src/ai/LLMAdapter.js +2 -0
  62. package/dist/src/ai/PersistentFlinkAgent.d.ts +278 -0
  63. package/dist/src/ai/PersistentFlinkAgent.js +403 -0
  64. package/dist/src/ai/SubAgentExecutor.d.ts +38 -0
  65. package/dist/src/ai/SubAgentExecutor.js +223 -0
  66. package/dist/src/ai/ToolExecutor.d.ts +64 -0
  67. package/dist/src/ai/ToolExecutor.js +497 -0
  68. package/dist/src/ai/agentInstructions.d.ts +68 -0
  69. package/dist/src/ai/agentInstructions.js +286 -0
  70. package/dist/src/ai/index.d.ts +8 -0
  71. package/dist/src/ai/index.js +26 -0
  72. package/dist/src/ai/instructionFileLoader.d.ts +44 -0
  73. package/dist/src/ai/instructionFileLoader.js +179 -0
  74. package/dist/src/auth/FlinkAuthPlugin.d.ts +1 -1
  75. package/dist/src/handlers/StreamWriterFactory.d.ts +20 -0
  76. package/dist/src/handlers/StreamWriterFactory.js +83 -0
  77. package/dist/src/index.d.ts +14 -0
  78. package/dist/src/index.js +17 -0
  79. package/dist/src/loadPluginSchemas.d.ts +45 -0
  80. package/dist/src/loadPluginSchemas.js +143 -0
  81. package/dist/src/schema-extraction/ComplexTypeDetection.d.ts +40 -0
  82. package/dist/src/schema-extraction/ComplexTypeDetection.js +75 -0
  83. package/dist/src/schema-extraction/TypeScriptSourceParser.d.ts +321 -0
  84. package/dist/src/schema-extraction/TypeScriptSourceParser.js +925 -0
  85. package/dist/src/schema-extraction/TypeScriptSourceParser.spec.d.ts +1 -0
  86. package/dist/src/schema-extraction/TypeScriptSourceParser.spec.js +233 -0
  87. package/dist/src/schema-extraction/TypeScriptTokenizer.d.ts +57 -0
  88. package/dist/src/schema-extraction/TypeScriptTokenizer.js +177 -0
  89. package/dist/src/schema-extraction/index.d.ts +2 -0
  90. package/dist/src/schema-extraction/index.js +20 -0
  91. package/dist/src/schema-extraction/types.d.ts +31 -0
  92. package/dist/src/schema-extraction/types.js +2 -0
  93. package/dist/src/utils/loadFlinkConfig.d.ts +53 -0
  94. package/dist/src/utils/loadFlinkConfig.js +77 -0
  95. package/dist/src/utils.d.ts +30 -0
  96. package/dist/src/utils.js +52 -0
  97. package/dist/src/workers/SchemaGeneratorWorker.d.ts +1 -0
  98. package/dist/src/workers/SchemaGeneratorWorker.js +49 -0
  99. package/dist/src/workers/WorkerPool.d.ts +60 -0
  100. package/dist/src/workers/WorkerPool.js +306 -0
  101. package/examples/logging-hierarchical-example.ts +125 -0
  102. package/package.json +27 -4
  103. package/readme.md +499 -0
  104. package/spec/AgentDescendantDetection.spec.ts +335 -0
  105. package/spec/AgentDuplicateDetection.spec.ts +112 -0
  106. package/spec/AgentObserver.spec.ts +266 -0
  107. package/spec/AgentRunner.spec.ts +1062 -0
  108. package/spec/AsyncLocalStorageContext.spec.ts +223 -0
  109. package/spec/ConversationHooks.spec.ts +257 -0
  110. package/spec/FlinkAgent.spec.ts +681 -0
  111. package/spec/FlinkApp.htmlResponse.spec.ts +260 -0
  112. package/spec/FlinkApp.onError.invocation.spec.ts +151 -0
  113. package/spec/FlinkApp.onError.spec.ts +1 -2
  114. package/spec/FlinkApp.routeOrdering.spec.ts +61 -0
  115. package/spec/FlinkApp.undefinedResponse.spec.ts +123 -0
  116. package/spec/FlinkJob.spec.ts +171 -0
  117. package/spec/FlinkLogFactory.spec.ts +337 -0
  118. package/spec/FlinkRepo.spec.ts +1 -1
  119. package/spec/LeaderElection.spec.ts +174 -0
  120. package/spec/StreamingIntegration.spec.ts +139 -0
  121. package/spec/ToolExecutor.spec.ts +465 -0
  122. package/spec/TypeScriptCompiler.spec.ts +1 -1
  123. package/spec/TypeScriptSourceParser.spec.ts +1215 -0
  124. package/spec/TypeScriptTokenizer.spec.ts +366 -0
  125. package/spec/ai/ContextCompaction.spec.ts +405 -0
  126. package/spec/ai/ConversationAgent.spec.ts +520 -0
  127. package/spec/ai/InMemoryConversationAgent.spec.ts +144 -0
  128. package/spec/ai/agentInstructions.spec.ts +358 -0
  129. package/spec/fixtures/agent-instructions/TestAgent.ts +24 -0
  130. package/spec/fixtures/agent-instructions/simple.md +3 -0
  131. package/spec/fixtures/agent-instructions/template.md +18 -0
  132. package/spec/fixtures/agent-instructions/yaml-format.yaml +9 -0
  133. package/spec/mock-project/dist/.tsbuildinfo +1 -0
  134. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar.js +56 -0
  135. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar2.js +58 -0
  136. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema.js +52 -0
  137. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema2.js +52 -0
  138. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema3.js +52 -0
  139. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema.js +54 -0
  140. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema2.js +54 -0
  141. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile.js +57 -0
  142. package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile2.js +57 -0
  143. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler.js +53 -0
  144. package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler2.js +55 -0
  145. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchCar.js +57 -0
  146. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOnboardingSession.js +75 -0
  147. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOrderWithComplexTypes.js +57 -0
  148. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchProductWithIntersection.js +58 -0
  149. package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchUserWithUnion.js +58 -0
  150. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostCar.js +54 -0
  151. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogin.js +55 -0
  152. package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogout.js +54 -0
  153. package/spec/mock-project/dist/spec/mock-project/src/handlers/PutCar.js +54 -0
  154. package/spec/mock-project/dist/spec/mock-project/src/index.js +83 -0
  155. package/spec/mock-project/dist/spec/mock-project/src/repos/CarRepo.js +26 -0
  156. package/spec/mock-project/dist/spec/mock-project/src/schemas/Car.js +2 -0
  157. package/spec/mock-project/dist/spec/mock-project/src/schemas/DefaultExportSchema.js +2 -0
  158. package/spec/mock-project/dist/spec/mock-project/src/schemas/FileWithTwoSchemas.js +2 -0
  159. package/spec/mock-project/dist/src/FlinkApp.js +1000 -0
  160. package/spec/mock-project/dist/src/FlinkContext.js +2 -0
  161. package/spec/mock-project/dist/src/FlinkErrors.js +143 -0
  162. package/spec/mock-project/dist/src/FlinkHttpHandler.js +47 -0
  163. package/spec/mock-project/dist/src/FlinkJob.js +2 -0
  164. package/spec/mock-project/dist/src/FlinkLog.js +119 -0
  165. package/spec/mock-project/dist/src/FlinkLogFactory.js +617 -0
  166. package/spec/mock-project/dist/src/FlinkPlugin.js +2 -0
  167. package/spec/mock-project/dist/src/FlinkRepo.js +224 -0
  168. package/spec/mock-project/dist/src/FlinkRequestContext.js +74 -0
  169. package/spec/mock-project/dist/src/FlinkResponse.js +2 -0
  170. package/spec/mock-project/dist/src/ai/AgentExecutor.js +279 -0
  171. package/spec/mock-project/dist/src/ai/AgentRunner.js +632 -0
  172. package/spec/mock-project/dist/src/ai/ConversationAgent.js +402 -0
  173. package/spec/mock-project/dist/src/ai/ConversationFlinkAgent.js +422 -0
  174. package/spec/mock-project/dist/src/ai/FlinkAgent.js +699 -0
  175. package/spec/mock-project/dist/src/ai/FlinkTool.js +2 -0
  176. package/spec/mock-project/dist/src/ai/InMemoryConversationAgent.js +209 -0
  177. package/spec/mock-project/dist/src/ai/LLMAdapter.js +2 -0
  178. package/spec/mock-project/dist/src/ai/SubAgentExecutor.js +223 -0
  179. package/spec/mock-project/dist/src/ai/ToolExecutor.js +412 -0
  180. package/spec/mock-project/dist/src/ai/agentInstructions.js +246 -0
  181. package/spec/mock-project/dist/src/auth/FlinkAuthPlugin.js +2 -0
  182. package/spec/mock-project/dist/src/auth/FlinkAuthUser.js +2 -0
  183. package/spec/mock-project/dist/src/handlers/GetCar.js +26 -52
  184. package/spec/mock-project/dist/src/handlers/GetCar.js.map +1 -0
  185. package/spec/mock-project/dist/src/handlers/GetCar2.js +32 -54
  186. package/spec/mock-project/dist/src/handlers/GetCar2.js.map +1 -0
  187. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js +26 -48
  188. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js.map +1 -0
  189. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js +28 -48
  190. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js.map +1 -0
  191. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js +29 -48
  192. package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js.map +1 -0
  193. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js +26 -50
  194. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js.map +1 -0
  195. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js +28 -50
  196. package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js.map +1 -0
  197. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js +27 -53
  198. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js.map +1 -0
  199. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js +29 -53
  200. package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js.map +1 -0
  201. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js +16 -49
  202. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js.map +1 -0
  203. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js +25 -50
  204. package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js.map +1 -0
  205. package/spec/mock-project/dist/src/handlers/PatchCar.js +27 -53
  206. package/spec/mock-project/dist/src/handlers/PatchCar.js.map +1 -0
  207. package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js +44 -70
  208. package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js.map +1 -0
  209. package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js +27 -53
  210. package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js.map +1 -0
  211. package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js +28 -54
  212. package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js.map +1 -0
  213. package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js +28 -54
  214. package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js.map +1 -0
  215. package/spec/mock-project/dist/src/handlers/PostCar.js +24 -50
  216. package/spec/mock-project/dist/src/handlers/PostCar.js.map +1 -0
  217. package/spec/mock-project/dist/src/handlers/PostLogin.js +25 -51
  218. package/spec/mock-project/dist/src/handlers/PostLogin.js.map +1 -0
  219. package/spec/mock-project/dist/src/handlers/PostLogout.js +24 -50
  220. package/spec/mock-project/dist/src/handlers/PostLogout.js.map +1 -0
  221. package/spec/mock-project/dist/src/handlers/PutCar.js +24 -50
  222. package/spec/mock-project/dist/src/handlers/PutCar.js.map +1 -0
  223. package/spec/mock-project/dist/src/handlers/StreamWriterFactory.js +83 -0
  224. package/spec/mock-project/dist/src/index.js +52 -76
  225. package/spec/mock-project/dist/src/index.js.map +1 -0
  226. package/spec/mock-project/dist/src/mock-data-generator.js +9 -0
  227. package/spec/mock-project/dist/src/repos/CarRepo.js +12 -24
  228. package/spec/mock-project/dist/src/repos/CarRepo.js.map +1 -0
  229. package/spec/mock-project/dist/src/schemas/Car.js +3 -1
  230. package/spec/mock-project/dist/src/schemas/Car.js.map +1 -0
  231. package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js +3 -1
  232. package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js.map +1 -0
  233. package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js +3 -1
  234. package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js.map +1 -0
  235. package/spec/mock-project/dist/src/utils.js +290 -0
  236. package/spec/mock-project/tsconfig.json +6 -1
  237. package/spec/schema-generation-nested-objects.spec.ts +97 -0
  238. package/spec/testHelpers.ts +49 -0
  239. package/spec/utils.caseConversion.spec.ts +78 -0
  240. package/spec/utils.spec.ts +13 -13
  241. package/src/DependencyTracker.ts +166 -0
  242. package/src/FlinkApp.ts +895 -154
  243. package/src/FlinkContext.ts +43 -0
  244. package/src/FlinkErrors.ts +32 -12
  245. package/src/FlinkHttpHandler.ts +182 -20
  246. package/src/FlinkJob.ts +11 -0
  247. package/src/FlinkLog.ts +119 -12
  248. package/src/FlinkLogFactory.ts +699 -0
  249. package/src/FlinkRepo.ts +10 -3
  250. package/src/FlinkRequestContext.ts +95 -0
  251. package/src/FlinkResponse.ts +6 -0
  252. package/src/FlinkService.ts +49 -0
  253. package/src/LeaderElection.ts +203 -0
  254. package/src/SchemaCache.ts +232 -0
  255. package/src/TypeScriptCompiler.ts +1347 -610
  256. package/src/TypeScriptUtils.ts +5 -0
  257. package/src/ai/AgentRunner.ts +646 -0
  258. package/src/ai/ConversationAgent.ts +413 -0
  259. package/src/ai/FlinkAgent.ts +1069 -0
  260. package/src/ai/FlinkTool.ts +165 -0
  261. package/src/ai/InMemoryConversationAgent.ts +149 -0
  262. package/src/ai/LLMAdapter.ts +126 -0
  263. package/src/ai/ToolExecutor.ts +485 -0
  264. package/src/ai/agentInstructions.ts +245 -0
  265. package/src/ai/index.ts +8 -0
  266. package/src/ai/instructionFileLoader.ts +156 -0
  267. package/src/auth/FlinkAuthPlugin.ts +2 -1
  268. package/src/handlers/StreamWriterFactory.ts +84 -0
  269. package/src/index.ts +14 -0
  270. package/src/loadPluginSchemas.ts +141 -0
  271. package/src/schema-extraction/TypeScriptSourceParser.ts +1058 -0
  272. package/src/schema-extraction/TypeScriptTokenizer.ts +205 -0
  273. package/src/schema-extraction/index.ts +2 -0
  274. package/src/schema-extraction/types.ts +34 -0
  275. package/src/utils/loadFlinkConfig.ts +89 -0
  276. package/src/utils.ts +52 -0
  277. 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
  }
@@ -62,9 +62,61 @@ type Params = Record<string, string>;
62
62
  type Query = Record<string, string | string[]>;
63
63
 
64
64
  /**
65
- * Flink request extends express Request but adds reqId and user object.
65
+ * Stream format for streaming handlers.
66
+ * - sse: Server-Sent Events (text/event-stream)
67
+ * - ndjson: Newline-Delimited JSON (application/x-ndjson)
66
68
  */
67
- export type FlinkRequest<T = any, P = Params, Q = Query> = Request<P, any, T, Q> & { reqId: string; user?: any };
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
+ }
100
+
101
+ /**
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.
112
+ */
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
+ };
68
120
 
69
121
  /**
70
122
  * Route props to control routing.
@@ -99,6 +151,27 @@ export interface RouteProps {
99
151
 
100
152
  /**
101
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
+ * ```
102
175
  */
103
176
  permissions?: string | string[];
104
177
 
@@ -147,16 +220,121 @@ export interface RouteProps {
147
220
  * @default ValidationMode.Validate
148
221
  */
149
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;
150
323
  }
151
324
 
152
325
  /**
153
326
  * Http handler function that handlers implements in order to
154
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.
155
332
  */
156
333
  export type Handler<Ctx extends FlinkContext, ReqSchema = any, ResSchema = any, P extends Params = Params, Q extends Query = Query> = (props: {
157
334
  req: FlinkRequest<ReqSchema, P, Q>;
158
335
  ctx: Ctx;
159
336
  origin?: string;
337
+ stream?: StreamWriter;
160
338
  }) => Promise<FlinkResponse<ResSchema | FlinkError>>;
161
339
 
162
340
  /**
@@ -177,26 +355,10 @@ export type HandlerFile = {
177
355
  default: Handler<any, any, any, any, any>;
178
356
  Route?: RouteProps;
179
357
  /**
180
- * Name of schemas, is set at compile time by Flink compiler.
181
- */
182
- __schemas?: {
183
- reqSchema?: JSONSchema;
184
- resSchema?: JSONSchema;
185
- };
186
- /**
187
- * 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.
188
360
  */
189
361
  __file?: string;
190
-
191
- /**
192
- * Description of query params, is set at compile time by Flink compiler.
193
- */
194
- __query?: QueryParamMetadata[];
195
-
196
- /**
197
- * Description of path params, is set at compile time by Flink compiler.
198
- */
199
- __params?: QueryParamMetadata[];
200
362
  };
201
363
 
202
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
  };