Skip to content

feat(objectql): auto-sync registered object schemas to database on startup#942

Merged
hotlong merged 3 commits into
mainfrom
copilot/fix-schema-sync-issues
Mar 21, 2026
Merged

feat(objectql): auto-sync registered object schemas to database on startup#942
hotlong merged 3 commits into
mainfrom
copilot/fix-schema-sync-issues

Conversation

Copilot AI commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

ObjectQLPlugin.start() connects drivers via init() but never syncs registered object schemas to the database. Plugin-registered objects (e.g. sys_user from plugin-auth) have metadata loaded but no backing tables — first access throws SQLITE_ERROR: no such table.

Changes

  • packages/objectql/src/plugin.ts — Added syncRegisteredSchemas() after init() in start(). Iterates SchemaRegistry.getAllObjects(), resolves each object's driver, calls driver.syncSchema(name, schema) per-object. Tolerates missing drivers, unsupported syncSchema, and per-object failures with warn-level logging.

  • packages/objectql/src/engine.ts — Added public getDriverForObject(objectName) that wraps the private getDriver() with error tolerance (returns undefined instead of throwing).

  • packages/spec/src/contracts/data-engine.ts — Added optional syncSchema? to DriverInterface, aligning it with the already-declared IDataDriver protocol and Zod schema.

  • packages/objectql/src/plugin.integration.test.ts — 4 new tests: normal sync, driver without syncSchema, partial failure resilience, empty registry.

Architecture

Sync orchestration lives in the Plugin layer (not ObjectQL.init()), consistent with the boundary: Plugin orchestrates, Engine handles registry + connect.

// ObjectQLPlugin.start() — after drivers are connected
await this.ql?.init();
await this.syncRegisteredSchemas(ctx); // NEW: per-object idempotent sync
Original prompt

This section details on the original issue you should resolve

<issue_title>[ObjectQL] 未自动同步所有注册对象 schema 至数据库(缺失 syncSchema 调用)</issue_title>
<issue_description>## 问题描述
ObjectQL 引擎当前的 init() 方法只负责调用所有数据驱动(driver)的 connect(),但不会自动同步步骤中注册到 SchemaRegistry 的对象定义(object schema)到实际数据库表结构。这导致 app 插件(如 plugin-auth、plugin-audit 等)注册的对象(如 sys_user 等)虽然元数据已加载,但数据表可能未被自动创建��更新——首次访问时会出现 SQLITE_ERROR: no such table: sys_user 等报错。

影响范围

  • 所有通过 registerService('app.<id>', manifest) 注册的对象无法保证表结构自动同步到数据库
  • plugin-auth、plugin-audit 等系统插件下的用户/审计等核心表不会自动初始化
  • 使用 SQLite/Turso 等"需自动建表"的驱动时表现尤为明显

