@objectql/cli 1.8.4 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/database-push.d.ts +5 -0
  3. package/dist/commands/database-push.js +15 -0
  4. package/dist/commands/database-push.js.map +1 -0
  5. package/dist/commands/dev.d.ts +2 -0
  6. package/dist/commands/dev.js +94 -6
  7. package/dist/commands/dev.js.map +1 -1
  8. package/dist/commands/doctor.d.ts +4 -0
  9. package/dist/commands/doctor.js +37 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/init.js +31 -30
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/serve.d.ts +2 -0
  14. package/dist/commands/serve.js +122 -46
  15. package/dist/commands/serve.js.map +1 -1
  16. package/dist/commands/start.d.ts +1 -0
  17. package/dist/commands/start.js +15 -0
  18. package/dist/commands/start.js.map +1 -1
  19. package/dist/index.js +15 -338
  20. package/dist/index.js.map +1 -1
  21. package/dist/register/ai.d.ts +2 -0
  22. package/dist/register/ai.js +24 -0
  23. package/dist/register/ai.js.map +1 -0
  24. package/dist/register/database.d.ts +2 -0
  25. package/dist/register/database.js +64 -0
  26. package/dist/register/database.js.map +1 -0
  27. package/dist/register/i18n.d.ts +2 -0
  28. package/dist/register/i18n.js +52 -0
  29. package/dist/register/i18n.js.map +1 -0
  30. package/dist/register/lifecycle.d.ts +2 -0
  31. package/dist/register/lifecycle.js +68 -0
  32. package/dist/register/lifecycle.js.map +1 -0
  33. package/dist/register/scaffold.d.ts +2 -0
  34. package/dist/register/scaffold.js +48 -0
  35. package/dist/register/scaffold.js.map +1 -0
  36. package/dist/register/tools.d.ts +2 -0
  37. package/dist/register/tools.js +49 -0
  38. package/dist/register/tools.js.map +1 -0
  39. package/package.json +13 -7
  40. package/templates/hello-world/.vscode/extensions.json +7 -0
  41. package/templates/hello-world/CHANGELOG.md +49 -0
  42. package/templates/hello-world/README.md +29 -0
  43. package/templates/hello-world/package.json +24 -0
  44. package/templates/hello-world/src/index.ts +58 -0
  45. package/templates/hello-world/tsconfig.json +10 -0
  46. package/templates/starter/.vscode/extensions.json +7 -0
  47. package/{CHANGELOG.md → templates/starter/CHANGELOG.md} +46 -42
  48. package/templates/starter/README.md +17 -0
  49. package/templates/starter/__tests__/projects-hooks-actions.test.ts +490 -0
  50. package/templates/starter/jest.config.js +16 -0
  51. package/templates/starter/package.json +52 -0
  52. package/templates/starter/src/README.pages.md +110 -0
  53. package/templates/starter/src/demo.app.yml +4 -0
  54. package/templates/starter/src/i18n/zh-CN/projects.json +22 -0
  55. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.data.yml +18 -0
  56. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.object.yml +156 -0
  57. package/templates/starter/src/modules/projects/project_approval.workflow.yml +51 -0
  58. package/templates/starter/src/modules/projects/projects.action.ts +472 -0
  59. package/templates/starter/src/modules/projects/projects.data.yml +13 -0
  60. package/templates/starter/src/modules/projects/projects.hook.ts +339 -0
  61. package/templates/starter/src/modules/projects/projects.object.yml +148 -0
  62. package/templates/starter/src/modules/projects/projects.permission.yml +141 -0
  63. package/templates/starter/src/modules/projects/projects.validation.yml +37 -0
  64. package/templates/starter/src/modules/tasks/tasks.data.yml +23 -0
  65. package/templates/starter/src/modules/tasks/tasks.object.yml +34 -0
  66. package/templates/starter/src/modules/tasks/tasks.permission.yml +167 -0
  67. package/templates/starter/src/seed.ts +55 -0
  68. package/templates/starter/src/types/index.ts +3 -0
  69. package/templates/starter/src/types/kitchen_sink.ts +101 -0
  70. package/templates/starter/src/types/projects.ts +49 -0
  71. package/templates/starter/src/types/tasks.ts +33 -0
  72. package/templates/starter/tsconfig.json +11 -0
  73. package/templates/starter/tsconfig.tsbuildinfo +1 -0
  74. package/AI_EXAMPLES.md +0 -154
  75. package/AI_IMPLEMENTATION_SUMMARY.md +0 -509
  76. package/AI_TUTORIAL.md +0 -144
  77. package/IMPLEMENTATION_SUMMARY.md +0 -437
  78. package/USAGE_EXAMPLES.md +0 -951
  79. package/__tests__/commands.test.ts +0 -426
  80. package/jest.config.js +0 -19
  81. package/src/commands/ai.ts +0 -509
  82. package/src/commands/build.ts +0 -98
  83. package/src/commands/dev.ts +0 -23
  84. package/src/commands/format.ts +0 -110
  85. package/src/commands/generate.ts +0 -135
  86. package/src/commands/i18n.ts +0 -303
  87. package/src/commands/init.ts +0 -191
  88. package/src/commands/lint.ts +0 -98
  89. package/src/commands/migrate.ts +0 -314
  90. package/src/commands/new.ts +0 -221
  91. package/src/commands/repl.ts +0 -120
  92. package/src/commands/serve.ts +0 -96
  93. package/src/commands/start.ts +0 -100
  94. package/src/commands/sync.ts +0 -328
  95. package/src/commands/test.ts +0 -98
  96. package/src/index.ts +0 -356
  97. package/tsconfig.json +0 -15
  98. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,18 @@
