@eventmodelers/cli 1.0.45 → 1.0.46

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 (102) hide show
  1. package/README.md +4 -1
  2. package/cli.js +53 -1
  3. package/package.json +2 -2
  4. package/shared/build-kit/lib/checks/README.md +59 -0
  5. package/shared/build-kit/lib/ralph.js +110 -28
  6. package/shared/build-kit/lib/util/find-slice.cjs +59 -0
  7. package/shared/build-kit/ralph-claude.js +6 -2
  8. package/stacks/blank/templates/build-kit/lib/backend-prompt.md +106 -102
  9. package/stacks/blank/templates/build-kit/lib/prompt.md +102 -106
  10. package/stacks/kurrent/templates/.claude/skills/build-automation/SKILL.md +422 -0
  11. package/stacks/kurrent/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +19 -0
  12. package/stacks/kurrent/templates/.claude/skills/build-automation/references/idempotent-dispatch-patterns.md +65 -0
  13. package/stacks/kurrent/templates/.claude/skills/build-state-change/SKILL.md +418 -0
  14. package/stacks/kurrent/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +41 -0
  15. package/stacks/kurrent/templates/.claude/skills/build-state-change/references/integration-test-patterns.md +64 -0
  16. package/stacks/kurrent/templates/.claude/skills/build-state-view/SKILL.md +391 -0
  17. package/stacks/kurrent/templates/build-kit/CLAUDE.md +122 -0
  18. package/stacks/kurrent/templates/build-kit/lib/AGENT.md +73 -0
  19. package/stacks/kurrent/templates/build-kit/lib/backend-prompt.md +169 -0
  20. package/stacks/kurrent/templates/build-kit/lib/prompt.md +128 -0
  21. package/stacks/kurrent/templates/root/README.md +46 -0
  22. package/stacks/kurrent/templates/root/docker-compose.yml +45 -0
  23. package/stacks/kurrent/templates/root/mvnw +259 -0
  24. package/stacks/kurrent/templates/root/mvnw.cmd +149 -0
  25. package/stacks/kurrent/templates/root/pom.xml +152 -0
  26. package/stacks/kurrent/templates/root/src/main/java/com/example/quickstart/QuickstartApplication.java +12 -0
  27. package/stacks/kurrent/templates/root/src/main/java/com/example/quickstart/common/EventStore.java +91 -0
  28. package/stacks/kurrent/templates/root/src/main/java/com/example/quickstart/config/KurrentConfiguration.java +42 -0
  29. package/stacks/kurrent/templates/root/src/main/resources/application.properties +14 -0
  30. package/stacks/kurrent/templates/root/src/main/resources/static/index.html +11 -0
  31. package/stacks/node/templates/build-kit/CLAUDE.md +22 -0
  32. package/stacks/node/templates/build-kit/lib/check-commit-scope.cjs +123 -0
  33. package/stacks/node/templates/build-kit/lib/checks/00-blocked-paths.cjs +28 -0
  34. package/stacks/node/templates/build-kit/lib/checks/10-slice-scope.cjs +29 -0
  35. package/stacks/node/templates/build-kit/lib/checks/20-append-only-migrations.cjs +20 -0
  36. package/stacks/node/templates/build-kit/lib/checks/30-test-file-present.cjs +44 -0
  37. package/stacks/node/templates/build-kit/lib/checks/40-no-invented-fields.cjs +92 -0
  38. package/stacks/node/templates/build-kit/lib/checks/50-spec-coverage.cjs +50 -0
  39. package/stacks/node/templates/build-kit/lib/checks/90-tsc-build.cjs +22 -0
  40. package/stacks/node/templates/root/.githooks/pre-commit +11 -0
  41. package/stacks/node/templates/root/package.json +2 -1
  42. package/stacks/node/templates/root/setup-env.sh +7 -1
  43. package/stacks/opencqrs/templates/.claude/skills/build-automation/SKILL.md +434 -0
  44. package/stacks/opencqrs/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +19 -0
  45. package/stacks/opencqrs/templates/.claude/skills/build-automation/references/idempotent-dispatch-patterns.md +62 -0
  46. package/stacks/opencqrs/templates/.claude/skills/build-state-change/SKILL.md +413 -0
  47. package/stacks/opencqrs/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +46 -0
  48. package/stacks/opencqrs/templates/.claude/skills/build-state-change/references/rest-api-patterns.md +145 -0
  49. package/stacks/opencqrs/templates/.claude/skills/build-state-change/references/test-fixture-patterns.md +85 -0
  50. package/stacks/opencqrs/templates/.claude/skills/build-state-view/SKILL.md +354 -0
  51. package/stacks/opencqrs/templates/build-kit/CLAUDE.md +101 -0
  52. package/stacks/opencqrs/templates/build-kit/lib/AGENT.md +58 -0
  53. package/stacks/opencqrs/templates/build-kit/lib/backend-prompt.md +169 -0
  54. package/stacks/opencqrs/templates/build-kit/lib/prompt.md +128 -0
  55. package/stacks/opencqrs/templates/root/README.md +42 -0
  56. package/stacks/opencqrs/templates/root/docker-compose.yml +37 -0
  57. package/stacks/opencqrs/templates/root/mvnw +259 -0
  58. package/stacks/opencqrs/templates/root/mvnw.cmd +149 -0
  59. package/stacks/opencqrs/templates/root/pom.xml +139 -0
  60. package/stacks/opencqrs/templates/root/src/main/java/com/example/quickstart/QuickstartApplication.java +12 -0
  61. package/stacks/opencqrs/templates/root/src/main/java/com/example/quickstart/config/CqrsConfiguration.java +74 -0
  62. package/stacks/opencqrs/templates/root/src/main/resources/application.properties +23 -0
  63. package/stacks/opencqrs/templates/root/src/main/resources/schema.sql +19 -0
  64. package/stacks/opencqrs/templates/root/src/main/resources/static/index.html +11 -0
  65. package/stacks/supabase/templates/build-kit/CLAUDE.md +25 -0
  66. package/stacks/supabase/templates/build-kit/lib/check-commit-scope.cjs +126 -0
  67. package/stacks/supabase/templates/build-kit/lib/checks/00-blocked-paths.cjs +28 -0
  68. package/stacks/supabase/templates/build-kit/lib/checks/10-slice-scope.cjs +34 -0
  69. package/stacks/supabase/templates/build-kit/lib/checks/20-append-only-migrations.cjs +21 -0
  70. package/stacks/supabase/templates/build-kit/lib/checks/30-test-file-present.cjs +44 -0
  71. package/stacks/supabase/templates/build-kit/lib/checks/40-no-invented-fields.cjs +92 -0
  72. package/stacks/supabase/templates/build-kit/lib/checks/50-spec-coverage.cjs +50 -0
  73. package/stacks/supabase/templates/build-kit/lib/checks/90-tsc-build.cjs +22 -0
  74. package/stacks/supabase/templates/root/.githooks/pre-commit +11 -0
  75. package/stacks/supabase/templates/root/package.json +2 -1
  76. package/stacks/supabase/templates/root/setup-env.sh +7 -1
  77. package/stacks/umadb/templates/.claude/skills/build-automation/SKILL.md +313 -0
  78. package/stacks/umadb/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +42 -0
  79. package/stacks/umadb/templates/.claude/skills/build-state-change/SKILL.md +376 -0
  80. package/stacks/umadb/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +42 -0
  81. package/stacks/umadb/templates/.claude/skills/build-state-change/references/umadb-query-patterns.md +78 -0
  82. package/stacks/umadb/templates/.claude/skills/build-state-view/SKILL.md +338 -0
  83. package/stacks/umadb/templates/build-kit/CLAUDE.md +94 -0
  84. package/stacks/umadb/templates/build-kit/lib/AGENT.md +47 -0
  85. package/stacks/umadb/templates/build-kit/lib/backend-prompt.md +169 -0
  86. package/stacks/umadb/templates/build-kit/lib/prompt.md +128 -0
  87. package/stacks/umadb/templates/root/.mvn/wrapper/maven-wrapper.properties +19 -0
  88. package/stacks/umadb/templates/root/README.md +48 -0
  89. package/stacks/umadb/templates/root/docker-compose.yml +29 -0
  90. package/stacks/umadb/templates/root/mvnw +259 -0
  91. package/stacks/umadb/templates/root/mvnw.cmd +149 -0
  92. package/stacks/umadb/templates/root/pom.xml +151 -0
  93. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/QuickstartApplication.java +12 -0
  94. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/config/UmaDbConfig.java +39 -0
  95. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/DecisionModelLoader.java +76 -0
  96. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/EventCodec.java +40 -0
  97. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/EventDispatcher.java +100 -0
  98. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/OptimisticConcurrencyException.java +14 -0
  99. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/SliceEventListener.java +23 -0
  100. package/stacks/umadb/templates/root/src/main/resources/application.properties +17 -0
  101. package/stacks/umadb/templates/root/src/test/java/io/umadb/quickstart/testsupport/InMemoryUmaDbClient.java +135 -0
  102. package/stacks/umadb/templates/root/src/test/resources/application.properties +8 -0
