@eventmodelers/cli 0.0.1

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 (220) hide show
  1. package/README.md +175 -0
  2. package/cli.js +676 -0
  3. package/package.json +35 -0
  4. package/shared/build-kit/code-export.mjs +560 -0
  5. package/shared/build-kit/lib/ollama-agent.js +147 -0
  6. package/shared/build-kit/package.json +11 -0
  7. package/shared/build-kit/ralph-ollama.js +39 -0
  8. package/shared/build-kit/realtime-agent.js +18 -0
  9. package/stacks/axon/templates/.claude/skills/build-automation/SKILL.md +407 -0
  10. package/stacks/axon/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +114 -0
  11. package/stacks/axon/templates/.claude/skills/build-automation-workflow/SKILL.md +496 -0
  12. package/stacks/axon/templates/.claude/skills/build-automation-workflow/references/axon-workflow-api.md +279 -0
  13. package/stacks/axon/templates/.claude/skills/build-automation-workflow/references/feature-flag-patterns.md +114 -0
  14. package/stacks/axon/templates/.claude/skills/build-state-change/SKILL.md +396 -0
  15. package/stacks/axon/templates/.claude/skills/build-state-change/references/axon-test-fixture-patterns.md +162 -0
  16. package/stacks/axon/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +56 -0
  17. package/stacks/axon/templates/.claude/skills/build-state-change/references/rest-api-patterns.md +115 -0
  18. package/stacks/axon/templates/.claude/skills/build-state-view/SKILL.md +282 -0
  19. package/stacks/axon/templates/.claude/skills/connect/SKILL.md +178 -0
  20. package/stacks/axon/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +611 -0
  21. package/stacks/axon/templates/.claude/skills/load-slice/SKILL.md +141 -0
  22. package/stacks/axon/templates/.claude/skills/update-slice-status/SKILL.md +105 -0
  23. package/stacks/axon/templates/build-kit/lib/AGENT.md +47 -0
  24. package/stacks/axon/templates/build-kit/lib/agent.sh +20 -0
  25. package/stacks/axon/templates/build-kit/lib/backend-prompt.md +158 -0
  26. package/stacks/axon/templates/build-kit/lib/prompt.md +126 -0
  27. package/stacks/axon/templates/build-kit/lib/ralph.js +369 -0
  28. package/stacks/axon/templates/build-kit/ralph-claude.js +44 -0
  29. package/stacks/axon/templates/build-kit/ralph.sh +98 -0
  30. package/stacks/axon/templates/root/.env.example +5 -0
  31. package/stacks/axon/templates/root/CLAUDE.md +60 -0
  32. package/stacks/axon/templates/root/README.md +44 -0
  33. package/stacks/axon/templates/root/docker-compose.yml +64 -0
  34. package/stacks/axon/templates/root/mvnw +259 -0
  35. package/stacks/axon/templates/root/mvnw.cmd +149 -0
  36. package/stacks/axon/templates/root/pom.xml +125 -0
  37. package/stacks/axon/templates/root/src/main/java/io/axoniq/quickstart/QuickstartApplication.java +128 -0
  38. package/stacks/axon/templates/root/src/main/java/io/axoniq/quickstart/config/WebConfig.java +155 -0
  39. package/stacks/axon/templates/root/src/main/resources/application.properties +2 -0
  40. package/stacks/axon/templates/root/src/main/resources/static/bg-body.png +0 -0
  41. package/stacks/axon/templates/root/src/main/resources/static/bg-header.png +0 -0
  42. package/stacks/axon/templates/root/src/main/resources/static/index.html +596 -0
  43. package/stacks/cratis-csharp/templates/.claude/skills/_shared/cratis-conventions.md +251 -0
  44. package/stacks/cratis-csharp/templates/.claude/skills/build-automation/SKILL.md +99 -0
  45. package/stacks/cratis-csharp/templates/.claude/skills/build-automation/references/patterns.md +115 -0
  46. package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/SKILL.md +163 -0
  47. package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/references/patterns.md +234 -0
  48. package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/SKILL.md +120 -0
  49. package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/references/patterns.md +166 -0
  50. package/stacks/cratis-csharp/templates/.claude/skills/connect/SKILL.md +169 -0
  51. package/stacks/cratis-csharp/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +609 -0
  52. package/stacks/cratis-csharp/templates/.claude/skills/load-slice/SKILL.md +141 -0
  53. package/stacks/cratis-csharp/templates/.claude/skills/update-slice-status/SKILL.md +105 -0
  54. package/stacks/cratis-csharp/templates/build-kit/lib/AGENT.md +58 -0
  55. package/stacks/cratis-csharp/templates/build-kit/lib/agent.sh +20 -0
  56. package/stacks/cratis-csharp/templates/build-kit/lib/backend-prompt.md +129 -0
  57. package/stacks/cratis-csharp/templates/build-kit/lib/prompt.md +124 -0
  58. package/stacks/cratis-csharp/templates/build-kit/lib/ralph.js +302 -0
  59. package/stacks/cratis-csharp/templates/build-kit/ralph-claude.js +37 -0
  60. package/stacks/cratis-csharp/templates/build-kit/ralph.sh +98 -0
  61. package/stacks/cratis-csharp/templates/root/.frontend/index.css +29 -0
  62. package/stacks/cratis-csharp/templates/root/.frontend/index.html +17 -0
  63. package/stacks/cratis-csharp/templates/root/.frontend/main.tsx +18 -0
  64. package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.json +42 -0
  65. package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.node.json +11 -0
  66. package/stacks/cratis-csharp/templates/root/.frontend/vite.config.ts +56 -0
  67. package/stacks/cratis-csharp/templates/root/App.tsx +23 -0
  68. package/stacks/cratis-csharp/templates/root/CLAUDE.md +62 -0
  69. package/stacks/cratis-csharp/templates/root/CratisApp.csproj +25 -0
  70. package/stacks/cratis-csharp/templates/root/CratisApp.sln +18 -0
  71. package/stacks/cratis-csharp/templates/root/GlobalUsings.cs +3 -0
  72. package/stacks/cratis-csharp/templates/root/Home.tsx +102 -0
  73. package/stacks/cratis-csharp/templates/root/Program.cs +26 -0
  74. package/stacks/cratis-csharp/templates/root/README.md +192 -0
  75. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/AllListings.ts +47 -0
  76. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.cs +11 -0
  77. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.ts +12 -0
  78. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/ListingDataTable.tsx +17 -0
  79. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/index.ts +1 -0
  80. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Register.ts +51 -0
  81. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/RegisterDialog.tsx +18 -0
  82. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Registration.cs +27 -0
  83. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/index.ts +1 -0
  84. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeFeature.tsx +22 -0
  85. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeName.cs +3 -0
  86. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/index.ts +1 -0
  87. package/stacks/cratis-csharp/templates/root/appsettings.Development.json +9 -0
  88. package/stacks/cratis-csharp/templates/root/appsettings.json +26 -0
  89. package/stacks/cratis-csharp/templates/root/docker-compose.yml +23 -0
  90. package/stacks/cratis-csharp/templates/root/package.json +33 -0
  91. package/stacks/cratis-csharp/templates/root/tsconfig.json +3 -0
  92. package/stacks/modeling-kit/templates/.claude/settings.local.json +13 -0
  93. package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/SKILL.md +215 -0
  94. package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +176 -0
  95. package/stacks/modeling-kit/templates/.claude/skills/connect/SKILL.md +182 -0
  96. package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +383 -0
  97. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-applying-conways-law/SKILL.md +465 -0
  98. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md +587 -0
  99. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/facilitating-event-modeling-workshops.md +626 -0
  100. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md +542 -0
  101. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-event-models/SKILL.md +497 -0
  102. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +783 -0
  103. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +604 -0
  104. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +699 -0
  105. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-integrating-legacy-systems/SKILL.md +674 -0
  106. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/SKILL.md +231 -0
  107. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/domain-patterns.md +150 -0
  108. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/patterns.md +329 -0
  109. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/snapshotting.md +204 -0
  110. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +352 -0
  111. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/references/project-planning-with-event-modeling.md +341 -0
  112. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/SKILL.md +153 -0
  113. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md +136 -0
  114. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/examples.md +78 -0
  115. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/patterns.md +85 -0
  116. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +655 -0
  117. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-translating-external-events/SKILL.md +479 -0
  118. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/SKILL.md +234 -0
  119. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/SKILL.md +353 -0
  120. package/stacks/modeling-kit/templates/.claude/skills/examples/SKILL.md +200 -0
  121. package/stacks/modeling-kit/templates/.claude/skills/handle-comment/SKILL.md +94 -0
  122. package/stacks/modeling-kit/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +441 -0
  123. package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +290 -0
  124. package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +212 -0
  125. package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +123 -0
  126. package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +353 -0
  127. package/stacks/modeling-kit/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
  128. package/stacks/modeling-kit/templates/.claude/skills/wdyt/SKILL.md +134 -0
  129. package/stacks/modeling-kit/templates/kit/lib/agent.sh +20 -0
  130. package/stacks/modeling-kit/templates/kit/lib/ollama-agent.js +147 -0
  131. package/stacks/modeling-kit/templates/kit/lib/ralph.js +286 -0
  132. package/stacks/modeling-kit/templates/kit/package.json +12 -0
  133. package/stacks/modeling-kit/templates/kit/ralph-claude.js +50 -0
  134. package/stacks/modeling-kit/templates/kit/ralph-ollama.js +38 -0
  135. package/stacks/modeling-kit/templates/kit/ralph.sh +61 -0
  136. package/stacks/modeling-kit/templates/kit/realtime-agent.js +18 -0
  137. package/stacks/modeling-kit/templates/root/CLAUDE.md +56 -0
  138. package/stacks/node/templates/.claude/skills/build-automation/SKILL.md +266 -0
  139. package/stacks/node/templates/.claude/skills/build-state-change/SKILL.md +344 -0
  140. package/stacks/node/templates/.claude/skills/build-state-view/SKILL.md +412 -0
  141. package/stacks/node/templates/.claude/skills/connect/SKILL.md +178 -0
  142. package/stacks/node/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +628 -0
  143. package/stacks/node/templates/.claude/skills/load-slice/SKILL.md +143 -0
  144. package/stacks/node/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
  145. package/stacks/node/templates/build-kit/README.md +86 -0
  146. package/stacks/node/templates/build-kit/lib/AGENT.md +47 -0
  147. package/stacks/node/templates/build-kit/lib/agent.sh +20 -0
  148. package/stacks/node/templates/build-kit/lib/backend-prompt.md +152 -0
  149. package/stacks/node/templates/build-kit/lib/prompt.md +122 -0
  150. package/stacks/node/templates/build-kit/lib/ralph.js +369 -0
  151. package/stacks/node/templates/build-kit/ralph-claude.js +44 -0
  152. package/stacks/node/templates/build-kit/ralph.sh +98 -0
  153. package/stacks/node/templates/root/.env.example +15 -0
  154. package/stacks/node/templates/root/CLAUDE.md +60 -0
  155. package/stacks/node/templates/root/docker-compose.yml +15 -0
  156. package/stacks/node/templates/root/flyway.conf +17 -0
  157. package/stacks/node/templates/root/migrations/V1__schema.sql.example +12 -0
  158. package/stacks/node/templates/root/package.json +50 -0
  159. package/stacks/node/templates/root/server.ts +130 -0
  160. package/stacks/node/templates/root/setup-env.sh +53 -0
  161. package/stacks/node/templates/root/src/common/assertions.ts +6 -0
  162. package/stacks/node/templates/root/src/common/db.ts +32 -0
  163. package/stacks/node/templates/root/src/common/loadPostgresEventstore.ts +23 -0
  164. package/stacks/node/templates/root/src/common/parseEndpoint.ts +51 -0
  165. package/stacks/node/templates/root/src/common/processorDlq.ts +28 -0
  166. package/stacks/node/templates/root/src/common/realtimeBroadcast.ts +12 -0
  167. package/stacks/node/templates/root/src/common/replay.ts +16 -0
  168. package/stacks/node/templates/root/src/common/routes.ts +19 -0
  169. package/stacks/node/templates/root/src/common/testHelpers.ts +44 -0
  170. package/stacks/node/templates/root/src/swagger.ts +34 -0
  171. package/stacks/node/templates/root/src/util/assertions.ts +6 -0
  172. package/stacks/node/templates/root/src/util/hash.ts +9 -0
  173. package/stacks/node/templates/root/src/util/sanitize.ts +23 -0
  174. package/stacks/node/templates/root/tsconfig.json +32 -0
  175. package/stacks/node/templates/root/vercel.json +8 -0
  176. package/stacks/supabase/templates/.claude/skills/build-automation/SKILL.md +266 -0
  177. package/stacks/supabase/templates/.claude/skills/build-state-change/SKILL.md +344 -0
  178. package/stacks/supabase/templates/.claude/skills/build-state-view/SKILL.md +390 -0
  179. package/stacks/supabase/templates/.claude/skills/build-webhook/SKILL.md +303 -0
  180. package/stacks/supabase/templates/.claude/skills/connect/SKILL.md +178 -0
  181. package/stacks/supabase/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +628 -0
  182. package/stacks/supabase/templates/.claude/skills/load-slice/SKILL.md +143 -0
  183. package/stacks/supabase/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
  184. package/stacks/supabase/templates/build-kit/README.md +86 -0
  185. package/stacks/supabase/templates/build-kit/lib/AGENT.md +47 -0
  186. package/stacks/supabase/templates/build-kit/lib/agent.sh +20 -0
  187. package/stacks/supabase/templates/build-kit/lib/backend-prompt.md +152 -0
  188. package/stacks/supabase/templates/build-kit/lib/prompt.md +123 -0
  189. package/stacks/supabase/templates/build-kit/lib/ralph.js +369 -0
  190. package/stacks/supabase/templates/build-kit/ralph-claude.js +44 -0
  191. package/stacks/supabase/templates/build-kit/ralph.sh +98 -0
  192. package/stacks/supabase/templates/root/.env.example +22 -0
  193. package/stacks/supabase/templates/root/CLAUDE.md +61 -0
  194. package/stacks/supabase/templates/root/flyway.conf +17 -0
  195. package/stacks/supabase/templates/root/package.json +52 -0
  196. package/stacks/supabase/templates/root/server.ts +177 -0
  197. package/stacks/supabase/templates/root/setup-env.sh +53 -0
  198. package/stacks/supabase/templates/root/src/common/assertions.ts +6 -0
  199. package/stacks/supabase/templates/root/src/common/db.ts +32 -0
  200. package/stacks/supabase/templates/root/src/common/loadPostgresEventstore.ts +23 -0
  201. package/stacks/supabase/templates/root/src/common/parseEndpoint.ts +51 -0
  202. package/stacks/supabase/templates/root/src/common/processorDlq.ts +28 -0
  203. package/stacks/supabase/templates/root/src/common/realtimeBroadcast.ts +19 -0
  204. package/stacks/supabase/templates/root/src/common/replay.ts +16 -0
  205. package/stacks/supabase/templates/root/src/common/routes.ts +23 -0
  206. package/stacks/supabase/templates/root/src/common/testHelpers.ts +54 -0
  207. package/stacks/supabase/templates/root/src/supabase/README.md +141 -0
  208. package/stacks/supabase/templates/root/src/supabase/api.ts +56 -0
  209. package/stacks/supabase/templates/root/src/supabase/requireOrgaAdmin.ts +32 -0
  210. package/stacks/supabase/templates/root/src/supabase/requireSysUser.ts +40 -0
  211. package/stacks/supabase/templates/root/src/supabase/requireUser.ts +72 -0
  212. package/stacks/supabase/templates/root/src/swagger.ts +34 -0
  213. package/stacks/supabase/templates/root/src/util/assertions.ts +6 -0
  214. package/stacks/supabase/templates/root/src/util/hash.ts +9 -0
  215. package/stacks/supabase/templates/root/src/util/sanitize.ts +23 -0
  216. package/stacks/supabase/templates/root/supabase/config.toml +295 -0
  217. package/stacks/supabase/templates/root/supabase/migrations/V1__schema.sql.example +12 -0
  218. package/stacks/supabase/templates/root/supabase/seed.sql +1 -0
  219. package/stacks/supabase/templates/root/tsconfig.json +32 -0
  220. package/stacks/supabase/templates/root/vercel.json +8 -0