1
+ - simple_text: "Hello World"
2
+ long_text: |
3
+ This is a long text.
4
+ It spans multiple lines.
5
+ rich_content: "# Markdown Header\n\n- List Item 1\n- List Item 2"
6
+ count: 42
7
+ price: 99.99
8
+ progress: 0.75
9
+ is_active: true
10
+ due_date: "2024-12-31"
11
+ meeting_time: "2024-12-31T10:00:00Z"
12
+ status: "published"
13
+ tags: ["tech", "blog"]
14
+ email_address: "test@example.com"
15
+ website: "https://objectql.org"
16
+ metadata:
17
+ source: "seed_data"
18
+ version: 1
@@ -0,0 +1,156 @@
1
+ label: "Kitchen Sink (All Fields)"
2
+ description: "A demonstration of every supported field type in ObjectQL"
3
+ icon: "layout-grid-line"
4
+
5
+ fields:
6
+ # --- String Types ---
7
+ simple_text:
8
+ type: text
9
+ label: Simple Text
10
+ required: true
11
+ min_length: 3
12
+ max_length: 100
13
+ help_text: "Basic single-line text"
14
+
15
+ long_text:
16
+ type: textarea
17
+ label: Multi-line Text
18
+ rows: 4
19
+
20
+ rich_content:
21
+ type: markdown
22
+ label: Markdown Content
23
+
24
+ raw_html:
25
+ type: html
26
+ label: HTML Content
27
+ readonly: true # Usually HTML is generated or read-only
28
+
29
+ # --- Number Types ---
30
+ count:
31
+ type: number
32
+ label: Integer Count
33
+ scale: 0
34
+
35
+ price:
36
+ type: currency
37
+ label: Price
38
+ currency: USD
39
+ scale: 2
40
+
41
+ progress:
42
+ type: percent
43
+ label: Progress
44
+ min: 0
45
+ max: 1
46
+ scale: 2 # 0.55 -> 55%
47
+
48
+ # --- Boolean ---
49
+ is_active:
50
+ type: boolean
51
+ label: Is Active
52
+ defaultValue: true
53
+
54
+ # --- Date & Time ---
55
+ due_date:
56
+ type: date
57
+ label: Due Date
58
+
59
+ meeting_time:
60
+ type: datetime
61
+ label: Meeting (Date & Time)
62
+
63
+ alarm:
64
+ type: time
65
+ label: Alarm Time
66
+
67
+ # --- Selection ---
68
+ status:
69
+ type: select
70
+ label: Status
71
+ options:
72
+ - label: Draft
73
+ value: draft
74
+ - label: Published
75
+ value: published
76
+ - label: Archived
77
+ value: archived
78
+ defaultValue: draft
79
+
80
+ tags:
81
+ type: select
82
+ label: Tags
83
+ multiple: true
84
+ options:
85
+ - tech
86
+ - news
87
+ - blog # Simple string array shorthand
88
+
89
+ # --- Contact & Web ---
90
+ email_address:
91
+ type: email
92
+ label: Email
93
+
94
+ phone_number:
95
+ type: phone
96
+ label: Phone
97
+
98
+ website:
99
+ type: url
100
+ label: Website URL
101
+
102
+ # --- Media ---
103
+ profile_pic:
104
+ type: image
105
+ label: Avatar
106
+ multiple: false
107
+ max_size: 1048576 # 1MB
108
+
109
+ attachment:
110
+ type: file
111
+ label: Attachment document
112
+
113
+ gallery:
114
+ type: image
115
+ label: Image Gallery
116
+ multiple: true
117
+
118
+ # --- Security ---
119
+ secret_code:
120
+ type: password
121
+ label: Secret Code
122
+
123
+ # --- Advanced / Complex ---
124
+ coords:
125
+ type: location
126
+ label: GPS Coordinates
127
+
128
+ metadata:
129
+ type: object
130
+ label: JSON Metadata
131
+
132
+ embedding:
133
+ type: vector
134
+ label: Vector Embedding
135
+ dimension: 1536 # e.g. OpenAI ada-002
136
+
137
+ # --- Relationships ---
138
+ related_project:
139
+ type: lookup
140
+ label: Related Project
141
+ reference_to: projects
142
+
143
+ # Note: master_detail requires this object to be the detail side
144
+ # parent_project:
145
+ # type: master_detail
146
+ # reference_to: projects
147
+
148
+ # --- Computed ---
149
+ # auto_id:
150
+ # type: auto_number
151
+ # prefix: "KS-"
152
+
153
+ # calc_value:
154
+ # type: formula
155
+ # expression: "{count} * 10"
156
+
@@ -0,0 +1,51 @@
1
+ name: project_approval
2
+ label: "High Budget Project Approval"
3
+ type: approval
4
+ object: projects
5
+ description: "Approval process for high budget projects (> $50,000)"
6
+
7
+ trigger:
8
+ event: create_or_update
9
+ conditions:
10
+ - field: budget
11
+ operator: ">"
12
+ value: 50000
13
+ - field: status
14
+ operator: "="
15
+ value: "planned"
16
+
17
+ steps:
18
+ - name: manager_review
19
+ label: "Manager Review"
20
+ type: approval
21
+
22
+ assignee:
23
+ type: role
24
+ role: manager
25
+
26
+ actions:
27
+ approve:
28
+ label: "Approve"
29
+ next_step: finance_review
30
+ reject:
31
+ label: "Reject"
32
+ outcome: rejected
33
+
34
+ - name: finance_review
35
+ label: "Finance Review"
36
+ type: approval
37
+
38
+ assignee:
39
+ type: role
40
+ role: finance_admin
41
+
42
+ actions:
43
+ approve:
44
+ label: "Approve Funding"
45
+ outcome: approved
46
+ updates:
47
+ status: "in_progress"
48
+ approved_at: "$now"
49
+ reject:
50
+ label: "Reject Funding"
51
+ outcome: rejected
@@ -0,0 +1,472 @@
1
+ import { ActionDefinition } from '@objectql/types';
2
+ import { Projects } from '../../types';
3
+
4
+ /**
5
+ * Project Actions - Comprehensive Examples
6
+ *
7
+ * This file demonstrates action patterns according to the ObjectQL specification:
8
+ * 1. Record actions (operate on specific records)
9
+ * 2. Global actions (operate on collections)
10
+ * 3. Input validation
11
+ * 4. Multi-step business logic
12
+ * 5. Error handling
13
+ */
14
+
15
+ /**
16
+ * Input type for the complete action
17
+ */
18
+ interface CompleteInput {
19
+ comment?: string;
20
+ completion_date?: Date;
21
+ }
22
+
23
+ /**
24
+ * complete - Record Action Example
25
+ *
26
+ * Demonstrates:
27
+ * - Fetching and validating current state
28
+ * - Performing atomic updates
29
+ * - Returning structured results
30
+ * - Input parameter usage
31
+ */
32
+ export const complete: ActionDefinition<Projects, CompleteInput> = {
33
+ handler: async ({ id, input, api, user }) => {
34
+ const { comment, completion_date } = input;
35
+
36
+ console.log(`[Action] Completing project ${id} by ${user?.id}`);
37
+
38
+ // 1. Validate - Fetch current state
39
+ const project = await api.findOne('projects', id!);
40
+
41
+ if (!project) {
42
+ throw new Error('Project not found');
43
+ }
44
+
45
+ if (project.status === 'completed') {
46
+ throw new Error('Project is already completed');
47
+ }
48
+
49
+ // 2. Validate - Check if user has permission
50
+ /*
51
+ if (!user?.isAdmin && project.owner !== user?.id) {
52
+ throw new Error('Only the project owner or admin can complete the project');
53
+ }
54
+ */
55
+
56
+ // 3. Perform update with atomic operation
57
+ const updateData: any = {
58
+ status: 'completed',
59
+ end_date: completion_date || new Date()
60
+ };
61
+
62
+ // Add completion comment to description
63
+ if (comment) {
64
+ updateData.description = project.description
65
+ ? `${project.description}\n\n[Completed on ${new Date().toISOString()}]: ${comment}`
66
+ : `[Completed on ${new Date().toISOString()}]: ${comment}`;
67
+ }
68
+
69
+ await api.update('projects', id!, updateData);
70
+
71
+ // 4. Optional: Create completion record or notification
72
+ /*
73
+ await api.create('project_completions', {
74
+ project_id: id,
75
+ completed_by: user?.id,
76
+ completed_at: new Date(),
77
+ comment: comment
78
+ });
79
+ */
80
+
81
+ // 5. Return structured result
82
+ return {
83
+ success: true,
84
+ message: "Project completed successfully",
85
+ project_id: id,
86
+ completed_at: updateData.end_date
87
+ };
88
+ }
89
+ };
90
+
91
+ /**
92
+ * Input type for the approve action
93
+ */
94
+ interface ApproveInput {
95
+ comment: string;
96
+ }
97
+
98
+ /**
99
+ * approve - Record Action with State Transition
100
+ *
101
+ * Demonstrates:
102
+ * - State machine validation
103
+ * - Required input parameters
104
+ * - Business logic enforcement
105
+ */
106
+ export const approve: ActionDefinition<Projects, ApproveInput> = {
107
+ handler: async ({ id, input, api, user }) => {
108
+ const { comment } = input;
109
+
110
+ // 1. Validate input
111
+ if (!comment || comment.trim().length === 0) {
112
+ throw new Error('Approval comment is required');
113
+ }
114
+
115
+ // 2. Fetch and validate current state
116
+ const project = await api.findOne('projects', id!);
117
+
118
+ if (!project) {
119
+ throw new Error('Project not found');
120
+ }
121
+
122
+ if (project.status !== 'planned') {
123
+ throw new Error('Only projects in "planned" status can be approved');
124
+ }
125
+
126
+ // 3. Check budget threshold
127
+ if (project.budget > 100000 && !user?.isAdmin) {
128
+ throw new Error('Projects with budget over $100,000 require admin approval');
129
+ }
130
+
131
+ // 4. Perform approval
132
+ await api.update('projects', id!, {
133
+ status: 'in_progress',
134
+ approved_by: user?.id,
135
+ approved_at: new Date(),
136
+ approval_comment: comment
137
+ });
138
+
139
+ // 5. Log approval
140
+ console.log(`[Action] Project ${project.name} approved by ${user?.id}`);
141
+
142
+ return {
143
+ success: true,
144
+ message: 'Project approved and moved to in_progress',
145
+ new_status: 'in_progress'
146
+ };
147
+ }
148
+ };
149
+
150
+ /**
151
+ * Input type for clone action
152
+ */
153
+ interface CloneInput {
154
+ new_name: string;
155
+ copy_tasks?: boolean;
156
+ }
157
+
158
+ /**
159
+ * clone - Record Action with Related Data
160
+ *
161
+ * Demonstrates:
162
+ * - Creating new records based on existing ones
163
+ * - Copying related data
164
+ * - Complex multi-step operations
165
+ */
166
+ export const clone: ActionDefinition<Projects, CloneInput> = {
167
+ handler: async ({ id, input, api, user }) => {
168
+ const { new_name, copy_tasks = false } = input;
169
+
170
+ // 1. Validate input
171
+ if (!new_name || new_name.trim().length === 0) {
172
+ throw new Error('New project name is required');
173
+ }
174
+
175
+ // 2. Fetch source project
176
+ const sourceProject = await api.findOne('projects', id!);
177
+
178
+ if (!sourceProject) {
179
+ throw new Error('Source project not found');
180
+ }
181
+
182
+ // 3. Create cloned project
183
+ const clonedData = {
184
+ name: new_name,
185
+ description: `Cloned from: ${sourceProject.name}\n\n${sourceProject.description || ''}`,
186
+ status: 'planned', // Always start as planned
187
+ priority: sourceProject.priority,
188
+ owner: user?.id || sourceProject.owner, // Assign to current user
189
+ budget: sourceProject.budget,
190
+ start_date: sourceProject.start_date
191
+ // Don't copy: end_date, completed status
192
+ };
193
+
194
+ const newProject = await api.create('projects', clonedData);
195
+
196
+ // 4. Optional: Copy related tasks
197
+ if (copy_tasks) {
198
+ /*
199
+ const tasks = await api.find('tasks', {
200
+ filters: [['project_id', '=', id]]
201
+ });
202
+
203
+ for (const task of tasks) {
204
+ await api.create('tasks', {
205
+ name: task.name,
206
+ description: task.description,
207
+ project_id: newProject._id,
208
+ status: 'pending', // Reset status
209
+ priority: task.priority
210
+ });
211
+ }
212
+ */
213
+ }
214
+
215
+ console.log(`[Action] Project cloned: ${sourceProject.name} -> ${new_name}`);
216
+
217
+ return {
218
+ success: true,
219
+ message: 'Project cloned successfully',
220
+ original_id: id,
221
+ new_project_id: newProject._id,
222
+ tasks_copied: copy_tasks
223
+ };
224
+ }
225
+ };
226
+
227
+ /**
228
+ * Input type for bulk import
229
+ */
230
+ interface ImportProjectsInput {
231
+ source: 'csv' | 'json' | 'api';
232
+ data?: any[];
233
+ file_url?: string;
234
+ }
235
+
236
+ /**
237
+ * import_projects - Global Action Example
238
+ *
239
+ * Demonstrates:
240
+ * - Batch operations
241
+ * - Data transformation
242
+ * - Error collection
243
+ * - Progress reporting
244
+ */
245
+ export const import_projects: ActionDefinition<Projects, ImportProjectsInput> = {
246
+ handler: async ({ input, api, user }) => {
247
+ const { source, data, file_url } = input;
248
+
249
+ console.log(`[Action] Importing projects from ${source} by ${user?.id}`);
250
+
251
+ // 1. Validate input
252
+ if (!data && !file_url) {
253
+ throw new Error('Either data array or file_url must be provided');
254
+ }
255
+
256
+ // 2. Fetch data based on source
257
+ let projectsData: any[] = data || [];
258
+
259
+ if (file_url) {
260
+ // Example: Fetch from URL
261
+ /*
262
+ const response = await fetch(file_url);
263
+ projectsData = await response.json();
264
+ */
265
+ throw new Error('file_url import not yet implemented');
266
+ }
267
+
268
+ // 3. Validate and import each project
269
+ const results = {
270
+ successCount: 0,
271
+ failed: 0,
272
+ errors: [] as any[]
273
+ };
274
+
275
+ for (let i = 0; i < projectsData.length; i++) {
276
+ const projectData = projectsData[i];
277
+
278
+ try {
279
+ // Validate required fields
280
+ if (!projectData.name) {
281
+ throw new Error('Project name is required');
282
+ }
283
+
284
+ // Set defaults
285
+ const importData = {
286
+ name: projectData.name,
287
+ description: projectData.description || '',
288
+ status: projectData.status || 'planned',
289
+ priority: projectData.priority || 'normal',
290
+ budget: projectData.budget || 0,
291
+ owner: projectData.owner || user?.id,
292
+ start_date: projectData.start_date
293
+ };
294
+
295
+ // Create project
296
+ await api.create('projects', importData);
297
+ results.successCount++;
298
+
299
+ } catch (error: any) {
300
+ results.failed++;
301
+ results.errors.push({
302
+ row: i + 1,
303
+ name: projectData.name || 'Unknown',
304
+ error: error.message
305
+ });
306
+ }
307
+ }
308
+
309
+ console.log(`[Action] Import completed: ${results.successCount} succeeded, ${results.failed} failed`);
310
+
311
+ return {
312
+ success: results.failed === 0,
313
+ message: `Imported ${results.successCount} projects, ${results.failed} failed`,
314
+ ...results
315
+ };
316
+ }
317
+ };
318
+
319
+ /**
320
+ * Input type for bulk update
321
+ */
322
+ interface BulkUpdateStatusInput {
323
+ project_ids: string[];
324
+ new_status: 'planned' | 'in_progress' | 'completed';
325
+ }
326
+
327
+ /**
328
+ * bulk_update_status - Global Action for Batch Updates
329
+ *
330
+ * Demonstrates:
331
+ * - Operating on multiple records
332
+ * - Validation across multiple items
333
+ * - Transactional operations (if supported)
334
+ */
335
+ export const bulk_update_status: ActionDefinition<Projects, BulkUpdateStatusInput> = {
336
+ handler: async ({ input, api, user }) => {
337
+ const { project_ids, new_status } = input;
338
+
339
+ // 1. Validate input
340
+ if (!project_ids || project_ids.length === 0) {
341
+ throw new Error('At least one project ID must be provided');
342
+ }
343
+
344
+ if (project_ids.length > 100) {
345
+ throw new Error('Cannot update more than 100 projects at once');
346
+ }
347
+
348
+ // 2. Update each project
349
+ // Note: This uses an N+1 query pattern (fetching each project individually)
350
+ // For production with large batches, consider fetching all projects at once:
351
+ // const projects = await api.find('projects', { filters: [['_id', 'in', project_ids]] });
352
+ // However, for this example with a 100-project limit, the current approach
353
+ // provides clearer per-record validation and error handling.
354
+ const results = {
355
+ updated: 0,
356
+ skipped: 0,
357
+ errors: [] as any[]
358
+ };
359
+
360
+ for (const id of project_ids) {
361
+ try {
362
+ const project = await api.findOne('projects', id);
363
+
364
+ if (!project) {
365
+ results.errors.push({
366
+ project_id: id,
367
+ error: 'Project not found'
368
+ });
369
+ results.skipped++;
370
+ continue;
371
+ }
372
+
373
+ // Validate transition (simplified)
374
+ if (project.status === 'completed' && new_status !== 'completed') {
375
+ results.errors.push({
376
+ project_id: id,
377
+ name: project.name,
378
+ error: 'Cannot change status of completed projects'
379
+ });
380
+ results.skipped++;
381
+ continue;
382
+ }
383
+
384
+ // Perform update
385
+ await api.update('projects', id, { status: new_status });
386
+ results.updated++;
387
+
388
+ } catch (error: any) {
389
+ results.errors.push({
390
+ project_id: id,
391
+ error: error.message
392
+ });
393
+ results.skipped++;
394
+ }
395
+ }
396
+
397
+ console.log(`[Action] Bulk update: ${results.updated} updated, ${results.skipped} skipped`);
398
+
399
+ return {
400
+ success: results.skipped === 0,
401
+ message: `Updated ${results.updated} projects, skipped ${results.skipped}`,
402
+ ...results
403
+ };
404
+ }
405
+ };
406
+
407
+ /**
408
+ * generate_report - Global Action for Reporting
409
+ *
410
+ * Demonstrates:
411
+ * - Aggregation and analysis
412
+ * - Data collection across records
413
+ * - Computed results
414
+ */
415
+ export const generate_report: ActionDefinition<Projects, {}> = {
416
+ handler: async ({ api, user }) => {
417
+ console.log(`[Action] Generating project report for ${user?.id}`);
418
+
419
+ // 1. Fetch all projects (or apply filters)
420
+ const allProjects = await api.find('projects', {});
421
+
422
+ // 2. Calculate statistics
423
+ const report = {
424
+ total_projects: allProjects.length,
425
+ by_status: {
426
+ planned: 0,
427
+ in_progress: 0,
428
+ completed: 0
429
+ },
430
+ by_priority: {
431
+ low: 0,
432
+ normal: 0,
433
+ high: 0
434
+ },
435
+ total_budget: 0,
436
+ average_budget: 0,
437
+ generated_at: new Date(),
438
+ generated_by: user?.id
439
+ };
440
+
441
+ // 3. Aggregate data
442
+ allProjects.forEach(project => {
443
+ // Count by status
444
+ const status = project.status || 'planned';
445
+ if (status in report.by_status) {
446
+ report.by_status[status as keyof typeof report.by_status]++;
447
+ }
448
+
449
+ // Count by priority
450
+ const priority = project.priority || 'normal';
451
+ if (priority in report.by_priority) {
452
+ report.by_priority[priority as keyof typeof report.by_priority]++;
453
+ }
454
+
455
+ // Sum budget
456
+ report.total_budget += project.budget || 0;
457
+ });
458
+
459
+ // 4. Calculate averages
460
+ report.average_budget = allProjects.length > 0
461
+ ? report.total_budget / allProjects.length
462
+ : 0;
463
+
464
+ console.log(`[Action] Report generated: ${report.total_projects} projects analyzed`);
465
+
466
+ return {
467
+ success: true,
468
+ message: 'Report generated successfully',
469
+ report
470
+ };
471
+ }
472
+ };
@@ -0,0 +1,13 @@
1
+ - _id: PROJ-001
2
+ name: "Website Redesign (From Data)"
3
+ description: "Initial data loaded from projects.data.yml"
4
+ status: "in_progress"
5
+ priority: "high"
6
+ owner: "u-123"
7
+
8
+ - _id: PROJ-002
9
+ name: "Mobile App (From Data)"
10
+ description: "Initial data loaded from projects.data.yml"
11
+ status: "planned"
12
+ priority: "medium"
13
+ owner: "u-999"