sonamu 0.10.7 → 0.10.9

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 (79) hide show
  1. package/dist/api/decorators.d.ts.map +1 -1
  2. package/dist/api/decorators.js +5 -12
  3. package/dist/bin/cli.js +10 -253
  4. package/dist/database/base-model.d.ts +1 -0
  5. package/dist/database/base-model.d.ts.map +1 -1
  6. package/dist/database/base-model.js +14 -8
  7. package/dist/database/db.d.ts +2 -1
  8. package/dist/database/db.d.ts.map +1 -1
  9. package/dist/database/db.js +13 -3
  10. package/dist/database/puri-wrapper.d.ts.map +1 -1
  11. package/dist/database/puri-wrapper.js +4 -11
  12. package/dist/database/transaction-context.d.ts +8 -3
  13. package/dist/database/transaction-context.d.ts.map +1 -1
  14. package/dist/database/transaction-context.js +12 -7
  15. package/dist/migration/code-generation.js +2 -2
  16. package/dist/naite/naite-reporter.d.ts +8 -3
  17. package/dist/naite/naite-reporter.d.ts.map +1 -1
  18. package/dist/naite/naite-reporter.js +9 -4
  19. package/dist/ui-web/assets/{index-D0MHYbxl.js → index-CJf8uJYf.js} +47 -41
  20. package/dist/ui-web/assets/index-GMMIVGja.css +1 -0
  21. package/dist/ui-web/index.html +2 -2
  22. package/package.json +3 -3
  23. package/src/api/decorators.ts +11 -22
  24. package/src/bin/cli.ts +12 -375
  25. package/src/database/__tests__/transaction-scope.test.ts +227 -0
  26. package/src/database/base-model.ts +31 -6
  27. package/src/database/db.ts +17 -2
  28. package/src/database/puri-wrapper.ts +5 -22
  29. package/src/database/transaction-context.ts +14 -6
  30. package/src/migration/code-generation.ts +1 -1
  31. package/src/naite/naite-reporter.ts +8 -3
  32. package/dist/ui-web/assets/index-Dx_JX4aQ.css +0 -1
  33. package/src/skills/AGENTS.md +0 -108
  34. package/src/skills/sonamu/SKILL.md +0 -75
  35. package/src/skills/sonamu-ai-agents/SKILL.md +0 -205
  36. package/src/skills/sonamu-api/SKILL.md +0 -480
  37. package/src/skills/sonamu-auth/SKILL.md +0 -327
  38. package/src/skills/sonamu-auth/references/plugins.md +0 -310
  39. package/src/skills/sonamu-auth/references/user-id-migration-followups.md +0 -192
  40. package/src/skills/sonamu-auth/references/user-id-migration.md +0 -455
  41. package/src/skills/sonamu-config/SKILL.md +0 -203
  42. package/src/skills/sonamu-config/references/database.md +0 -452
  43. package/src/skills/sonamu-config/references/environments.md +0 -178
  44. package/src/skills/sonamu-config/references/server-options.md +0 -400
  45. package/src/skills/sonamu-entity/SKILL.md +0 -180
  46. package/src/skills/sonamu-entity/references/creation-workflow.md +0 -581
  47. package/src/skills/sonamu-entity/references/design-guides.md +0 -243
  48. package/src/skills/sonamu-entity/references/field-types.md +0 -170
  49. package/src/skills/sonamu-entity/references/relations-detail.md +0 -245
  50. package/src/skills/sonamu-entity/references/relations.md +0 -463
  51. package/src/skills/sonamu-entity/references/subset.md +0 -156
  52. package/src/skills/sonamu-fixture/SKILL.md +0 -180
  53. package/src/skills/sonamu-fixture/references/cli-usage.md +0 -439
  54. package/src/skills/sonamu-fixture/references/cone.md +0 -298
  55. package/src/skills/sonamu-frontend/SKILL.md +0 -142
  56. package/src/skills/sonamu-frontend/references/components.md +0 -323
  57. package/src/skills/sonamu-frontend/references/examples.md +0 -64
  58. package/src/skills/sonamu-frontend/references/hooks.md +0 -273
  59. package/src/skills/sonamu-frontend/references/runtime.md +0 -165
  60. package/src/skills/sonamu-frontend/references/scaffolding.md +0 -439
  61. package/src/skills/sonamu-i18n/SKILL.md +0 -287
  62. package/src/skills/sonamu-migration/SKILL.md +0 -316
  63. package/src/skills/sonamu-naite/SKILL.md +0 -266
  64. package/src/skills/sonamu-query/SKILL.md +0 -48
  65. package/src/skills/sonamu-query/references/model-patterns.md +0 -390
  66. package/src/skills/sonamu-query/references/model.md +0 -366
  67. package/src/skills/sonamu-query/references/puri.md +0 -413
  68. package/src/skills/sonamu-query/references/search.md +0 -238
  69. package/src/skills/sonamu-query/references/upsert.md +0 -324
  70. package/src/skills/sonamu-tasks/SKILL.md +0 -236
  71. package/src/skills/sonamu-testing/SKILL.md +0 -251
  72. package/src/skills/sonamu-testing/references/devrunner.md +0 -405
  73. package/src/skills/sonamu-testing/references/helpers.md +0 -185
  74. package/src/skills/sonamu-testing/references/patterns.md +0 -263
  75. package/src/skills/sonamu-testing/references/pitfalls.md +0 -588
  76. package/src/skills/sonamu-testing/references/quick-start.md +0 -285
  77. package/src/skills/sonamu-testing/references/type-safety.md +0 -172
  78. package/src/skills/sonamu-testing/references/writing-plan.md +0 -375
  79. package/src/skills/sonamu-vector/SKILL.md +0 -222
