@objectql/create 1.0.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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +28 -0
  3. package/dist/bin.js +125 -0
  4. package/package.json +27 -0
  5. package/templates/enterprise/CHANGELOG.md +140 -0
  6. package/templates/enterprise/README.md +352 -0
  7. package/templates/enterprise/__tests__/data-api.test.ts +546 -0
  8. package/templates/enterprise/__tests__/data-api.test.ts.backup +526 -0
  9. package/templates/enterprise/__tests__/metadata-api.test.ts +307 -0
  10. package/templates/enterprise/__tests__/metadata-loading.test.ts +250 -0
  11. package/templates/enterprise/jest.config.js +22 -0
  12. package/templates/enterprise/package.json +51 -0
  13. package/templates/enterprise/src/apps/erp.app.yml +4 -0
  14. package/templates/enterprise/src/core/attachment.object.yml +57 -0
  15. package/templates/enterprise/src/core/i18n/en/core.json +60 -0
  16. package/templates/enterprise/src/core/i18n/zh-CN/core.json +60 -0
  17. package/templates/enterprise/src/core/index.ts +24 -0
  18. package/templates/enterprise/src/core/organization.object.yml +78 -0
  19. package/templates/enterprise/src/core/user.object.yml +80 -0
  20. package/templates/enterprise/src/extensions/README.md +56 -0
  21. package/templates/enterprise/src/extensions/user.extension.object.yml +42 -0
  22. package/templates/enterprise/src/extensions/user.ts +26 -0
  23. package/templates/enterprise/src/index.ts +47 -0
  24. package/templates/enterprise/src/modules/crm/README.md +99 -0
  25. package/templates/enterprise/src/modules/crm/crm_account.object.yml +105 -0
  26. package/templates/enterprise/src/modules/crm/crm_contact.object.yml +103 -0
  27. package/templates/enterprise/src/modules/crm/crm_lead.object.yml +148 -0
  28. package/templates/enterprise/src/modules/crm/crm_opportunity.object.yml +128 -0
  29. package/templates/enterprise/src/modules/crm/i18n/en/crm.json +61 -0
  30. package/templates/enterprise/src/modules/crm/i18n/zh-CN/crm.json +61 -0
  31. package/templates/enterprise/src/modules/crm/index.ts +29 -0
  32. package/templates/enterprise/src/modules/finance/README.md +112 -0
  33. package/templates/enterprise/src/modules/finance/finance_budget.object.yml +108 -0
  34. package/templates/enterprise/src/modules/finance/finance_expense.object.yml +151 -0
  35. package/templates/enterprise/src/modules/finance/finance_invoice.object.yml +143 -0
  36. package/templates/enterprise/src/modules/finance/finance_payment.object.yml +96 -0
  37. package/templates/enterprise/src/modules/finance/index.ts +26 -0
  38. package/templates/enterprise/src/modules/hr/README.md +95 -0
  39. package/templates/enterprise/src/modules/hr/hr_department.object.yml +59 -0
  40. package/templates/enterprise/src/modules/hr/hr_employee.object.yml +137 -0
  41. package/templates/enterprise/src/modules/hr/hr_position.object.yml +79 -0
  42. package/templates/enterprise/src/modules/hr/hr_timesheet.object.yml +114 -0
  43. package/templates/enterprise/src/modules/hr/index.ts +26 -0
  44. package/templates/enterprise/src/modules/project/README.md +132 -0
  45. package/templates/enterprise/src/modules/project/index.ts +26 -0
  46. package/templates/enterprise/src/modules/project/project_milestone.object.yml +70 -0
  47. package/templates/enterprise/src/modules/project/project_project.object.yml +135 -0
  48. package/templates/enterprise/src/modules/project/project_task.object.yml +121 -0
  49. package/templates/enterprise/src/modules/project/project_timesheet_entry.object.yml +95 -0
  50. package/templates/enterprise/src/plugins/audit/audit.plugin.ts +23 -0
  51. package/templates/enterprise/src/plugins/audit/index.ts +6 -0
  52. package/templates/enterprise/src/plugins/audit/note.object.yml +3 -0
  53. package/templates/enterprise/src/shared/constants.ts +30 -0
  54. package/templates/enterprise/src/shared/utils.ts +54 -0
  55. package/templates/enterprise/src/shared/validators.ts +47 -0
  56. package/templates/enterprise/src/types/attachment.ts +41 -0
  57. package/templates/enterprise/src/types/crm_account.ts +61 -0
  58. package/templates/enterprise/src/types/crm_contact.ts +61 -0
  59. package/templates/enterprise/src/types/crm_lead.ts +77 -0
  60. package/templates/enterprise/src/types/crm_opportunity.ts +53 -0
  61. package/templates/enterprise/src/types/finance_budget.ts +61 -0
  62. package/templates/enterprise/src/types/finance_expense.ts +65 -0
  63. package/templates/enterprise/src/types/finance_invoice.ts +69 -0
  64. package/templates/enterprise/src/types/finance_payment.ts +49 -0
  65. package/templates/enterprise/src/types/hr_department.ts +37 -0
  66. package/templates/enterprise/src/types/hr_employee.ts +85 -0
  67. package/templates/enterprise/src/types/hr_position.ts +49 -0
  68. package/templates/enterprise/src/types/hr_timesheet.ts +57 -0
  69. package/templates/enterprise/src/types/index.ts +20 -0
  70. package/templates/enterprise/src/types/note.ts +9 -0
  71. package/templates/enterprise/src/types/organization.ts +53 -0
  72. package/templates/enterprise/src/types/project_milestone.ts +41 -0
  73. package/templates/enterprise/src/types/project_project.ts +69 -0
  74. package/templates/enterprise/src/types/project_task.ts +57 -0
  75. package/templates/enterprise/src/types/project_timesheet_entry.ts +45 -0
  76. package/templates/enterprise/src/types/user.ts +65 -0
  77. package/templates/enterprise/tsconfig.json +10 -0
  78. package/templates/enterprise/tsconfig.tsbuildinfo +1 -0
  79. package/templates/hello-world/CHANGELOG.md +33 -0
  80. package/templates/hello-world/README.md +29 -0
  81. package/templates/hello-world/package.json +24 -0
  82. package/templates/hello-world/src/index.ts +58 -0
  83. package/templates/hello-world/tsconfig.json +10 -0
  84. package/templates/starter/CHANGELOG.md +191 -0
  85. package/templates/starter/README.md +17 -0
  86. package/templates/starter/__tests__/projects-hooks-actions.test.ts +490 -0
  87. package/templates/starter/jest.config.js +22 -0
  88. package/templates/starter/package.json +51 -0
  89. package/templates/starter/src/README.pages.md +110 -0
  90. package/templates/starter/src/demo.app.yml +4 -0
  91. package/templates/starter/src/i18n/zh-CN/projects.json +22 -0
  92. package/templates/starter/src/index.ts +55 -0
  93. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.data.yml +18 -0
  94. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.object.yml +156 -0
  95. package/templates/starter/src/modules/projects/project_approval.workflow.yml +51 -0
  96. package/templates/starter/src/modules/projects/projects.action.ts +472 -0
  97. package/templates/starter/src/modules/projects/projects.data.yml +13 -0
  98. package/templates/starter/src/modules/projects/projects.hook.ts +339 -0
  99. package/templates/starter/src/modules/projects/projects.object.yml +148 -0
  100. package/templates/starter/src/modules/projects/projects.permission.yml +141 -0
  101. package/templates/starter/src/modules/projects/projects.validation.yml +37 -0
  102. package/templates/starter/src/modules/tasks/tasks.data.yml +23 -0
  103. package/templates/starter/src/modules/tasks/tasks.object.yml +34 -0
  104. package/templates/starter/src/modules/tasks/tasks.permission.yml +167 -0
  105. package/templates/starter/src/types/index.ts +3 -0
  106. package/templates/starter/src/types/kitchen_sink.ts +101 -0
  107. package/templates/starter/src/types/projects.ts +49 -0
  108. package/templates/starter/src/types/tasks.ts +33 -0
  109. package/templates/starter/tsconfig.json +11 -0
  110. package/templates/starter/tsconfig.tsbuildinfo +1 -0
@@ -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"