@objectql/platform-node 1.6.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.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/dist/driver.d.ts +2 -0
- package/dist/driver.js +55 -0
- package/dist/driver.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +12 -0
- package/dist/loader.js +358 -0
- package/dist/loader.js.map +1 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.js +56 -0
- package/dist/plugin.js.map +1 -0
- package/jest.config.js +13 -0
- package/package.json +19 -0
- package/src/driver.ts +54 -0
- package/src/index.ts +3 -0
- package/src/loader.ts +349 -0
- package/src/plugin.ts +53 -0
- package/test/dynamic.test.ts +33 -0
- package/test/fixtures/project-with-validation.object.yml +124 -0
- package/test/fixtures/project.action.js +8 -0
- package/test/fixtures/project.object.yml +41 -0
- package/test/fixtures/test_dashboard.page.yml +42 -0
- package/test/fixtures/test_page.page.yml +54 -0
- package/test/fixtures/test_responsive.page.yml +39 -0
- package/test/fixtures/test_sections.page.yml +45 -0
- package/test/loader.test.ts +15 -0
- package/test/metadata.test.ts +49 -0
- package/test/page.test.ts +137 -0
- package/test/validation.test.ts +486 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Test Dashboard Page
|
|
2
|
+
name: test_dashboard
|
|
3
|
+
label: Test Dashboard
|
|
4
|
+
description: Dashboard layout test
|
|
5
|
+
icon: dashboard
|
|
6
|
+
layout: dashboard
|
|
7
|
+
|
|
8
|
+
components:
|
|
9
|
+
# Metric with grid positioning
|
|
10
|
+
- id: metric_1
|
|
11
|
+
type: metric
|
|
12
|
+
label: Total Count
|
|
13
|
+
data_source:
|
|
14
|
+
object: projects
|
|
15
|
+
query:
|
|
16
|
+
op: count
|
|
17
|
+
config:
|
|
18
|
+
format: number
|
|
19
|
+
color: blue
|
|
20
|
+
grid:
|
|
21
|
+
x: 0
|
|
22
|
+
y: 0
|
|
23
|
+
w: 3
|
|
24
|
+
h: 2
|
|
25
|
+
|
|
26
|
+
# Chart with grid positioning
|
|
27
|
+
- id: chart_1
|
|
28
|
+
type: chart
|
|
29
|
+
label: Projects by Status
|
|
30
|
+
data_source:
|
|
31
|
+
object: projects
|
|
32
|
+
fields: ['status']
|
|
33
|
+
config:
|
|
34
|
+
chart_type: bar
|
|
35
|
+
grid:
|
|
36
|
+
x: 3
|
|
37
|
+
y: 0
|
|
38
|
+
w: 6
|
|
39
|
+
h: 4
|
|
40
|
+
|
|
41
|
+
permissions:
|
|
42
|
+
view: ['admin', 'manager']
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Test Page - Simple Single Column Layout
|
|
2
|
+
name: test_page
|
|
3
|
+
label: Test Page
|
|
4
|
+
description: A test page for unit tests
|
|
5
|
+
icon: test-tube
|
|
6
|
+
layout: single_column
|
|
7
|
+
|
|
8
|
+
# Permissions
|
|
9
|
+
permissions:
|
|
10
|
+
view: ['admin', 'user']
|
|
11
|
+
edit: ['admin']
|
|
12
|
+
|
|
13
|
+
# Simple components
|
|
14
|
+
components:
|
|
15
|
+
- id: header_text
|
|
16
|
+
type: text
|
|
17
|
+
label: Welcome
|
|
18
|
+
config:
|
|
19
|
+
content: Welcome to the test page
|
|
20
|
+
format: text
|
|
21
|
+
|
|
22
|
+
- id: test_grid
|
|
23
|
+
type: data_grid
|
|
24
|
+
label: Test Data
|
|
25
|
+
data_source:
|
|
26
|
+
object: projects
|
|
27
|
+
fields: ['name', 'status']
|
|
28
|
+
sort: [['created_at', 'desc']]
|
|
29
|
+
limit: 10
|
|
30
|
+
config:
|
|
31
|
+
columns:
|
|
32
|
+
- field: name
|
|
33
|
+
label: Project Name
|
|
34
|
+
- field: status
|
|
35
|
+
label: Status
|
|
36
|
+
actions:
|
|
37
|
+
on_click:
|
|
38
|
+
type: navigate
|
|
39
|
+
path: /projects/{{id}}
|
|
40
|
+
|
|
41
|
+
- id: action_button
|
|
42
|
+
type: button
|
|
43
|
+
label: Click Me
|
|
44
|
+
actions:
|
|
45
|
+
on_click:
|
|
46
|
+
type: run_action
|
|
47
|
+
object: projects
|
|
48
|
+
action: custom_action
|
|
49
|
+
success_message: Action completed
|
|
50
|
+
|
|
51
|
+
# AI context
|
|
52
|
+
ai_context:
|
|
53
|
+
intent: Test page for unit testing
|
|
54
|
+
persona: Test users
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Test Responsive Page
|
|
2
|
+
name: test_responsive
|
|
3
|
+
label: Test Responsive
|
|
4
|
+
description: Page with responsive configuration
|
|
5
|
+
icon: mobile
|
|
6
|
+
layout: single_column
|
|
7
|
+
|
|
8
|
+
# Responsive breakpoints
|
|
9
|
+
responsive:
|
|
10
|
+
mobile:
|
|
11
|
+
columns: 1
|
|
12
|
+
visible: true
|
|
13
|
+
tablet:
|
|
14
|
+
columns: 2
|
|
15
|
+
visible: true
|
|
16
|
+
desktop:
|
|
17
|
+
columns: 3
|
|
18
|
+
visible: true
|
|
19
|
+
|
|
20
|
+
components:
|
|
21
|
+
- id: responsive_grid
|
|
22
|
+
type: data_grid
|
|
23
|
+
label: Responsive Grid
|
|
24
|
+
data_source:
|
|
25
|
+
object: projects
|
|
26
|
+
fields: ['name']
|
|
27
|
+
responsive:
|
|
28
|
+
mobile:
|
|
29
|
+
visible: true
|
|
30
|
+
columns: 1
|
|
31
|
+
tablet:
|
|
32
|
+
visible: true
|
|
33
|
+
columns: 2
|
|
34
|
+
desktop:
|
|
35
|
+
visible: true
|
|
36
|
+
columns: 3
|
|
37
|
+
|
|
38
|
+
permissions:
|
|
39
|
+
view: ['*']
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Test Sections Page
|
|
2
|
+
name: test_sections
|
|
3
|
+
label: Test Sections
|
|
4
|
+
description: Two-column layout with sections
|
|
5
|
+
icon: layout
|
|
6
|
+
layout: two_column
|
|
7
|
+
|
|
8
|
+
sections:
|
|
9
|
+
# Main content section
|
|
10
|
+
- id: main_section
|
|
11
|
+
type: content
|
|
12
|
+
label: Main Content
|
|
13
|
+
style:
|
|
14
|
+
width: 70%
|
|
15
|
+
components:
|
|
16
|
+
- id: content_form
|
|
17
|
+
type: form
|
|
18
|
+
label: Edit Form
|
|
19
|
+
data_source:
|
|
20
|
+
object: projects
|
|
21
|
+
config:
|
|
22
|
+
mode: edit
|
|
23
|
+
fields:
|
|
24
|
+
- name: name
|
|
25
|
+
label: Name
|
|
26
|
+
type: text
|
|
27
|
+
- name: description
|
|
28
|
+
label: Description
|
|
29
|
+
type: textarea
|
|
30
|
+
|
|
31
|
+
# Sidebar section
|
|
32
|
+
- id: sidebar_section
|
|
33
|
+
type: sidebar
|
|
34
|
+
label: Sidebar
|
|
35
|
+
style:
|
|
36
|
+
width: 30%
|
|
37
|
+
components:
|
|
38
|
+
- id: stats
|
|
39
|
+
type: metric
|
|
40
|
+
label: Statistics
|
|
41
|
+
config:
|
|
42
|
+
format: number
|
|
43
|
+
|
|
44
|
+
permissions:
|
|
45
|
+
view: ['admin']
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { loadObjectConfigs } from '../src/loader';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
describe('Loader', () => {
|
|
5
|
+
it('should load object configs from directory', () => {
|
|
6
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
7
|
+
const configs = loadObjectConfigs(fixturesDir);
|
|
8
|
+
expect(configs).toBeDefined();
|
|
9
|
+
expect(configs['project']).toBeDefined();
|
|
10
|
+
expect(configs['project'].name).toBe('project');
|
|
11
|
+
expect(configs['project'].fields).toBeDefined();
|
|
12
|
+
expect(configs['project'].fields.name).toBeDefined();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ObjectQL } from '@objectql/core';
|
|
2
|
+
import { ObjectConfig } from '@objectql/types';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import * as yaml from 'js-yaml';
|
|
6
|
+
|
|
7
|
+
describe('Metadata Loading', () => {
|
|
8
|
+
|
|
9
|
+
it('should load definitions from .object.yml file', () => {
|
|
10
|
+
// 1. Read YAML file
|
|
11
|
+
const yamlPath = path.join(__dirname, 'fixtures', 'project.object.yml');
|
|
12
|
+
const fileContents = fs.readFileSync(yamlPath, 'utf8');
|
|
13
|
+
|
|
14
|
+
// 2. Parse YAML
|
|
15
|
+
const objectDef = yaml.load(fileContents) as ObjectConfig;
|
|
16
|
+
|
|
17
|
+
// 3. Verify Structure
|
|
18
|
+
expect(objectDef.name).toBe('project');
|
|
19
|
+
expect(objectDef.fields.name.type).toBe('text');
|
|
20
|
+
expect(objectDef.fields.status.options).toHaveLength(3);
|
|
21
|
+
expect(objectDef.fields.budget.type).toBe('currency');
|
|
22
|
+
expect(objectDef.fields.owner.reference_to).toBe('users');
|
|
23
|
+
|
|
24
|
+
// 4. Register with ObjectQL
|
|
25
|
+
const app = new ObjectQL({
|
|
26
|
+
datasources: {}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
app.registerObject(objectDef);
|
|
30
|
+
|
|
31
|
+
// 5. Verify Registration
|
|
32
|
+
const retrieved = app.getObject('project');
|
|
33
|
+
expect(retrieved).toBeDefined();
|
|
34
|
+
expect(retrieved?.label).toBe('Project');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should validate required properties (manual validation simulation)', () => {
|
|
38
|
+
const yamlPath = path.join(__dirname, 'fixtures', 'project.object.yml');
|
|
39
|
+
const fileContents = fs.readFileSync(yamlPath, 'utf8');
|
|
40
|
+
const objectDef = yaml.load(fileContents) as ObjectConfig;
|
|
41
|
+
|
|
42
|
+
function validateObject(obj: ObjectConfig) {
|
|
43
|
+
if (!obj.name) throw new Error('Object name is required');
|
|
44
|
+
if (!obj.fields) throw new Error('Object fields are required');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
expect(() => validateObject(objectDef)).not.toThrow();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { ObjectLoader } from '../src/loader';
|
|
2
|
+
import { MetadataRegistry } from '@objectql/types';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
|
|
6
|
+
describe('Page Metadata Loader', () => {
|
|
7
|
+
let registry: MetadataRegistry;
|
|
8
|
+
let loader: ObjectLoader;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
registry = new MetadataRegistry();
|
|
12
|
+
loader = new ObjectLoader(registry);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should load page from .page.yml file', () => {
|
|
16
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
17
|
+
loader.load(fixturesDir);
|
|
18
|
+
|
|
19
|
+
const pages = registry.list('page');
|
|
20
|
+
expect(pages.length).toBeGreaterThan(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should parse page metadata correctly', () => {
|
|
24
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
25
|
+
loader.load(fixturesDir);
|
|
26
|
+
|
|
27
|
+
const page = registry.get('page', 'test_page');
|
|
28
|
+
expect(page).toBeDefined();
|
|
29
|
+
expect(page.label).toBe('Test Page');
|
|
30
|
+
expect(page.layout).toBe('single_column');
|
|
31
|
+
expect(page.components).toBeDefined();
|
|
32
|
+
expect(Array.isArray(page.components)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should load page with dashboard layout', () => {
|
|
36
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
37
|
+
loader.load(fixturesDir);
|
|
38
|
+
|
|
39
|
+
const page = registry.get('page', 'test_dashboard');
|
|
40
|
+
expect(page).toBeDefined();
|
|
41
|
+
expect(page.layout).toBe('dashboard');
|
|
42
|
+
expect(page.components).toBeDefined();
|
|
43
|
+
|
|
44
|
+
// Check if components have grid positions
|
|
45
|
+
const componentWithGrid = page.components?.find((c: any) => c.grid);
|
|
46
|
+
expect(componentWithGrid).toBeDefined();
|
|
47
|
+
if (componentWithGrid && componentWithGrid.grid) {
|
|
48
|
+
expect(componentWithGrid.grid.x).toBeDefined();
|
|
49
|
+
expect(componentWithGrid.grid.y).toBeDefined();
|
|
50
|
+
expect(componentWithGrid.grid.w).toBeDefined();
|
|
51
|
+
expect(componentWithGrid.grid.h).toBeDefined();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should load page with sections', () => {
|
|
56
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
57
|
+
loader.load(fixturesDir);
|
|
58
|
+
|
|
59
|
+
const page = registry.get('page', 'test_sections');
|
|
60
|
+
expect(page).toBeDefined();
|
|
61
|
+
expect(page.sections).toBeDefined();
|
|
62
|
+
expect(Array.isArray(page.sections)).toBe(true);
|
|
63
|
+
|
|
64
|
+
if (page.sections && page.sections.length > 0) {
|
|
65
|
+
const section = page.sections[0];
|
|
66
|
+
expect(section.id).toBeDefined();
|
|
67
|
+
expect(section.components).toBeDefined();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should support page permissions', () => {
|
|
72
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
73
|
+
loader.load(fixturesDir);
|
|
74
|
+
|
|
75
|
+
const page = registry.get('page', 'test_page');
|
|
76
|
+
expect(page).toBeDefined();
|
|
77
|
+
expect(page.permissions).toBeDefined();
|
|
78
|
+
expect(page.permissions.view).toBeDefined();
|
|
79
|
+
expect(Array.isArray(page.permissions.view)).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should support component actions', () => {
|
|
83
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
84
|
+
loader.load(fixturesDir);
|
|
85
|
+
|
|
86
|
+
const page = registry.get('page', 'test_page');
|
|
87
|
+
expect(page).toBeDefined();
|
|
88
|
+
|
|
89
|
+
const componentWithAction = page.components?.find((c: any) => c.actions);
|
|
90
|
+
expect(componentWithAction).toBeDefined();
|
|
91
|
+
if (componentWithAction && componentWithAction.actions) {
|
|
92
|
+
expect(componentWithAction.actions.on_click).toBeDefined();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should support data source configuration', () => {
|
|
97
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
98
|
+
loader.load(fixturesDir);
|
|
99
|
+
|
|
100
|
+
const page = registry.get('page', 'test_page');
|
|
101
|
+
expect(page).toBeDefined();
|
|
102
|
+
|
|
103
|
+
const componentWithDataSource = page.components?.find((c: any) => c.data_source);
|
|
104
|
+
expect(componentWithDataSource).toBeDefined();
|
|
105
|
+
if (componentWithDataSource && componentWithDataSource.data_source) {
|
|
106
|
+
expect(componentWithDataSource.data_source.object).toBeDefined();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should support responsive configuration', () => {
|
|
111
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
112
|
+
loader.load(fixturesDir);
|
|
113
|
+
|
|
114
|
+
const page = registry.get('page', 'test_responsive');
|
|
115
|
+
expect(page).toBeDefined();
|
|
116
|
+
expect(page.responsive).toBeDefined();
|
|
117
|
+
|
|
118
|
+
if (page.responsive) {
|
|
119
|
+
expect(page.responsive.mobile).toBeDefined();
|
|
120
|
+
expect(page.responsive.tablet).toBeDefined();
|
|
121
|
+
expect(page.responsive.desktop).toBeDefined();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should support AI context', () => {
|
|
126
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
127
|
+
loader.load(fixturesDir);
|
|
128
|
+
|
|
129
|
+
const page = registry.get('page', 'test_page');
|
|
130
|
+
expect(page).toBeDefined();
|
|
131
|
+
expect(page.ai_context).toBeDefined();
|
|
132
|
+
|
|
133
|
+
if (page.ai_context) {
|
|
134
|
+
expect(page.ai_context.intent).toBeDefined();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
});
|