@objectql/core 0.1.0 → 1.1.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 +18 -0
- package/README.md +53 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +56 -12
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +5 -0
- package/dist/loader.js +17 -136
- package/dist/loader.js.map +1 -1
- package/dist/metadata.d.ts +2 -0
- package/dist/registry.d.ts +4 -0
- package/dist/registry.js +8 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +65 -13
- package/src/loader.ts +17 -115
- package/src/metadata.ts +3 -1
- package/src/registry.ts +6 -0
- package/src/types.ts +8 -0
- package/test/dynamic.test.ts +34 -0
- package/test/fixtures/project.action.ts +6 -0
- package/test/loader.test.ts +8 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/loader.ts
CHANGED
|
@@ -1,120 +1,22 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// 1. Load YAML Configs
|
|
11
|
-
const files = glob.sync(['**/*.object.yml', '**/*.object.yaml'], {
|
|
12
|
-
cwd: dir,
|
|
13
|
-
absolute: true
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
for (const file of files) {
|
|
17
|
-
try {
|
|
18
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
19
|
-
const doc = yaml.load(content) as any;
|
|
20
|
-
|
|
21
|
-
if (doc.name && doc.fields) {
|
|
22
|
-
configs[doc.name] = doc as ObjectConfig;
|
|
23
|
-
} else {
|
|
24
|
-
for (const [key, value] of Object.entries(doc)) {
|
|
25
|
-
if (typeof value === 'object' && (value as any).fields) {
|
|
26
|
-
configs[key] = value as ObjectConfig;
|
|
27
|
-
if (!configs[key].name) configs[key].name = key;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
} catch (e) {
|
|
32
|
-
console.error(`Error loading object config from ${file}:`, e);
|
|
33
|
-
}
|
|
1
|
+
import { MetadataRegistry } from './registry';
|
|
2
|
+
import { ObjectConfig } from './types';
|
|
3
|
+
import { MetadataLoader as BaseLoader, registerObjectQLPlugins } from '@objectql/metadata';
|
|
4
|
+
|
|
5
|
+
export class MetadataLoader extends BaseLoader {
|
|
6
|
+
constructor(registry: MetadataRegistry) {
|
|
7
|
+
super(registry);
|
|
8
|
+
registerObjectQLPlugins(this);
|
|
34
9
|
}
|
|
10
|
+
}
|
|
35
11
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
for (const file of hookFiles) {
|
|
46
|
-
try {
|
|
47
|
-
// Check if we should ignore .ts if .js exists?
|
|
48
|
-
// Or assume env handles it.
|
|
49
|
-
// If we are in `dist`, `src` shouldn't be there usually.
|
|
50
|
-
|
|
51
|
-
const hookModule = require(file);
|
|
52
|
-
// Default export or named exports?
|
|
53
|
-
// Convention: export const listenTo = 'objectName';
|
|
54
|
-
// or filename based: 'project.hook.js' -> 'project' (flaky)
|
|
55
|
-
|
|
56
|
-
let objectName = hookModule.listenTo;
|
|
57
|
-
|
|
58
|
-
if (!objectName) {
|
|
59
|
-
// Try to guess from filename?
|
|
60
|
-
// project.hook.ts -> project
|
|
61
|
-
const basename = path.basename(file);
|
|
62
|
-
const match = basename.match(/^(.+)\.hook\.(ts|js)$/);
|
|
63
|
-
if (match) {
|
|
64
|
-
objectName = match[1];
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (objectName && configs[objectName]) {
|
|
69
|
-
if (!configs[objectName].listeners) {
|
|
70
|
-
configs[objectName].listeners = {};
|
|
71
|
-
}
|
|
72
|
-
const listeners = configs[objectName].listeners!;
|
|
73
|
-
|
|
74
|
-
// Merge exported functions into listeners
|
|
75
|
-
// Common hooks: beforeFind, afterFind, beforeCreate, etc.
|
|
76
|
-
const hookNames = [
|
|
77
|
-
'beforeFind', 'afterFind',
|
|
78
|
-
'beforeCreate', 'afterCreate',
|
|
79
|
-
'beforeUpdate', 'afterUpdate',
|
|
80
|
-
'beforeDelete', 'afterDelete'
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
for (const name of hookNames) {
|
|
84
|
-
if (typeof hookModule[name] === 'function') {
|
|
85
|
-
listeners[name as keyof typeof listeners] = hookModule[name];
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
// Support default export having listeners object?
|
|
89
|
-
if (hookModule.default && typeof hookModule.default === 'object') {
|
|
90
|
-
Object.assign(listeners, hookModule.default);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Load Actions
|
|
94
|
-
// Convention: export const actions = { myAction: (ctx, params) => ... }
|
|
95
|
-
// OR export function myAction(ctx, params) ... (Ambiguous with hooks? No, hooks have explicit names)
|
|
96
|
-
// Safer: look for `actions` export.
|
|
97
|
-
|
|
98
|
-
if (hookModule.actions && typeof hookModule.actions === 'object') {
|
|
99
|
-
if (!configs[objectName].actions) {
|
|
100
|
-
configs[objectName].actions = {};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
for (const [actionName, handler] of Object.entries(hookModule.actions)) {
|
|
104
|
-
// We might have metadata from YAML already
|
|
105
|
-
if (!configs[objectName].actions![actionName]) {
|
|
106
|
-
configs[objectName].actions![actionName] = { };
|
|
107
|
-
}
|
|
108
|
-
// Attach handler
|
|
109
|
-
configs[objectName].actions![actionName].handler = handler as any;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
} catch (e) {
|
|
114
|
-
console.error(`Error loading hook from ${file}:`, e);
|
|
115
|
-
}
|
|
12
|
+
export function loadObjectConfigs(dir: string): Record<string, ObjectConfig> {
|
|
13
|
+
const registry = new MetadataRegistry();
|
|
14
|
+
const loader = new MetadataLoader(registry);
|
|
15
|
+
loader.load(dir);
|
|
16
|
+
const result: Record<string, ObjectConfig> = {};
|
|
17
|
+
for (const obj of registry.list<ObjectConfig>('object')) {
|
|
18
|
+
result[obj.name] = obj;
|
|
116
19
|
}
|
|
117
|
-
|
|
118
|
-
return configs;
|
|
20
|
+
return result;
|
|
119
21
|
}
|
|
120
22
|
|
package/src/metadata.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
/**
|
|
3
2
|
* Represents the supported field data types in the ObjectQL schema.
|
|
4
3
|
* These types determine how data is stored, validated, and rendered.
|
|
@@ -138,4 +137,7 @@ export interface ObjectConfig {
|
|
|
138
137
|
|
|
139
138
|
/** Lifecycle hooks. */
|
|
140
139
|
listeners?: ObjectListeners;
|
|
140
|
+
|
|
141
|
+
/** Initial data to populate when system starts. */
|
|
142
|
+
data?: any[];
|
|
141
143
|
}
|
package/src/registry.ts
ADDED
package/src/types.ts
CHANGED
|
@@ -2,18 +2,26 @@ import { ObjectRepository } from "./repository";
|
|
|
2
2
|
import { ObjectConfig } from "./metadata";
|
|
3
3
|
import { Driver } from "./driver";
|
|
4
4
|
import { UnifiedQuery, FilterCriterion } from "./query";
|
|
5
|
+
import { MetadataRegistry } from "./registry";
|
|
5
6
|
|
|
6
7
|
export { ObjectConfig } from "./metadata";
|
|
8
|
+
export { MetadataRegistry } from "./registry";
|
|
7
9
|
|
|
8
10
|
export interface ObjectQLConfig {
|
|
11
|
+
registry?: MetadataRegistry;
|
|
9
12
|
datasources: Record<string, Driver>;
|
|
10
13
|
objects?: Record<string, ObjectConfig>;
|
|
14
|
+
packages?: string[];
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export interface IObjectQL {
|
|
14
18
|
getObject(name: string): ObjectConfig | undefined;
|
|
15
19
|
getConfigs(): Record<string, ObjectConfig>;
|
|
16
20
|
datasource(name: string): Driver;
|
|
21
|
+
init(): Promise<void>;
|
|
22
|
+
addPackage(name: string): void;
|
|
23
|
+
removePackage(name: string): void;
|
|
24
|
+
metadata: MetadataRegistry;
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
export interface HookContext<T = any> {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ObjectQL } from '../src/index';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
describe('Dynamic Package Loading', () => {
|
|
5
|
+
let objectql: ObjectQL;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
objectql = new ObjectQL({
|
|
9
|
+
datasources: {}
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('should load directory manually', () => {
|
|
14
|
+
const fixtureDir = path.join(__dirname, 'fixtures');
|
|
15
|
+
objectql.loadFromDirectory(fixtureDir, 'test-pkg');
|
|
16
|
+
|
|
17
|
+
expect(objectql.getObject('project')).toBeDefined();
|
|
18
|
+
// Since 'test-pkg' is passed, it should be tracked
|
|
19
|
+
// but packageObjects is private, so we test behavior by removal
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('should remove package objects', () => {
|
|
23
|
+
const fixtureDir = path.join(__dirname, 'fixtures');
|
|
24
|
+
objectql.loadFromDirectory(fixtureDir, 'test-pkg');
|
|
25
|
+
|
|
26
|
+
expect(objectql.getObject('project')).toBeDefined();
|
|
27
|
+
|
|
28
|
+
objectql.removePackage('test-pkg');
|
|
29
|
+
expect(objectql.getObject('project')).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Mocking require for loadFromPackage is harder in jest without creating a real node module.
|
|
33
|
+
// relying on loadFromDirectory with packageName argument is sufficient to test the tracking logic.
|
|
34
|
+
});
|
package/test/loader.test.ts
CHANGED
|
@@ -11,4 +11,12 @@ describe('Loader', () => {
|
|
|
11
11
|
expect(configs['project'].fields).toBeDefined();
|
|
12
12
|
expect(configs['project'].fields.name).toBeDefined();
|
|
13
13
|
});
|
|
14
|
+
|
|
15
|
+
it('should load actions from .action.ts files', () => {
|
|
16
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
17
|
+
const configs = loadObjectConfigs(fixturesDir);
|
|
18
|
+
expect(configs['project'].actions).toBeDefined();
|
|
19
|
+
expect(configs['project'].actions!.closeProject).toBeDefined();
|
|
20
|
+
expect(typeof configs['project'].actions!.closeProject.handler).toBe('function');
|
|
21
|
+
});
|
|
14
22
|
});
|