Skip to content

Commit 8901d29

Browse files
Claudehotlong
andauthored
feat(metadata): implement dual-table projection logic for Phase 1
- Create MetadataProjector service for sys_metadata → type-specific table projections - Add projection functions for object, view, agent, tool, flow metadata types - Integrate projection into DatabaseLoader save() and delete() operations - Register system objects from @objectstack/objectos in MetadataPlugin - Add enableProjection config option (default: true) - Projection enables Studio to query metadata via Object Protocol API Phase 1 implementation complete. Next: Studio integration (Phase 2) Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/e95ef127-f74b-4ddd-8a6e-fe86e33fe1b0 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 5afaf72 commit 8901d29

8 files changed

Lines changed: 575 additions & 5 deletions

File tree

OBJECTOS_IMPLEMENTATION.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,17 @@ packages/objectos/
106106

107107
## Next Steps
108108

109-
### Phase 1: Integration (Immediate)
110-
- [ ] Update `packages/metadata` service to support projection
111-
- [ ] Implement dual-table sync logic
112-
- [ ] Register system objects in runtime bootstrap
109+
### Phase 1: Integration (Immediate) ✅ COMPLETED
110+
- [x] Update `packages/metadata` service to support projection
111+
- [x] Implement dual-table sync logic
112+
- [x] Register system objects in runtime bootstrap
113+
114+
**Implementation Details:**
115+
- Created `MetadataProjector` service in `packages/metadata/src/projection/`
116+
- Integrated projection into `DatabaseLoader.save()` and `DatabaseLoader.delete()`
117+
- Added projection functions for each metadata type: object, view, agent, tool, flow
118+
- Updated `MetadataPlugin` to register all system objects from `@objectstack/objectos`
119+
- Projection is enabled by default, can be disabled via `enableProjection: false` option
113120

114121
### Phase 2: Studio Integration (Next)
115122
- [ ] Update Studio to query type-specific tables

