@objectstack/runtime 0.6.1 → 0.7.1

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.
@@ -0,0 +1,153 @@
1
+ import { RouteHandler, IHttpServer } from '@objectstack/core';
2
+ import { System, Shared } from '@objectstack/spec';
3
+ type RouteHandlerMetadata = System.RouteHandlerMetadata;
4
+ type HttpMethod = Shared.HttpMethod;
5
+ /**
6
+ * Route Entry
7
+ * Internal representation of registered routes
8
+ */
9
+ export interface RouteEntry {
10
+ method: HttpMethod;
11
+ path: string;
12
+ handler: RouteHandler;
13
+ metadata?: RouteHandlerMetadata['metadata'];
14
+ security?: RouteHandlerMetadata['security'];
15
+ }
16
+ /**
17
+ * RouteManager
18
+ *
19
+ * Manages route registration and organization for HTTP servers.
20
+ * Provides:
21
+ * - Route registration with metadata
22
+ * - Route lookup and querying
23
+ * - Bulk route registration
24
+ * - Route grouping by prefix
25
+ *
26
+ * @example
27
+ * const manager = new RouteManager(server);
28
+ *
29
+ * // Register individual route
30
+ * manager.register({
31
+ * method: 'GET',
32
+ * path: '/api/users/:id',
33
+ * handler: getUserHandler,
34
+ * metadata: {
35
+ * summary: 'Get user by ID',
36
+ * tags: ['users']
37
+ * }
38
+ * });
39
+ *
40
+ * // Register route group
41
+ * manager.group('/api/users', (group) => {
42
+ * group.get('/', listUsersHandler);
43
+ * group.post('/', createUserHandler);
44
+ * group.get('/:id', getUserHandler);
45
+ * });
46
+ */
47
+ export declare class RouteManager {
48
+ private server;
49
+ private routes;
50
+ constructor(server: IHttpServer);
51
+ /**
52
+ * Register a route
53
+ * @param entry - Route entry with method, path, handler, and metadata
54
+ */
55
+ register(entry: Omit<RouteEntry, 'handler'> & {
56
+ handler: RouteHandler | string;
57
+ }): void;
58
+ /**
59
+ * Register multiple routes
60
+ * @param entries - Array of route entries
61
+ */
62
+ registerMany(entries: Array<Omit<RouteEntry, 'handler'> & {
63
+ handler: RouteHandler | string;
64
+ }>): void;
65
+ /**
66
+ * Unregister a route
67
+ * @param method - HTTP method
68
+ * @param path - Route path
69
+ */
70
+ unregister(method: HttpMethod, path: string): void;
71
+ /**
72
+ * Get route by method and path
73
+ * @param method - HTTP method
74
+ * @param path - Route path
75
+ */
76
+ get(method: HttpMethod, path: string): RouteEntry | undefined;
77
+ /**
78
+ * Get all routes
79
+ */
80
+ getAll(): RouteEntry[];
81
+ /**
82
+ * Get routes by method
83
+ * @param method - HTTP method
84
+ */
85
+ getByMethod(method: HttpMethod): RouteEntry[];
86
+ /**
87
+ * Get routes by path prefix
88
+ * @param prefix - Path prefix
89
+ */
90
+ getByPrefix(prefix: string): RouteEntry[];
91
+ /**
92
+ * Get routes by tag
93
+ * @param tag - Tag name
94
+ */
95
+ getByTag(tag: string): RouteEntry[];
96
+ /**
97
+ * Create a route group with common prefix
98
+ * @param prefix - Common path prefix
99
+ * @param configure - Function to configure routes in the group
100
+ */
101
+ group(prefix: string, configure: (group: RouteGroupBuilder) => void): void;
102
+ /**
103
+ * Get route count
104
+ */
105
+ count(): number;
106
+ /**
107
+ * Clear all routes
108
+ */
109
+ clear(): void;
110
+ /**
111
+ * Get route key for storage
112
+ */
113
+ private getRouteKey;
114
+ /**
115
+ * Register route with underlying server
116
+ */
117
+ private registerWithServer;
118
+ }
119
+ /**
120
+ * RouteGroupBuilder
121
+ *
122
+ * Builder for creating route groups with common prefix
123
+ */
124
+ export declare class RouteGroupBuilder {
125
+ private manager;
126
+ private prefix;
127
+ constructor(manager: RouteManager, prefix: string);
128
+ /**
129
+ * Register GET route in group
130
+ */
131
+ get(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this;
132
+ /**
133
+ * Register POST route in group
134
+ */
135
+ post(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this;
136
+ /**
137
+ * Register PUT route in group
138
+ */
139
+ put(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this;
140
+ /**
141
+ * Register PATCH route in group
142
+ */
143
+ patch(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this;
144
+ /**
145
+ * Register DELETE route in group
146
+ */
147
+ delete(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this;
148
+ /**
149
+ * Resolve full path with prefix
150
+ */
151
+ private resolvePath;
152
+ }
153
+ export {};
@@ -0,0 +1,251 @@
1
+ /**
2
+ * RouteManager
3
+ *
4
+ * Manages route registration and organization for HTTP servers.
5
+ * Provides:
6
+ * - Route registration with metadata
7
+ * - Route lookup and querying
8
+ * - Bulk route registration
9
+ * - Route grouping by prefix
10
+ *
11
+ * @example
12
+ * const manager = new RouteManager(server);
13
+ *
14
+ * // Register individual route
15
+ * manager.register({
16
+ * method: 'GET',
17
+ * path: '/api/users/:id',
18
+ * handler: getUserHandler,
19
+ * metadata: {
20
+ * summary: 'Get user by ID',
21
+ * tags: ['users']
22
+ * }
23
+ * });
24
+ *
25
+ * // Register route group
26
+ * manager.group('/api/users', (group) => {
27
+ * group.get('/', listUsersHandler);
28
+ * group.post('/', createUserHandler);
29
+ * group.get('/:id', getUserHandler);
30
+ * });
31
+ */
32
+ export class RouteManager {
33
+ constructor(server) {
34
+ this.server = server;
35
+ this.routes = new Map();
36
+ }
37
+ /**
38
+ * Register a route
39
+ * @param entry - Route entry with method, path, handler, and metadata
40
+ */
41
+ register(entry) {
42
+ // Validate handler type - string handlers not yet supported
43
+ if (typeof entry.handler === 'string') {
44
+ throw new Error(`String-based route handlers are not supported yet. ` +
45
+ `Received handler identifier "${entry.handler}". ` +
46
+ `Please provide a RouteHandler function instead.`);
47
+ }
48
+ const handler = entry.handler;
49
+ const routeEntry = {
50
+ method: entry.method,
51
+ path: entry.path,
52
+ handler,
53
+ metadata: entry.metadata,
54
+ security: entry.security,
55
+ };
56
+ const key = this.getRouteKey(entry.method, entry.path);
57
+ this.routes.set(key, routeEntry);
58
+ // Register with underlying server
59
+ this.registerWithServer(routeEntry);
60
+ }
61
+ /**
62
+ * Register multiple routes
63
+ * @param entries - Array of route entries
64
+ */
65
+ registerMany(entries) {
66
+ entries.forEach(entry => this.register(entry));
67
+ }
68
+ /**
69
+ * Unregister a route
70
+ * @param method - HTTP method
71
+ * @param path - Route path
72
+ */
73
+ unregister(method, path) {
74
+ const key = this.getRouteKey(method, path);
75
+ this.routes.delete(key);
76
+ // Note: Most server frameworks don't support unregistering routes at runtime
77
+ // This just removes it from our registry
78
+ }
79
+ /**
80
+ * Get route by method and path
81
+ * @param method - HTTP method
82
+ * @param path - Route path
83
+ */
84
+ get(method, path) {
85
+ const key = this.getRouteKey(method, path);
86
+ return this.routes.get(key);
87
+ }
88
+ /**
89
+ * Get all routes
90
+ */
91
+ getAll() {
92
+ return Array.from(this.routes.values());
93
+ }
94
+ /**
95
+ * Get routes by method
96
+ * @param method - HTTP method
97
+ */
98
+ getByMethod(method) {
99
+ return this.getAll().filter(route => route.method === method);
100
+ }
101
+ /**
102
+ * Get routes by path prefix
103
+ * @param prefix - Path prefix
104
+ */
105
+ getByPrefix(prefix) {
106
+ return this.getAll().filter(route => route.path.startsWith(prefix));
107
+ }
108
+ /**
109
+ * Get routes by tag
110
+ * @param tag - Tag name
111
+ */
112
+ getByTag(tag) {
113
+ return this.getAll().filter(route => route.metadata?.tags?.includes(tag));
114
+ }
115
+ /**
116
+ * Create a route group with common prefix
117
+ * @param prefix - Common path prefix
118
+ * @param configure - Function to configure routes in the group
119
+ */
120
+ group(prefix, configure) {
121
+ const builder = new RouteGroupBuilder(this, prefix);
122
+ configure(builder);
123
+ }
124
+ /**
125
+ * Get route count
126
+ */
127
+ count() {
128
+ return this.routes.size;
129
+ }
130
+ /**
131
+ * Clear all routes
132
+ */
133
+ clear() {
134
+ this.routes.clear();
135
+ }
136
+ /**
137
+ * Get route key for storage
138
+ */
139
+ getRouteKey(method, path) {
140
+ return `${method}:${path}`;
141
+ }
142
+ /**
143
+ * Register route with underlying server
144
+ */
145
+ registerWithServer(entry) {
146
+ const { method, path, handler } = entry;
147
+ switch (method) {
148
+ case 'GET':
149
+ this.server.get(path, handler);
150
+ break;
151
+ case 'POST':
152
+ this.server.post(path, handler);
153
+ break;
154
+ case 'PUT':
155
+ this.server.put(path, handler);
156
+ break;
157
+ case 'DELETE':
158
+ this.server.delete(path, handler);
159
+ break;
160
+ case 'PATCH':
161
+ this.server.patch(path, handler);
162
+ break;
163
+ default:
164
+ throw new Error(`Unsupported HTTP method: ${method}`);
165
+ }
166
+ }
167
+ }
168
+ /**
169
+ * RouteGroupBuilder
170
+ *
171
+ * Builder for creating route groups with common prefix
172
+ */
173
+ export class RouteGroupBuilder {
174
+ constructor(manager, prefix) {
175
+ this.manager = manager;
176
+ this.prefix = prefix;
177
+ }
178
+ /**
179
+ * Register GET route in group
180
+ */
181
+ get(path, handler, metadata) {
182
+ this.manager.register({
183
+ method: 'GET',
184
+ path: this.resolvePath(path),
185
+ handler,
186
+ metadata,
187
+ });
188
+ return this;
189
+ }
190
+ /**
191
+ * Register POST route in group
192
+ */
193
+ post(path, handler, metadata) {
194
+ this.manager.register({
195
+ method: 'POST',
196
+ path: this.resolvePath(path),
197
+ handler,
198
+ metadata,
199
+ });
200
+ return this;
201
+ }
202
+ /**
203
+ * Register PUT route in group
204
+ */
205
+ put(path, handler, metadata) {
206
+ this.manager.register({
207
+ method: 'PUT',
208
+ path: this.resolvePath(path),
209
+ handler,
210
+ metadata,
211
+ });
212
+ return this;
213
+ }
214
+ /**
215
+ * Register PATCH route in group
216
+ */
217
+ patch(path, handler, metadata) {
218
+ this.manager.register({
219
+ method: 'PATCH',
220
+ path: this.resolvePath(path),
221
+ handler,
222
+ metadata,
223
+ });
224
+ return this;
225
+ }
226
+ /**
227
+ * Register DELETE route in group
228
+ */
229
+ delete(path, handler, metadata) {
230
+ this.manager.register({
231
+ method: 'DELETE',
232
+ path: this.resolvePath(path),
233
+ handler,
234
+ metadata,
235
+ });
236
+ return this;
237
+ }
238
+ /**
239
+ * Resolve full path with prefix
240
+ */
241
+ resolvePath(path) {
242
+ // Normalize slashes
243
+ const normalizedPrefix = this.prefix.endsWith('/')
244
+ ? this.prefix.slice(0, -1)
245
+ : this.prefix;
246
+ const normalizedPath = path.startsWith('/')
247
+ ? path
248
+ : '/' + path;
249
+ return normalizedPrefix + normalizedPath;
250
+ }
251
+ }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@objectstack/runtime",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "ObjectStack Core Runtime & Query Engine",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "dependencies": {
9
- "@objectstack/core": "0.6.1",
10
- "@objectstack/types": "0.6.1",
11
- "@objectstack/spec": "0.6.1"
9
+ "@objectstack/core": "0.7.1",
10
+ "@objectstack/types": "0.7.1",
11
+ "@objectstack/spec": "0.7.1"
12
12
  },
13
13
  "devDependencies": {
14
14
  "typescript": "^5.0.0"
package/src/app-plugin.ts CHANGED
@@ -29,41 +29,72 @@ export class AppPlugin implements Plugin {
29
29
  const sys = this.bundle.manifest || this.bundle;
30
30
  const appId = sys.id || sys.name;
31
31
 
32
- ctx.logger?.log(`[AppPlugin] Registering App Service: ${appId}`);
32
+ ctx.logger.info('Registering App Service', {
33
+ appId,
34
+ pluginName: this.name,
35
+ version: this.version
36
+ });
33
37
 
34
38
  // Register the app manifest as a service
35
39
  // ObjectQLPlugin will discover this and call ql.registerApp()
36
40
  const serviceName = `app.${appId}`;
37
- ctx.registerService(serviceName, this.bundle.manifest || this.bundle);
41
+
42
+ // Merge manifest with the bundle to ensure objects/apps are accessible at root
43
+ // This supports both Legacy Manifests and new Stack Definitions
44
+ const servicePayload = this.bundle.manifest
45
+ ? { ...this.bundle.manifest, ...this.bundle }
46
+ : this.bundle;
47
+
48
+ ctx.registerService(serviceName, servicePayload);
38
49
  }
39
50
 
40
51
  async start(ctx: PluginContext) {
52
+ const sys = this.bundle.manifest || this.bundle;
53
+ const appId = sys.id || sys.name;
54
+
41
55
  // Execute Runtime Step
42
56
  // Retrieve ObjectQL engine from services
43
57
  // We cast to any/ObjectQL because ctx.getService returns unknown
44
58
  const ql = ctx.getService('objectql') as any;
45
59
 
46
60
  if (!ql) {
47
- ctx.logger?.warn(`[AppPlugin] ObjectQL engine service not found for app: ${this.name}`);
61
+ ctx.logger.warn('ObjectQL engine service not found', {
62
+ appName: this.name,
63
+ appId
64
+ });
48
65
  return;
49
66
  }
50
67
 
68
+ ctx.logger.debug('Retrieved ObjectQL engine service', { appId });
69
+
51
70
  const runtime = this.bundle.default || this.bundle;
52
71
 
53
72
  if (runtime && typeof runtime.onEnable === 'function') {
54
- ctx.logger?.log(`[AppPlugin] Executing runtime.onEnable for: ${this.name}`);
73
+ ctx.logger.info('Executing runtime.onEnable', {
74
+ appName: this.name,
75
+ appId
76
+ });
55
77
 
56
78
  // Construct the Host Context (mirroring old ObjectQL.use logic)
57
79
  const hostContext = {
58
80
  ...ctx,
59
81
  ql,
60
- logger: ctx.logger || console,
82
+ logger: ctx.logger,
61
83
  drivers: {
62
- register: (driver: any) => ql.registerDriver(driver)
84
+ register: (driver: any) => {
85
+ ctx.logger.debug('Registering driver via app runtime', {
86
+ driverName: driver.name,
87
+ appId
88
+ });
89
+ ql.registerDriver(driver);
90
+ }
63
91
  },
64
92
  };
65
93
 
66
94
  await runtime.onEnable(hostContext);
95
+ ctx.logger.debug('Runtime.onEnable completed', { appId });
96
+ } else {
97
+ ctx.logger.debug('No runtime.onEnable function found', { appId });
67
98
  }
68
99
  }
69
100
  }
@@ -30,11 +30,15 @@ export class DriverPlugin implements Plugin {
30
30
  // Register driver as a service instead of directly to objectql
31
31
  const serviceName = `driver.${this.driver.name || 'unknown'}`;
32
32
  ctx.registerService(serviceName, this.driver);
33
- ctx.logger.log(`[DriverPlugin] Registered driver service: ${serviceName}`);
33
+ ctx.logger.info('Driver service registered', {
34
+ serviceName,
35
+ driverName: this.driver.name,
36
+ driverVersion: this.driver.version
37
+ });
34
38
  }
35
39
 
36
40
  async start(ctx: PluginContext) {
37
41
  // Drivers don't need start phase, initialization happens in init
38
- ctx.logger.log(`[DriverPlugin] Driver ready: ${this.driver.name || 'unknown'}`);
42
+ ctx.logger.debug('Driver plugin started', { driverName: this.driver.name || 'unknown' });
39
43
  }
40
44
  }
@@ -0,0 +1,140 @@
1
+ import { IHttpServer, RouteHandler, Middleware } from '@objectstack/core';
2
+
3
+ /**
4
+ * HttpServer - Unified HTTP Server Abstraction
5
+ *
6
+ * Provides a framework-agnostic HTTP server interface that wraps
7
+ * underlying server implementations (Hono, Express, Fastify, etc.)
8
+ *
9
+ * This class serves as an adapter between the IHttpServer interface
10
+ * and concrete server implementations, allowing plugins to register
11
+ * routes and middleware without depending on specific frameworks.
12
+ *
13
+ * Features:
14
+ * - Unified route registration API
15
+ * - Middleware management with ordering
16
+ * - Request/response lifecycle hooks
17
+ * - Framework-agnostic abstractions
18
+ */
19
+ export class HttpServer implements IHttpServer {
20
+ protected server: IHttpServer;
21
+ protected routes: Map<string, RouteHandler>;
22
+ protected middlewares: Middleware[];
23
+
24
+ /**
25
+ * Create an HTTP server wrapper
26
+ * @param server - The underlying server implementation (Hono, Express, etc.)
27
+ */
28
+ constructor(server: IHttpServer) {
29
+ this.server = server;
30
+ this.routes = new Map();
31
+ this.middlewares = [];
32
+ }
33
+
34
+ /**
35
+ * Register a GET route handler
36
+ * @param path - Route path (e.g., '/api/users/:id')
37
+ * @param handler - Route handler function
38
+ */
39
+ get(path: string, handler: RouteHandler): void {
40
+ const key = `GET:${path}`;
41
+ this.routes.set(key, handler);
42
+ this.server.get(path, handler);
43
+ }
44
+
45
+ /**
46
+ * Register a POST route handler
47
+ * @param path - Route path
48
+ * @param handler - Route handler function
49
+ */
50
+ post(path: string, handler: RouteHandler): void {
51
+ const key = `POST:${path}`;
52
+ this.routes.set(key, handler);
53
+ this.server.post(path, handler);
54
+ }
55
+
56
+ /**
57
+ * Register a PUT route handler
58
+ * @param path - Route path
59
+ * @param handler - Route handler function
60
+ */
61
+ put(path: string, handler: RouteHandler): void {
62
+ const key = `PUT:${path}`;
63
+ this.routes.set(key, handler);
64
+ this.server.put(path, handler);
65
+ }
66
+
67
+ /**
68
+ * Register a DELETE route handler
69
+ * @param path - Route path
70
+ * @param handler - Route handler function
71
+ */
72
+ delete(path: string, handler: RouteHandler): void {
73
+ const key = `DELETE:${path}`;
74
+ this.routes.set(key, handler);
75
+ this.server.delete(path, handler);
76
+ }
77
+
78
+ /**
79
+ * Register a PATCH route handler
80
+ * @param path - Route path
81
+ * @param handler - Route handler function
82
+ */
83
+ patch(path: string, handler: RouteHandler): void {
84
+ const key = `PATCH:${path}`;
85
+ this.routes.set(key, handler);
86
+ this.server.patch(path, handler);
87
+ }
88
+
89
+ /**
90
+ * Register middleware
91
+ * @param path - Optional path to apply middleware to (if omitted, applies globally)
92
+ * @param handler - Middleware function
93
+ */
94
+ use(path: string | Middleware, handler?: Middleware): void {
95
+ if (typeof path === 'function') {
96
+ // Global middleware
97
+ this.middlewares.push(path);
98
+ this.server.use(path);
99
+ } else if (handler) {
100
+ // Path-specific middleware
101
+ this.middlewares.push(handler);
102
+ this.server.use(path, handler);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Start the HTTP server
108
+ * @param port - Port number to listen on
109
+ * @returns Promise that resolves when server is ready
110
+ */
111
+ async listen(port: number): Promise<void> {
112
+ await this.server.listen(port);
113
+ }
114
+
115
+ /**
116
+ * Stop the HTTP server
117
+ * @returns Promise that resolves when server is stopped
118
+ */
119
+ async close(): Promise<void> {
120
+ if (this.server.close) {
121
+ await this.server.close();
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Get registered routes
127
+ * @returns Map of route keys to handlers
128
+ */
129
+ getRoutes(): Map<string, RouteHandler> {
130
+ return new Map(this.routes);
131
+ }
132
+
133
+ /**
134
+ * Get registered middlewares
135
+ * @returns Array of middleware functions
136
+ */
137
+ getMiddlewares(): Middleware[] {
138
+ return [...this.middlewares];
139
+ }
140
+ }
package/src/index.ts CHANGED
@@ -2,10 +2,17 @@
2
2
  export { ObjectKernel } from '@objectstack/core';
3
3
 
4
4
  // Export Plugins
5
- export { DriverPlugin } from './driver-plugin';
6
- export { AppPlugin } from './app-plugin';
5
+ export { DriverPlugin } from './driver-plugin.js';
6
+ export { AppPlugin } from './app-plugin.js';
7
+
8
+ // Export HTTP Server Components
9
+ export { HttpServer } from './http-server.js';
10
+ export { RestServer } from './rest-server.js';
11
+ export { RouteManager, RouteGroupBuilder } from './route-manager.js';
12
+ export { MiddlewareManager } from './middleware.js';
7
13
 
8
14
  // Export Types
9
15
  export * from '@objectstack/core';
10
16
 
11
17
 
18
+