Skip to content

Commit 92d61ad

Browse files
Copilothotlong
andcommitted
Phase 1 complete: Type alignment - extend runtime types for ObjectQL compatibility
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 8bd32b7 commit 92d61ad

3 files changed

Lines changed: 114 additions & 13 deletions

File tree

packages/foundation/core/src/app.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,26 +108,30 @@ export class ObjectQL implements IObjectQL {
108108

109109
on(event: HookName, objectName: string, handler: HookHandler, packageName?: string) {
110110
// Delegate to kernel hook manager
111-
// Note: Type casting needed due to type incompatibility between ObjectQL HookName (includes beforeCount)
112-
// and runtime HookName. This is safe as the kernel will accept all hook types.
113-
this.kernel.hooks.register(event as any, objectName, handler as any, packageName);
111+
// We wrap the handler to bridge ObjectQL's rich context types with runtime's base types
112+
// The runtime HookContext supports all fields via index signature, so this is safe
113+
const wrappedHandler = handler as unknown as import('@objectstack/runtime').HookHandler;
114+
this.kernel.hooks.register(event, objectName, wrappedHandler, packageName);
114115
}
115116

116117
async triggerHook(event: HookName, objectName: string, ctx: HookContext) {
117118
// Delegate to kernel hook manager
118-
await this.kernel.hooks.trigger(event as any, objectName, ctx as any);
119+
// Runtime HookContext supports ObjectQL-specific fields via index signature
120+
await this.kernel.hooks.trigger(event, objectName, ctx);
119121
}
120122

121123
registerAction(objectName: string, actionName: string, handler: ActionHandler, packageName?: string) {
122124
// Delegate to kernel action manager
123-
// Note: Type casting needed due to type incompatibility between ObjectQL ActionHandler
124-
// (includes input/api fields) and runtime ActionHandler. This is safe as runtime is more permissive.
125-
this.kernel.actions.register(objectName, actionName, handler as any, packageName);
125+
// We wrap the handler to bridge ObjectQL's rich context types with runtime's base types
126+
// The runtime ActionContext supports all fields via index signature, so this is safe
127+
const wrappedHandler = handler as unknown as import('@objectstack/runtime').ActionHandler;
128+
this.kernel.actions.register(objectName, actionName, wrappedHandler, packageName);
126129
}
127130

128131
async executeAction(objectName: string, actionName: string, ctx: ActionContext) {
129132
// Delegate to kernel action manager
130-
return await this.kernel.actions.execute(objectName, actionName, ctx as any);
133+
// Runtime ActionContext supports ObjectQL-specific fields via index signature
134+
return await this.kernel.actions.execute(objectName, actionName, ctx);
131135
}
132136

133137
createContext(options: ObjectQLContextOptions): ObjectQLContext {

packages/objectstack/runtime/src/actions.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,61 @@ export class RuntimeError extends Error {
1919
/**
2020
* Action Context
2121
* Context passed to action handlers
22+
*
23+
* Extended to support ObjectQL's rich context requirements including:
24+
* - Database API access for performing operations
25+
* - User session information
26+
* - Validated input parameters
27+
* - Record ID for record-level actions
2228
*/
2329
export interface ActionContext {
2430
/** Object name */
2531
objectName: string;
32+
2633
/** Action name */
2734
actionName: string;
28-
/** Input data */
35+
36+
/** Input data (legacy field, prefer 'input' for ObjectQL) */
2937
data?: any;
30-
/** Record IDs (for record-level actions) */
38+
39+
/** Record IDs (for record-level actions, legacy array form) */
3140
ids?: string[];
41+
3242
/** User context */
33-
user?: any;
43+
user?: {
44+
id: string | number;
45+
[key: string]: any;
46+
};
47+
3448
/** Additional metadata */
3549
metadata?: any;
50+
51+
/**
52+
* The ID of the record being acted upon (ObjectQL extension)
53+
* Only available if type is 'record'
54+
*/
55+
id?: string | number;
56+
57+
/**
58+
* The validated input arguments (ObjectQL extension)
59+
* Prefer this over 'data' for typed action inputs
60+
*/
61+
input?: any;
62+
63+
/**
64+
* Database Access API (ObjectQL extension)
65+
* Same interface as used in hooks
66+
*/
67+
api?: {
68+
find(objectName: string, query?: any): Promise<any[]>;
69+
findOne(objectName: string, id: string | number): Promise<any>;
70+
count(objectName: string, query?: any): Promise<number>;
71+
create(objectName: string, data: any): Promise<any>;
72+
update(objectName: string, id: string | number, data: any): Promise<any>;
73+
delete(objectName: string, id: string | number): Promise<any>;
74+
};
75+
76+
/** Allow additional properties for extensibility */
3677
[key: string]: any;
3778
}
3879

packages/objectstack/runtime/src/hooks.ts

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
export type HookName =
99
| 'beforeFind'
1010
| 'afterFind'
11+
| 'beforeCount'
12+
| 'afterCount'
1113
| 'beforeCreate'
1214
| 'afterCreate'
1315
| 'beforeUpdate'
@@ -17,21 +19,75 @@ export type HookName =
1719
| 'beforeValidate'
1820
| 'afterValidate';
1921

22+
/**
23+
* HookAPI - Minimal API surface exposed to hooks for performing side-effects or checks
24+
* Allows hooks to perform database operations without circular dependencies
25+
*/
26+
export interface HookAPI {
27+
find(objectName: string, query?: any): Promise<any[]>;
28+
findOne(objectName: string, id: string | number): Promise<any>;
29+
count(objectName: string, query?: any): Promise<number>;
30+
create(objectName: string, data: any): Promise<any>;
31+
update(objectName: string, id: string | number, data: any): Promise<any>;
32+
delete(objectName: string, id: string | number): Promise<any>;
33+
}
34+
2035
/**
2136
* Hook Context
2237
* Context passed to hook handlers
38+
*
39+
* Extended to support ObjectQL's rich context requirements including:
40+
* - Database API access for cross-object operations
41+
* - User session information
42+
* - Shared state between before/after hooks
43+
* - Operation type tracking
44+
* - Query/result data for retrieval operations
45+
* - Record data for mutation operations
2346
*/
2447
export interface HookContext {
2548
/** Object name */
2649
objectName: string;
27-
/** Current data */
50+
51+
/** Current data (for create/update operations) */
2852
data?: any;
53+
2954
/** Original data (for updates) */
3055
originalData?: any;
56+
3157
/** User context */
32-
user?: any;
58+
user?: {
59+
id: string | number;
60+
[key: string]: any;
61+
};
62+
3363
/** Additional metadata */
3464
metadata?: any;
65+
66+
/** The triggering operation (ObjectQL extension) */
67+
operation?: 'find' | 'count' | 'create' | 'update' | 'delete';
68+
69+
/** Access to the database/engine to perform extra queries (ObjectQL extension) */
70+
api?: HookAPI;
71+
72+
/**
73+
* Shared state for passing data between matching 'before' and 'after' hooks (ObjectQL extension)
74+
* e.g. Calculate a diff in 'beforeUpdate' and read it in 'afterUpdate'
75+
*/
76+
state?: Record<string, any>;
77+
78+
/** The query criteria being executed (for retrieval operations, ObjectQL extension) */
79+
query?: any;
80+
81+
/** The result of the query/operation (available in 'after' hooks, ObjectQL extension) */
82+
result?: any;
83+
84+
/** The record ID (for update/delete operations, ObjectQL extension) */
85+
id?: string | number;
86+
87+
/** The existing record fetched from DB before operation (ObjectQL extension) */
88+
previousData?: any;
89+
90+
/** Allow additional properties for extensibility */
3591
[key: string]: any;
3692
}
3793

0 commit comments

Comments
 (0)