PHASE_1_IMPLEMENTATION.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Phase 1 Implementation Summary: Dual-Table Metadata Projection
2+
3+
## Overview
4+
5+
Successfully implemented the dual-table projection logic for the metadata service. This establishes the architectural foundation for treating metadata as queryable data following industry best practices from Salesforce, ServiceNow, and Kubernetes.
6+
7+
## Architecture
8+
9+
The dual-table pattern maintains two layers:
10+
11+
1. **sys_metadata** (Source of Truth)
12+
- Package management
13+
- Version control
14+
- Deployment tracking
15+
- History via sys_metadata_history
16+
17+
2. **Type-Specific Tables** (Queryable Projections)
18+
- sys_object — Object definitions
19+
- sys_view — View configurations
20+
- sys_agent — AI agent metadata
21+
- sys_tool — AI tool registry
22+
- sys_flow — Automation flows
23+
24+
## Implementation Details
25+
26+
### 1. MetadataProjector Service
27+
28+
Created `packages/metadata/src/projection/metadata-projector.ts` with:
29+
30+
- **Project transformation functions** for each metadata type
31+
- **Denormalization logic** to flatten complex structures for querying
32+
- **Support for both IDataDriver and IDataEngine** (ObjectQL)
33+
- **Automatic CRUD operations** on projection tables
34+
35+
Key methods:
36+
- `project(type, name, data)` — Create/update projection
37+
- `deleteProjection(type, name)` — Remove projection
38+
- `transformToProjection(type, name, data)` — Type-specific transformation
39+
40+
### 2. DatabaseLoader Integration
41+
42+
Updated `packages/metadata/src/loaders/database-loader.ts`:
43+
44+
- Added `enableProjection` configuration option (default: true)
45+
- Integrated `MetadataProjector` into save() flow
46+
- Added projection cleanup in delete() flow
47+
- Projection occurs AFTER sys_metadata save (async safety)
48+
49+
### 3. System Object Registration
50+
51+
Updated `packages/metadata/src/plugin.ts`:
52+
53+
- Added dependency on `@objectstack/objectos`
54+
- Registered all system objects from SystemObjects registry
55+
- Objects registered via manifest service during plugin init()
56+
- Includes: sys_object, sys_view, sys_agent, sys_tool, sys_flow
57+
58+
## Projection Mapping
59+
60+
Each metadata type is projected with denormalized fields for efficient querying:
61+
62+
### Object Projection (object → sys_object)
63+
- Complex structures (fields, indexes, validations) → JSON columns
64+
- Capabilities → individual boolean columns for filtering
65+
- Denormalized field_count for sorting/filtering
66+
67+
### View Projection (view → sys_view)
68+
- Columns, filters, sort, config → JSON columns
69+
- Display options → individual columns
70+
- Object reference for joins
71+
72+
### Agent Projection (agent → sys_agent)
73+
- Model configuration → individual columns
74+
- Tools, skills → JSON columns
75+
- Memory settings → individual columns
76+
77+
### Tool Projection (tool → sys_tool)
78+
- Parameters schema → JSON column
79+
- Handler code → text column
80+
81+
### Flow Projection (flow → sys_flow)
82+
- Nodes, edges, variables → JSON columns
83+
- Trigger configuration → individual columns
84+
- Active status for filtering
85+
86+
## Benefits Achieved
87+
88+
1. **Unified Query Protocol**: Metadata can be queried using Object Protocol API
89+
2. **Studio Auto-Generation**: UI can use `/api/v1/data/sys_*` endpoints
90+
3. **Efficient Filtering**: Denormalized fields enable fast queries
91+
4. **Preserved History**: sys_metadata maintains full version control
92+
5. **Package Tracking**: All projections include package_id and managed_by
93+
94+
## Files Changed
95+
96+
### New Files
97+
- `packages/metadata/src/projection/metadata-projector.ts` — Projection service
98+
- `packages/metadata/src/projection/index.ts` — Module exports
99+
100+
### Modified Files
101+
- `packages/metadata/src/loaders/database-loader.ts` — Added projection integration
102+
- `packages/metadata/src/plugin.ts` — Registered system objects
103+
- `packages/metadata/src/index.ts` — Exported projection module
104+
- `packages/metadata/package.json` — Added @objectstack/objectos dependency
105+
- `OBJECTOS_IMPLEMENTATION.md` — Updated Phase 1 status
106+
107+
## Usage Example
108+
109+
```typescript
110+
// Projection happens automatically when saving metadata
111+
await metadataService.register('object', 'account', {
112+
name: 'account',
113+
label: 'Account',
114+
fields: { /* ... */ },
115+
// ... object definition
116+
});
117+
118+
// Results in TWO database writes:
119+
// 1. sys_metadata: Full envelope with JSON payload
120+
// 2. sys_object: Denormalized projection with queryable columns
121+
122+
// Studio can now query via Object Protocol
123+
const objects = await client.data.find('sys_object', {
124+
filter: { namespace: 'crm' },
125+
sort: 'name',
126+
});
127+
```
128+
129+
## Next Steps
130+
131+
Phase 2 will focus on Studio integration:
132+
- Update Studio to use type-specific table queries
133+
- Auto-generate metadata management UI
134+
- Leverage existing grid/form/kanban components
135+
136+
Phase 3 will add:
137+
- Comprehensive test coverage
138+
- Documentation updates
139+
- Migration guides for existing deployments

packages/metadata/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
],
4040
"dependencies": {
4141
"@objectstack/core": "workspace:*",
42+
"@objectstack/objectos": "workspace:*",
4243
"@objectstack/spec": "workspace:*",
4344
"@objectstack/types": "workspace:*",
4445
"chokidar": "^5.0.0",

packages/metadata/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ export { MetadataManager, type WatchCallback, type MetadataManagerOptions } from
1313
// Plugin
1414
export { MetadataPlugin } from './plugin.js';
1515

16+
// Projection
17+
export { MetadataProjector, type MetadataProjectorOptions } from './projection/index.js';
18+
1619
// Loaders
1720
export { type MetadataLoader } from './loaders/loader-interface.js';
1821
export { MemoryLoader } from './loaders/memory-loader.js';

packages/metadata/src/loaders/database-loader.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { SysMetadataHistoryObject } from '../objects/sys-metadata-history.object
2424
import type { IDataDriver, IDataEngine } from '@objectstack/spec/contracts';
2525
import type { MetadataLoader } from './loader-interface.js';
2626
import { calculateChecksum } from '../utils/metadata-history-utils.js';
27+
import { MetadataProjector } from '../projection/metadata-projector.js';
2728

2829
/**
2930
* Configuration for the DatabaseLoader.
@@ -54,6 +55,9 @@ export interface DatabaseLoaderOptions {
5455

5556
/** Enable history tracking (default: true) */
5657
trackHistory?: boolean;
58+
59+
/** Enable metadata projection to type-specific tables (default: true) */
60+
enableProjection?: boolean;
5761
}
5862

