@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/src/loader.ts CHANGED
@@ -1,120 +1,22 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import * as yaml from 'js-yaml';
4
- import * as glob from 'fast-glob';
5
- import { ObjectQLConfig, ObjectConfig } from './types';
6
-
7
- export function loadObjectConfigs(dir: string): Record<string, ObjectConfig> {
8
- const configs: Record<string, ObjectConfig> = {};
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
- // 2. Load Hooks (.hook.js, .hook.ts)
37
- // We only load .js if running in node, or .ts if ts-node/register is present.
38
- // simpler: look for both, require will handle extension resolution if we are careful.
39
- // Actually, in `dist` we only find .js. In `src` (test) we find .ts.
40
- const hookFiles = glob.sync(['**/*.hook.{js,ts}'], {
41
- cwd: dir,
42
- absolute: true
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
  }
@@ -0,0 +1,6 @@
1
+ import { MetadataRegistry as BaseRegistry, Metadata } from '@objectql/metadata';
2
+
3
+ export { Metadata };
4
+ export class MetadataRegistry extends BaseRegistry {
5
+ // Re-export or extend if needed
6
+ }
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
+ });
@@ -0,0 +1,6 @@
1
+ // Fixture for testing action loader
2
+ export const listenTo = 'project';
3
+
4
+ export async function closeProject(ctx: any, params: any) {
5
+ return { success: true };
6
+ }
@@ -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
  });