@@ -0,0 +1,130 @@
1
+ import {join} from 'path';
2
+ import {getApplication, startAPI, WebApiSetup} from '@event-driven-io/emmett-expressjs';
3
+ import {glob} from "glob";
4
+ import express, {Application, Request, Response} from 'express';
5
+ import {jsonBigIntReplacer} from './src/util/sanitize';
6
+ import {closeDb} from "./src/common/db";
7
+ import swaggerUi from 'swagger-ui-express'
8
+ import {specs} from './src/swagger';
9
+ import cors from 'cors';
10
+ import {findEventstore} from "./src/common/loadPostgresEventstore";
11
+ import {PostgresEventStore} from "@event-driven-io/emmett-postgresql";
12
+
13
+ async function startServer() {
14
+
15
+ const eventStore = await findEventstore()
16
+ const slicesBase = join(__dirname, 'dist/src/slices');
17
+ const routesPattern = join(slicesBase, '**/routes{,-*}.js');
18
+
19
+ const routeFiles = await glob(routesPattern, {nodir: true});
20
+ console.log('Found route files:', routeFiles);
21
+
22
+ const processorPattern = join(slicesBase, '**/processor{,-*}.js');
23
+ const processorFiles = await glob(processorPattern, {nodir: true});
24
+ console.log('Found processor files:', processorFiles);
25
+
26
+ const commonPattern = join(__dirname, 'src/common/routes{,-*}.@(ts|js)');
27
+ const commonRouteFiles = await glob(commonPattern, {nodir: true});
28
+ console.log('Found common route files:', commonRouteFiles);
29
+
30
+
31
+ const rootApp: Application = express();
32
+ rootApp.set('json replacer', jsonBigIntReplacer);
33
+
34
+ const corsOrigins = process.env.CORS_ORIGINS?.split(',').map(o => o.trim()) ?? ['http://localhost:3000', 'http://localhost:3001'];
35
+ rootApp.use(cors({
36
+ origin: corsOrigins,
37
+ credentials: true,
38
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
39
+ allowedHeaders: ['Content-Type', 'Content-Encoding', 'accept-encoding', 'Authorization','x-user-id','x-causation-id','x-correlation-id']
40
+ }));
41
+
42
+ const webApis: WebApiSetup[] = [];
43
+
44
+ for (const file of routeFiles.concat(commonRouteFiles)) {
45
+ const webApiModule: { api: () => WebApiSetup } = await import(file);
46
+ if (typeof webApiModule.api == 'function') {
47
+ var module = webApiModule.api()
48
+ webApis.push(module);
49
+ } else {
50
+ console.error(`Expected api function to be defined in ${file}`);
51
+ }
52
+ }
53
+
54
+ const startedProcessors: Array<{ stop: () => Promise<void> }> = [];
55
+
56
+ for (const processorFile of processorFiles) {
57
+ const processor: { processor: { start: (eventStore: PostgresEventStore) => Promise<void>; stop: () => Promise<void> } } = await import(processorFile);
58
+ if (typeof processor.processor.start == "function") {
59
+ console.log(`starting processor ${processorFile}`)
60
+ processor.processor.start(eventStore).catch(err => console.error(`Processor ${processorFile} failed:`, err));
61
+ startedProcessors.push(processor.processor);
62
+ }
63
+ }
64
+
65
+ const shutdown = async (signal: string) => {
66
+ console.log(`${signal} received, shutting down processors...`);
67
+ await Promise.allSettled(startedProcessors.map(p => p.stop()));
68
+ await eventStore.close();
69
+ await closeDb();
70
+ console.log('shutdown complete');
71
+ process.exit(0);
72
+ };
73
+
74
+ process.on('SIGINT', () => shutdown('SIGINT'));
75
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
76
+
77
+ // Get the main application from emmett
78
+ const childApp: Application = getApplication({
79
+ apis: webApis,
80
+ disableJsonMiddleware: false,
81
+ enableDefaultExpressEtag: true,
82
+ });
83
+ childApp.set('json replacer', jsonBigIntReplacer);
84
+
85
+ // Swagger UI endpoints
86
+ childApp.use('/api-docs', swaggerUi.serve);
87
+ childApp.get('/api-docs', swaggerUi.setup(specs, {
88
+ swaggerOptions: {
89
+ urls: [
90
+ {
91
+ url: '/swagger.json',
92
+ name: 'JSON',
93
+ },
94
+ ],
95
+ },
96
+ }));
97
+
98
+ // OpenAPI spec endpoint
99
+ childApp.get('/swagger.json', (req: Request, res: Response) => {
100
+ res.setHeader('Content-Type', 'application/json');
101
+ res.send(specs);
102
+ });
103
+
104
+ const port = parseInt(process.env.PORT || '3000', 10);
105
+ console.log(`> Ready on port ${port}`);
106
+
107
+ rootApp.use((req: Request, _res: Response, next) => {
108
+ console.log(`[${req.method}] ${req.path}`);
109
+ next();
110
+ });
111
+
112
+ rootApp.use(express.json());
113
+
114
+
115
+ rootApp.use(childApp)
116
+ // Start the main application
117
+ startAPI(rootApp, {port: port});
118
+
119
+ process.on('unhandledRejection', (reason, promise) => {
120
+ console.error('⛔ Unhandled Rejection:', reason);
121
+ if (reason instanceof Error && reason.stack) {
122
+ console.error('Stack trace:\n', reason.stack);
123
+ }
124
+ });
125
+ }
126
+
127
+ startServer().catch(error => {
128
+ console.error('Failed to start server:', error);
129
+ process.exit(1);
130
+ });
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ prompt() {
5
+ local var_name="$1"
6
+ local prompt_text="$2"
7
+ local value
8
+ read -rp "$prompt_text: " value
9
+ if [[ -z "$value" ]]; then
10
+ echo "Error: $var_name cannot be empty." >&2
11
+ exit 1
12
+ fi
13
+ echo "$value"
14
+ }
15
+
16
+ prompt_secret() {
17
+ local var_name="$1"
18
+ local prompt_text="$2"
19
+ local value
20
+ read -rsp "$prompt_text: " value
21
+ echo "" >&2
22
+ if [[ -z "$value" ]]; then
23
+ echo "Error: $var_name cannot be empty." >&2
24
+ exit 1
25
+ fi
26
+ echo "$value"
27
+ }
28
+
29
+ echo "=== Postgres .env setup ==="
30
+ echo ""
31
+
32
+ DB_HOST=$(prompt DB_HOST "Postgres host (e.g. localhost)")
33
+ DB_PORT=$(prompt DB_PORT "Postgres port (e.g. 5432)")
34
+ DB_NAME=$(prompt DB_NAME "Database name (e.g. postgres)")
35
+ DB_USER=$(prompt DB_USER "Database user (e.g. postgres)")
36
+ DB_PASSWORD=$(prompt_secret DB_PASSWORD "Database password")
37
+ BACKEND_URL=$(prompt BACKEND_URL "Backend URL (e.g. http://localhost:3000)")
38
+
39
+ cat > .env <<EOF
40
+ DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?prepareThreshold=0
41
+
42
+ BACKEND_URL=${BACKEND_URL}
43
+ PORT=3000
44
+ API_URL=${BACKEND_URL}
45
+
46
+ # Flyway configuration
47
+ FLYWAY_URL=jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}
48
+ FLYWAY_USER=${DB_USER}
49
+ FLYWAY_PASSWORD=${DB_PASSWORD}
50
+ EOF
51
+
52
+ echo ""
53
+ echo ".env created successfully."
@@ -0,0 +1,6 @@
1
+ export function assertNotEmpty<T>(value: T): NonNullable<T> {
2
+ if (value === null || value === undefined) {
3
+ throw new Error("Expected non-empty value");
4
+ }
5
+ return value!!;
6
+ }
@@ -0,0 +1,32 @@
1
+ import knex, {Knex} from "knex";
2
+ import pg from "pg";
3
+
4
+ export const postgresUrl = process.env.DATABASE_URL ?? "missing-url"
5
+
6
+ let knexInstance: Knex | null = null;
7
+ let sharedPool: pg.Pool | null = null;
8
+
9
+ export const getKnexInstance = (): Knex => {
10
+ if (!knexInstance) {
11
+ knexInstance = knex({
12
+ client: 'pg',
13
+ connection: postgresUrl,
14
+ pool: { min: 0, max: 5 },
15
+ });
16
+ }
17
+ return knexInstance;
18
+ };
19
+
20
+ export const getSharedPool = (): pg.Pool => {
21
+ if (!sharedPool) {
22
+ sharedPool = new pg.Pool({ connectionString: postgresUrl, max: 5 });
23
+ }
24
+ return sharedPool;
25
+ };
26
+
27
+ export const closeDb = async (): Promise<void> => {
28
+ await knexInstance?.destroy();
29
+ await sharedPool?.end();
30
+ knexInstance = null;
31
+ sharedPool = null;
32
+ };
@@ -0,0 +1,23 @@
1
+ import {getPostgreSQLEventStore} from "@event-driven-io/emmett-postgresql";
2
+ import {projections} from "@event-driven-io/emmett";
3
+ import {postgresUrl, getSharedPool} from "./db";
4
+
5
+ let eventStoreInstance: ReturnType<typeof getPostgreSQLEventStore> | null = null;
6
+
7
+ export const findEventstore = async () => {
8
+ if (!eventStoreInstance) {
9
+ eventStoreInstance = getPostgreSQLEventStore(postgresUrl, {
10
+ schema: {
11
+ autoMigration: "CreateOrUpdate"
12
+ },
13
+ connectionOptions: {
14
+ pooled: true,
15
+ pool: getSharedPool(),
16
+ },
17
+ projections: projections.inline([
18
+ ]),
19
+ });
20
+ await eventStoreInstance.schema.migrate();
21
+ }
22
+ return eventStoreInstance;
23
+ };
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Copyright (c) 2025 Nebulit GmbH
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ const serviceURI = "http://localhost:3000"
7
+
8
+ export function parseEndpoint(endpoint: string, data?: any) {
9
+ var parsedEndpoint = endpoint?.startsWith("/") ? endpoint.substring(1) : endpoint
10
+ return serviceURI + "/" + lowercaseFirstCharacter(parsedEndpoint).replace(/{(\w+)}/g, (match, param) => {
11
+ return param && data && data[param] !== undefined ? data[param] : match;
12
+ })
13
+ }
14
+
15
+
16
+ export function parseQueryEndpoint(
17
+ endpoint: string,
18
+ queries?: Record<string, string>
19
+ ) {
20
+ const parsedEndpoint = endpoint.startsWith("/")
21
+ ? endpoint.substring(1)
22
+ : endpoint;
23
+
24
+ const basePath =
25
+ serviceURI + "/api/query/" + parsedEndpoint;
26
+
27
+ const queryString = queries
28
+ ? "?" + new URLSearchParams(filterEmptyEntries(queries)).toString()
29
+ : "";
30
+
31
+ return basePath + queryString;
32
+ }
33
+
34
+ function filterEmptyEntries(queries?: Record<string, string>): Record<string, string> {
35
+ if (!queries) return {};
36
+ return Object.fromEntries(
37
+ Object.entries(queries).filter(([key, value]) => value !== "")
38
+ );
39
+ }
40
+
41
+
42
+ function lowercaseFirstCharacter(inputString: string) {
43
+ // Check if the string is not empty
44
+ if (inputString?.length > 0) {
45
+ // Capitalize the first character and concatenate the rest of the string
46
+ return inputString.charAt(0).toLowerCase() + inputString.substring(1);
47
+ } else {
48
+ // Return an empty string if the input is empty
49
+ return "";
50
+ }
51
+ }
@@ -0,0 +1,28 @@
1
+ import {getKnexInstance} from './db';
2
+ import type {AnyRecordedMessageMetadata, RecordedMessage} from '@event-driven-io/emmett';
3
+
4
+ export const storeDlqMessage = async (
5
+ processorId: string,
6
+ message: RecordedMessage<any, AnyRecordedMessageMetadata>,
7
+ error: unknown,
8
+ ): Promise<void> => {
9
+
10
+ try {
11
+ console.log(`Processing DLQ ${JSON.stringify({ type: message.type, data: message.data, metadata: message.metadata } , (key, value) =>
12
+ typeof value === 'bigint' ? value.toString() : value
13
+ )}`)
14
+ await getKnexInstance()('processor_dlq').insert({
15
+ processor_id: processorId,
16
+ stream_id: message.metadata.streamName,
17
+ event: JSON.parse(
18
+ JSON.stringify({ type: message.type, data: message.data, metadata: message.metadata } , (key, value) =>
19
+ typeof value === 'bigint' ? value.toString() : value
20
+ )
21
+ ),
22
+ error: error instanceof Error ? error.message : String(error),
23
+ });
24
+ } catch (dlqError) {
25
+ console.error('Failed to write to processor_dlq:', dlqError);
26
+ }
27
+ };
28
+
@@ -0,0 +1,12 @@
1
+ const clients = new Set<(event: string, payload: unknown) => void>();
2
+
3
+ export function subscribeRealtime(handler: (event: string, payload: unknown) => void): () => void {
4
+ clients.add(handler);
5
+ return () => clients.delete(handler);
6
+ }
7
+
8
+ export async function broadcastRealtime(topic: string, event: string, payload: unknown): Promise<void> {
9
+ for (const handler of clients) {
10
+ handler(event, payload);
11
+ }
12
+ }
@@ -0,0 +1,16 @@
1
+ import {PostgreSQLProjectionDefinition, rebuildPostgreSQLProjections} from "@event-driven-io/emmett-postgresql";
2
+ import {postgresUrl} from "./db";
3
+ import {glob} from "glob";
4
+ import path from "path";
5
+
6
+ const slicesRoot = path.resolve(__dirname, '../slices');
7
+
8
+ export const replayProjection = async (projectionName: string): Promise<void> => {
9
+ const [filePath] = await glob(`**/${projectionName}.{ts|js}`, {cwd: slicesRoot, absolute: true});
10
+ if (!filePath) throw new Error(`Projection not found: ${projectionName}`);
11
+
12
+ const projectionImport = await import(filePath);
13
+ const projection: PostgreSQLProjectionDefinition = projectionImport[projectionName];
14
+
15
+ return rebuildPostgreSQLProjections({projection, connectionString: postgresUrl}).start();
16
+ }
@@ -0,0 +1,19 @@
1
+ import {Request, Response, Router} from 'express';
2
+ import {WebApiSetup} from "@event-driven-io/emmett-expressjs";
3
+ import {assertNotEmpty} from "../util/assertions";
4
+ import {replayProjection} from "./replay";
5
+
6
+
7
+ export const api =
8
+ (
9
+ // external dependencies
10
+ ): WebApiSetup =>
11
+ (router: Router): void => {
12
+
13
+ router.post('/api/replay/:projection', async (req: Request, res: Response) => {
14
+ const projection = assertNotEmpty(req.params.projection)
15
+ await replayProjection(projection)
16
+ res.status(200).json({"projection":projection})
17
+ });
18
+ };
19
+
@@ -0,0 +1,44 @@
1
+ import {execSync} from 'child_process';
2
+ import {writeFileSync, unlinkSync} from 'fs';
3
+ import {tmpdir} from 'os';
4
+ import {join} from 'path';
5
+
6
+ export async function runFlywayMigrations(connectionString: string): Promise<void> {
7
+ const url = new URL(connectionString);
8
+ const jdbcUrl = `jdbc:postgresql://${url.hostname}:${url.port || 5432}${url.pathname}`;
9
+ const user = url.username;
10
+ const password = url.password;
11
+
12
+ const tempConfigPath = join(tmpdir(), `flyway-test-${Date.now()}.conf`);
13
+ const migrationsPath = join(process.cwd(), 'migrations');
14
+
15
+ const config = `
16
+ flyway.url=${jdbcUrl}
17
+ flyway.user=${user}
18
+ flyway.password=${password}
19
+ flyway.locations=filesystem:${migrationsPath}
20
+ flyway.schemas=public
21
+ flyway.placeholderReplacement=false
22
+ flyway.validateOnMigrate=true
23
+ flyway.cleanDisabled=false
24
+ `;
25
+
26
+ try {
27
+ writeFileSync(tempConfigPath, config, 'utf8');
28
+ execSync(`flyway -configFiles=${tempConfigPath} migrate`, {
29
+ stdio: 'pipe',
30
+ encoding: 'utf8'
31
+ });
32
+ } catch (error: any) {
33
+ console.error('Flyway migration failed:', error.message);
34
+ if (error.stdout) console.error('STDOUT:', error.stdout);
35
+ if (error.stderr) console.error('STDERR:', error.stderr);
36
+ throw new Error(`Flyway migration failed: ${error.message}`);
37
+ } finally {
38
+ try {
39
+ unlinkSync(tempConfigPath);
40
+ } catch {
41
+ // ignore
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,34 @@
1
+ import swaggerJsdoc from 'swagger-jsdoc';
2
+
3
+ const options = {
4
+ definition: {
5
+ openapi: '3.0.0',
6
+ info: {
7
+ title: 'Context API',
8
+ version: '1.0.0',
9
+ description: 'Event-driven API for shift, clerk, and task management',
10
+ },
11
+ servers: [
12
+ {
13
+ url: 'http://localhost:3000',
14
+ description: 'Development server',
15
+ },
16
+ {
17
+ url: process.env.API_URL || 'http://localhost:3000',
18
+ description: 'Production server',
19
+ },
20
+ ],
21
+ components: {
22
+ securitySchemes: {
23
+ bearerAuth: {
24
+ type: 'http',
25
+ scheme: 'bearer',
26
+ bearerFormat: 'JWT',
27
+ },
28
+ },
29
+ },
30
+ },
31
+ apis: ['./src/slices/**/routes.ts'],
32
+ };
33
+
34
+ export const specs = swaggerJsdoc(options);
@@ -0,0 +1,6 @@
1
+ export function assertNotEmpty<T>(value: T): NonNullable<T> {
2
+ if (value === null || value === undefined) {
3
+ throw new Error("Expected non-empty value");
4
+ }
5
+ return value!!;
6
+ }
@@ -0,0 +1,9 @@
1
+ // Fast djb2-style hash of any object — used to detect drift
2
+ export function hashMeta(entry: unknown): string {
3
+ const str = JSON.stringify(entry);
4
+ let h = 5381;
5
+ for (let i = 0; i < str.length; i++) {
6
+ h = (((h << 5) + h) ^ str.charCodeAt(i)) >>> 0;
7
+ }
8
+ return h.toString(36);
9
+ }
@@ -0,0 +1,23 @@
1
+ function sanitizeValue(value: unknown): unknown {
2
+ if (typeof value === 'bigint') {
3
+ return Number.isSafeInteger(Number(value)) ? Number(value) : value.toString();
4
+ }
5
+ if (Array.isArray(value)) {
6
+ return value.map(sanitizeValue);
7
+ }
8
+ if (value !== null && typeof value === 'object') {
9
+ return Object.fromEntries(
10
+ Object.entries(value).map(([k, v]) => [k, sanitizeValue(v)])
11
+ );
12
+ }
13
+ return value;
14
+ }
15
+
16
+ export function sanitize<T>(value: T): unknown {
17
+ return sanitizeValue(value);
18
+ }
19
+
20
+ export const jsonBigIntReplacer = (_key: string, value: unknown): unknown =>
21
+ typeof value === 'bigint'
22
+ ? (Number.isSafeInteger(Number(value)) ? Number(value) : value.toString())
23
+ : value;
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES6",
4
+ "module": "commonjs",
5
+ "strict": true,
6
+ "esModuleInterop": true,
7
+ "moduleResolution": "node",
8
+ "outDir": "dist",
9
+ "baseUrl": ".",
10
+ "paths": {
11
+ "*": [
12
+ "node_modules/*"
13
+ ]
14
+ },
15
+ "resolveJsonModule": true,
16
+ "skipLibCheck": true,
17
+ "lib": [
18
+ "es2020"
19
+ ],
20
+ "allowJs": true,
21
+ "incremental": true,
22
+ "isolatedModules": true
23
+ },
24
+ "include": [
25
+ "**/*.ts"
26
+ ],
27
+ "exclude": [
28
+ "node_modules",
29
+ "**/*.sample",
30
+ "realtime-agent"
31
+ ]
32
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "buildCommand": "npm run build",
3
+ "functions": {
4
+ "server.js": {
5
+ "includeFiles": "dist/**"
6
+ }
7
+ }
8
+ }