@objectstack/runtime 0.9.2 → 1.0.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.
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { Runtime } from './runtime';
3
+ // Mock ObjectKernel to isolate Runtime logic
4
+ vi.mock('@objectstack/core', async () => {
5
+ const actual = await vi.importActual('@objectstack/core');
6
+ return {
7
+ ...actual,
8
+ ObjectKernel: class {
9
+ constructor() {
10
+ this.use = vi.fn();
11
+ this.registerService = vi.fn();
12
+ this.bootstrap = vi.fn().mockResolvedValue(undefined);
13
+ this.getServices = vi.fn().mockReturnValue(new Map());
14
+ }
15
+ }
16
+ };
17
+ });
18
+ describe('Runtime', () => {
19
+ it('should initialize successfully', () => {
20
+ const runtime = new Runtime();
21
+ expect(runtime).toBeDefined();
22
+ // Should create a kernel
23
+ expect(runtime.getKernel()).toBeDefined();
24
+ });
25
+ it('should register api registry plugin by default', () => {
26
+ const runtime = new Runtime();
27
+ const kernel = runtime.getKernel();
28
+ // Check if use was called (at least once for api registry)
29
+ expect(kernel.use).toHaveBeenCalled();
30
+ });
31
+ it('should register external http server if provided', () => {
32
+ const mockServer = {
33
+ listen: vi.fn(),
34
+ close: vi.fn(),
35
+ get: vi.fn(),
36
+ post: vi.fn(),
37
+ put: vi.fn(),
38
+ delete: vi.fn(),
39
+ patch: vi.fn(),
40
+ use: vi.fn(),
41
+ };
42
+ const runtime = new Runtime({ server: mockServer });
43
+ const kernel = runtime.getKernel();
44
+ expect(kernel.registerService).toHaveBeenCalledWith('http.server', mockServer);
45
+ });
46
+ it('should delegate use() to kernel', () => {
47
+ const runtime = new Runtime();
48
+ const mockPlugin = { name: 'test', init: vi.fn() };
49
+ runtime.use(mockPlugin);
50
+ expect(runtime.getKernel().use).toHaveBeenCalledWith(mockPlugin);
51
+ });
52
+ it('should delegate start() to kernel.bootstrap()', async () => {
53
+ const runtime = new Runtime();
54
+ await runtime.start();
55
+ expect(runtime.getKernel().bootstrap).toHaveBeenCalled();
56
+ });
57
+ });
package/package.json CHANGED
@@ -1,20 +1,23 @@
1
1
  {
2
2
  "name": "@objectstack/runtime",
3
- "version": "0.9.2",
3
+ "version": "1.0.0",
4
+ "license": "Apache-2.0",
4
5
  "description": "ObjectStack Core Runtime & Query Engine",
5
6
  "type": "module",
6
7
  "main": "dist/index.js",
7
8
  "types": "dist/index.d.ts",
8
9
  "dependencies": {
9
- "@objectstack/core": "0.9.2",
10
- "@objectstack/spec": "0.9.2",
11
- "@objectstack/types": "0.9.2"
10
+ "@objectstack/core": "1.0.0",
11
+ "@objectstack/spec": "1.0.0",
12
+ "@objectstack/types": "1.0.0"
12
13
  },
13
14
  "devDependencies": {
14
- "typescript": "^5.0.0"
15
+ "typescript": "^5.0.0",
16
+ "vitest": "^4.0.18"
15
17
  },
16
18
  "scripts": {
17
19
  "build": "tsc",
18
- "dev": "tsc -w"
20
+ "dev": "tsc -w",
21
+ "test": "vitest run"
19
22
  }
20
23
  }
