Skip to content

Commit 86b2bcc

Browse files
committed
Add convention-based hook loading to MetadataLoader
Introduces support for loading and registering hooks using a file naming convention (`[object_name].hook.ts/js`) in MetadataLoader. Updates documentation to describe the hook loading and registration process, aligning it with the existing action loading strategy.
1 parent 44a1642 commit 86b2bcc

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

docs/spec/hook.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,21 @@ beforeFind: async ({ query, user }) => {
101101
query.filters.push(['organization_id', '=', user.org_id]);
102102
}
103103
```
104+
105+
## 5. Loading & Registration
106+
107+
Hooks follow the same convention-based loading strategy as Actions.
108+
109+
* **File Name**: `[object_name].hook.ts` (e.g., `user.hook.ts`)
110+
* **Exports**: Must export a default object complying with `ObjectHookDefinition`, or named exports matching hook methods.
111+
112+
```typescript
113+
// src/objects/user.hook.ts
114+
import { ObjectHookDefinition } from '@objectql/types';
115+
116+
const hooks: ObjectHookDefinition = {
117+
beforeCreate: async (ctx) => { ... }
118+
};
119+
120+
export default hooks;
121+
```

packages/core/src/loader.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,73 @@ export class MetadataLoader {
5353
}
5454
}
5555
});
56+
57+
// Hooks
58+
this.use({
59+
name: 'hook',
60+
glob: ['**/*.hook.ts', '**/*.hook.js'],
61+
handler: (ctx) => {
62+
const basename = path.basename(ctx.file);
63+
// Extract object name from filename: user.hook.ts -> user
64+
const objectName = basename.replace(/\.hook\.(ts|js)$/, '');
65+
66+
try {
67+
const mod = require(ctx.file);
68+
// Support default export or named exports
69+
const hooks = mod.default || mod;
70+
71+
ctx.registry.register('hook', {
72+
type: 'hook',
73+
id: objectName, // Hook ID is the object name
74+
path: ctx.file,
75+
package: ctx.packageName,
76+
content: hooks
77+
});
78+
} catch (e) {
79+
console.error(`Error loading hook from ${ctx.file}:`, e);
80+
}
81+
}
82+
});
83+
84+
// Actions
85+
this.use({
86+
name: 'action',
87+
glob: ['**/*.action.ts', '**/*.action.js'],
88+
handler: (ctx) => {
89+
const basename = path.basename(ctx.file);
90+
// Extract object name: invoice.action.ts -> invoice
91+
const objectName = basename.replace(/\.action\.(ts|js)$/, '');
92+
93+
try {
94+
const mod = require(ctx.file);
95+
// Action file exports multiple actions
96+
// export const approve = { ... };
97+
// export const reject = { ... };
98+
99+
const actions: Record<string, any> = {};
100+
101+
for (const [key, value] of Object.entries(mod)) {
102+
if (key === 'default') continue;
103+
if (typeof value === 'object' && (value as any).handler) {
104+
actions[key] = value;
105+
}
106+
}
107+
108+
if (Object.keys(actions).length > 0) {
109+
ctx.registry.register('action', {
110+
type: 'action',
111+
id: objectName, // Action collection ID is the object name
112+
path: ctx.file,
113+
package: ctx.packageName,
114+
content: actions
115+
});
116+
}
117+
118+
} catch (e) {
119+
console.error(`Error loading action from ${ctx.file}:`, e);
120+
}
121+
}
122+
});
56123
}
57124

58125
use(plugin: LoaderPlugin) {

0 commit comments

Comments
 (0)