Skip to content

Commit 71864a0

Browse files
Copilothotlong
andcommitted
Add core runtime features to @objectstack/runtime (metadata, hooks, actions)
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent c4d13c6 commit 71864a0

4 files changed

Lines changed: 398 additions & 3 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/**
2+
* @objectstack/runtime
3+
* Action System - Custom action management
4+
*
5+
* Provides a system for registering and executing custom actions on objects
6+
*/
7+
8+
/**
9+
* Action Context
10+
* Context passed to action handlers
11+
*/
12+
export interface ActionContext {
13+
/** Object name */
14+
objectName: string;
15+
/** Action name */
16+
actionName: string;
17+
/** Input data */
18+
data?: any;
19+
/** Record IDs (for record-level actions) */
20+
ids?: string[];
21+
/** User context */
22+
user?: any;
23+
/** Additional metadata */
24+
metadata?: any;
25+
[key: string]: any;
26+
}
27+
28+
/**
29+
* Action Handler
30+
* Function signature for action handlers
31+
*/
32+
export type ActionHandler = (ctx: ActionContext) => any | Promise<any>;
33+
34+
/**
35+
* Action Entry
36+
* Internal representation of a registered action
37+
*/
38+
export interface ActionEntry {
39+
handler: ActionHandler;
40+
packageName?: string;
41+
}
42+
43+
/**
44+
* Action Manager
45+
* Manages registration and execution of custom actions
46+
*/
47+
export class ActionManager {
48+
private actions: Map<string, ActionEntry> = new Map();
49+
50+
/**
51+
* Register an action
52+
* @param objectName - Object name
53+
* @param actionName - Action name
54+
* @param handler - Action handler function
55+
* @param packageName - Package name for tracking
56+
*/
57+
register(
58+
objectName: string,
59+
actionName: string,
60+
handler: ActionHandler,
61+
packageName?: string
62+
): void {
63+
const key = `${objectName}:${actionName}`;
64+
this.actions.set(key, { handler, packageName });
65+
}
66+
67+
/**
68+
* Execute an action
69+
* @param objectName - Object name
70+
* @param actionName - Action name
71+
* @param ctx - Action context
72+
* @returns Action result
73+
*/
74+
async execute(
75+
objectName: string,
76+
actionName: string,
77+
ctx: ActionContext
78+
): Promise<any> {
79+
const key = `${objectName}:${actionName}`;
80+
const entry = this.actions.get(key);
81+
82+
if (!entry) {
83+
throw new Error(`Action '${actionName}' not found for object '${objectName}'`);
84+
}
85+
86+
return await entry.handler(ctx);
87+
}
88+
89+
/**
90+
* Check if an action exists
91+
*/
92+
has(objectName: string, actionName: string): boolean {
93+
const key = `${objectName}:${actionName}`;
94+
return this.actions.has(key);
95+
}
96+
97+
/**
98+
* Remove actions from a package
99+
*/
100+
removePackage(packageName: string): void {
101+
for (const [key, entry] of this.actions.entries()) {
102+
if (entry.packageName === packageName) {
103+
this.actions.delete(key);
104+
}
105+
}
106+
}
107+
108+
/**
109+
* Clear all actions
110+
*/
111+
clear(): void {
112+
this.actions.clear();
113+
}
114+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/**
2+
* @objectstack/runtime
3+
* Hook System - Event lifecycle management
4+
*
5+
* Provides a generic hook system for lifecycle events (before/after CRUD operations)
6+
*/
7+
8+
export type HookName =
9+
| 'beforeFind'
10+
| 'afterFind'
11+
| 'beforeCreate'
12+
| 'afterCreate'
13+
| 'beforeUpdate'
14+
| 'afterUpdate'
15+
| 'beforeDelete'
16+
| 'afterDelete'
17+
| 'beforeValidate'
18+
| 'afterValidate';
19+
20+
/**
21+
* Hook Context
22+
* Context passed to hook handlers
23+
*/
24+
export interface HookContext {
25+
/** Object name */
26+
objectName: string;
27+
/** Current data */
28+
data?: any;
29+
/** Original data (for updates) */
30+
originalData?: any;
31+
/** User context */
32+
user?: any;
33+
/** Additional metadata */
34+
metadata?: any;
35+
[key: string]: any;
36+
}
37+
38+
/**
39+
* Hook Handler
40+
* Function signature for hook handlers
41+
*/
42+
export type HookHandler = (ctx: HookContext) => void | Promise<void>;
43+
44+
/**
45+
* Hook Entry
46+
* Internal representation of a registered hook
47+
*/
48+
export interface HookEntry {
49+
objectName: string;
50+
handler: HookHandler;
51+
packageName?: string;
52+
}
53+
54+
/**
55+
* Hook Manager
56+
* Manages registration and execution of hooks
57+
*/
58+
export class HookManager {
59+
private hooks: Map<HookName, HookEntry[]> = new Map();
60+
61+
/**
62+
* Register a hook
63+
*/
64+
register(
65+
event: HookName,
66+
objectName: string,
67+
handler: HookHandler,
68+
packageName?: string
69+
): void {
70+
if (!this.hooks.has(event)) {
71+
this.hooks.set(event, []);
72+
}
73+
74+
const entries = this.hooks.get(event)!;
75+
entries.push({ objectName, handler, packageName });
76+
}
77+
78+
/**
79+
* Trigger hooks for an event
80+
*/
81+
async trigger(
82+
event: HookName,
83+
objectName: string,
84+
ctx: HookContext
85+
): Promise<void> {
86+
const entries = this.hooks.get(event) || [];
87+
88+
for (const entry of entries) {
89+
// Match on wildcard '*' or specific object name
90+
if (entry.objectName === '*' || entry.objectName === objectName) {
91+
await entry.handler(ctx);
92+
}
93+
}
94+
}
95+
96+
/**
97+
* Remove hooks from a package
98+
*/
99+
removePackage(packageName: string): void {
100+
for (const [event, entries] of this.hooks.entries()) {
101+
this.hooks.set(
102+
event,
103+
entries.filter(e => e.packageName !== packageName)
104+
);
105+
}
106+
}
107+
108+
/**
109+
* Clear all hooks
110+
*/
111+
clear(): void {
112+
this.hooks.clear();
113+
}
114+
}

packages/objectstack/runtime/src/index.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
* This package defines the runtime types for the ObjectStack ecosystem.
66
*/
77

8+
// Import core modules for use in kernel
9+
import { MetadataRegistry } from './metadata';
10+
import { HookManager } from './hooks';
11+
import { ActionManager } from './actions';
12+
13+
// Export core runtime modules
14+
export * from './metadata';
15+
export * from './hooks';
16+
export * from './actions';
17+
818
/**
919
* Runtime Context
1020
* Provides access to the ObjectStack kernel during plugin execution
@@ -38,19 +48,52 @@ export interface RuntimePlugin {
3848
export class ObjectStackKernel {
3949
/** Query interface (QL) */
4050
public ql: unknown = null;
51+
52+
/** Metadata registry */
53+
public metadata: MetadataRegistry;
54+
55+
/** Hook manager */
56+
public hooks: HookManager;
57+
58+
/** Action manager */
59+
public actions: ActionManager;
60+
61+
/** Registered plugins */
62+
private plugins: RuntimePlugin[] = [];
4163

4264
constructor(plugins: RuntimePlugin[] = []) {
43-
// Stub implementation
65+
this.plugins = plugins;
66+
this.metadata = new MetadataRegistry();
67+
this.hooks = new HookManager();
68+
this.actions = new ActionManager();
4469
}
4570

4671
/** Start the kernel */
4772
async start(): Promise<void> {
48-
// Stub implementation
73+
// Install all plugins
74+
for (const plugin of this.plugins) {
75+
if (plugin.install) {
76+
await plugin.install({ engine: this });
77+
}
78+
}
79+
80+
// Start all plugins
81+
for (const plugin of this.plugins) {
82+
if (plugin.onStart) {
83+
await plugin.onStart({ engine: this });
84+
}
85+
}
4986
}
5087

5188
/** Stop the kernel */
5289
async stop(): Promise<void> {
53-
// Stub implementation
90+
// Stop all plugins in reverse order
91+
for (let i = this.plugins.length - 1; i >= 0; i--) {
92+
const plugin = this.plugins[i];
93+
if (plugin.onStop) {
94+
await plugin.onStop({ engine: this });
95+
}
96+
}
5497
}
5598

5699
/** Seed initial data */

0 commit comments

Comments
 (0)