@@ -0,0 +1,58 @@
1
+ import { Plugin, PluginContext, IHttpServer } from '@objectstack/core';
2
+ import { RestServer } from './rest-server.js';
3
+ import { ObjectStackProtocol, RestServerConfig } from '@objectstack/spec/api';
4
+
5
+ export interface ApiRegistryConfig {
6
+ serverServiceName?: string;
7
+ protocolServiceName?: string;
8
+ api?: RestServerConfig;
9
+ }
10
+
11
+ /**
12
+ * ApiRegistryPlugin
13
+ *
14
+ * Responsibilities:
15
+ * 1. Consumes 'http.server' (or configured service)
16
+ * 2. Consumes 'protocol' (ObjectStackProtocol)
17
+ * 3. Instantiates RestServer to auto-generate routes
18
+ */
19
+ export function createApiRegistryPlugin(config: ApiRegistryConfig = {}): Plugin {
20
+ return {
21
+ name: 'com.objectstack.runtime.api-registry',
22
+ version: '1.0.0',
23
+
24
+ init: async (ctx: PluginContext) => {
25
+ // No service registration, this is a consumer plugin
26
+ },
27
+
28
+ start: async (ctx: PluginContext) => {
29
+ const serverService = config.serverServiceName || 'http.server';
30
+ const protocolService = config.protocolServiceName || 'protocol';
31
+
32
+ const server = ctx.getService<IHttpServer>(serverService);
33
+ const protocol = ctx.getService<ObjectStackProtocol>(protocolService);
34
+
35
+ if (!server) {
36
+ ctx.logger.warn(`ApiRegistryPlugin: HTTP Server service '${serverService}' not found. REST routes skipped.`);
37
+ return;
38
+ }
39
+
40
+ if (!protocol) {
41
+ ctx.logger.warn(`ApiRegistryPlugin: Protocol service '${protocolService}' not found. REST routes skipped.`);
42
+ return;
43
+ }
44
+
45
+ ctx.logger.info('Hydrating REST API from Protocol...');
46
+
47
+ try {
48
+ const restServer = new RestServer(server, protocol, config.api as any);
49
+ restServer.registerRoutes();
50
+
51
+ ctx.logger.info('REST API successfully registered');
52
+ } catch (err: any) {
53
+ ctx.logger.error('Failed to register REST API routes', { error: err.message } as any);
54
+ throw err;
55
+ }
56
+ }
57
+ };
58
+ }
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { AppPlugin } from './app-plugin';
3
+ import { PluginContext } from '@objectstack/core';
4
+
5
+ describe('AppPlugin', () => {
6
+ let mockContext: PluginContext;
7
+
8
+ beforeEach(() => {
9
+ mockContext = {
10
+ logger: {
11
+ info: vi.fn(),
12
+ error: vi.fn(),
13
+ warn: vi.fn(),
14
+ debug: vi.fn()
15
+ },
16
+ registerService: vi.fn(),
17
+ getService: vi.fn(),
18
+ getServices: vi.fn()
19
+ } as unknown as PluginContext;
20
+ });
21
+
22
+ it('should initialize with manifest info', () => {
23
+ const bundle = {
24
+ id: 'com.test.app',
25
+ name: 'Test App',
26
+ version: '1.0.0'
27
+ };
28
+ const plugin = new AppPlugin(bundle);
29
+ expect(plugin.name).toBe('plugin.app.com.test.app');
30
+ expect(plugin.version).toBe('1.0.0');
31
+ });
32
+
33
+ it('should handle nested stack definition manifest', () => {
34
+ const bundle = {
35
+ manifest: {
36
+ id: 'com.test.stack',
37
+ version: '2.0.0'
38
+ },
39
+ objects: []
40
+ };
41
+ const plugin = new AppPlugin(bundle);
42
+ expect(plugin.name).toBe('plugin.app.com.test.stack');
43
+ expect(plugin.version).toBe('2.0.0');
44
+ });
45
+
46
+ it('registerService should register raw manifest in init phase', async () => {
47
+ const bundle = {
48
+ id: 'com.test.simple',
49
+ objects: []
50
+ };
51
+ const plugin = new AppPlugin(bundle);
52
+
53
+ await plugin.init(mockContext);
54
+
55
+ expect(mockContext.registerService).toHaveBeenCalledWith('app.com.test.simple', bundle);
56
+ });
57
+
58
+ it('start should do nothing if no runtime hooks', async () => {
59
+ const bundle = { id: 'com.test.static' };
60
+ const plugin = new AppPlugin(bundle);
61
+
62
+ vi.mocked(mockContext.getService).mockReturnValue({}); // Mock ObjectQL exists
63
+
64
+ await plugin.start!(mockContext);
65
+ // Only logs, no errors
66
+ expect(mockContext.logger.debug).toHaveBeenCalled();
67
+ });
68
+
69
+ it('start should invoke onEnable if present', async () => {
70
+ const onEnableSpy = vi.fn();
71
+ const bundle = {
72
+ id: 'com.test.code',
73
+ onEnable: onEnableSpy
74
+ };
75
+ const plugin = new AppPlugin(bundle);
76
+
77
+ // Mock ObjectQL engine
78
+ const mockQL = { registry: {} };
79
+ vi.mocked(mockContext.getService).mockReturnValue(mockQL);
80
+
81
+ await plugin.start!(mockContext);
82
+
83
+ expect(onEnableSpy).toHaveBeenCalled();
84
+ // Check context passed to onEnable
85
+ const callArg = onEnableSpy.mock.calls[0][0];
86
+ expect(callArg.ql).toBe(mockQL);
87
+ });
88
+
89
+ it('start should warn if objectql not found', async () => {
90
+ const bundle = { id: 'com.test.warn' };
91
+ const plugin = new AppPlugin(bundle);
92
+
93
+ vi.mocked(mockContext.getService).mockReturnValue(undefined); // No ObjectQL
94
+
95
+ await plugin.start!(mockContext);
96
+
97
+ expect(mockContext.logger.warn).toHaveBeenCalledWith(
98
+ expect.stringContaining('ObjectQL engine service not found'),
99
+ expect.any(Object)
100
+ );
101
+ });
102
+ });
package/src/app-plugin.ts CHANGED
@@ -25,7 +25,7 @@ export class AppPlugin implements Plugin {
25
25
  this.version = sys.version;
26
26
  }
27
27
 
28
- async init(ctx: PluginContext) {
28
+ init = async (ctx: PluginContext) => {
29
29
  const sys = this.bundle.manifest || this.bundle;
30
30
  const appId = sys.id || sys.name;
31
31
 
@@ -48,7 +48,7 @@ export class AppPlugin implements Plugin {
48
48
  ctx.registerService(serviceName, servicePayload);
49
49
  }
50
50
 
51
- async start(ctx: PluginContext) {
51
+ start = async (ctx: PluginContext) => {
52
52
  const sys = this.bundle.manifest || this.bundle;
53
53
  const appId = sys.id || sys.name;
54
54
 
@@ -26,7 +26,7 @@ export class DriverPlugin implements Plugin {
26
26
  this.name = `com.objectstack.driver.${driverName || driver.name || 'unknown'}`;
27
27
  }
28
28
 
29
- async init(ctx: PluginContext) {
29
+ init = async (ctx: PluginContext) => {
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);
@@ -37,7 +37,7 @@ export class DriverPlugin implements Plugin {
37
37
  });
38
38
  }
39
39
 
40
- async start(ctx: PluginContext) {
40
+ start = async (ctx: PluginContext) => {
41
41
  // Drivers don't need start phase, initialization happens in init
42
42
  ctx.logger.debug('Driver plugin started', { driverName: this.driver.name || 'unknown' });
43
43
  }