sirius-framework-mcp 0.1.0

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 (58) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +160 -0
  3. package/dist/grammars/tree-sitter-java.wasm +0 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +247 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/java-parser.d.ts +25 -0
  8. package/dist/java-parser.js +281 -0
  9. package/dist/java-parser.js.map +1 -0
  10. package/dist/prompts/index.d.ts +9 -0
  11. package/dist/prompts/index.js +15 -0
  12. package/dist/prompts/index.js.map +1 -0
  13. package/dist/prompts/workflows.d.ts +27 -0
  14. package/dist/prompts/workflows.js +317 -0
  15. package/dist/prompts/workflows.js.map +1 -0
  16. package/dist/resources/biz/analytics.md +157 -0
  17. package/dist/resources/biz/biz-controller.md +151 -0
  18. package/dist/resources/biz/codelists.md +154 -0
  19. package/dist/resources/biz/entity-triple.md +142 -0
  20. package/dist/resources/biz/importer.md +153 -0
  21. package/dist/resources/biz/isenguard.md +156 -0
  22. package/dist/resources/biz/jobs.md +145 -0
  23. package/dist/resources/biz/processes.md +155 -0
  24. package/dist/resources/biz/storage.md +149 -0
  25. package/dist/resources/biz/tenants.md +159 -0
  26. package/dist/resources/biz/testing.md +127 -0
  27. package/dist/resources/db/composites.md +145 -0
  28. package/dist/resources/db/entities.md +156 -0
  29. package/dist/resources/db/mixing.md +176 -0
  30. package/dist/resources/db/queries.md +178 -0
  31. package/dist/resources/db/refs.md +135 -0
  32. package/dist/resources/index.d.ts +27 -0
  33. package/dist/resources/index.js +68 -0
  34. package/dist/resources/index.js.map +1 -0
  35. package/dist/resources/kernel/async.md +189 -0
  36. package/dist/resources/kernel/commons.md +203 -0
  37. package/dist/resources/kernel/config.md +155 -0
  38. package/dist/resources/kernel/di.md +138 -0
  39. package/dist/resources/kernel/lifecycle.md +146 -0
  40. package/dist/resources/loader.d.ts +9 -0
  41. package/dist/resources/loader.js +17 -0
  42. package/dist/resources/loader.js.map +1 -0
  43. package/dist/resources/web/controllers.md +151 -0
  44. package/dist/resources/web/services.md +136 -0
  45. package/dist/resources/web/templates.md +162 -0
  46. package/dist/tools/index.d.ts +4 -0
  47. package/dist/tools/index.js +3 -0
  48. package/dist/tools/index.js.map +1 -0
  49. package/dist/tools/introspection.d.ts +55 -0
  50. package/dist/tools/introspection.js +233 -0
  51. package/dist/tools/introspection.js.map +1 -0
  52. package/dist/tools/scaffold.d.ts +64 -0
  53. package/dist/tools/scaffold.js +505 -0
  54. package/dist/tools/scaffold.js.map +1 -0
  55. package/dist/workspace.d.ts +37 -0
  56. package/dist/workspace.js +185 -0
  57. package/dist/workspace.js.map +1 -0
  58. package/package.json +41 -0
