Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 28, 2026

The protocol specification requires a micro-kernel architecture where applications, drivers, and plugins are loaded uniformly rather than through separate registration mechanisms.

Changes

Runtime (@objectql/runtime)

  • Heterogeneous component array: ObjectStackKernel constructor now accepts KernelComponent[] (union of RuntimePlugin | RuntimeDriver | RuntimeAppConfig) instead of RuntimePlugin[] only
  • Automatic type classification: Added type guards to detect component types without explicit markers
  • 4-phase initialization: Applications load first (Phase 1), then drivers connect (Phase 2), followed by plugin install (Phase 3) and start (Phase 4)
  • Driver registry: Added getDriver(name?) and getAllDrivers() methods for plugin access to registered drivers
  • Error handling: Startup failures trigger automatic cleanup; shutdown collects all errors to prevent resource leaks

Core (@objectql/core)

  • Auto-discovery: ObjectQLPlugin now detects drivers from kernel when datasources config is omitted
  • Consistent naming: Auto-detected drivers use default, driver_1, driver_2 pattern

Protocol Plugins

No changes required - all three protocol plugins (graphql, odata-v4, json-rpc) remain compatible.

Usage

Before:

const kernel = new ObjectStackKernel([
  new GraphQLPlugin({ port: 4000 })
]);

kernel.metadata.register('object', {
  id: 'users',
  content: { name: 'users', fields: {...} }
});

After:

const kernel = new ObjectStackKernel([
  { name: 'my-app', objects: { users: {...} } },  // App manifest
  new MemoryDriver(),                              // Driver
  new ObjectQLPlugin(),                            // Auto-detects driver
  new GraphQLPlugin({ port: 4000 })               // Protocol
]);

Backward compatibility maintained - existing code continues to work.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • protocol.objectstack.ai
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

按照新的协议规范要求,修改runtime和所有插件 https://protocol.objectstack.ai/docs/developers/micro-kernel


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@vercel
Copy link

vercel bot commented Jan 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
objectql Ready Ready Preview, Comment Jan 28, 2026 11:51am

Request Review

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@github-actions
Copy link
Contributor

⚠️ No Changeset Found

This PR does not include a changeset file.
If this PR includes user-facing changes, please add a changeset by running:

pnpm changeset

@github-actions
Copy link
Contributor

⚠️ No Changeset Found

This PR does not include a changeset file.
If this PR includes user-facing changes, please add a changeset by running:

pnpm changeset

@hotlong hotlong marked this pull request as ready for review January 28, 2026 11:49
Copilot AI review requested due to automatic review settings January 28, 2026 11:49
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Update runtime and all plugins to meet new protocol specifications Implement micro-kernel protocol: unified component loading Jan 28, 2026
Copilot AI requested a review from hotlong January 28, 2026 11:54
@github-actions
Copy link
Contributor

⚠️ No Changeset Found

This PR does not include a changeset file.
If this PR includes user-facing changes, please add a changeset by running:

pnpm changeset

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the micro-kernel architecture pattern for ObjectStack, enabling a more declarative approach to component loading. The kernel now accepts a heterogeneous array of application configs, drivers, and plugins, automatically classifying them and initializing them in a phased sequence.