@@ -1,324 +0,0 @@
1
- # UpsertBuilder — Batch Saving Relation Data
2
-
3
- ## UBRef Type
4
-
5
- The reference object returned by `ubRegister()`:
6
-
7
- ```typescript
8
- type UBRef = {
9
- uuid: string; // unique identifier
10
- of: string; // table name
11
- use?: string; // field to reference (default: "id")
12
- };
13
- ```
14
-
15
- ## Basic Pattern
16
-
17
- ```typescript
18
- const wdb = this.getPuri("w");
19
-
20
- // Register data (returns UBRef)
21
- const userRef = wdb.ubRegister("users", { email: "john@test.com", username: "john" });
22
-
23
- // Use UBRef in related data
24
- wdb.ubRegister("employees", { user_id: userRef, department_id: deptId });
25
-
26
- // Save in order inside a transaction
27
- return wdb.transaction(async (trx) => {
28
- await trx.ubUpsert("users"); // Save first (referenced by FK)
29
- return trx.ubUpsert("employees"); // Save after (uses FK)
30
- });
31
- ```
32
-
33
- ## CRITICAL: All Required Fields Must Be Included
34
-
35
- **ubUpsert uses PostgreSQL's `ON CONFLICT ... DO UPDATE` query.**
36
-
37
- Even when updating, **all required fields (fields with NOT NULL constraints)** must be included.
38
-
39
- ```typescript
40
- // BAD - missing required field
41
- wdb.ubRegister("posts", {
42
- id: 1,
43
- title: "Updated Title",
44
- // content required field missing! → ON CONFLICT UPDATE tries to set NULL → DB error
45
- });
46
- // Error: null value in column "content" violates not-null constraint
47
-
48
- // GOOD - all required fields included
49
- wdb.ubRegister("posts", {
50
- id: 1,
51
- title: "Updated Title",
52
- content: "Updated Content", // required field included!
53
- author_id: 1, // FK also included if required!
54
- });
55
- ```
56
-
57
- **How to identify required fields**:
58
-
59
- 1. Check props in entity.json
60
- 2. Fields without `nullable: true` = required fields
61
- 3. `id`, `created_at`, fields with `dbDefault` can be omitted
62
-
63
- ```json
64
- // entity.json example
65
- {
66
- "props": [
67
- { "name": "id", "type": "integer" }, // can be omitted
68
- { "name": "title", "type": "string" }, // required! (no nullable)
69
- { "name": "content", "type": "string" }, // required! (no nullable)
70
- { "name": "category", "type": "string", "nullable": true }, // optional
71
- { "name": "created_at", "type": "date", "dbDefault": "CURRENT_TIMESTAMP" } // can be omitted
72
- ]
73
- }
74
- ```
75
-
76
- ## Save Order (Important!)
77
-
78
- Save the table referenced by FK first:
79
-
80
- ```typescript
81
- await trx.ubUpsert("companies"); // 1. No dependencies
82
- await trx.ubUpsert("departments"); // 2. Needs company_id
83
- await trx.ubUpsert("users"); // 3. No dependencies
84
- await trx.ubUpsert("employees"); // 4. Needs user_id, department_id
85
- ```
86
-
87
- ## Model save Pattern
88
-
89
- ```typescript
90
- @api({ httpMethod: "POST" })
91
- async save(spa: UserSaveParams[]): Promise<number[]> {
92
- const wdb = this.getPuri("w");
93
- spa.forEach((sp) => wdb.ubRegister("users", sp));
94
-
95
- return wdb.transaction(async (trx) => {
96
- return trx.ubUpsert("users");
97
- });
98
- }
99
- ```
100
-
101
- ## Saving Related Data
102
-
103
- ```typescript
104
- await this.getPuri("w").transaction(async (trx) => {
105
- // Register User
106
- const userRef = trx.ubRegister("users", {
107
- email: data.email,
108
- username: data.username,
109
- password: bcrypt.hashSync(data.password, 10),
110
- });
111
-
112
- // Register Employee (using userRef)
113
- trx.ubRegister("employees", {
114
- user_id: userRef,
115
- department_id: data.departmentId,
116
- salary: data.salary,
117
- });
118
-
119
- // Save in order
120
- await trx.ubUpsert("users");
121
- const [employeeId] = await trx.ubUpsert("employees");
122
- return employeeId;
123
- });
124
- ```
125
-
126
- ## Upsert (Insert or Update)
127
-
128
- ```typescript
129
- // INSERT when no id
130
- wdb.ubRegister("users", { email: "new@test.com", username: "new" });
131
-
132
- // UPDATE when id is present
133
- wdb.ubRegister("users", { id: 1, email: "updated@test.com" });
134
- ```
135
-
136
- **Conflict handling**: If the Entity has a unique index, automatically pre-fetches to populate the existing record's id, then performs UPDATE
137
-
138
- ## ManyToMany Relationships
139
-
140
- ```typescript
141
- await wdb.transaction(async (trx) => {
142
- const projectRef = trx.ubRegister("projects", { name: "Project A" });
143
-
144
- for (const empId of employeeIds) {
145
- trx.ubRegister("projects__employees", {
146
- project_id: projectRef,
147
- employee_id: empId,
148
- });
149
- }
150
-
151
- await trx.ubUpsert("projects");
152
- await trx.ubUpsert("projects__employees");
153
- });
154
- ```
155
-
156
- ## Self-Reference
157
-
158
- In hierarchical structures (e.g. categories, org charts), self-referential relationships are automatically processed level by level:
159
-
160
- ```typescript
161
- await wdb.transaction(async (trx) => {
162
- // Root category
163
- const rootRef = trx.ubRegister("categories", { name: "Root", parent_id: null });
164
-
165
- // Child category (references rootRef)
166
- const childRef = trx.ubRegister("categories", { name: "Child", parent_id: rootRef });
167
-
168
- // Grandchild category (references childRef)
169
- trx.ubRegister("categories", { name: "Grandchild", parent_id: childRef });
170
-
171
- // Internally processed level by level (Root → Child → Grandchild)
172
- await trx.ubUpsert("categories");
173
- });
174
- ```
175
-
176
- ## Bulk Insert / Bulk Upsert (Large Datasets)
177
-
178
- **Principle (same as regular saves):** call `ubRegister` for every row **outside** the transaction, then call only `ubUpsert` / `ubInsertOnly` **inside** the transaction. Split large datasets with `chunkSize`. Calling `ubRegister` inside the transaction is reserved for the special case where a row depends on the real id produced by a preceding `ubUpsert`.
179
-
180
- ### (a) Bulk insert inside a Model
181
-
182
- ```typescript
183
- async createMany(params: PostCreateParams[]): Promise<number[]> {
184
- const wdb = this.getPuri("w");
185
- params.forEach((p) => wdb.ubRegister("posts", p));
186
-
187
- return wdb.transaction(async (trx) => {
188
- return trx.ubInsertOnly("posts", { chunkSize: 5000 });
189
- });
190
- }
191
- ```
192
-
193
- ### (b) Standalone Puri (seed / batch scripts outside a Model)
194
-
195
- Outside a Model there is no `this.getPuri("w")`, so build a `PuriWrapper` directly. Use `DB.getDB("w")` only in this script context — inside Models always use `getPuri("w")`.
196
-
197
- ```typescript
198
- import { DB, PuriWrapper, UpsertBuilder } from "sonamu";
199
-
200
- const puri = new PuriWrapper(DB.getDB("w"), new UpsertBuilder());
201
- for (const row of rows) puri.ubRegister("audit_logs", row);
202
-
203
- await puri.transaction(async (trx) => {
204
- await trx.ubInsertOnly("audit_logs", { chunkSize: 5000 }); // or ubUpsert
205
- });
206
- ```
207
-
208
- ### (c) Multiple tables in one transaction (FK order)
209
-
210
- Register every table outside the transaction, then upsert parents before children.
211
-
212
- ```typescript
213
- const puri = new PuriWrapper(DB.getDB("w"), new UpsertBuilder());
214
- for (const row of postRows) puri.ubRegister("posts", row);
215
- for (const row of tagRows) puri.ubRegister("post_tags", row);
216
-
217
- await puri.transaction(async (trx) => {
218
- await trx.ubInsertOnly("posts", { chunkSize: 5000 }); // parent first
219
- await trx.ubInsertOnly("post_tags", { chunkSize: 5000 }); // child next
220
- });
221
- ```
222
-
223
- ## ubInsertOnly (INSERT Only)
224
-
225
- Perform INSERT without UPDATE:
226
-
227
- ```typescript
228
- await trx.ubInsertOnly("logs", { chunkSize: 1000 });
229
- ```
230
-
231
- ## ubUpdateBatch (Batch Update)
232
-
233
- Bulk UPDATE operations:
234
-
235
- ```typescript
236
- // Register multiple records
237
- wdb.ubRegister("users", { id: 1, status: "active" });
238
- wdb.ubRegister("users", { id: 2, status: "active" });
239
- wdb.ubRegister("users", { id: 3, status: "inactive" });
240
-
241
- await wdb.transaction(async (trx) => {
242
- await trx.ubUpdateBatch("users", {
243
- chunkSize: 500, // batch size (default: 500)
244
- where: "id", // WHERE condition column (default: "id")
245
- });
246
- });
247
-
248
- // Composite key for WHERE condition
249
- await trx.ubUpdateBatch("user_settings", {
250
- where: ["user_id", "setting_key"],
251
- });
252
- ```
253
-
254
- ## UpsertOptions
255
-
256
- Options for `ubUpsert()`:
257
-
258
- ```typescript
259
- type UpsertOptions = {
260
- chunkSize?: number; // batch size
261
- cleanOrphans?: string | string[]; // FK column(s) to use as basis for deleting orphan records
262
- inherit?: string[]; // columns to preserve existing values on UPDATE
263
- };
264
- ```
265
-
266
- ### chunkSize
267
-
268
- Specify batch size for large data processing:
269
-
270
- ```typescript
271
- await trx.ubUpsert("logs", { chunkSize: 1000 });
272
- ```
273
-
274
- ### cleanOrphans
275
-
276
- Automatically delete orphan records based on FK:
277
-
278
- ```typescript
279
- // Single FK
280
- await trx.ubUpsert("order_items", {
281
- cleanOrphans: "order_id", // delete records with the same order_id that were not upserted this time
282
- });
283
-
284
- // Composite FK
285
- await trx.ubUpsert("project_members", {
286
- cleanOrphans: ["project_id", "team_id"],
287
- });
288
- ```
289
-
290
- ### inherit
291
-
292
- Preserve existing values for specific columns on UPDATE:
293
-
294
- ```typescript
295
- await trx.ubUpsert("users", {
296
- inherit: ["created_at", "password"], // these columns are excluded from UPDATE
297
- });
298
- ```
299
-
300
- ## ubUpsertOrInsert (Conditional Mode)
301
-
302
- Select upsert or insert mode at runtime.
303
-
304
- ```typescript
305
- await trx.ubUpsertOrInsert("logs", "insert"); // INSERT only
306
- await trx.ubUpsertOrInsert("users", "upsert"); // UPSERT (default)
307
- await trx.ubUpsertOrInsert("users", "upsert", { cleanOrphans: "team_id" });
308
- ```
309
-
310
- | Parameter | Type | Description |
311
- | ----------- | ------------------------ | --------------------------------------------------- |
312
- | `tableName` | string | table name |
313
- | `mode` | `"upsert"` \| `"insert"` | operation mode |
314
- | `options` | `UpsertOptions` | chunkSize, cleanOrphans, inherit (same as ubUpsert) |
315
-
316
- When `mode: "insert"`, unlike `insertOnly`, `UpsertOptions` (cleanOrphans, inherit) can be used.
317
-
318
- ## Rules
319
-
320
- - MUST use inside `transaction()`
321
- - MUST call `ubUpsert()` for FK-referenced tables first (correct order)
322
- - UBRef can ONLY be used inside `ubRegister` (not for direct DB queries)
323
- - Self-reference is auto-handled by level-based insertion
324
- - Unique index conflicts are auto-resolved by pre-fetching existing IDs
@@ -1,236 +0,0 @@
1
- ---
2
- name: sonamu-tasks
3
- description: Runs background work with Sonamu Tasks. Use when implementing a background job, scheduling recurring work, or building a multi-step process that must survive a restart. Covers durable steps, cron scheduling, retry policies, and the task worker.
4
- ---
5
-
6
- # Tasks (Workflow System)
7
-
8
- PostgreSQL-based durable workflow engine. Uses the `@sonamu-kit/tasks` package.
9
-
10
- **Source code:**
11
-
12
- - Decorator: `modules/sonamu/src/tasks/decorator.ts`
13
- - StepWrapper: `modules/sonamu/src/tasks/step-wrapper.ts`
14
- - WorkflowManager: `modules/sonamu/src/tasks/workflow-manager.ts`
15
- - @sonamu-kit/tasks: `modules/tasks/`
16
-
17
- ## Workflow Definition
18
-
19
- Define with the `workflow()` function. When exported, the syncer automatically collects and registers it with WorkflowManager.
20
-
21
- ```typescript
22
- import { workflow } from "sonamu";
23
-
24
- // Method 1: decorator + function separated
25
- export const myTask = workflow({
26
- version: "1",
27
- })(async ({ input, step, logger, version }) => {
28
- // ...
29
- });
30
-
31
- // Method 2: decorator + function inlined
32
- export const myTask = workflow(
33
- {
34
- version: "1",
35
- },
36
- async ({ input, step, logger, version }) => {
37
- // ...
38
- },
39
- );
40
- ```
41
-
42
- ### DefineWorkflowOptions
43
-
44
- | Option | Type | Required | Description |
45
- | ------------- | ------------------ | -------- | ------------------------------------------------------------------- |
46
- | `version` | `string` | Y | Workflow version (distinguishes from existing runs when changed) |
47
- | `name` | `string` | N | Workflow name (default: function name converted to underscore case) |
48
- | `schema` | `StandardSchemaV1` | N | Input validation schema (Zod, etc.) |
49
- | `schedules` | `Schedule[]` | N | Array of cron schedules |
50
- | `retryPolicy` | `RetryPolicy` | N | Retry policy |
51
-
52
- ### Workflow Function Parameters
53
-
54
- | Parameter | Type | Description |
55
- | --------- | ---------------- | ----------------------------------------- |
56
- | `input` | `Input` | Input value passed when the workflow runs |
57
- | `step` | `StepWrapper` | Tool for defining/executing steps |
58
- | `logger` | `Logger` | @logtape/logtape logger |
59
- | `version` | `string \| null` | Current workflow version |
60
-
61
- ## Step
62
-
63
- An atomic unit of execution within a workflow. On failure, retry begins from that step.
64
-
65
- ### step.define — Inline Function
66
-
67
- ```typescript
68
- const result = await step
69
- .define({ name: "fetch-data" }, async () => {
70
- const data = await fetchSomething();
71
- return data;
72
- })
73
- .run();
74
- ```
75
-
76
- ### step.get — Wrapping an Existing Method
77
-
78
- ```typescript
79
- // Wrap a Model method as a Step
80
- const result = await step.get(MyModel, "processData").run(inputData);
81
-
82
- // Specify a custom name
83
- const result = await step.get({ name: "custom_step" }, MyService, "execute").run(params);
84
- ```
85
-
86
- Overloads for `step.get`:
87
-
88
- - `step.get(object, methodName)` — Step name is the methodName converted to underscore case
89
- - `step.get({ name }, object, methodName)` — Step name specified directly
90
-
91
- ### step.sleep — Durable Wait
92
-
93
- ```typescript
94
- await step.sleep("wait-before-retry", "30m");
95
- await step.sleep("daily-delay", "1d");
96
- ```
97
-
98
- The wait time is preserved even if the server restarts.
99
-
100
- **DurationString format:** `{number}{unit}` — e.g. `"5s"`, `"30m"`, `"2h"`, `"7d"`, `"1w"`, `"1y"`
101
-
102
- ## Scheduling (cron)
103
-
104
- ```typescript
105
- export const dailyReport = workflow(
106
- {
107
- version: "1",
108
- schedules: [
109
- {
110
- expression: "0 9 * * *", // every day at 9am
111
- name: "daily-report", // optional (default: workflowName[expression])
112
- input: () => ({ date: new Date().toISOString() }), // optional
113
- },
114
- ],
115
- },
116
- async ({ input, step }) => {
117
- // ...
118
- },
119
- );
120
- ```
121
-
122
- | Field | Type | Required | Description |
123
- | ------------ | ------------------- | -------- | ---------------------------------------------------- |
124
- | `expression` | `string` | Y | cron expression |
125
- | `name` | `string` | N | Schedule name (default: `workflowName[expression]`) |
126
- | `input` | `Executable<Input>` | N | Input value to pass on execution (function or value) |
127
-
128
- The timezone follows the `api.timezone` setting in `sonamu.config.ts`.
129
-
130
- ## Retry Policy
131
-
132
- ### Static Policy (Default)
133
-
134
- ```typescript
135
- export const reliableTask = workflow(
136
- {
137
- version: "1",
138
- retryPolicy: {
139
- maxAttempts: 5, // maximum retry attempts (default: 5)
140
- initialIntervalMs: 1000, // first retry wait time (default: 1000ms)
141
- backoffCoefficient: 2, // wait time multiplier (default: 2)
142
- maximumIntervalMs: 60000, // maximum wait time
143
- },
144
- },
145
- async ({ step }) => {
146
- // ...
147
- },
148
- );
149
- ```
150
-
151
- ### Dynamic Policy
152
-
153
- ```typescript
154
- retryPolicy: {
155
- maxAttempts: 10,
156
- shouldRetry: (error, attempt) => ({
157
- shouldRetry: error.message !== "FATAL",
158
- delayMs: attempt * 2000,
159
- }),
160
- }
161
- ```
162
-
163
- ## sonamu.config.ts Configuration
164
-
165
- ```typescript
166
- export default defineConfig({
167
- tasks: {
168
- enableWorker: true,
169
- workerOptions: {
170
- concurrency: 4, // concurrent execution count (default: CPU cores - 1)
171
- usePubSub: true, // use DB pub/sub (default: true)
172
- listenDelay: 500, // execution delay after pub/sub receive in ms (default: 500)
173
- },
174
- contextProvider: (defaultContext) => {
175
- // build Context to use within workflows
176
- return { ...defaultContext };
177
- },
178
- },
179
- });
180
- ```
181
-
182
- | Option | Type | Description |
183
- | --------------------------- | ------------------ | -------------------------------------------------- |
184
- | `enableWorker` | `boolean` | Whether to enable Worker (use only in daemon mode) |
185
- | `workerOptions.concurrency` | `number` | Number of concurrently executing tasks |
186
- | `workerOptions.usePubSub` | `boolean` | Use PostgreSQL pub/sub |
187
- | `workerOptions.listenDelay` | `number` | Execution delay after pub/sub receive (ms) |
188
- | `contextProvider` | `(ctx) => Context` | Context creation function when workflow runs |
189
-
190
- ## Manual Execution
191
-
192
- ```typescript
193
- import { Sonamu } from "sonamu";
194
-
195
- // Run via WorkflowManager
196
- const handle = await Sonamu.workflowManager.run(
197
- { name: "my-task", version: "1" },
198
- { target: "manual" },
199
- );
200
-
201
- // Wait for result
202
- const result = await handle.result();
203
- ```
204
-
205
- ## File Placement
206
-
207
- ```
208
- packages/api/src/application/
209
- ├── {domain}/
210
- │ ├── {domain}.model.ts
211
- │ ├── {domain}.types.ts
212
- │ └── {domain}.workflow.ts ← workflow file
213
- ```
214
-
215
- Define with `workflow()` and export in the workflow file; the syncer will collect it automatically.
216
-
217
- ## Architecture
218
-
219
- ```
220
- Dev Server startup
221
- → WorkflowManager initialization (BackendPostgres)
222
- → Worker startup (when enableWorker: true)
223
- → syncer collects .workflow.ts files
224
- → registers via WorkflowManager.synchronize()
225
- → cron schedules start automatically
226
-
227
- On HMR
228
- → re-register workflows from changed files (synchronize)
229
-
230
- Execution flow
231
- → run() called → workflow execution record created in DB
232
- → Worker receives via pub/sub → steps executed sequentially
233
- → checkpoint saved to DB on each step completion
234
- → on failure: retry from that step according to retryPolicy
235
- → on server restart: incomplete workflows recovered from DB and continue
236
- ```