@@ -0,0 +1,149 @@
1
+ <# : batch portion
2
+ @REM ----------------------------------------------------------------------------
3
+ @REM Licensed to the Apache Software Foundation (ASF) under one
4
+ @REM or more contributor license agreements. See the NOTICE file
5
+ @REM distributed with this work for additional information
6
+ @REM regarding copyright ownership. The ASF licenses this file
7
+ @REM to you under the Apache License, Version 2.0 (the
8
+ @REM "License"); you may not use this file except in compliance
9
+ @REM with the License. You may obtain a copy of the License at
10
+ @REM
11
+ @REM http://www.apache.org/licenses/LICENSE-2.0
12
+ @REM
13
+ @REM Unless required by applicable law or agreed to in writing,
14
+ @REM software distributed under the License is distributed on an
15
+ @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ @REM KIND, either express or implied. See the License for the
17
+ @REM specific language governing permissions and limitations
18
+ @REM under the License.
19
+ @REM ----------------------------------------------------------------------------
20
+
21
+ @REM ----------------------------------------------------------------------------
22
+ @REM Apache Maven Wrapper startup batch script, version 3.3.2
23
+ @REM
24
+ @REM Optional ENV vars
25
+ @REM MVNW_REPOURL - repo url base for downloading maven distribution
26
+ @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
27
+ @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
28
+ @REM ----------------------------------------------------------------------------
29
+
30
+ @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
31
+ @SET __MVNW_CMD__=
32
+ @SET __MVNW_ERROR__=
33
+ @SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
34
+ @SET PSModulePath=
35
+ @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
36
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
37
+ )
38
+ @SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
39
+ @SET __MVNW_PSMODULEP_SAVE=
40
+ @SET __MVNW_ARG0_NAME__=
41
+ @SET MVNW_USERNAME=
42
+ @SET MVNW_PASSWORD=
43
+ @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
44
+ @echo Cannot start maven from wrapper >&2 && exit /b 1
45
+ @GOTO :EOF
46
+ : end batch / begin powershell #>
47
+
48
+ $ErrorActionPreference = "Stop"
49
+ if ($env:MVNW_VERBOSE -eq "true") {
50
+ $VerbosePreference = "Continue"
51
+ }
52
+
53
+ # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
54
+ $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
55
+ if (!$distributionUrl) {
56
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
57
+ }
58
+
59
+ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
60
+ "maven-mvnd-*" {
61
+ $USE_MVND = $true
62
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
63
+ $MVN_CMD = "mvnd.cmd"
64
+ break
65
+ }
66
+ default {
67
+ $USE_MVND = $false
68
+ $MVN_CMD = $script -replace '^mvnw','mvn'
69
+ break
70
+ }
71
+ }
72
+
73
+ # apply MVNW_REPOURL and calculate MAVEN_HOME
74
+ # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
75
+ if ($env:MVNW_REPOURL) {
76
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
77
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
78
+ }
79
+ $distributionUrlName = $distributionUrl -replace '^.*/',''
80
+ $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
81
+ $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
82
+ if ($env:MAVEN_USER_HOME) {
83
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
84
+ }
85
+ $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
86
+ $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
87
+
88
+ if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
89
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
90
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
91
+ exit $?
92
+ }
93
+
94
+ if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
95
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
96
+ }
97
+
98
+ # prepare tmp dir
99
+ $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
100
+ $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
101
+ $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
102
+ trap {
103
+ if ($TMP_DOWNLOAD_DIR.Exists) {
104
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
105
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
106
+ }
107
+ }
108
+
109
+ New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
110
+
111
+ # Download and Install Apache Maven
112
+ Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
113
+ Write-Verbose "Downloading from: $distributionUrl"
114
+ Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
115
+
116
+ $webclient = New-Object System.Net.WebClient
117
+ if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
118
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
119
+ }
120
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
121
+ $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
122
+
123
+ # If specified, validate the SHA-256 sum of the Maven distribution zip file
124
+ $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
125
+ if ($distributionSha256Sum) {
126
+ if ($USE_MVND) {
127
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
128
+ }
129
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
130
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
131
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
132
+ }
133
+ }
134
+
135
+ # unzip and move
136
+ Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
137
+ Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
138
+ try {
139
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
140
+ } catch {
141
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
142
+ Write-Error "fail to move MAVEN_HOME"
143
+ }
144
+ } finally {
145
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
146
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
147
+ }
148
+
149
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
@@ -0,0 +1,139 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4
+ <modelVersion>4.0.0</modelVersion>
5
+ <groupId>com.example.quickstart</groupId>
6
+ <artifactId>quickstart</artifactId>
7
+ <name>OpenCQRS Quickstart</name>
8
+ <version>0.0.1-SNAPSHOT</version>
9
+
10
+ <properties>
11
+ <java.version>21</java.version>
12
+ <opencqrs.version>2.0.0</opencqrs.version>
13
+ <spring-boot.version>4.1.0</spring-boot.version>
14
+ </properties>
15
+
16
+ <dependencies>
17
+ <dependency>
18
+ <groupId>org.springframework.boot</groupId>
19
+ <artifactId>spring-boot-starter-webmvc</artifactId>
20
+ </dependency>
21
+
22
+ <!-- Read model persistence: Spring Data JPA + PostgreSQL, default for all projections -->
23
+ <dependency>
24
+ <groupId>org.springframework.boot</groupId>
25
+ <artifactId>spring-boot-starter-data-jpa</artifactId>
26
+ </dependency>
27
+ <dependency>
28
+ <groupId>org.postgresql</groupId>
29
+ <artifactId>postgresql</artifactId>
30
+ <scope>runtime</scope>
31
+ </dependency>
32
+
33
+ <!-- Spring Integration JDBC: backs the distributed lock registry EventHandlingProcessor uses for
34
+ leader election (exactly one instance processes a given group+partition at a time) -->
35
+ <dependency>
36
+ <groupId>org.springframework.boot</groupId>
37
+ <artifactId>spring-boot-starter-integration</artifactId>
38
+ </dependency>
39
+ <dependency>
40
+ <groupId>org.springframework.integration</groupId>
41
+ <artifactId>spring-integration-jdbc</artifactId>
42
+ </dependency>
43
+
44
+ <dependency>
45
+ <groupId>org.springframework.boot</groupId>
46
+ <artifactId>spring-boot-starter-actuator</artifactId>
47
+ </dependency>
48
+
49
+ <!-- OpenCQRS framework: command/event handling, EventSourcingDB client, Spring Boot auto-configuration -->
50
+ <dependency>
51
+ <groupId>com.opencqrs</groupId>
52
+ <artifactId>framework-spring-boot-starter</artifactId>
53
+ <version>${opencqrs.version}</version>
54
+ </dependency>
55
+
56
+ <!-- Auto-starts docker-compose.yml services (EventSourcingDB, Postgres) on app run -->
57
+ <dependency>
58
+ <groupId>org.springframework.boot</groupId>
59
+ <artifactId>spring-boot-docker-compose</artifactId>
60
+ <scope>runtime</scope>
61
+ <optional>true</optional>
62
+ </dependency>
63
+
64
+ <!-- Testing -->
65
+ <dependency>
66
+ <groupId>com.opencqrs</groupId>
67
+ <artifactId>framework-test</artifactId>
68
+ <version>${opencqrs.version}</version>
69
+ <scope>test</scope>
70
+ </dependency>
71
+ <dependency>
72
+ <groupId>org.springframework.boot</groupId>
73
+ <artifactId>spring-boot-starter-test</artifactId>
74
+ <scope>test</scope>
75
+ </dependency>
76
+ <!-- Spring Boot 4 test slices: @DataJpaTest (build-state-view/build-automation) and
77
+ @WebMvcTest (build-state-change's rest-api-patterns.md) each moved to their own artifact -->
78
+ <dependency>
79
+ <groupId>org.springframework.boot</groupId>
80
+ <artifactId>spring-boot-data-jpa-test</artifactId>
81
+ <scope>test</scope>
82
+ </dependency>
83
+ <dependency>
84
+ <groupId>org.springframework.boot</groupId>
85
+ <artifactId>spring-boot-webmvc-test</artifactId>
86
+ <scope>test</scope>
87
+ </dependency>
88
+ <dependency>
89
+ <groupId>org.junit.jupiter</groupId>
90
+ <artifactId>junit-jupiter</artifactId>
91
+ <scope>test</scope>
92
+ </dependency>
93
+ <dependency>
94
+ <groupId>org.springframework.boot</groupId>
95
+ <artifactId>spring-boot-testcontainers</artifactId>
96
+ <scope>test</scope>
97
+ </dependency>
98
+ <dependency>
99
+ <groupId>org.testcontainers</groupId>
100
+ <artifactId>testcontainers-postgresql</artifactId>
101
+ <scope>test</scope>
102
+ </dependency>
103
+ <dependency>
104
+ <groupId>org.testcontainers</groupId>
105
+ <artifactId>testcontainers-junit-jupiter</artifactId>
106
+ <scope>test</scope>
107
+ </dependency>
108
+ </dependencies>
109
+
110
+ <build>
111
+ <plugins>
112
+ <plugin>
113
+ <groupId>org.apache.maven.plugins</groupId>
114
+ <artifactId>maven-compiler-plugin</artifactId>
115
+ <configuration>
116
+ <source>${java.version}</source>
117
+ <target>${java.version}</target>
118
+ <parameters>true</parameters>
119
+ </configuration>
120
+ </plugin>
121
+ <plugin>
122
+ <groupId>org.springframework.boot</groupId>
123
+ <artifactId>spring-boot-maven-plugin</artifactId>
124
+ </plugin>
125
+ </plugins>
126
+ </build>
127
+
128
+ <dependencyManagement>
129
+ <dependencies>
130
+ <dependency>
131
+ <groupId>org.springframework.boot</groupId>
132
+ <artifactId>spring-boot-dependencies</artifactId>
133
+ <version>${spring-boot.version}</version>
134
+ <type>pom</type>
135
+ <scope>import</scope>
136
+ </dependency>
137
+ </dependencies>
138
+ </dependencyManagement>
139
+ </project>
@@ -0,0 +1,12 @@
1
+ package com.example.quickstart;
2
+
3
+ import org.springframework.boot.SpringApplication;
4
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+
6
+ @SpringBootApplication
7
+ public class QuickstartApplication {
8
+
9
+ public static void main(String[] args) {
10
+ SpringApplication.run(QuickstartApplication.class, args);
11
+ }
12
+ }
@@ -0,0 +1,74 @@
1
+ package com.example.quickstart.config;
2
+
3
+ import com.opencqrs.framework.eventhandler.progress.JdbcProgressTracker;
4
+ import com.opencqrs.framework.persistence.EventSource;
5
+ import com.opencqrs.framework.types.PreconfiguredAssignableClassEventTypeResolver;
6
+ import java.util.Map;
7
+ import javax.sql.DataSource;
8
+ import org.springframework.context.annotation.Bean;
9
+ import org.springframework.context.annotation.Configuration;
10
+ import org.springframework.integration.jdbc.lock.DefaultLockRepository;
11
+ import org.springframework.integration.jdbc.lock.JdbcLockRegistry;
12
+ import org.springframework.integration.jdbc.lock.LockRepository;
13
+ import org.springframework.transaction.PlatformTransactionManager;
14
+
15
+ @Configuration
16
+ public class CqrsConfiguration {
17
+
18
+ /**
19
+ * Identifies this service as the publisher of every event it appends (the CloudEvents {@code source}
20
+ * field). One fixed value for the whole service — not per-slice.
21
+ */
22
+ @Bean
23
+ public EventSource eventSource() {
24
+ return new EventSource("tag://quickstart");
25
+ }
26
+
27
+ /**
28
+ * Maps every event's stored {@code type} string to its Java class explicitly. Required for production —
29
+ * the default {@code ClassNameEventTypeResolver} fallback uses the fully-qualified Java classname as the
30
+ * stored type, which breaks the moment a class is renamed or moved package, and can't interoperate with
31
+ * non-Java event producers.
32
+ *
33
+ * <p><strong>Every event type used by any slice must be registered here.</strong> build-state-change and
34
+ * build-automation both add a line when they introduce a new event — see their SKILL.md for the exact
35
+ * step. Naming convention: {@code "quickstart.{context}.{event-name}.v1"}, lowercase, dot-separated.
36
+ */
37
+ @Bean
38
+ public PreconfiguredAssignableClassEventTypeResolver eventTypeResolver() {
39
+ return new PreconfiguredAssignableClassEventTypeResolver(Map.ofEntries(
40
+ // "quickstart.{context}.{eventname}.v1", {EventClass}.class
41
+ ));
42
+ }
43
+
44
+ /**
45
+ * Distributed-lock leader election, backed by the {@code EVENTHANDLER_LOCK} table (see schema.sql) —
46
+ * ensures exactly one running instance processes events for a given processing group + partition at a
47
+ * time when this service is scaled to multiple instances.
48
+ */
49
+ @Bean
50
+ public DefaultLockRepository defaultLockRepository(DataSource dataSource) {
51
+ var result = new DefaultLockRepository(dataSource);
52
+ result.setPrefix("EVENTHANDLER_");
53
+ return result;
54
+ }
55
+
56
+ @Bean
57
+ public JdbcLockRegistry jdbcLockRegistry(LockRepository lockRepository) {
58
+ return new JdbcLockRegistry(lockRepository);
59
+ }
60
+
61
+ /**
62
+ * Durable checkpoint per processing group + partition (see {@code EVENTHANDLER_PROGRESS} in schema.sql),
63
+ * so a restart or fail-over resumes exactly where the previous instance left off instead of re-processing
64
+ * or skipping events. {@code setProceedTransactionally(true)} lets an {@code @EventHandling} method
65
+ * participate in the same transaction as the checkpoint update, making that handler's side effect and its
66
+ * checkpoint advance atomic together.
67
+ */
68
+ @Bean
69
+ public JdbcProgressTracker jdbcProgressTracker(DataSource dataSource, PlatformTransactionManager transactionManager) {
70
+ var result = new JdbcProgressTracker(dataSource, transactionManager);
71
+ result.setProceedTransactionally(true);
72
+ return result;
73
+ }
74
+ }
@@ -0,0 +1,23 @@
1
+ spring.application.name=quickstart
2
+ server.port=8080
3
+
4
+ esdb.server.uri=http://localhost:3000
5
+ esdb.server.api-token=${ESDB_API_TOKEN:secret}
6
+
7
+ spring.datasource.url=jdbc:postgresql://localhost:5432/quickstart
8
+ spring.datasource.username=quickstart
9
+ spring.datasource.password=quickstart
10
+
11
+ # Applies schema.sql (lock + progress tables) on every startup; JPA-mapped read model tables
12
+ # are handled by Hibernate below, not by schema.sql.
13
+ spring.sql.init.mode=always
14
+ spring.jpa.hibernate.ddl-auto=update
15
+
16
+ spring.docker.compose.enabled=true
17
+ spring.docker.compose.file=docker-compose.yml
18
+
19
+ management.endpoint.health.show-components=always
20
+
21
+ # In-memory command-handling cache is fine for a single instance / local dev. Revisit for
22
+ # multi-instance production deployments (see docs.opencqrs.com's Event Handling Processor reference).
23
+ opencqrs.command-handling.cache.type=in_memory
@@ -0,0 +1,19 @@
1
+ -- Backing tables for the distributed-lock leader election (EVENTHANDLER_LOCK, used by JdbcLockRegistry)
2
+ -- and the durable event-handling checkpoint (EVENTHANDLER_PROGRESS, used by JdbcProgressTracker) —
3
+ -- see com.example.quickstart.config.CqrsConfiguration. Verified against OpenCQRS's own reference
4
+ -- application (framework-spring-boot-autoconfigure); portable as-is to PostgreSQL.
5
+ CREATE TABLE IF NOT EXISTS EVENTHANDLER_LOCK (
6
+ LOCK_KEY CHAR(36) NOT NULL,
7
+ REGION VARCHAR(100) NOT NULL,
8
+ CLIENT_ID CHAR(36),
9
+ CREATED_DATE TIMESTAMP NOT NULL,
10
+ EXPIRED_AFTER TIMESTAMP NOT NULL,
11
+ CONSTRAINT EVENTHANDLER_LOCK_PK PRIMARY KEY (LOCK_KEY, REGION)
12
+ );
13
+
14
+ CREATE TABLE IF NOT EXISTS EVENTHANDLER_PROGRESS (
15
+ GROUP_KEY VARCHAR(100) NOT NULL,
16
+ PARTITION_ID BIGINT NOT NULL,
17
+ EVENT_ID VARCHAR(100) NOT NULL,
18
+ CONSTRAINT EVENTHANDLER_PROGRESS_PK PRIMARY KEY (GROUP_KEY, PARTITION_ID)
19
+ );
@@ -0,0 +1,11 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>OpenCQRS Quickstart</title>
6
+ </head>
7
+ <body>
8
+ <h1>OpenCQRS Quickstart</h1>
9
+ <p>Backend is running. Slices built from the event model board are wired up under their own REST paths.</p>
10
+ </body>
11
+ </html>
@@ -58,6 +58,31 @@ When asked to build a slice, always follow this flow:
58
58
 
59
59
  After you are done, automatically run the tests for the slice that was edited.
60
60
 
61
+ ## Commit Scope Guard
62
+
63
+ A pre-commit hook (`.githooks/pre-commit`, installed via `init --hooks` — see its own project's setup)
64
+ runs `.build-kit/lib/check-commit-scope.cjs` on every commit that touches `src/slices/{context}/{slice}/`
65
+ or a webhook's `supabase/functions/{fn}/index.ts`. It loads every check under `.build-kit/lib/checks/`
66
+ and rejects the commit if any of them find a problem:
67
+
68
+ - **blocked-paths** — `package.json`/lockfiles and `server.ts` are never touched by slice work
69
+ - **slice-scope** — everything staged must be inside the slice folder (or the webhook's own edge
70
+ function) or a documented exception: `src/slices/{context}/{Context}Events.ts`,
71
+ `src/common/loadPostgresEventstore.ts`, or `supabase/config.toml`
72
+ - **append-only-migrations** — `supabase/migrations/V{n}__*.sql` may only be **added**, never edited
73
+ - **test-file-present** — a changed Command/Projection/processor file needs a sibling `*.test.ts`
74
+ (webhook edge functions are tested via `supabase functions serve`, not a test file — exempt)
75
+ - **no-invented-fields** — heuristic: flags a field used in code that isn't declared anywhere in
76
+ `.build-kit/.slices/{context}/{slice}/slice.json`
77
+ - **spec-coverage** — heuristic: the test file needs at least as many `it(...)` blocks as slice.json
78
+ has `specifications[]` entries
79
+ - **tsc-build** — `npx tsc --noEmit` must still pass
80
+
81
+ If a commit is rejected, split it — commit the out-of-scope file separately from the slice work, or add
82
+ the missing test/fix the field — rather than passing `--no-verify`. Run `npm run check:scope` any time
83
+ you want to check staged files before committing. To add a new check, read
84
+ `.build-kit/lib/checks/README.md` and drop in a file following its interface — no other wiring needed.
85
+
61
86
  ## Example Slice Structure
62
87
 
63
88
  ```
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Runner for the slice commit-scope guard. Loads every check module from
5
+ // ./checks/*.cjs and runs it against the currently staged changeset.
6
+ //
7
+ // Check interface (see ./checks/README.md for the full contract + a template):
8
+ // module.exports = {
9
+ // name: 'my-check', // short id, shown in violation output
10
+ // skipIfAlreadyFailing: false, // optional — skip this check once an earlier
11
+ // // one has already failed (use for slow checks)
12
+ // run(ctx) {
13
+ // return [{ path: 'some/file.ts', reason: 'why this is a problem' }];
14
+ // },
15
+ // };
16
+ // `run()` returns an array of violations (empty array/undefined/null = pass).
17
+ //
18
+ // `ctx` passed to every check:
19
+ // changes [{status, path}] — staged files (git status letter + path)
20
+ // touchesSlice true — this commit touches src/slices/{context}/{slice}/**
21
+ // (the runner already gates on this before loading checks)
22
+ // repoRoot absolute path to the repo root
23
+ // SLICE_PATTERN RegExp matching a path inside a slice's own folder
24
+ //
25
+ // Zero dependencies — plain Node, so it works from git's pre-commit hook
26
+ // (see ../../.githooks/pre-commit), from `npm run check:scope`, or from CI.
27
+ // Invoked as: node .build-kit/lib/check-commit-scope.cjs
28
+
29
+ const { execSync } = require('child_process');
30
+ const fs = require('fs');
31
+ const path = require('path');
32
+
33
+ const SLICE_PATTERN = /^src\/slices\/[^/]+\/[^/]+\//;
34
+ // A webhook slice is a self-contained edge function with no src/slices/ folder
35
+ // of its own — still counts as "this is a slice commit" for the gate below.
36
+ const WEBHOOK_FUNCTION_PATTERN = /^supabase\/functions\/[^/]+\/index\.ts$/;
37
+
38
+ function stagedChanges() {
39
+ const out = execSync('git diff --cached --name-status --no-renames', { encoding: 'utf8' });
40
+ return out
41
+ .split('\n')
42
+ .filter(Boolean)
43
+ .map((line) => {
44
+ const [status, ...rest] = line.split('\t');
45
+ return { status: status[0], path: rest.join('\t') };
46
+ });
47
+ }
48
+
49
+ function loadChecks() {
50
+ const checksDir = path.join(__dirname, 'checks');
51
+ if (!fs.existsSync(checksDir)) return [];
52
+ return fs
53
+ .readdirSync(checksDir)
54
+ .filter((f) => f.endsWith('.cjs'))
55
+ .sort() // numeric filename prefixes (00-, 10-, ...) control run order
56
+ .map((f) => {
57
+ let mod;
58
+ try {
59
+ mod = require(path.join(checksDir, f));
60
+ } catch (err) {
61
+ console.error(`check-commit-scope: failed to load checks/${f} — ${err.message}`);
62
+ return null;
63
+ }
64
+ if (typeof mod?.run !== 'function') {
65
+ console.error(`check-commit-scope: skipping checks/${f} — does not export { name, run(ctx) }`);
66
+ return null;
67
+ }
68
+ return { file: f, name: mod.name || f, run: mod.run, skipIfAlreadyFailing: !!mod.skipIfAlreadyFailing };
69
+ })
70
+ .filter(Boolean);
71
+ }
72
+
73
+ function main() {
74
+ let changes;
75
+ try {
76
+ changes = stagedChanges();
77
+ } catch (err) {
78
+ console.error('check-commit-scope: could not read staged changes —', err.message);
79
+ process.exit(1);
80
+ }
81
+
82
+ if (changes.length === 0) process.exit(0);
83
+
84
+ const touchesSlice = changes.some((c) => SLICE_PATTERN.test(c.path) || WEBHOOK_FUNCTION_PATTERN.test(c.path));
85
+ if (!touchesSlice) process.exit(0); // not a slice commit — nothing to enforce
86
+
87
+ const ctx = {
88
+ changes,
89
+ touchesSlice,
90
+ repoRoot: execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim(),
91
+ SLICE_PATTERN,
92
+ };
93
+
94
+ const checks = loadChecks();
95
+ const violations = [];
96
+ const claimedPaths = new Set(); // first check to flag a path wins — avoids repeat noise
97
+
98
+ for (const check of checks) {
99
+ if (check.skipIfAlreadyFailing && violations.length > 0) continue;
100
+
101
+ let result;
102
+ try {
103
+ result = check.run(ctx) || [];
104
+ } catch (err) {
105
+ violations.push({ path: '(check error)', reason: `[${check.name}] threw: ${err.message}` });
106
+ continue;
107
+ }
108
+
109
+ for (const v of result) {
110
+ if (claimedPaths.has(v.path)) continue;
111
+ claimedPaths.add(v.path);
112
+ violations.push({ path: v.path, reason: `[${check.name}] ${v.reason}` });
113
+ }
114
+ }
115
+
116
+ if (violations.length > 0) {
117
+ console.error('\n❌ commit blocked — slice commit-scope guard found issues:\n');
118
+ for (const v of violations) console.error(` - ${v.path} — ${v.reason}`);
119
+ console.error('\nSee .build-kit/lib/checks/ for what each check enforces.\n');
120
+ process.exit(1);
121
+ }
122
+
123
+ process.exit(0);
124
+ }
125
+
126
+ main();
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ // Rejects a slice commit that touches shared infra which must never change from
4
+ // slice work: the package manifest/lockfiles (no new/changed dependencies) and
5
+ // server.ts (routes/processors are auto-discovered — never wired there by hand).
6
+
7
+ const BLOCKED = [
8
+ {
9
+ pattern: /^(package(-lock)?\.json|pnpm-lock\.yaml|yarn\.lock|npm-shrinkwrap\.json)$/,
10
+ reason: 'dependency/package manifest changes are not allowed from a slice commit',
11
+ },
12
+ {
13
+ pattern: /^server\.ts$/,
14
+ reason: 'server.ts is shared infra (routes/processors are auto-discovered) — never touched by slice work',
15
+ },
16
+ ];
17
+
18
+ module.exports = {
19
+ name: 'blocked-paths',
20
+ run(ctx) {
21
+ const violations = [];
22
+ for (const { path: p } of ctx.changes) {
23
+ const hit = BLOCKED.find((b) => b.pattern.test(p));
24
+ if (hit) violations.push({ path: p, reason: hit.reason });
25
+ }
26
+ return violations;
27
+ },
28
+ };
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ // Everything staged in a slice commit must be inside the slice's own folder (or
4
+ // a webhook's self-contained edge function), or one of the documented
5
+ // shared-infra exceptions a slice legitimately registers *into* (never rewrites
6
+ // wholesale). See build-kit/lib/AGENT.md and the build-state-view/
7
+ // build-automation/build-webhook SKILL.md files for what a compliant edit to an
8
+ // exception file looks like. Migration files have their own dedicated check
9
+ // (20-append-only-migrations.cjs), so they're allowed through here.
10
+
11
+ const WEBHOOK_FUNCTION_PATTERN = /^supabase\/functions\/[^/]+\/index\.ts$/;
12
+
13
+ const ALLOWED_EXCEPTIONS = [
14
+ /^src\/slices\/[^/]+\/[A-Za-z0-9]+Events\.ts$/, // per-context event union (append-only)
15
+ /^src\/common\/loadPostgresEventstore\.ts$/, // projection registration / schema.migrate()
16
+ /^supabase\/config\.toml$/, // webhook function registration (verify_jwt entry)
17
+ ];
18
+
19
+ const MIGRATION_PATTERN = /^supabase\/migrations\/V\d+__.*\.sql$/;
20
+
21
+ module.exports = {
22
+ name: 'slice-scope',
23
+ run(ctx) {
24
+ const violations = [];
25
+ for (const { path: p } of ctx.changes) {
26
+ if (ctx.SLICE_PATTERN.test(p)) continue;
27
+ if (WEBHOOK_FUNCTION_PATTERN.test(p)) continue;
28
+ if (MIGRATION_PATTERN.test(p)) continue;
29
+ if (ALLOWED_EXCEPTIONS.some((r) => r.test(p))) continue;
30
+ violations.push({ path: p, reason: 'outside src/slices/{context}/{slice}/ and not a documented exception' });
31
+ }
32
+ return violations;
33
+ },
34
+ };