Changes:

  • Added RuntimeDriver and RuntimeAppConfig interfaces to support the micro-kernel pattern
  • Updated ObjectStackKernel to accept mixed component arrays with automatic type detection and phased initialization (apps → drivers → plugins)
  • Enhanced ObjectQLPlugin to auto-detect drivers from the kernel when datasources are not explicitly provided
  • Updated documentation with comprehensive micro-kernel architecture guide and usage examples

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
pnpm-lock.yaml Added tsx dependency for the multi-protocol-server example
packages/objectstack/runtime/src/index.ts Core micro-kernel implementation: added component interfaces, type guards, classification logic, phased initialization, and driver management methods
packages/objectstack/runtime/README.md Updated documentation to describe micro-kernel pattern, component types, initialization phases, and migration guide (contains InMemoryDriver vs MemoryDriver naming error)
packages/foundation/core/src/plugin.ts Added auto-detection of drivers from kernel in ObjectQLPlugin when datasources not provided
examples/protocols/multi-protocol-server/src/index.ts Updated example to demonstrate new micro-kernel pattern with heterogeneous component array
examples/protocols/multi-protocol-server/package.json Reordered dependencies alphabetically and updated tsx version
MICRO_KERNEL_ARCHITECTURE.md Comprehensive new documentation covering architecture principles, component types, initialization sequence, usage patterns, and migration guide
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment on lines +48 to +57
export interface RuntimeDriver {
/** Driver name/identifier */
name?: string;
/** Connect to the database */
connect?: () => Promise<void>;
/** Disconnect from the database */
disconnect?: () => Promise<void>;
/** Any additional driver methods */
[key: string]: any;
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RuntimeDriver interface uses [key: string]: any as an index signature, which defeats TypeScript's type safety and violates the strict typing principle in the custom coding guidelines (CodingGuidelineID: 1000000). According to the guidelines, "NO any" should be used - instead use Generics or unknown with guards.

Consider either:

