@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.
- package/README.md +175 -0
- package/cli.js +676 -0
- package/package.json +35 -0
- package/shared/build-kit/code-export.mjs +560 -0
- package/shared/build-kit/lib/ollama-agent.js +147 -0
- package/shared/build-kit/package.json +11 -0
- package/shared/build-kit/ralph-ollama.js +39 -0
- package/shared/build-kit/realtime-agent.js +18 -0
- package/stacks/axon/templates/.claude/skills/build-automation/SKILL.md +407 -0
- package/stacks/axon/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +114 -0
- package/stacks/axon/templates/.claude/skills/build-automation-workflow/SKILL.md +496 -0
- package/stacks/axon/templates/.claude/skills/build-automation-workflow/references/axon-workflow-api.md +279 -0
- package/stacks/axon/templates/.claude/skills/build-automation-workflow/references/feature-flag-patterns.md +114 -0
- package/stacks/axon/templates/.claude/skills/build-state-change/SKILL.md +396 -0
- package/stacks/axon/templates/.claude/skills/build-state-change/references/axon-test-fixture-patterns.md +162 -0
- package/stacks/axon/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +56 -0
- package/stacks/axon/templates/.claude/skills/build-state-change/references/rest-api-patterns.md +115 -0
- package/stacks/axon/templates/.claude/skills/build-state-view/SKILL.md +282 -0
- package/stacks/axon/templates/.claude/skills/connect/SKILL.md +178 -0
- package/stacks/axon/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +611 -0
- package/stacks/axon/templates/.claude/skills/load-slice/SKILL.md +141 -0
- package/stacks/axon/templates/.claude/skills/update-slice-status/SKILL.md +105 -0
- package/stacks/axon/templates/build-kit/lib/AGENT.md +47 -0
- package/stacks/axon/templates/build-kit/lib/agent.sh +20 -0
- package/stacks/axon/templates/build-kit/lib/backend-prompt.md +158 -0
- package/stacks/axon/templates/build-kit/lib/prompt.md +126 -0
- package/stacks/axon/templates/build-kit/lib/ralph.js +369 -0
- package/stacks/axon/templates/build-kit/ralph-claude.js +44 -0
- package/stacks/axon/templates/build-kit/ralph.sh +98 -0
- package/stacks/axon/templates/root/.env.example +5 -0
- package/stacks/axon/templates/root/CLAUDE.md +60 -0
- package/stacks/axon/templates/root/README.md +44 -0
- package/stacks/axon/templates/root/docker-compose.yml +64 -0
- package/stacks/axon/templates/root/mvnw +259 -0
- package/stacks/axon/templates/root/mvnw.cmd +149 -0
- package/stacks/axon/templates/root/pom.xml +125 -0
- package/stacks/axon/templates/root/src/main/java/io/axoniq/quickstart/QuickstartApplication.java +128 -0
- package/stacks/axon/templates/root/src/main/java/io/axoniq/quickstart/config/WebConfig.java +155 -0
- package/stacks/axon/templates/root/src/main/resources/application.properties +2 -0
- package/stacks/axon/templates/root/src/main/resources/static/bg-body.png +0 -0
- package/stacks/axon/templates/root/src/main/resources/static/bg-header.png +0 -0
- package/stacks/axon/templates/root/src/main/resources/static/index.html +596 -0
- package/stacks/cratis-csharp/templates/.claude/skills/_shared/cratis-conventions.md +251 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-automation/SKILL.md +99 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-automation/references/patterns.md +115 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/SKILL.md +163 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/references/patterns.md +234 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/SKILL.md +120 -0
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/references/patterns.md +166 -0
- package/stacks/cratis-csharp/templates/.claude/skills/connect/SKILL.md +169 -0
- package/stacks/cratis-csharp/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +609 -0
- package/stacks/cratis-csharp/templates/.claude/skills/load-slice/SKILL.md +141 -0
- package/stacks/cratis-csharp/templates/.claude/skills/update-slice-status/SKILL.md +105 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/AGENT.md +58 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/agent.sh +20 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/backend-prompt.md +129 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/prompt.md +124 -0
- package/stacks/cratis-csharp/templates/build-kit/lib/ralph.js +302 -0
- package/stacks/cratis-csharp/templates/build-kit/ralph-claude.js +37 -0
- package/stacks/cratis-csharp/templates/build-kit/ralph.sh +98 -0
- package/stacks/cratis-csharp/templates/root/.frontend/index.css +29 -0
- package/stacks/cratis-csharp/templates/root/.frontend/index.html +17 -0
- package/stacks/cratis-csharp/templates/root/.frontend/main.tsx +18 -0
- package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.json +42 -0
- package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.node.json +11 -0
- package/stacks/cratis-csharp/templates/root/.frontend/vite.config.ts +56 -0
- package/stacks/cratis-csharp/templates/root/App.tsx +23 -0
- package/stacks/cratis-csharp/templates/root/CLAUDE.md +62 -0
- package/stacks/cratis-csharp/templates/root/CratisApp.csproj +25 -0
- package/stacks/cratis-csharp/templates/root/CratisApp.sln +18 -0
- package/stacks/cratis-csharp/templates/root/GlobalUsings.cs +3 -0
- package/stacks/cratis-csharp/templates/root/Home.tsx +102 -0
- package/stacks/cratis-csharp/templates/root/Program.cs +26 -0
- package/stacks/cratis-csharp/templates/root/README.md +192 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/AllListings.ts +47 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.cs +11 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.ts +12 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/ListingDataTable.tsx +17 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/index.ts +1 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Register.ts +51 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/RegisterDialog.tsx +18 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Registration.cs +27 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/index.ts +1 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeFeature.tsx +22 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeName.cs +3 -0
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/index.ts +1 -0
- package/stacks/cratis-csharp/templates/root/appsettings.Development.json +9 -0
- package/stacks/cratis-csharp/templates/root/appsettings.json +26 -0
- package/stacks/cratis-csharp/templates/root/docker-compose.yml +23 -0
- package/stacks/cratis-csharp/templates/root/package.json +33 -0
- package/stacks/cratis-csharp/templates/root/tsconfig.json +3 -0
- package/stacks/modeling-kit/templates/.claude/settings.local.json +13 -0
- package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/SKILL.md +215 -0
- package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +176 -0
- package/stacks/modeling-kit/templates/.claude/skills/connect/SKILL.md +182 -0
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +383 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-applying-conways-law/SKILL.md +465 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md +587 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/facilitating-event-modeling-workshops.md +626 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md +542 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-event-models/SKILL.md +497 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +783 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +604 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +699 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-integrating-legacy-systems/SKILL.md +674 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/SKILL.md +231 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/domain-patterns.md +150 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/patterns.md +329 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/snapshotting.md +204 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +352 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/references/project-planning-with-event-modeling.md +341 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/SKILL.md +153 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md +136 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/examples.md +78 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/patterns.md +85 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +655 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-translating-external-events/SKILL.md +479 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/SKILL.md +234 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/SKILL.md +353 -0
- package/stacks/modeling-kit/templates/.claude/skills/examples/SKILL.md +200 -0
- package/stacks/modeling-kit/templates/.claude/skills/handle-comment/SKILL.md +94 -0
- package/stacks/modeling-kit/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +441 -0
- package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +290 -0
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +212 -0
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +123 -0
- package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +353 -0
- package/stacks/modeling-kit/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
- package/stacks/modeling-kit/templates/.claude/skills/wdyt/SKILL.md +134 -0
- package/stacks/modeling-kit/templates/kit/lib/agent.sh +20 -0
- package/stacks/modeling-kit/templates/kit/lib/ollama-agent.js +147 -0
- package/stacks/modeling-kit/templates/kit/lib/ralph.js +286 -0
- package/stacks/modeling-kit/templates/kit/package.json +12 -0
- package/stacks/modeling-kit/templates/kit/ralph-claude.js +50 -0
- package/stacks/modeling-kit/templates/kit/ralph-ollama.js +38 -0
- package/stacks/modeling-kit/templates/kit/ralph.sh +61 -0
- package/stacks/modeling-kit/templates/kit/realtime-agent.js +18 -0
- package/stacks/modeling-kit/templates/root/CLAUDE.md +56 -0
- package/stacks/node/templates/.claude/skills/build-automation/SKILL.md +266 -0
- package/stacks/node/templates/.claude/skills/build-state-change/SKILL.md +344 -0
- package/stacks/node/templates/.claude/skills/build-state-view/SKILL.md +412 -0
- package/stacks/node/templates/.claude/skills/connect/SKILL.md +178 -0
- package/stacks/node/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +628 -0
- package/stacks/node/templates/.claude/skills/load-slice/SKILL.md +143 -0
- package/stacks/node/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
- package/stacks/node/templates/build-kit/README.md +86 -0
- package/stacks/node/templates/build-kit/lib/AGENT.md +47 -0
- package/stacks/node/templates/build-kit/lib/agent.sh +20 -0
- package/stacks/node/templates/build-kit/lib/backend-prompt.md +152 -0
- package/stacks/node/templates/build-kit/lib/prompt.md +122 -0
- package/stacks/node/templates/build-kit/lib/ralph.js +369 -0
- package/stacks/node/templates/build-kit/ralph-claude.js +44 -0
- package/stacks/node/templates/build-kit/ralph.sh +98 -0
- package/stacks/node/templates/root/.env.example +15 -0
- package/stacks/node/templates/root/CLAUDE.md +60 -0
- package/stacks/node/templates/root/docker-compose.yml +15 -0
- package/stacks/node/templates/root/flyway.conf +17 -0
- package/stacks/node/templates/root/migrations/V1__schema.sql.example +12 -0
- package/stacks/node/templates/root/package.json +50 -0
- package/stacks/node/templates/root/server.ts +130 -0
- package/stacks/node/templates/root/setup-env.sh +53 -0
- package/stacks/node/templates/root/src/common/assertions.ts +6 -0
- package/stacks/node/templates/root/src/common/db.ts +32 -0
- package/stacks/node/templates/root/src/common/loadPostgresEventstore.ts +23 -0
- package/stacks/node/templates/root/src/common/parseEndpoint.ts +51 -0
- package/stacks/node/templates/root/src/common/processorDlq.ts +28 -0
- package/stacks/node/templates/root/src/common/realtimeBroadcast.ts +12 -0
- package/stacks/node/templates/root/src/common/replay.ts +16 -0
- package/stacks/node/templates/root/src/common/routes.ts +19 -0
- package/stacks/node/templates/root/src/common/testHelpers.ts +44 -0
- package/stacks/node/templates/root/src/swagger.ts +34 -0
- package/stacks/node/templates/root/src/util/assertions.ts +6 -0
- package/stacks/node/templates/root/src/util/hash.ts +9 -0
- package/stacks/node/templates/root/src/util/sanitize.ts +23 -0
- package/stacks/node/templates/root/tsconfig.json +32 -0
- package/stacks/node/templates/root/vercel.json +8 -0
- package/stacks/supabase/templates/.claude/skills/build-automation/SKILL.md +266 -0
- package/stacks/supabase/templates/.claude/skills/build-state-change/SKILL.md +344 -0
- package/stacks/supabase/templates/.claude/skills/build-state-view/SKILL.md +390 -0
- package/stacks/supabase/templates/.claude/skills/build-webhook/SKILL.md +303 -0
- package/stacks/supabase/templates/.claude/skills/connect/SKILL.md +178 -0
- package/stacks/supabase/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +628 -0
- package/stacks/supabase/templates/.claude/skills/load-slice/SKILL.md +143 -0
- package/stacks/supabase/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
- package/stacks/supabase/templates/build-kit/README.md +86 -0
- package/stacks/supabase/templates/build-kit/lib/AGENT.md +47 -0
- package/stacks/supabase/templates/build-kit/lib/agent.sh +20 -0
- package/stacks/supabase/templates/build-kit/lib/backend-prompt.md +152 -0
- package/stacks/supabase/templates/build-kit/lib/prompt.md +123 -0
- package/stacks/supabase/templates/build-kit/lib/ralph.js +369 -0
- package/stacks/supabase/templates/build-kit/ralph-claude.js +44 -0
- package/stacks/supabase/templates/build-kit/ralph.sh +98 -0
- package/stacks/supabase/templates/root/.env.example +22 -0
- package/stacks/supabase/templates/root/CLAUDE.md +61 -0
- package/stacks/supabase/templates/root/flyway.conf +17 -0
- package/stacks/supabase/templates/root/package.json +52 -0
- package/stacks/supabase/templates/root/server.ts +177 -0
- package/stacks/supabase/templates/root/setup-env.sh +53 -0
- package/stacks/supabase/templates/root/src/common/assertions.ts +6 -0
- package/stacks/supabase/templates/root/src/common/db.ts +32 -0
- package/stacks/supabase/templates/root/src/common/loadPostgresEventstore.ts +23 -0
- package/stacks/supabase/templates/root/src/common/parseEndpoint.ts +51 -0
- package/stacks/supabase/templates/root/src/common/processorDlq.ts +28 -0
- package/stacks/supabase/templates/root/src/common/realtimeBroadcast.ts +19 -0
- package/stacks/supabase/templates/root/src/common/replay.ts +16 -0
- package/stacks/supabase/templates/root/src/common/routes.ts +23 -0
- package/stacks/supabase/templates/root/src/common/testHelpers.ts +54 -0
- package/stacks/supabase/templates/root/src/supabase/README.md +141 -0
- package/stacks/supabase/templates/root/src/supabase/api.ts +56 -0
- package/stacks/supabase/templates/root/src/supabase/requireOrgaAdmin.ts +32 -0
- package/stacks/supabase/templates/root/src/supabase/requireSysUser.ts +40 -0
- package/stacks/supabase/templates/root/src/supabase/requireUser.ts +72 -0
- package/stacks/supabase/templates/root/src/swagger.ts +34 -0
- package/stacks/supabase/templates/root/src/util/assertions.ts +6 -0
- package/stacks/supabase/templates/root/src/util/hash.ts +9 -0
- package/stacks/supabase/templates/root/src/util/sanitize.ts +23 -0
- package/stacks/supabase/templates/root/supabase/config.toml +295 -0
- package/stacks/supabase/templates/root/supabase/migrations/V1__schema.sql.example +12 -0
- package/stacks/supabase/templates/root/supabase/seed.sql +1 -0
- package/stacks/supabase/templates/root/tsconfig.json +32 -0
- 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,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,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
|
+
}
|