5963
/**
@@ -84,6 +88,8 @@ export class DatabaseLoader implements MetadataLoader {
8488
private trackHistory: boolean;
8589
private schemaReady = false;
8690
private historySchemaReady = false;
91+
private enableProjection: boolean;
92+
private projector?: MetadataProjector;
8793

8894
constructor(options: DatabaseLoaderOptions) {
8995
if (!options.driver && !options.engine) {
@@ -96,6 +102,17 @@ export class DatabaseLoader implements MetadataLoader {
96102
this.organizationId = options.organizationId;
97103
this.environmentId = options.environmentId;
98104
this.trackHistory = options.trackHistory !== false; // Default to true
105+
this.enableProjection = options.enableProjection !== false; // Default to true
106+
107+
// Initialize projector if projection is enabled
108+
if (this.enableProjection) {
109+
this.projector = new MetadataProjector({
110+
driver: this.driver,
111+
engine: this.engine,
112+
organizationId: this.organizationId,
113+
environmentId: this.environmentId,
114+
});
115+
}
99116
}
100117

101118
// ==========================================
@@ -709,6 +726,11 @@ export class DatabaseLoader implements MetadataLoader {
709726
previousChecksum
710727
);
711728

729+
// Project to type-specific table
730+
if (this.projector) {
731+
await this.projector.project(type, name, data);
732+
}
733+
712734
return {
713735
success: true,
714736
path: `datasource://${this.tableName}/${type}/${name}`,
@@ -746,6 +768,11 @@ export class DatabaseLoader implements MetadataLoader {
746768
'create'
747769
);
748770

771+
// Project to type-specific table
772+
if (this.projector) {
773+
await this.projector.project(type, name, data);
774+
}
775+
749776
return {
750777
success: true,
751778
path: `datasource://${this.tableName}/${type}/${name}`,
@@ -780,6 +807,11 @@ export class DatabaseLoader implements MetadataLoader {
780807

781808
// Delete from the main metadata table using the record's ID
782809
await this._delete(this.tableName, existing.id as string);
810+
811+
// Delete projection from type-specific table
812+
if (this.projector) {
813+
await this.projector.deleteProjection(type, name);
814+
}
783815
}
784816
}
785817

packages/metadata/src/plugin.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel';
66
import type { MetadataPluginConfig } from '@objectstack/spec/kernel';
77
import { SysMetadataObject } from './objects/sys-metadata.object.js';
88
import { SysMetadataHistoryObject } from './objects/sys-metadata-history.object.js';
9+
import { SystemObjects } from '@objectstack/objectos';
910

1011
export interface MetadataPluginOptions {
1112
rootDir?: string;
@@ -56,7 +57,10 @@ export class MetadataPlugin implements Plugin {
5657
// Register metadata system objects via the manifest service (if available).
5758
// MetadataPlugin may init before ObjectQLPlugin, so wrap in try/catch.
5859
try {
59-
ctx.getService<{ register(m: any): void }>('manifest').register({
60+
const manifestService = ctx.getService<{ register(m: any): void }>('manifest');
61+
62+
// Register the metadata envelope tables
63+
manifestService.register({
6064
id: 'com.objectstack.metadata',
6165
name: 'Metadata',
6266
version: '1.0.0',
@@ -65,6 +69,22 @@ export class MetadataPlugin implements Plugin {
6569
namespace: 'sys',
6670
objects: [SysMetadataObject, SysMetadataHistoryObject],
6771
});
72+
73+
// Register the queryable system objects from @objectstack/objectos
74+
manifestService.register({
75+
id: 'com.objectstack.objectos',
76+
name: 'ObjectOS System Objects',
77+
version: '1.0.0',
78+
type: 'plugin',
79+
scope: 'platform',
80+
namespace: 'sys',
81+
objects: Object.values(SystemObjects),
82+
});
83+
84+
ctx.logger.info('Registered system metadata objects', {
85+
metadata: ['sys_metadata', 'sys_metadata_history'],
86+
objectos: Object.keys(SystemObjects),
87+
});
6888
} catch {
6989
// ObjectQL not loaded yet — objects will be discovered via legacy fallback
7090
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* Metadata Projection
5+
*
6+
* Dual-table architecture: sys_metadata → type-specific tables
7+
*/
8+
9+
export { MetadataProjector, type MetadataProjectorOptions } from './metadata-projector.js';

0 commit comments

Comments
 (0)