-
Notifications
You must be signed in to change notification settings - Fork 1
fix: restore persisted metadata from sys_metadata on ObjectQLPlugin cold start #1076
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,25 @@ import { Plugin, PluginContext } from '@objectstack/core'; | |
|
|
||
| export type { Plugin, PluginContext }; | ||
|
|
||
| /** | ||
| * Protocol extension for DB-based metadata hydration. | ||
| * `loadMetaFromDb` is implemented by ObjectStackProtocolImplementation but | ||
| * is NOT (yet) part of the canonical ObjectStackProtocol wire-contract in | ||
| * `@objectstack/spec`, since it is a server-side bootstrap concern only. | ||
| */ | ||
| interface ProtocolWithDbRestore { | ||
| loadMetaFromDb(): Promise<{ loaded: number; errors: number }>; | ||
| } | ||
|
|
||
| /** Type guard — checks whether the service exposes `loadMetaFromDb`. */ | ||
| function hasLoadMetaFromDb(service: unknown): service is ProtocolWithDbRestore { | ||
| return ( | ||
| typeof service === 'object' && | ||
| service !== null && | ||
| typeof (service as Record<string, unknown>)['loadMetaFromDb'] === 'function' | ||
| ); | ||
| } | ||
|
|
||
| export class ObjectQLPlugin implements Plugin { | ||
| name = 'com.objectstack.engine.objectql'; | ||
| type = 'objectql'; | ||
|
|
@@ -99,6 +118,12 @@ export class ObjectQLPlugin implements Plugin { | |
| // Initialize drivers (calls driver.connect() which sets up persistence) | ||
| await this.ql?.init(); | ||
|
|
||
| // Restore persisted metadata from sys_metadata table. | ||
| // This hydrates SchemaRegistry with objects/views/apps that were saved | ||
| // via protocol.saveMetaItem() in a previous session, ensuring custom | ||
| // schemas survive cold starts and redeployments. | ||
| await this.restoreMetadataFromDb(ctx); | ||
|
|
||
| // 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). | ||
|
|
@@ -332,6 +357,53 @@ export class ObjectQLPlugin implements Plugin { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Restore persisted metadata from the database (sys_metadata) on startup. | ||
| * | ||
| * Calls `protocol.loadMetaFromDb()` to bulk-load all active metadata | ||
| * records (objects, views, apps, etc.) into the in-memory SchemaRegistry. | ||
| * This closes the persistence loop so that user-created schemas survive | ||
| * kernel cold starts and redeployments. | ||
| * | ||
| * Gracefully degrades when: | ||
| * - The protocol service is unavailable (e.g., in-memory-only mode). | ||
| * - `loadMetaFromDb` is not implemented by the protocol shim. | ||
| * - The underlying driver/table does not exist yet (first-run scenario). | ||
| */ | ||
| private async restoreMetadataFromDb(ctx: PluginContext): Promise<void> { | ||
| // Phase 1: Resolve protocol service (separate from DB I/O for clearer diagnostics) | ||
| let protocol: ProtocolWithDbRestore; | ||
| try { | ||
| const service = ctx.getService('protocol'); | ||
| if (!service || !hasLoadMetaFromDb(service)) { | ||
| ctx.logger.debug('Protocol service does not support loadMetaFromDb, skipping DB restore'); | ||
| return; | ||
| } | ||
| protocol = service; | ||
| } catch (e: unknown) { | ||
| ctx.logger.debug('Protocol service unavailable, skipping DB restore', { | ||
| error: e instanceof Error ? e.message : String(e), | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| // Phase 2: DB hydration | ||
| try { | ||
| const { loaded, errors } = await protocol.loadMetaFromDb(); | ||
|
|
||
| if (loaded > 0 || errors > 0) { | ||
| ctx.logger.info('Metadata restored from database', { loaded, errors }); | ||
| } else { | ||
| ctx.logger.debug('No persisted metadata found in database'); | ||
| } | ||
| } catch (e: unknown) { | ||
| // Non-fatal: first-run or in-memory driver may not have sys_metadata yet | ||
| ctx.logger.debug('DB metadata restore failed (non-fatal)', { | ||
| error: e instanceof Error ? e.message : String(e), | ||
| }); | ||
| } | ||
|
Comment on lines
+373
to
+404
|
||
| } | ||
|
|
||
| /** | ||
| * Load metadata from external metadata service into ObjectQL registry | ||
| * This enables ObjectQL to use file-based or remote metadata | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
restoreMetadataFromDbrelies onctx.getService('protocol') as anyto accessloadMetaFromDb, butloadMetaFromDbis not part of the typed protocol contract (and theas anydefeats type-safety). Consider introducing a localProtocolWithDbRestoretype + type guard (or extending the protocol contract with an optionalloadMetaFromDb?) so this call is type-checked and discoverable.