期望行为

  • ObjectQL 应在启动流程中,自动遍历所有注册对象(SchemaRegistry.getAllObjects())并调用各 driver 的 syncSchema(object, schema) 方法完成表结构同步
  • 同步操作需保证幂等(多次 sync 不会重复建表或报错,兼容字段/索引新增)
  • 该逻辑应在 Plugin 层(即 ObjectQLPlugin.start() 的 init() 之后)实现,逐对象调用 syncSchema,而不是批量传递对象数组(符合协议定义

推荐修复方案

// packages/objectql/src/plugin.ts
// ObjectQLPlugin.start() 中 await this.ql?.init() 后新增:
const allObjects = this.ql?.registry?.getAllObjects?.() ?? [];
if (allObjects.length > 0) {
  for (const obj of allObjects) {
    const driver = this.ql!.getDriver(obj.name);
    await driver.syncSchema(obj.name, obj); // 符合 syncSchema 单对象签名
  }
}
  • 若 driver 不支持 syncSchema,需有容错日志
  • 不建议将 schema 同步逻辑下沉到 ObjectQL.init(),应保持边界分明(Plugin/Kernel 层负责 orchestrate,Engine 层只负责 registry+connect)

协议兼容性检查

  • syncSchema(idempotent) 方法已在 data-driver.ts 协议定义 和 Zod schema 中声明
  • 多驱动、插件动态注册均适配(按需调用 getDriver)
  • 完全符合"Data as Code/幂等基建"理念

相关链接


注:本 Issue 属于 runtime/实现补丁,不涉及协议定义变更。
</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@vercel

vercel Bot commented Mar 21, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
objectstack-play Ready Ready Preview, Comment Mar 21, 2026 6:32am
spec Ready Ready Preview, Comment Mar 21, 2026 6:32am

Request Review

…artup

ObjectQLPlugin.start() now iterates all objects in SchemaRegistry after
driver.connect() and calls driver.syncSchema() for each one. This ensures
plugin-registered objects (e.g. sys_user from plugin-auth) have their
database tables created/updated automatically.

- Add syncRegisteredSchemas() to ObjectQLPlugin with per-object error tolerance
- Add getDriverForObject() public method to ObjectQL engine
- Add optional syncSchema to DriverInterface (aligns with IDataDriver)
- Add integration tests for sync behavior, error tolerance, and edge cases

Closes #940

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Agent-Logs-Url: https://github.com/objectstack-ai/spec/sessions/a6a76ea8-5b90-40cb-88c8-26a9cb27cc6b
Copilot AI changed the title [WIP] Fix automatic schema synchronization for registered objects feat(objectql): auto-sync registered object schemas to database on startup Mar 21, 2026
Copilot AI requested a review from hotlong March 21, 2026 06:19
@hotlong
hotlong marked this pull request as ready for review March 21, 2026 06:23
Copilot AI review requested due to automatic review settings March 21, 2026 06:23
@hotlong
hotlong merged commit 59858ea into main Mar 21, 2026
3 of 5 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 ensures ObjectQL plugin-registered object schemas are automatically synchronized to the backing database during startup, preventing first-access failures due to missing tables/collections.

Changes:

  • Invoke schema synchronization after ObjectQLPlugin.start() finishes driver initialization.
  • Add ObjectQL.getDriverForObject() as a tolerant driver resolver for per-object sync orchestration.
  • Extend the DriverInterface contract with optional syncSchema?, and add integration tests covering sync behavior and resilience.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/objectql/src/plugin.ts Calls new per-object schema sync after init(); adds syncRegisteredSchemas() implementation.
packages/objectql/src/engine.ts Adds getDriverForObject() to resolve the driver responsible for an object with error tolerance.
packages/spec/src/contracts/data-engine.ts Adds optional syncSchema? to DriverInterface to reflect schema sync capability.
packages/objectql/src/plugin.integration.test.ts Adds/extends integration tests for startup schema sync behavior and resilience; resets static registry per test.
packages/objectql/CHANGELOG.md Documents the new startup schema sync behavior and related API/contract additions.

getDriverForObject(objectName: string): DriverInterface | undefined {
try {
return this.getDriver(objectName);
} catch {

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

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

getDriverForObject() catches and suppresses all errors from getDriver() without logging. This makes datasource misconfiguration (e.g., object bound to an unregistered datasource) hard to debug because callers only see undefined. Consider catching the error, logging it at debug/warn with objectName + message, and then returning undefined for tolerance.

Suggested change
} catch {
} catch (error) {
this.logger?.warn?.(
`[ObjectQL] getDriverForObject: failed to resolve driver for object '${objectName}': ${
error instanceof Error ? error.message : String(error)
}`,
);

Copilot uses AI. Check for mistakes.

### Patch Changes

- Auto-sync all registered object schemas to database on startup: `ObjectQLPlugin.start()` now iterates every object in `SchemaRegistry` and calls `driver.syncSchema()` after driver connections are established. This ensures tables for plugin-registered objects (e.g. `sys_user` from plugin-auth) are created or updated automatically.

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

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

Changelog entry references sys_user, but system plugin objects in the sys namespace are registered as sys__user (FQN with double underscore). Updating the example name here would better match what users will actually see in logs/DB identifiers.

Suggested change
- Auto-sync all registered object schemas to database on startup: `ObjectQLPlugin.start()` now iterates every object in `SchemaRegistry` and calls `driver.syncSchema()` after driver connections are established. This ensures tables for plugin-registered objects (e.g. `sys_user` from plugin-auth) are created or updated automatically.
- Auto-sync all registered object schemas to database on startup: `ObjectQLPlugin.start()` now iterates every object in `SchemaRegistry` and calls `driver.syncSchema()` after driver connections are established. This ensures tables for plugin-registered objects (e.g. `sys__user` from plugin-auth) are created or updated automatically.

Copilot uses AI. Check for mistakes.

// Sync all registered object schemas to database
// This ensures tables/collections are created or updated for every
// object registered by plugins (e.g., sys_user from plugin-auth).

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

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

The example object name in this comment uses sys_user, but objects in the sys namespace are registered as FQNs using the sys__<name> pattern (double underscore). Using sys__user here would avoid confusion when troubleshooting schema sync/table names.

Suggested change
// object registered by plugins (e.g., sys_user from plugin-auth).
// object registered by plugins (e.g., sys__user from plugin-auth).

Copilot uses AI. Check for mistakes.
Comment on lines +289 to +291
if (synced > 0 || skipped > 0) {
ctx.logger.info('Schema sync complete', { synced, skipped, total: allObjects.length });
}

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

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

syncRegisteredSchemas() only emits the final "Schema sync complete" info log when synced > 0 || skipped > 0. If all objects have a driver+syncSchema but every call throws, this produces no summary log even though work was attempted. Consider tracking a failed counter and always logging a summary (or at least logging when failed > 0) so operators can tell startup schema sync ran but encountered errors.

Copilot uses AI. Check for mistakes.
Comment on lines +281 to +285
ctx.logger.warn('Failed to sync schema for object', {
object: obj.name,
driver: driver.name,
error: e instanceof Error ? e.message : String(e),
});

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

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

When a schema sync fails, the warn log only captures e.message (or String(e)), which drops stack traces for Error instances and makes diagnosis harder. Consider including stack (when available) in the metadata or logging the full error details in a structured way.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ObjectQL] 未自动同步所有注册对象 schema 至数据库(缺失 syncSchema 调用)

3 participants