@@ -0,0 +1,317 @@
1
+ /**
2
+ * MCP prompt definitions for common Sirius development workflows.
3
+ *
4
+ * Each prompt generates an initial conversation that guides an AI assistant
5
+ * through a specific Sirius framework task.
6
+ */
7
+ function userMessage(text) {
8
+ return { role: "user", content: { type: "text", text } };
9
+ }
10
+ function assistantMessage(text) {
11
+ return {
12
+ role: "assistant",
13
+ content: { type: "text", text },
14
+ };
15
+ }
16
+ // ---------------------------------------------------------------------------
17
+ // 1. add-entity
18
+ // ---------------------------------------------------------------------------
19
+ export const addEntityPrompt = {
20
+ name: "add-entity",
21
+ description: "I want to add a new Sirius entity",
22
+ arguments: [
23
+ {
24
+ name: "name",
25
+ description: "The entity name (e.g. 'Product'). Used as the base for interface, SQL, and Mongo class names.",
26
+ required: true,
27
+ },
28
+ {
29
+ name: "package",
30
+ description: "The target Java package (e.g. 'sirius.biz.products'). If omitted, a sensible default under sirius.biz will be suggested.",
31
+ required: false,
32
+ },
33
+ ],
34
+ generateMessages(args) {
35
+ const name = args.name;
36
+ const pkg = args.package ?? `sirius.biz.${name.toLowerCase()}s`;
37
+ return [
38
+ userMessage(`I want to add a new Sirius entity called "${name}" in the package "${pkg}". ` +
39
+ `Please guide me through the full entity-triple pattern.`),
40
+ assistantMessage(`Great -- adding a new entity in Sirius follows the **entity-triple** pattern ` +
41
+ `(see resource \`sirius://biz/entity-triple\`). Here is the step-by-step plan:\n` +
42
+ `\n` +
43
+ `## 1. Database-independent interface\n` +
44
+ `Create \`${pkg}.${name}\` as an interface that extends \`Entity\` (and typically \`Traced\`, \`Journaled\`).\n` +
45
+ `Define a \`${name}Data\` composite if the entity carries reusable field groups.\n` +
46
+ `Use \`Mapping.named()\` constants for every persistent field.\n` +
47
+ `\n` +
48
+ `## 2. JDBC implementation\n` +
49
+ `Create \`${pkg}.jdbc.SQL${name}\` extending \`SQLTenantAware\` and implementing \`${name}\`.\n` +
50
+ `Add appropriate \`@Index\` annotations for unique constraints and lookup fields.\n` +
51
+ `Add \`@Framework("${pkg.replace(/^sirius\.biz\./, "biz.")}-jdbc")\` so the entity is only loaded when the flag is enabled.\n` +
52
+ `\n` +
53
+ `## 3. MongoDB implementation\n` +
54
+ `Create \`${pkg}.mongo.Mongo${name}\` extending \`MongoTenantAware\` and implementing \`${name}\`.\n` +
55
+ `Add \`@Framework("${pkg.replace(/^sirius\.biz\./, "biz.")}-mongo")\`.\n` +
56
+ `\n` +
57
+ `## 4. Framework flags\n` +
58
+ `Register framework flags in your \`.conf\` file:\n` +
59
+ `\`\`\`hocon\n` +
60
+ `sirius.frameworks {\n` +
61
+ ` "${pkg.replace(/^sirius\.biz\./, "biz.")}" = true\n` +
62
+ ` "${pkg.replace(/^sirius\.biz\./, "biz.")}-jdbc" = true\n` +
63
+ ` "${pkg.replace(/^sirius\.biz\./, "biz.")}-mongo" = false\n` +
64
+ `}\n` +
65
+ `\`\`\`\n` +
66
+ `\n` +
67
+ `## 5. Translation source\n` +
68
+ `Add \`@TranslationSource\` to provide i18n keys for the entity and its fields.\n` +
69
+ `Create corresponding \`.properties\` files under \`resources/\`.\n` +
70
+ `\n` +
71
+ `Shall I generate the skeleton code for each of these files?`),
72
+ ];
73
+ },
74
+ };
75
+ // ---------------------------------------------------------------------------
76
+ // 2. add-job
77
+ // ---------------------------------------------------------------------------
78
+ export const addJobPrompt = {
79
+ name: "add-job",
80
+ description: "I want to add a new background job",
81
+ arguments: [
82
+ {
83
+ name: "name",
84
+ description: "The job name (e.g. 'ProductImportJob'). Used as the class name for the JobFactory implementation.",
85
+ required: true,
86
+ },
87
+ {
88
+ name: "type",
89
+ description: "The kind of job: 'batch' (long-running data processing), 'report' (generates output), or 'interactive' (user-triggered, short-lived). Determines the base class.",
90
+ required: false,
91
+ },
92
+ ],
93
+ generateMessages(args) {
94
+ const name = args.name;
95
+ const type = args.type ?? "batch";
96
+ const baseClassMap = {
97
+ batch: "BatchProcessJobFactory",
98
+ report: "ReportJobFactory",
99
+ interactive: "InteractiveJobFactory",
100
+ };
101
+ const baseClass = baseClassMap[type] ?? baseClassMap.batch;
102
+ return [
103
+ userMessage(`I want to add a new ${type} background job called "${name}". ` +
104
+ `Please guide me through the Sirius jobs framework.`),
105
+ assistantMessage(`The Sirius jobs framework (see resource \`sirius://biz/jobs\`) uses a ` +
106
+ `\`JobFactory\` hierarchy to define background jobs. For a **${type}** job, ` +
107
+ `the right base class is \`${baseClass}\`.\n` +
108
+ `\n` +
109
+ `## Step-by-step plan\n` +
110
+ `\n` +
111
+ `### 1. Choose and extend the base class\n` +
112
+ `Create \`${name}\` extending \`${baseClass}\`.\n` +
113
+ `Register it with \`@Register\` so the DI framework picks it up.\n` +
114
+ `\n` +
115
+ `### 2. Define parameters\n` +
116
+ `Override the \`collectParameters()\` method to declare job parameters.\n` +
117
+ `Use parameter types like \`StringParameter\`, \`BooleanParameter\`, \`EntityParameter\`, etc.\n` +
118
+ `Parameters appear in the job's UI form automatically.\n` +
119
+ `\n` +
120
+ `### 3. Implement execution logic\n` +
121
+ (type === "batch"
122
+ ? `Override \`execute(ProcessContext process)\` to implement the batch processing logic.\n` +
123
+ `Use \`process.log()\` for progress reporting and \`process.addTiming()\` for performance tracking.\n` +
124
+ `Handle cancellation via \`process.isActive()\`.\n`
125
+ : type === "report"
126
+ ? `Override \`execute(ProcessContext process)\` and use the report output helpers.\n` +
127
+ `The report framework handles file generation and storage automatically.\n`
128
+ : `Override \`execute(ProcessContext process)\` for the interactive job logic.\n` +
129
+ `Interactive jobs are typically short-lived and may provide immediate feedback.\n`) +
130
+ `\n` +
131
+ `### 4. Permissions and labels\n` +
132
+ `Override \`getRequiredPermission()\` to restrict who can run the job.\n` +
133
+ `Provide a label and description via NLS keys.\n` +
134
+ `\n` +
135
+ `### 5. Optional: scheduling\n` +
136
+ `If the job should run on a schedule, implement the \`ScheduledJobFactory\` interface ` +
137
+ `or configure it via the admin UI.\n` +
138
+ `\n` +
139
+ `Shall I generate the skeleton code for \`${name}\`?`),
140
+ ];
141
+ },
142
+ };
143
+ // ---------------------------------------------------------------------------
144
+ // 3. add-feature
145
+ // ---------------------------------------------------------------------------
146
+ export const addFeaturePrompt = {
147
+ name: "add-feature",
148
+ description: "I want to add a complete feature module",
149
+ arguments: [
150
+ {
151
+ name: "name",
152
+ description: "The feature name (e.g. 'products'). This determines the package, entity names, controller, and templates.",
153
+ required: true,
154
+ },
155
+ ],
156
+ generateMessages(args) {
157
+ const name = args.name;
158
+ const singular = name.endsWith("s") && name.length > 1 ? name.slice(0, -1) : name;
159
+ const entityName = singular.charAt(0).toUpperCase() + singular.slice(1);
160
+ const pkg = `sirius.biz.${name.toLowerCase()}`;
161
+ return [
162
+ userMessage(`I want to add a complete feature module called "${name}" to a Sirius application. ` +
163
+ `This should be a full vertical slice: entity, controller, template, and test.`),
164
+ assistantMessage(`A full feature module in Sirius is a vertical slice that touches multiple layers. ` +
165
+ `Here is the plan for the "${name}" feature. Relevant resources:\n` +
166
+ `- \`sirius://biz/entity-triple\` (entity pattern)\n` +
167
+ `- \`sirius://biz/jobs\` (if the feature needs background processing)\n` +
168
+ `- \`sirius://biz/importer\` (if the feature needs data import)\n` +
169
+ `- \`sirius://kernel/di\` (dependency injection and registration)\n` +
170
+ `\n` +
171
+ `## 1. Entity layer\n` +
172
+ `Follow the entity-triple pattern to create:\n` +
173
+ `- \`${pkg}.${entityName}\` -- database-independent interface\n` +
174
+ `- \`${pkg}.jdbc.SQL${entityName}\` -- JDBC implementation (extends \`SQLTenantAware\`)\n` +
175
+ `- \`${pkg}.mongo.Mongo${entityName}\` -- MongoDB implementation (extends \`MongoTenantAware\`)\n` +
176
+ `- \`${pkg}.${entityName}Data\` -- composite for reusable field groups (if needed)\n` +
177
+ `\n` +
178
+ `## 2. Controller layer\n` +
179
+ `Create \`${pkg}.${entityName}Controller\` extending \`BizController\`:\n` +
180
+ `- Use \`@Routed\` annotations for URL mappings (e.g. \`/${name}\`, \`/${singular}/{id}\`)\n` +
181
+ `- Add \`@LoginRequired\` and \`@Permission\` for access control\n` +
182
+ `- Implement list, detail/edit, and delete endpoints\n` +
183
+ `- Make the controller generic over the entity interface so it works with both SQL and Mongo\n` +
184
+ `\n` +
185
+ `## 3. Template layer\n` +
186
+ `Create Pasta/Tagliatelle templates under \`resources/default/templates/biz/${name}/\`:\n` +
187
+ `- \`${singular}.html.pasta\` -- detail/edit view\n` +
188
+ `- \`${name}.html.pasta\` -- list view\n` +
189
+ `Use the Tycho UI component library (\`<t:...>\` tags) for consistent look-and-feel.\n` +
190
+ `\n` +
191
+ `## 4. Framework flags\n` +
192
+ `Register in your \`.conf\`:\n` +
193
+ `\`\`\`hocon\n` +
194
+ `sirius.frameworks {\n` +
195
+ ` "biz.${name}" = true\n` +
196
+ ` "biz.${name}-jdbc" = true\n` +
197
+ ` "biz.${name}-mongo" = false\n` +
198
+ `}\n` +
199
+ `\`\`\`\n` +
200
+ `\n` +
201
+ `## 5. Test\n` +
202
+ `Create \`${entityName}Test.kt\` in \`src/test/kotlin/${pkg.replace(/\./g, "/")}/\`:\n` +
203
+ `- Use \`@ExtendWith(SiriusExtension::class)\`\n` +
204
+ `- Wait for database readiness in \`@BeforeAll\`\n` +
205
+ `- Test CRUD operations and any business logic\n` +
206
+ `\n` +
207
+ `## 6. i18n\n` +
208
+ `Add \`@TranslationSource\` to the entity and create \`.properties\` files ` +
209
+ `for labels and validation messages.\n` +
210
+ `\n` +
211
+ `Shall I start generating the code for each layer?`),
212
+ ];
213
+ },
214
+ };
215
+ // ---------------------------------------------------------------------------
216
+ // 4. add-import-handler
217
+ // ---------------------------------------------------------------------------
218
+ export const addImportHandlerPrompt = {
219
+ name: "add-import-handler",
220
+ description: "I want to add a data import handler",
221
+ arguments: [
222
+ {
223
+ name: "entity",
224
+ description: "The entity name this import handler targets (e.g. 'Product'). Must correspond to an existing Sirius entity.",
225
+ required: true,
226
+ },
227
+ ],
228
+ generateMessages(args) {
229
+ const entity = args.entity;
230
+ return [
231
+ userMessage(`I want to add a data import handler for the "${entity}" entity. ` +
232
+ `Please guide me through the Sirius importer framework.`),
233
+ assistantMessage(`The Sirius importer framework (see resource \`sirius://biz/importer\`) provides ` +
234
+ `\`EntityImportHandler\` classes that define how external data maps to entities. ` +
235
+ `Here is the plan:\n` +
236
+ `\n` +
237
+ `## 1. Choose the base class\n` +
238
+ `- For JDBC entities: extend \`SQLEntityImportHandler<SQL${entity}>\`\n` +
239
+ `- For MongoDB entities: extend \`MongoEntityImportHandler<Mongo${entity}>\`\n` +
240
+ `Register with \`@Register\` and annotate with the appropriate \`@Framework\`.\n` +
241
+ `\n` +
242
+ `## 2. Define find queries\n` +
243
+ `Override \`determineFindQuery()\` to specify how existing records are located ` +
244
+ `during import (usually by a unique business key like an external ID or code).\n` +
245
+ `This is critical for upsert behavior -- without it, imports always create new records.\n` +
246
+ `\n` +
247
+ `## 3. Map fields\n` +
248
+ `Override \`collectFieldMappings()\` or use the auto-mapping from \`@AutoImport\` annotations.\n` +
249
+ `Fields annotated with \`@AutoImport\` on the entity are automatically mapped.\n` +
250
+ `For custom/computed fields, add explicit mappings in the handler.\n` +
251
+ `\n` +
252
+ `## 4. Handle events\n` +
253
+ `Override lifecycle callbacks to add custom logic:\n` +
254
+ `- \`beforeImport()\` -- validate or transform data before persisting\n` +
255
+ `- \`afterImport()\` -- trigger side effects after a record is imported\n` +
256
+ `- \`collectDefaultExportableMappings()\` -- define which fields appear in exports\n` +
257
+ `\n` +
258
+ `## 5. Register and test\n` +
259
+ `The handler is automatically discovered by the import framework via DI.\n` +
260
+ `Test by creating a small CSV or XML import and running it through the import UI or jobs.\n` +
261
+ `\n` +
262
+ `Shall I generate the handler skeleton for \`${entity}\`?`),
263
+ ];
264
+ },
265
+ };
266
+ // ---------------------------------------------------------------------------
267
+ // 5. debug-framework-flags
268
+ // ---------------------------------------------------------------------------
269
+ export const debugFrameworkFlagsPrompt = {
270
+ name: "debug-framework-flags",
271
+ description: "My entity/service isn't being picked up",
272
+ arguments: [],
273
+ generateMessages() {
274
+ return [
275
+ userMessage(`My entity or service is not being picked up by the Sirius framework. ` +
276
+ `I have the class annotated with @Register but it does not seem to be active. ` +
277
+ `Help me debug this.`),
278
+ assistantMessage(`This is a common issue and almost always comes down to **framework flags**. ` +
279
+ `Let me walk you through the diagnostic steps (see resource \`sirius://kernel/di\`).\n` +
280
+ `\n` +
281
+ `## Step 1: Check the framework flag on your class\n` +
282
+ `Look at the \`@Register\` annotation on your class. If it specifies a \`framework\` attribute:\n` +
283
+ `\`\`\`java\n` +
284
+ `@Register(framework = "biz.myfeature")\n` +
285
+ `\`\`\`\n` +
286
+ `...then the flag \`biz.myfeature\` must be **enabled** in the config.\n` +
287
+ `\n` +
288
+ `Also check for a \`@Framework\` annotation on the class, which serves the same purpose.\n` +
289
+ `\n` +
290
+ `## Step 2: Verify the flag is enabled in config\n` +
291
+ `Check your \`develop.conf\` (or \`instance.conf\`) and the component config files:\n` +
292
+ `\`\`\`hocon\n` +
293
+ `sirius.frameworks {\n` +
294
+ ` "biz.myfeature" = true # Must be true!\n` +
295
+ ` "biz.myfeature-jdbc" = true # If using JDBC entities\n` +
296
+ `}\n` +
297
+ `\`\`\`\n` +
298
+ `A missing or \`false\` flag means the class is **completely invisible** to the DI system.\n` +
299
+ `\n` +
300
+ `## Step 3: Check for typos in framework names\n` +
301
+ `The framework name in \`@Register(framework = "...")\` must **exactly match** ` +
302
+ `the key in \`sirius.frameworks\`. A typo in either place silently disables the class.\n` +
303
+ `\n` +
304
+ `## Step 4: Check the class hierarchy\n` +
305
+ `- Entities must extend the correct base class (\`SQLTenantAware\`, \`MongoTenantAware\`, etc.)\n` +
306
+ `- Services must implement an interface or extend a known base class\n` +
307
+ `- The class must be in a package that is scanned (under the configured base packages)\n` +
308
+ `\n` +
309
+ `## Step 5: Use the system console\n` +
310
+ `In dev mode, go to \`/system/console\` and use the \`frameworks\` command to see ` +
311
+ `which flags are active. You can also check \`/system/cluster\` for registered components.\n` +
312
+ `\n` +
313
+ `What is the exact class and its annotations? I can help pinpoint the issue.`),
314
+ ];
315
+ },
316
+ };
317
+ //# sourceMappingURL=workflows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows.js","sourceRoot":"","sources":["../../src/prompts/workflows.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,157 @@
1
+ # Analytics
2
+
3
+ The analytics module provides scheduled metric computation, performance flags,
4
+ event recording, and chart visualization. Metrics are computed per entity on
5
+ daily or monthly schedules.
6
+
7
+ ## Metric Computers
8
+
9
+ ### DailyMetricComputer
10
+
11
+ Computes a metric for each entity on a daily basis. Annotated with `@AutoRegister`
12
+ for automatic discovery:
13
+
14
+ ```java
15
+ @AutoRegister
16
+ public abstract class DailyMetricComputer<E extends BaseEntity<?>>
17
+ implements AnalyticalTask<E> {
18
+
19
+ @Part @Nullable
20
+ protected Metrics metrics;
21
+
22
+ public abstract void compute(MetricComputerContext context, E entity) throws Exception;
23
+ }
24
+ ```
25
+
26
+ Implementation example:
27
+
28
+ ```java
29
+ public class OrderCountComputer extends DailyMetricComputer<SQLTenant> {
30
+
31
+ @Override
32
+ public Class<SQLTenant> getType() {
33
+ return SQLTenant.class;
34
+ }
35
+
36
+ @Override
37
+ public void compute(MetricComputerContext context, SQLTenant tenant) throws Exception {
38
+ int count = oma.select(SQLOrder.class)
39
+ .eq(SQLOrder.TENANT, tenant)
40
+ .where(OMA.FILTERS.gte(SQLOrder.CREATED_AT, context.periodStart()))
41
+ .where(OMA.FILTERS.lte(SQLOrder.CREATED_AT, context.periodEnd()))
42
+ .count();
43
+ metrics.updateDailyMetric("orders", tenant, context.date(), count);
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### MonthlyMetricComputer
49
+
50
+ Same pattern, but invoked monthly. Also runs daily for the current month via
51
+ best-effort scheduling to keep preliminary values up to date:
52
+
53
+ ```java
54
+ public class MonthlyRevenueComputer extends MonthlyMetricComputer<SQLTenant> {
55
+
56
+ @Override
57
+ public void compute(MetricComputerContext context, SQLTenant tenant) throws Exception {
58
+ // context.periodStart() and context.periodEnd() span the full month
59
+ Amount revenue = computeRevenue(tenant, context.periodStart(), context.periodEnd());
60
+ metrics.updateMonthlyMetric("revenue", tenant, context.date(), revenue.getAmount());
61
+ }
62
+
63
+ @Override
64
+ public boolean suppressBestEffortScheduling() {
65
+ return false; // default: allow daily re-computation of current month
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### MonthlyLargeMetricComputer
71
+
72
+ For expensive computations that should only run once per month (not re-computed
73
+ daily via best effort).
74
+
75
+ ## MetricComputerContext
76
+
77
+ Passed to every `compute()` call with pre-calculated time boundaries:
78
+
79
+ | Method | Description |
80
+ |---------------------------------|----------------------------------------------|
81
+ | `date()` | The reference date for the metric |
82
+ | `periodStart()` | Start of the period (LocalDateTime) |
83
+ | `periodEnd()` | End of the period (LocalDateTime) |
84
+ | `periodOutsideOfCurrentInterest()` | True if computing a historical period |
85
+ | `bestEffort()` | True if this is a best-effort re-computation |
86
+
87
+ ## Scheduling
88
+
89
+ The analytics scheduler iterates over entities and invokes registered computers.
90
+ There are separate schedulers for JDBC and MongoDB entities:
91
+
92
+ - `SQLAnalyticalTaskScheduler` — schedules tasks for `SQLEntity` types
93
+ - `MongoAnalyticalTaskScheduler` — schedules tasks for `MongoEntity` types
94
+
95
+ Enable the appropriate framework flags:
96
+
97
+ ```hocon
98
+ sirius.frameworks {
99
+ biz.analytics-metrics-jdbc = true # Enable JDBC metric computation
100
+ biz.analytics-metrics-mongo = false # Enable MongoDB metric computation
101
+ biz.scheduler-jdbc = true # Enable JDBC scheduler
102
+ biz.scheduler-mongo = false # Enable MongoDB scheduler
103
+ }
104
+ ```
105
+
106
+ Schedulers use batch emitters (`SQLEntityBatchEmitter`, `MongoEntityBatchEmitter`)
107
+ to process entities in configurable batch sizes via `DistributedTasks`.
108
+
109
+ ## Execution Flags (Performance Flags)
110
+
111
+ Performance flags track boolean states per entity (e.g., "has overdue invoices",
112
+ "needs review"). They are stored as composites on entities:
113
+
114
+ - **`SQLPerformanceData`** — for JDBC entities
115
+ - **`MongoPerformanceData`** — for MongoDB entities
116
+
117
+ ```java
118
+ @Framework("biz.tenants-jdbc")
119
+ public class SQLTenant extends BizEntity implements Tenant<Long>, PerformanceFlagged {
120
+ private final SQLPerformanceData performanceData = new SQLPerformanceData(this);
121
+
122
+ @Override
123
+ public SQLPerformanceData getPerformanceData() {
124
+ return performanceData;
125
+ }
126
+ }
127
+ ```
128
+
129
+ Enable with:
130
+
131
+ ```hocon
132
+ sirius.frameworks {
133
+ biz.analytics-execution-flags-jdbc = true
134
+ biz.analytics-execution-flags-mongo = false
135
+ }
136
+ ```
137
+
138
+ ## AnalyticalTask Interface
139
+
140
+ Both `DailyMetricComputer` and `MonthlyMetricComputer` implement `AnalyticalTask<E>`.
141
+ Override `getLevel()` to control execution order (lower = earlier) when one computer
142
+ depends on another's results. Override `isEnabled()` to conditionally disable.
143
+
144
+ ## Common Mistakes
145
+
146
+ 1. **Missing scheduler framework flag** — Registering metric computers without
147
+ enabling `biz.scheduler-jdbc` or `biz.scheduler-mongo` means they never execute.
148
+
149
+ 2. **Wrong entity type** — A `DailyMetricComputer<SQLTenant>` only runs for
150
+ `SQLTenant` entities. If your app uses MongoDB, you need a Mongo variant.
151
+
152
+ 3. **Ignoring `periodOutsideOfCurrentInterest()`** — Use this flag to skip
153
+ expensive external API calls when backfilling historical data.
154
+
155
+ 4. **Not using `bestEffort()` correctly** — Best-effort runs provide preliminary
156
+ values for the current period. Do not perform destructive operations during
157
+ best-effort runs.
@@ -0,0 +1,151 @@
1
+ # BizController
2
+
3
+ `BizController` extends `BasicController` from sirius-web and serves as the base
4
+ class for all controllers that operate on entities in the biz layer. It provides
5
+ tenant-aware helpers, entity loading from web requests, and pre-injected database
6
+ access parts.
7
+
8
+ ## Injected Parts
9
+
10
+ `BizController` comes with these fields already injected:
11
+
12
+ ```java
13
+ @Part protected Mixing mixing; // Schema/descriptor access
14
+ @Part protected OMA oma; // JDBC entity operations
15
+ @Part protected Mango mango; // MongoDB entity operations
16
+ @Part protected Elastic elastic; // Elasticsearch operations
17
+ @Part @Nullable protected Tenants<?, ?, ?> tenants; // Tenant management
18
+ ```
19
+
20
+ You can use `oma`, `mango`, and `elastic` directly in subclasses without declaring
21
+ your own `@Part` fields.
22
+
23
+ ## Loading Entities from Requests — load()
24
+
25
+ The `load()` method reads HTTP parameters into an entity, populating all fields
26
+ marked with `@Autoloaded`:
27
+
28
+ ```java
29
+ @Routed(value = "/product/:1/save", methods = HttpMethod.POST)
30
+ @LoginRequired
31
+ public void saveProduct(WebContext webContext, String productId) {
32
+ Product product = find(Product.class, productId);
33
+
34
+ load(webContext, product); // fills all @Autoloaded fields from the request
35
+ oma.update(product);
36
+ showSavedMessage();
37
+ webContext.respondWith().redirectToGet("/products");
38
+ }
39
+ ```
40
+
41
+ Overloads:
42
+ - `load(webContext, entity)` — loads all `@Autoloaded` properties.
43
+ - `load(webContext, entity, Mapping... properties)` — loads only the listed properties.
44
+ - `load(webContext, entity, List<Mapping> properties)` — same, with a list.
45
+
46
+ ## Resolving Entities — find()
47
+
48
+ The `find()` method resolves an entity by its ID string. It also handles the special
49
+ value `"new"`, which creates a fresh instance instead of querying the database:
50
+
51
+ ```java
52
+ Product product = find(Product.class, productId);
53
+ // If productId is "new", returns a new Product()
54
+ // Otherwise, looks up the entity — throws 404 if not found
55
+ ```
56
+
57
+ For tenant-scoped entities, use `findForTenant()` which additionally verifies that
58
+ the entity belongs to the current user's tenant.
59
+
60
+ ## CRUD Flow Pattern
61
+
62
+ The standard pattern for list/edit/delete:
63
+
64
+ ```java
65
+ @Register
66
+ public class ProductController extends BizController {
67
+
68
+ @Routed("/products")
69
+ @DefaultRoute
70
+ @LoginRequired
71
+ @Permission("permission-manage-products")
72
+ public void products(WebContext webContext) {
73
+ SQLPageHelper<Product> pageHelper =
74
+ SQLPageHelper.withQuery(tenants.forCurrentTenant(oma.select(Product.class)));
75
+ pageHelper.withBasePath("/products");
76
+ pageHelper.withSearchFields(QueryField.contains(Product.NAME));
77
+ webContext.respondWith()
78
+ .template("/templates/products/list.html.pasta", pageHelper.asPage());
79
+ }
80
+
81
+ @Routed("/product/:1")
82
+ @LoginRequired
83
+ @Permission("permission-manage-products")
84
+ public void editProduct(WebContext webContext, String productId) {
85
+ Product product = findForTenant(Product.class, productId);
86
+
87
+ if (webContext.ensureSafePOST()) {
88
+ load(webContext, product);
89
+ oma.update(product);
90
+ showSavedMessage();
91
+ webContext.respondWith().redirectToGet("/products");
92
+ return;
93
+ }
94
+
95
+ webContext.respondWith()
96
+ .template("/templates/products/edit.html.pasta", product);
97
+ }
98
+
99
+ @Routed("/product/:1/delete")
100
+ @LoginRequired
101
+ @Permission("permission-manage-products")
102
+ public void deleteProduct(WebContext webContext, String productId) {
103
+ Product product = findForTenant(Product.class, productId);
104
+ oma.delete(product);
105
+ showDeletedMessage();
106
+ webContext.respondWith().redirectToGet("/products");
107
+ }
108
+ }
109
+ ```
110
+
111
+ ## Page Helpers
112
+
113
+ For list views with pagination, search, and filtering:
114
+
115
+ - **`SQLPageHelper`** — wraps a `SmartQuery<SQLEntity>` for JDBC entities.
116
+ - **`MongoPageHelper`** — wraps a `MongoQuery<MongoEntity>` for MongoDB entities.
117
+ - **`ElasticPageHelper`** — wraps an `ElasticQuery<ElasticEntity>` for Elasticsearch.
118
+
119
+ ```java
120
+ SQLPageHelper<Product> pageHelper =
121
+ SQLPageHelper.withQuery(oma.select(Product.class).orderAsc(Product.NAME));
122
+ pageHelper.withBasePath("/products");
123
+ pageHelper.withSearchFields(QueryField.contains(Product.NAME));
124
+ pageHelper.addBooleanFacet(Product.ACTIVE, NLS.get("Product.active"));
125
+ ```
126
+
127
+ ## @Autoloaded
128
+
129
+ Mark entity properties with `@Autoloaded` to have `load()` pick them up automatically
130
+ from HTTP request parameters. The parameter name matches the property name:
131
+
132
+ ```java
133
+ @Autoloaded
134
+ @Length(150)
135
+ @Trim
136
+ private String name; // loaded from request param "name"
137
+ ```
138
+
139
+ ## Common Mistakes
140
+
141
+ 1. **Forgetting `ensureSafePOST()`** — Always check for safe POST before mutating
142
+ data. Without this, you are vulnerable to CSRF attacks.
143
+
144
+ 2. **Not returning after redirect** — After `redirectToGet()`, always `return`.
145
+ Otherwise the method continues and may try to render a template too.
146
+
147
+ 3. **Using `find()` for tenant-scoped entities** — Use `findForTenant()` instead.
148
+ Plain `find()` does not verify the tenant, which allows cross-tenant data access.
149
+
150
+ 4. **Forgetting `@DefaultRoute`** — Without a default route, exceptions in other
151
+ routes render a generic error page instead of staying in context.