@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,490 @@
1
+ /**
2
+ * Comprehensive Test Suite for Project Hooks and Actions
3
+ *
4
+ * This test file demonstrates and validates:
5
+ * 1. All hook types (beforeCreate, afterCreate, beforeUpdate, etc.)
6
+ * 2. All action types (record actions and global actions)
7
+ * 3. Business logic patterns from the specification
8
+ */
9
+
10
+ import { ObjectQL } from '@objectql/core';
11
+ import hooks from '../src/modules/projects/projects.hook';
12
+ import * as actions from '../src/modules/projects/projects.action';
13
+
14
+ describe('Project Hooks - Comprehensive Examples', () => {
15
+ let app: ObjectQL;
16
+
17
+ beforeEach(async () => {
18
+ // Use in-memory driver for testing
19
+ app = new ObjectQL({
20
+ datasources: {
21
+ default: {
22
+ find: jest.fn().mockResolvedValue([]),
23
+ findOne: jest.fn().mockResolvedValue(null),
24
+ create: jest.fn((obj, data) => ({ ...data, _id: 'test-id' })),
25
+ update: jest.fn((obj, id, data) => data),
26
+ delete: jest.fn().mockResolvedValue(true),
27
+ count: jest.fn().mockResolvedValue(0)
28
+ } as any
29
+ },
30
+ objects: {
31
+ 'projects': {
32
+ name: 'projects',
33
+ fields: {
34
+ name: { type: 'text' },
35
+ status: { type: 'text' },
36
+ budget: { type: 'number' },
37
+ owner: { type: 'text' }
38
+ }
39
+ }
40
+ }
41
+ });
42
+ await app.init();
43
+
44
+ // Register hooks
45
+ if (hooks.beforeCreate) app.on('beforeCreate', 'projects', hooks.beforeCreate);
46
+ if (hooks.afterCreate) app.on('afterCreate', 'projects', hooks.afterCreate);
47
+ if (hooks.beforeFind) app.on('beforeFind', 'projects', hooks.beforeFind);
48
+ if (hooks.afterFind) app.on('afterFind', 'projects', hooks.afterFind);
49
+ if (hooks.beforeUpdate) app.on('beforeUpdate', 'projects', hooks.beforeUpdate);
50
+ if (hooks.afterUpdate) app.on('afterUpdate', 'projects', hooks.afterUpdate);
51
+ if (hooks.beforeDelete) app.on('beforeDelete', 'projects', hooks.beforeDelete);
52
+ if (hooks.afterDelete) app.on('afterDelete', 'projects', hooks.afterDelete);
53
+ });
54
+
55
+ describe('beforeCreate Hook', () => {
56
+ it('should auto-assign owner from user context', async () => {
57
+ const repo = app.createContext({ userId: 'user123' }).object('projects');
58
+
59
+ await repo.create({ name: 'Test Project' });
60
+
61
+ const driver = app.datasource('default');
62
+ expect(driver.create).toHaveBeenCalledWith(
63
+ 'projects',
64
+ expect.objectContaining({
65
+ name: 'Test Project',
66
+ owner: 'user123'
67
+ }),
68
+ expect.any(Object)
69
+ );
70
+ });
71
+
72
+ it('should set default status to planned', async () => {
73
+ const repo = app.createContext({}).object('projects');
74
+
75
+ await repo.create({ name: 'Test Project' });
76
+
77
+ const driver = app.datasource('default');
78
+ expect(driver.create).toHaveBeenCalledWith(
79
+ 'projects',
80
+ expect.objectContaining({
81
+ status: 'planned'
82
+ }),
83
+ expect.any(Object)
84
+ );
85
+ });
86
+
87
+ it('should validate project name is required', async () => {
88
+ const repo = app.createContext({}).object('projects');
89
+
90
+ await expect(repo.create({ name: '' }))
91
+ .rejects
92
+ .toThrow('Project name is required');
93
+ });
94
+
95
+ it('should validate project name length', async () => {
96
+ const repo = app.createContext({}).object('projects');
97
+ const longName = 'a'.repeat(101);
98
+
99
+ await expect(repo.create({ name: longName }))
100
+ .rejects
101
+ .toThrow('Project name must be 100 characters or less');
102
+ });
103
+
104
+ it('should set default budget to 0', async () => {
105
+ const repo = app.createContext({}).object('projects');
106
+
107
+ await repo.create({ name: 'Test Project' });
108
+
109
+ const driver = app.datasource('default');
110
+ expect(driver.create).toHaveBeenCalledWith(
111
+ 'projects',
112
+ expect.objectContaining({
113
+ budget: 0
114
+ }),
115
+ expect.any(Object)
116
+ );
117
+ });
118
+ });
119
+
120
+ describe('beforeUpdate Hook', () => {
121
+ it('should validate budget is not negative', async () => {
122
+ const repo = app.createContext({}).object('projects');
123
+ const driver = app.datasource('default');
124
+
125
+ // Mock existing project
126
+ (driver.findOne as jest.Mock).mockResolvedValueOnce({
127
+ _id: '1',
128
+ name: 'Test',
129
+ status: 'planned',
130
+ budget: 1000
131
+ });
132
+
133
+ await expect(repo.update('1', { budget: -100 }))
134
+ .rejects
135
+ .toThrow('Budget cannot be negative');
136
+ });
137
+
138
+ it('should prevent invalid status transitions', async () => {
139
+ const repo = app.createContext({}).object('projects');
140
+ const driver = app.datasource('default');
141
+
142
+ // Mock completed project
143
+ (driver.findOne as jest.Mock).mockResolvedValueOnce({
144
+ _id: '1',
145
+ name: 'Test',
146
+ status: 'completed',
147
+ budget: 1000
148
+ });
149
+
150
+ await expect(repo.update('1', { status: 'planned' }))
151
+ .rejects
152
+ .toThrow('Invalid status transition');
153
+ });
154
+
155
+ it('should allow valid status transition from planned to in_progress', async () => {
156
+ const repo = app.createContext({}).object('projects');
157
+ const driver = app.datasource('default');
158
+
159
+ (driver.findOne as jest.Mock).mockResolvedValueOnce({
160
+ _id: '1',
161
+ name: 'Test',
162
+ status: 'planned',
163
+ budget: 1000
164
+ });
165
+
166
+ await repo.update('1', { status: 'in_progress' });
167
+
168
+ expect(driver.update).toHaveBeenCalled();
169
+ });
170
+
171
+ it('should require end_date when marking as completed', async () => {
172
+ const repo = app.createContext({}).object('projects');
173
+ const driver = app.datasource('default');
174
+
175
+ (driver.findOne as jest.Mock).mockResolvedValueOnce({
176
+ _id: '1',
177
+ name: 'Test',
178
+ status: 'in_progress',
179
+ budget: 1000
180
+ });
181
+
182
+ await expect(repo.update('1', { status: 'completed' }))
183
+ .rejects
184
+ .toThrow('End date is required when completing a project');
185
+ });
186
+ });
187
+
188
+ describe('beforeDelete Hook', () => {
189
+ it('should prevent deletion of completed projects', async () => {
190
+ const repo = app.createContext({}).object('projects');
191
+ const driver = app.datasource('default');
192
+
193
+ (driver.findOne as jest.Mock).mockResolvedValueOnce({
194
+ _id: '1',
195
+ name: 'Test',
196
+ status: 'completed'
197
+ });
198
+
199
+ await expect(repo.delete('1'))
200
+ .rejects
201
+ .toThrow('Cannot delete completed projects');
202
+ });
203
+ });
204
+
205
+ describe('afterFind Hook', () => {
206
+ it('should add computed progress field', async () => {
207
+ const repo = app.createContext({}).object('projects');
208
+ const driver = app.datasource('default');
209
+
210
+ (driver.find as jest.Mock).mockResolvedValueOnce([
211
+ { name: 'Project 1', status: 'planned' },
212
+ { name: 'Project 2', status: 'in_progress' },
213
+ { name: 'Project 3', status: 'completed' }
214
+ ]);
215
+
216
+ const results = await repo.find({});
217
+
218
+ expect(results[0].progress).toBe(0);
219
+ expect(results[1].progress).toBe(50);
220
+ expect(results[2].progress).toBe(100);
221
+ });
222
+ });
223
+ });
224
+
225
+ describe('Project Actions - Comprehensive Examples', () => {
226
+ let mockApi: any;
227
+ let mockUser: any;
228
+
229
+ beforeEach(() => {
230
+ mockUser = { id: 'user123', isAdmin: false };
231
+ mockApi = {
232
+ findOne: jest.fn(),
233
+ find: jest.fn(),
234
+ create: jest.fn(),
235
+ update: jest.fn(),
236
+ delete: jest.fn(),
237
+ count: jest.fn()
238
+ };
239
+ });
240
+
241
+ describe('complete - Record Action', () => {
242
+ it('should complete a project successfully', async () => {
243
+ const mockProject = {
244
+ _id: 'proj1',
245
+ name: 'Test Project',
246
+ status: 'in_progress',
247
+ description: 'Original description'
248
+ };
249
+
250
+ mockApi.findOne.mockResolvedValue(mockProject);
251
+ mockApi.update.mockResolvedValue({ ...mockProject, status: 'completed' });
252
+
253
+ const result = await actions.complete.handler({
254
+ objectName: 'projects',
255
+ actionName: 'complete',
256
+ id: 'proj1',
257
+ input: { comment: 'All tasks done' },
258
+ api: mockApi,
259
+ user: mockUser
260
+ });
261
+
262
+ expect(result.success).toBe(true);
263
+ expect(result.message).toContain('completed successfully');
264
+ expect(mockApi.update).toHaveBeenCalledWith(
265
+ 'projects',
266
+ 'proj1',
267
+ expect.objectContaining({
268
+ status: 'completed'
269
+ })
270
+ );
271
+ });
272
+
273
+ it('should reject if project is already completed', async () => {
274
+ mockApi.findOne.mockResolvedValue({
275
+ _id: 'proj1',
276
+ status: 'completed'
277
+ });
278
+
279
+ await expect(actions.complete.handler({
280
+ objectName: 'projects',
281
+ actionName: 'complete',
282
+ id: 'proj1',
283
+ input: {},
284
+ api: mockApi,
285
+ user: mockUser
286
+ })).rejects.toThrow('already completed');
287
+ });
288
+ });
289
+
290
+ describe('approve - Record Action', () => {
291
+ it('should approve a planned project', async () => {
292
+ mockApi.findOne.mockResolvedValue({
293
+ _id: 'proj1',
294
+ name: 'Test Project',
295
+ status: 'planned',
296
+ budget: 50000
297
+ });
298
+
299
+ const result = await actions.approve.handler({
300
+ objectName: 'projects',
301
+ actionName: 'approve',
302
+ id: 'proj1',
303
+ input: { comment: 'Looks good' },
304
+ api: mockApi,
305
+ user: mockUser
306
+ });
307
+
308
+ expect(result.success).toBe(true);
309
+ expect(result.new_status).toBe('in_progress');
310
+ expect(mockApi.update).toHaveBeenCalledWith(
311
+ 'projects',
312
+ 'proj1',
313
+ expect.objectContaining({
314
+ status: 'in_progress',
315
+ approved_by: 'user123'
316
+ })
317
+ );
318
+ });
319
+
320
+ it('should require approval comment', async () => {
321
+ await expect(actions.approve.handler({
322
+ objectName: 'projects',
323
+ actionName: 'approve',
324
+ id: 'proj1',
325
+ input: { comment: '' },
326
+ api: mockApi,
327
+ user: mockUser
328
+ })).rejects.toThrow('Approval comment is required');
329
+ });
330
+ });
331
+
332
+ describe('clone - Record Action', () => {
333
+ it('should clone a project with new name', async () => {
334
+ const sourceProject = {
335
+ _id: 'proj1',
336
+ name: 'Original Project',
337
+ description: 'Original description',
338
+ status: 'in_progress',
339
+ priority: 'high',
340
+ budget: 10000,
341
+ owner: 'owner123'
342
+ };
343
+
344
+ mockApi.findOne.mockResolvedValue(sourceProject);
345
+ mockApi.create.mockResolvedValue({ _id: 'proj2', name: 'Cloned Project' });
346
+
347
+ const result = await actions.clone.handler({
348
+ objectName: 'projects',
349
+ actionName: 'clone',
350
+ id: 'proj1',
351
+ input: { new_name: 'Cloned Project', copy_tasks: false },
352
+ api: mockApi,
353
+ user: mockUser
354
+ });
355
+
356
+ expect(result.success).toBe(true);
357
+ expect(result.new_project_id).toBe('proj2');
358
+ expect(mockApi.create).toHaveBeenCalledWith(
359
+ 'projects',
360
+ expect.objectContaining({
361
+ name: 'Cloned Project',
362
+ status: 'planned', // Reset to planned
363
+ priority: 'high',
364
+ budget: 10000,
365
+ owner: 'user123' // Assigned to current user
366
+ })
367
+ );
368
+ });
369
+ });
370
+
371
+ describe('import_projects - Global Action', () => {
372
+ it('should import multiple projects successfully', async () => {
373
+ const projectsData = [
374
+ { name: 'Project 1', description: 'Desc 1' },
375
+ { name: 'Project 2', description: 'Desc 2', status: 'in_progress' }
376
+ ];
377
+
378
+ mockApi.create.mockResolvedValue({ _id: 'new-id' });
379
+
380
+ const result = await actions.import_projects.handler({
381
+ objectName: 'projects',
382
+ actionName: 'import_projects',
383
+ input: { source: 'json', data: projectsData },
384
+ api: mockApi,
385
+ user: mockUser
386
+ });
387
+
388
+ expect(result.success).toBe(true);
389
+ expect(result.successCount).toBe(2);
390
+ expect(mockApi.create).toHaveBeenCalledTimes(2);
391
+ });
392
+
393
+ it('should collect errors for invalid projects', async () => {
394
+ const projectsData = [
395
+ { name: 'Valid Project' },
396
+ { description: 'Missing name' }, // Invalid - no name
397
+ { name: 'Another Valid' }
398
+ ];
399
+
400
+ mockApi.create.mockResolvedValue({ _id: 'new-id' });
401
+
402
+ const result = await actions.import_projects.handler({
403
+ objectName: 'projects',
404
+ actionName: 'import_projects',
405
+ input: { source: 'json', data: projectsData },
406
+ api: mockApi,
407
+ user: mockUser
408
+ });
409
+
410
+ expect(result.failed).toBe(1);
411
+ expect(result.errors).toHaveLength(1);
412
+ });
413
+ });
414
+
415
+ describe('bulk_update_status - Global Action', () => {
416
+ it('should update multiple projects status', async () => {
417
+ mockApi.findOne.mockImplementation((obj: string, id: string) => ({
418
+ _id: id,
419
+ name: `Project ${id}`,
420
+ status: 'planned'
421
+ }));
422
+
423
+ const result = await actions.bulk_update_status.handler({
424
+ objectName: 'projects',
425
+ actionName: 'bulk_update_status',
426
+ input: {
427
+ project_ids: ['proj1', 'proj2', 'proj3'],
428
+ new_status: 'in_progress'
429
+ },
430
+ api: mockApi,
431
+ user: mockUser
432
+ });
433
+
434
+ expect(result.updated).toBe(3);
435
+ expect(mockApi.update).toHaveBeenCalledTimes(3);
436
+ });
437
+
438
+ it('should skip completed projects', async () => {
439
+ mockApi.findOne.mockImplementation((obj: string, id: string) => {
440
+ if (id === 'proj2') {
441
+ return { _id: id, name: 'Completed Project', status: 'completed' };
442
+ }
443
+ return { _id: id, name: 'Project', status: 'planned' };
444
+ });
445
+
446
+ const result = await actions.bulk_update_status.handler({
447
+ objectName: 'projects',
448
+ actionName: 'bulk_update_status',
449
+ input: {
450
+ project_ids: ['proj1', 'proj2', 'proj3'],
451
+ new_status: 'planned'
452
+ },
453
+ api: mockApi,
454
+ user: mockUser
455
+ });
456
+
457
+ expect(result.updated).toBe(2);
458
+ expect(result.skipped).toBe(1);
459
+ });
460
+ });
461
+
462
+ describe('generate_report - Global Action', () => {
463
+ it('should generate statistical report', async () => {
464
+ const mockProjects = [
465
+ { name: 'P1', status: 'planned', priority: 'high', budget: 10000 },
466
+ { name: 'P2', status: 'in_progress', priority: 'normal', budget: 20000 },
467
+ { name: 'P3', status: 'completed', priority: 'low', budget: 15000 },
468
+ { name: 'P4', status: 'planned', priority: 'normal', budget: 5000 }
469
+ ];
470
+
471
+ mockApi.find.mockResolvedValue(mockProjects);
472
+
473
+ const result = await actions.generate_report.handler({
474
+ objectName: 'projects',
475
+ actionName: 'generate_report',
476
+ input: {},
477
+ api: mockApi,
478
+ user: mockUser
479
+ });
480
+
481
+ expect(result.success).toBe(true);
482
+ expect(result.report.total_projects).toBe(4);
483
+ expect(result.report.by_status.planned).toBe(2);
484
+ expect(result.report.by_status.in_progress).toBe(1);
485
+ expect(result.report.by_status.completed).toBe(1);
486
+ expect(result.report.total_budget).toBe(50000);
487
+ expect(result.report.average_budget).toBe(12500);
488
+ });
489
+ });
490
+ });
@@ -0,0 +1,22 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ testMatch: ['**/__tests__/**/*.test.ts'],
5
+ moduleNameMapper: {
6
+ '^@objectql/core$': '<rootDir>/../../foundation/core/src',
7
+ '^@objectql/types$': '<rootDir>/../../foundation/types/src',
8
+ '^@objectql/platform-node$': '<rootDir>/../../foundation/platform-node/src',
9
+ '^@objectql/driver-sql$': '<rootDir>/../../drivers/sql/src',
10
+ '^@objectql/driver-mongo$': '<rootDir>/../../drivers/mongo/src',
11
+ '^@objectql/server$': '<rootDir>/../../runtime/server/src',
12
+ },
13
+ transform: {
14
+ '^.+\\.ts$': ['ts-jest', {
15
+ isolatedModules: true,
16
+ }],
17
+ },
18
+ collectCoverageFrom: [
19
+ 'src/**/*.ts',
20
+ '!src/**/*.d.ts',
21
+ ],
22
+ };
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@objectql/example-project-tracker",
3
+ "version": "1.8.4",
4
+ "description": "ObjectQL Basic Example Project",
5
+ "private": true,
6
+ "keywords": [
7
+ "objectql",
8
+ "module",
9
+ "projects",
10
+ "tasks",
11
+ "basic",
12
+ "standard-library"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "ObjectQL Contributors",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/objectql/objectql.git",
19
+ "directory": "examples/showcase/project-tracker"
20
+ },
21
+ "main": "dist/index.js",
22
+ "types": "dist/index.d.ts",
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "start": "ts-node src/index.ts",
28
+ "codegen": "objectql generate -s src -o src/types",
29
+ "build": "npm run codegen && tsc && rsync -a --include '*/' --include '*.yml' --exclude '*' src/ dist/",
30
+ "repl": "objectql repl",
31
+ "test": "jest"
32
+ },
33
+ "peerDependencies": {
34
+ "@objectql/core": "workspace:*",
35
+ "@objectql/driver-sql": "workspace:*",
36
+ "@objectql/platform-node": "workspace:*",
37
+ "@objectql/types": "workspace:*",
38
+ "sqlite3": "^5.1.7"
39
+ },
40
+ "devDependencies": {
41
+ "@objectql/cli": "workspace:*",
42
+ "@objectql/core": "workspace:*",
43
+ "@objectql/driver-sql": "workspace:*",
44
+ "@objectql/platform-node": "workspace:*",
45
+ "@objectql/types": "workspace:*",
46
+ "@types/jest": "^30.0.0",
47
+ "jest": "^30.2.0",
48
+ "ts-jest": "^29.4.6",
49
+ "typescript": "^5.3.0"
50
+ }
51
+ }
@@ -0,0 +1,110 @@
1
+ # Page Examples
2
+
3
+ This directory contains example page definitions demonstrating ObjectQL's page metadata capabilities.
4
+
5
+ ## Available Pages
6
+
7
+ ### 1. Dashboard (`dashboard.page.yml`)
8
+ A comprehensive dashboard page showing:
9
+ - KPI metrics (total projects, active tasks, etc.)
10
+ - Charts (pie chart, line chart)
11
+ - Data grid with recent tasks
12
+ - Grid-based layout for flexible positioning
13
+ - Real-time updates enabled
14
+
15
+ **Layout:** Dashboard (grid-based)
16
+ **Use Case:** Overview and monitoring
17
+
18
+ ### 2. Project Detail (`project_detail.page.yml`)
19
+ A two-column detail page featuring:
20
+ - Main content area with editable form
21
+ - Sidebar with metrics and activity timeline
22
+ - Task list for the project
23
+ - Quick action buttons
24
+ - Responsive design that stacks on mobile
25
+
26
+ **Layout:** Two Column
27
+ **Use Case:** Viewing and editing individual records
28
+
29
+ ### 3. Create Project Wizard (`create_project_wizard.page.yml`)
30
+ A multi-step wizard for creating projects:
31
+ - Step 1: Basic information
32
+ - Step 2: Team and resources
33
+ - Step 3: Timeline and milestones
34
+ - Step 4: Review and confirmation
35
+ - State management with draft saving
36
+
37
+ **Layout:** Wizard (multi-step)
38
+ **Use Case:** Guided data entry processes
39
+
40
+ ### 4. Landing Page (`landing.page.yml`)
41
+ A custom landing page with:
42
+ - Hero section with CTA
43
+ - Features grid
44
+ - Statistics display
45
+ - Canvas layout for absolute positioning
46
+ - Public access (no authentication)
47
+
48
+ **Layout:** Canvas (free-form)
49
+ **Use Case:** Marketing and public pages
50
+
51
+ ## Loading Pages
52
+
53
+ Pages are automatically loaded by ObjectQL when scanning the directory:
54
+
55
+ ```typescript
56
+ import { ObjectQL } from '@objectql/core';
57
+
58
+ const app = new ObjectQL({
59
+ source: './src'
60
+ });
61
+
62
+ await app.init();
63
+
64
+ // Access loaded pages
65
+ const pages = app.registry.list('page');
66
+ ```
67
+
68
+ ## Using Pages in Navigation
69
+
70
+ Reference pages in your application navigation:
71
+
72
+ ```yaml
73
+ # demo.app.yml
74
+ navigation:
75
+ - type: page
76
+ name: dashboard
77
+ label: Dashboard
78
+ path: /dashboard
79
+
80
+ - type: page
81
+ name: create_project_wizard
82
+ label: New Project
83
+ path: /projects/new
84
+ ```
85
+
86
+ ## Best Practices Demonstrated
87
+
88
+ 1. **Descriptive IDs**: Components use clear, descriptive identifiers
89
+ 2. **Data Binding**: Examples use `{{}}` syntax for dynamic values
90
+ 3. **Responsive Design**: Pages include responsive configurations
91
+ 4. **Access Control**: Different permission levels shown
92
+ 5. **AI Context**: All pages include AI context for understanding
93
+ 6. **State Management**: Wizard demonstrates state handling
94
+ 7. **Component Composition**: Nested components for complex UIs
95
+
96
+ ## Documentation
97
+
98
+ - [Page Metadata Guide](../../../docs/guide/page-metadata.md)
99
+ - [Page Specification](../../../docs/spec/page.md)
100
+ - [Data Modeling](../../../docs/guide/data-modeling.md)
101
+
102
+ ## Contributing
103
+
104
+ When adding new page examples:
105
+ 1. Follow the naming convention: `[name].page.yml`
106
+ 2. Include all standard fields (name, label, layout)
107
+ 3. Add helpful comments in YAML
108
+ 4. Provide AI context
109
+ 5. Configure appropriate permissions
110
+ 6. Test responsive behavior
@@ -0,0 +1,4 @@
1
+ name: demo_app
2
+ label: Demo Application
3
+ description: "A showcase of ObjectQL capabilities including all field types."
4
+ homepage: /kitchen_sink