  1. Defining the expected driver methods explicitly (find, create, update, delete, findOne, etc.)
  2. Using a more specific type like [key: string]: unknown with proper type guards
  3. Making this a discriminated union if different driver types have different method signatures

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +163 to +202
private isRuntimePlugin(component: any): component is RuntimePlugin {
return (
typeof component === 'object' &&
component !== null &&
typeof component.name === 'string' &&
(typeof component.install === 'function' ||
typeof component.onStart === 'function' ||
typeof component.onStop === 'function')
);
}

/**
* Type guard for RuntimeDriver
* @private
*/
private isRuntimeDriver(component: any): component is RuntimeDriver {
return (
typeof component === 'object' &&
component !== null &&
(typeof component.connect === 'function' ||
typeof component.find === 'function' ||
typeof component.create === 'function' ||
typeof component.update === 'function' ||
typeof component.delete === 'function')
);
}

/**
* Type guard for RuntimeAppConfig
* @private
*/
private isRuntimeAppConfig(component: any): component is RuntimeAppConfig {
return (
typeof component === 'object' &&
component !== null &&
typeof component.name === 'string' &&
// App configs typically have 'objects' or 'label' fields
(component.objects !== undefined || component.label !== undefined) &&
// Must NOT be a plugin (no lifecycle methods)
typeof component.install !== 'function' &&
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type guards use any as the parameter type, which violates the strict typing guidelines (CodingGuidelineID: 1000000). According to the guidelines, "NO any" - use unknown instead.

Consider changing all type guard signatures from (component: any) to (component: unknown) for better type safety.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +117 to +120
drivers.forEach((driver: any, index: number) => {
// Use driver name if available, otherwise use 'default' for first driver
const driverName = driver.name || (index === 0 ? 'default' : `driver_${index + 1}`);
datasources![driverName] = driver;
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The driver auto-detection logic uses any type for the driver when iterating: drivers.forEach((driver: any, index: number). This violates the strict typing guidelines (CodingGuidelineID: 1000000) which state "NO any".

The type should be RuntimeDriver (which is already imported via the kernel's type) or use a more specific Driver type from @objectql/types. Consider: drivers.forEach((driver: RuntimeDriver, index: number) or better yet, use the Driver type from @objectql/types for consistency.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +114 to +121
const drivers = kernel.getAllDrivers?.();
if (drivers && drivers.length > 0) {
datasources = {};
drivers.forEach((driver: any, index: number) => {
// Use driver name if available, otherwise use 'default' for first driver
const driverName = driver.name || (index === 0 ? 'default' : `driver_${index + 1}`);
datasources![driverName] = driver;
});
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code uses optional chaining kernel.getAllDrivers?.() to safely call the method, but this creates a type safety issue. The ExtendedKernel interface doesn't include getAllDrivers, so TypeScript won't catch if this method is removed or renamed from ObjectStackKernel.

Since getAllDrivers() is a new public method added to ObjectStackKernel in this PR, the optional chaining suggests uncertainty about its existence. Either:

  1. The method should always exist (remove optional chaining)
  2. Or proper type checking should be done before calling it

Consider removing the optional chaining since getAllDrivers() is now part of the ObjectStackKernel API.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +52
import { InMemoryDriver } from '@objectql/driver-memory';
import { GraphQLPlugin } from '@objectql/protocol-graphql';
import { ObjectQLPlugin } from '@objectql/core';

// Define application manifest
const myApp = {
name: 'my-app',
label: 'My Application',
objects: {
users: {
name: 'users',
label: 'Users',
fields: {
name: { type: 'text', label: 'Name' },
email: { type: 'email', label: 'Email' }
}
}
}
};

// Create kernel with all components
const kernel = new ObjectStackKernel([
myApp, // Application config
new InMemoryDriver(), // Driver
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README uses the incorrect class name InMemoryDriver when importing from '@objectql/driver-memory'. The actual exported class is MemoryDriver (as shown in the example at examples/protocols/multi-protocol-server/src/index.ts and the driver's source code).

The import should be:

import { MemoryDriver } from '@objectql/driver-memory';

And the usage should be:

new MemoryDriver(),  // Not InMemoryDriver()

Copilot uses AI. Check for mistakes.
Comment on lines +110 to +123
// Get datasources - either from config or from kernel drivers
let datasources = this.config.datasources;
if (!datasources) {
// Try to get drivers from kernel (micro-kernel pattern)
const drivers = kernel.getAllDrivers?.();
if (drivers && drivers.length > 0) {
datasources = {};
drivers.forEach((driver: any, index: number) => {
// Use driver name if available, otherwise use 'default' for first driver
const driverName = driver.name || (index === 0 ? 'default' : `driver_${index + 1}`);
datasources![driverName] = driver;
});
console.log(`[${this.name}] Using drivers from kernel:`, Object.keys(datasources));
} else {
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auto-detection logic for drivers in ObjectQLPlugin lacks test coverage. The repository uses comprehensive automated testing (as evidenced by packages/objectstack/runtime/src/protocol.test.ts), so this new feature should also have tests.

Consider adding tests for:

  1. Auto-detection when datasources is not provided
  2. Proper fallback when no drivers are available from kernel
  3. Driver naming logic (default for first driver, driver_N for others)
  4. Integration with manually provided datasources

Copilot uses AI. Check for mistakes.
Comment on lines +205 to +248
// Must NOT be a driver (no driver methods)
typeof component.connect !== 'function' &&
typeof component.find !== 'function' &&
typeof component.create !== 'function'
);
}

/** Start the kernel */
async start(): Promise<void> {
console.log('[ObjectStackKernel] Starting kernel...');

// Start all plugins
for (const plugin of this.plugins) {
if (plugin.onStart) {
await plugin.onStart({ engine: this });
try {
// Phase 1: Load application manifests
for (const app of this.applications) {
console.log(`[ObjectStackKernel] Loading application: ${app.name}`);
if (app.objects) {
for (const [objName, objConfig] of Object.entries(app.objects)) {
this.metadata.register('object', {
id: objName,
content: objConfig,
packageName: app.name
} as any);
}
}
}

// Phase 2: Connect drivers
for (const driver of this.drivers) {
if (driver.connect) {
console.log(`[ObjectStackKernel] Connecting driver: ${driver.name || 'unnamed'}`);
await driver.connect();
}
}

// Phase 3: Install all plugins
for (const plugin of this.plugins) {
if (plugin.install) {
console.log(`[ObjectStackKernel] Installing plugin: ${plugin.name}`);
await plugin.install({ engine: this });
}
}

// Phase 4: Start all plugins
for (const plugin of this.plugins) {
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phase initialization in start() lacks error handling. If any phase fails (e.g., driver connection fails, plugin installation throws), the kernel will be left in an inconsistent state. According to the custom coding guidelines (CodingGuidelineID: 1000000), errors should be thrown as ObjectQLError instead of regular Error, and proper error handling should be in place.

Consider wrapping each phase in try-catch blocks and either:

  1. Roll back previous phases on failure (disconnect already-connected drivers, etc.)
  2. Throw a descriptive ObjectQLError indicating which phase failed
  3. Provide a cleanup mechanism to ensure the kernel can be stopped even if start() fails midway

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +251 to 272
await plugin.onStart({ engine: this });
}
}

console.log('[ObjectStackKernel] Kernel started successfully');
} catch (error) {
console.error('[ObjectStackKernel] Error during kernel startup:', error);
// Attempt to stop any partially initialized components
await this.stop().catch(stopError => {
console.error('[ObjectStackKernel] Error during cleanup after failed startup:', stopError);
});
throw error;
}
}

/** Stop the kernel */
async stop(): Promise<void> {
console.log('[ObjectStackKernel] Stopping kernel...');

const errors: Error[] = [];

// Stop all plugins in reverse order
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stop() method lacks error handling when disconnecting drivers or stopping plugins. If a plugin's onStop throws an error, it will prevent subsequent plugins from being stopped and drivers from being disconnected, potentially leaving resources in a leaked state.

Consider wrapping each cleanup operation in a try-catch block to ensure all components are given a chance to clean up, even if some fail. Collect any errors and optionally throw a composite error at the end if any occurred.

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +74
export interface RuntimeAppConfig {
/** Unique application name */
name: string;
/** Application label */
label?: string;
/** Application description */
description?: string;
/** Objects to register */
objects?: Record<string, any>;
/** Custom metadata */
[key: string]: any;
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RuntimeAppConfig interface uses [key: string]: any as an index signature, which violates the strict typing principle in the custom coding guidelines (CodingGuidelineID: 1000000). According to the guidelines, "NO any" should be used.

Consider changing the index signature to [key: string]: unknown for better type safety while still allowing custom metadata fields.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +201 to +208
// Must NOT be a plugin (no lifecycle methods)
typeof component.install !== 'function' &&
typeof component.onStart !== 'function' &&
typeof component.onStop !== 'function' &&
// Must NOT be a driver (no driver methods)
typeof component.connect !== 'function' &&
typeof component.find !== 'function' &&
typeof component.create !== 'function'
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type guard isRuntimeAppConfig has a potential issue with circular logic. It calls isRuntimePlugin and isRuntimeDriver to exclude those types, but those guards check for the presence of specific methods. An object with a name property that has both plugin methods AND driver methods would be classified as a plugin (checked first), but if you later add an object with name and no plugin/driver methods, it would be classified as RuntimeAppConfig even if it's not actually an app config.

Consider adding a more explicit check for RuntimeAppConfig, such as verifying the presence of expected properties like objects or checking that it's a plain object without callable methods (except potentially custom metadata).

Suggested change
// Must NOT be a plugin (no lifecycle methods)
typeof component.install !== 'function' &&
typeof component.onStart !== 'function' &&
typeof component.onStop !== 'function' &&
// Must NOT be a driver (no driver methods)
typeof component.connect !== 'function' &&
typeof component.find !== 'function' &&
typeof component.create !== 'function'
// Must NOT be a plugin
!this.isRuntimePlugin(component) &&
// Must NOT be a driver
!this.isRuntimeDriver(component)

Copilot uses AI. Check for mistakes.
@hotlong hotlong merged commit 2ca1c0b into main Jan 28, 2026
14 checks passed
@hotlong hotlong deleted the copilot/update-runtime-and-plugins branch January 28, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants