@objectstack/runtime 0.1.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.
package/dist/engine.js ADDED
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ObjectQL = void 0;
4
+ const registry_1 = require("./registry");
5
+ /**
6
+ * ObjectQL Engine
7
+ */
8
+ class ObjectQL {
9
+ constructor(hostContext = {}) {
10
+ this.drivers = new Map();
11
+ this.defaultDriver = null;
12
+ this.plugins = new Map();
13
+ // Host provided context additions (e.g. Server router)
14
+ this.hostContext = {};
15
+ this.hostContext = hostContext;
16
+ console.log(`[ObjectQL] Engine Instance Created`);
17
+ }
18
+ /**
19
+ * Load and Register a Plugin
20
+ */
21
+ async use(manifestPart, runtimePart) {
22
+ // 1. Validate / Register Manifest
23
+ if (manifestPart) {
24
+ // 1. Handle Module Imports (commonjs/esm interop)
25
+ // If the passed object is a module namespace with a default export, use that.
26
+ const manifest = manifestPart.default || manifestPart;
27
+ // In a real scenario, we might strictly parse this using Zod
28
+ // For now, simple ID check
29
+ const id = manifest.id || manifest.name;
30
+ if (!id) {
31
+ console.warn(`[ObjectQL] Plugin manifest missing ID (keys: ${Object.keys(manifest)})`, manifest);
32
+ // Don't return, try to proceed if it looks like an App (Apps might use 'name' instead of 'id')
33
+ // return;
34
+ }
35
+ console.log(`[ObjectQL] Loading Plugin: ${id}`);
36
+ registry_1.SchemaRegistry.registerPlugin(manifest);
37
+ // Register Objects from App/Plugin
38
+ if (manifest.objects) {
39
+ for (const obj of manifest.objects) {
40
+ // Ensure object name is registered globally
41
+ registry_1.SchemaRegistry.registerObject(obj);
42
+ console.log(`[ObjectQL] Registered Object: ${obj.name}`);
43
+ }
44
+ }
45
+ // Register contributions
46
+ if (manifest.contributes?.kinds) {
47
+ for (const kind of manifest.contributes.kinds) {
48
+ registry_1.SchemaRegistry.registerKind(kind);
49
+ }
50
+ }
51
+ // Register Data Seeding (Lazy execution or immediate?)
52
+ // We store it in a temporary registry or execute immediately if engine is ready.
53
+ // Since `use` is init time, we might need to store it and run later in `seed()`.
54
+ // For this MVP, let's attach it to the manifest object in registry so DataEngine can find it.
55
+ }
56
+ // 2. Execute Runtime
57
+ if (runtimePart) {
58
+ const pluginDef = runtimePart.default || runtimePart;
59
+ if (pluginDef.onEnable) {
60
+ const context = {
61
+ ql: this,
62
+ logger: console,
63
+ // Expose the driver registry helper explicitly if needed
64
+ drivers: {
65
+ register: (driver) => this.registerDriver(driver)
66
+ },
67
+ ...this.hostContext
68
+ };
69
+ await pluginDef.onEnable(context);
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Register a new storage driver
75
+ */
76
+ registerDriver(driver, isDefault = false) {
77
+ if (this.drivers.has(driver.name)) {
78
+ console.warn(`[ObjectQL] Driver ${driver.name} is already registered. Skipping.`);
79
+ return;
80
+ }
81
+ this.drivers.set(driver.name, driver);
82
+ console.log(`[ObjectQL] Registered driver: ${driver.name} v${driver.version}`);
83
+ if (isDefault || this.drivers.size === 1) {
84
+ this.defaultDriver = driver.name;
85
+ }
86
+ }
87
+ /**
88
+ * Helper to get the target driver
89
+ */
90
+ getDriver(object) {
91
+ // TODO: Look up Object definition to see if it specifies a specific datasource/driver
92
+ // For now, always return default
93
+ if (!this.defaultDriver) {
94
+ throw new Error('[ObjectQL] No drivers registered!');
95
+ }
96
+ return this.drivers.get(this.defaultDriver);
97
+ }
98
+ /**
99
+ * Initialize the engine and all registered drivers
100
+ */
101
+ async init() {
102
+ console.log('[ObjectQL] Initializing drivers...');
103
+ for (const [name, driver] of this.drivers) {
104
+ try {
105
+ await driver.connect();
106
+ }
107
+ catch (e) {
108
+ console.error(`[ObjectQL] Failed to connect driver ${name}`, e);
109
+ }
110
+ }
111
+ // In a real app, we would sync schemas here
112
+ }
113
+ async destroy() {
114
+ for (const driver of this.drivers.values()) {
115
+ await driver.disconnect();
116
+ }
117
+ }
118
+ // ============================================
119
+ // Data Access Methods
120
+ // ============================================
121
+ async find(object, filters = {}, options) {
122
+ const driver = this.getDriver(object);
123
+ console.log(`[ObjectQL] Finding ${object} on ${driver.name}...`);
124
+ // Transform simplified filters to QueryAST
125
+ // This is a simplified "Mock" transform.
126
+ // Real implementation would parse complex JSON or FilterBuilders.
127
+ const ast = {
128
+ object, // Add missing required field
129
+ // Pass through if it looks like AST, otherwise empty
130
+ // In this demo, we assume the caller passes a simplified object or raw AST
131
+ filters: filters.filters || undefined,
132
+ top: filters.top || 100,
133
+ sort: filters.sort || []
134
+ };
135
+ return driver.find(object, ast, options);
136
+ }
137
+ async insert(object, data, options) {
138
+ const driver = this.getDriver(object);
139
+ console.log(`[ObjectQL] Creating ${object} on ${driver.name}...`);
140
+ // 1. Validate Schema
141
+ // 2. Run "Before Insert" Triggers
142
+ const result = await driver.create(object, data, options);
143
+ // 3. Run "After Insert" Triggers
144
+ return result;
145
+ }
146
+ async update(object, id, data, options) {
147
+ const driver = this.getDriver(object);
148
+ console.log(`[ObjectQL] Updating ${object} ${id}...`);
149
+ return driver.update(object, id, data, options);
150
+ }
151
+ async delete(object, id, options) {
152
+ const driver = this.getDriver(object);
153
+ console.log(`[ObjectQL] Deleting ${object} ${id}...`);
154
+ return driver.delete(object, id, options);
155
+ }
156
+ }
157
+ exports.ObjectQL = ObjectQL;
@@ -0,0 +1,5 @@
1
+ export { ObjectQL } from './engine';
2
+ export { SchemaRegistry } from './registry';
3
+ export { DataEngine } from './data-engine';
4
+ export { ObjectStackRuntimeProtocol } from './protocol';
5
+ export type { DriverInterface, DriverOptions, QueryAST } from '@objectstack/spec';
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ObjectStackRuntimeProtocol = exports.DataEngine = exports.SchemaRegistry = exports.ObjectQL = void 0;
4
+ // Export core engine
5
+ var engine_1 = require("./engine");
6
+ Object.defineProperty(exports, "ObjectQL", { enumerable: true, get: function () { return engine_1.ObjectQL; } });
7
+ var registry_1 = require("./registry");
8
+ Object.defineProperty(exports, "SchemaRegistry", { enumerable: true, get: function () { return registry_1.SchemaRegistry; } });
9
+ var data_engine_1 = require("./data-engine");
10
+ Object.defineProperty(exports, "DataEngine", { enumerable: true, get: function () { return data_engine_1.DataEngine; } });
11
+ var protocol_1 = require("./protocol");
12
+ Object.defineProperty(exports, "ObjectStackRuntimeProtocol", { enumerable: true, get: function () { return protocol_1.ObjectStackRuntimeProtocol; } });
@@ -0,0 +1,68 @@
1
+ import { DataEngine } from './data-engine';
2
+ export interface ApiRequest {
3
+ params: Record<string, string>;
4
+ query: Record<string, string | string[]>;
5
+ body?: any;
6
+ }
7
+ export declare class ObjectStackRuntimeProtocol {
8
+ private engine;
9
+ constructor(engine: DataEngine);
10
+ getDiscovery(): {
11
+ name: string;
12
+ version: string;
13
+ environment: string;
14
+ routes: {
15
+ discovery: string;
16
+ metadata: string;
17
+ data: string;
18
+ auth: string;
19
+ ui: string;
20
+ };
21
+ capabilities: {
22
+ search: boolean;
23
+ files: boolean;
24
+ };
25
+ };
26
+ getMetaTypes(): {
27
+ data: {
28
+ type: string;
29
+ href: string;
30
+ count: number;
31
+ }[];
32
+ };
33
+ getMetaItems(typePlural: string): {
34
+ data: {
35
+ self: string;
36
+ path?: string | undefined;
37
+ id: any;
38
+ name: any;
39
+ label: any;
40
+ type: any;
41
+ icon: any;
42
+ description: any;
43
+ }[];
44
+ };
45
+ getMetaItem(typePlural: string, name: string): {};
46
+ getUiView(objectName: string, type: 'list' | 'form'): {
47
+ type: string;
48
+ title: string;
49
+ columns: {
50
+ field: string;
51
+ label: string;
52
+ width: number;
53
+ }[];
54
+ actions: string[];
55
+ };
56
+ findData(objectName: string, query: any): Promise<{
57
+ value: Record<string, any>[];
58
+ count: number;
59
+ }>;
60
+ queryData(objectName: string, body: any): Promise<{
61
+ value: Record<string, any>[];
62
+ count: number;
63
+ }>;
64
+ getData(objectName: string, id: string): Promise<Record<string, any>>;
65
+ createData(objectName: string, body: any): Promise<Record<string, any>>;
66
+ updateData(objectName: string, id: string, body: any): Promise<Record<string, any>>;
67
+ deleteData(objectName: string, id: string): Promise<boolean>;
68
+ }
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ObjectStackRuntimeProtocol = void 0;
4
+ const registry_1 = require("./registry");
5
+ class ObjectStackRuntimeProtocol {
6
+ constructor(engine) {
7
+ this.engine = engine;
8
+ }
9
+ // 1. Discovery
10
+ getDiscovery() {
11
+ return {
12
+ name: 'ObjectOS Server',
13
+ version: '1.0.0',
14
+ environment: process.env.NODE_ENV || 'development',
15
+ routes: {
16
+ discovery: '/api/v1',
17
+ metadata: '/api/v1/meta',
18
+ data: '/api/v1/data',
19
+ auth: '/api/v1/auth',
20
+ ui: '/api/v1/ui'
21
+ },
22
+ capabilities: {
23
+ search: true,
24
+ files: true
25
+ }
26
+ };
27
+ }
28
+ // 2. Metadata: List Types
29
+ getMetaTypes() {
30
+ const types = registry_1.SchemaRegistry.getRegisteredTypes();
31
+ return {
32
+ data: types.map(type => ({
33
+ type,
34
+ href: `/api/v1/meta/${type}s`,
35
+ count: registry_1.SchemaRegistry.listItems(type).length
36
+ }))
37
+ };
38
+ }
39
+ // 3. Metadata: List Items by Type
40
+ getMetaItems(typePlural) {
41
+ // Simple Singularization Mapping
42
+ const typeMap = {
43
+ 'objects': 'object',
44
+ 'apps': 'app',
45
+ 'flows': 'flow',
46
+ 'reports': 'report',
47
+ 'plugins': 'plugin',
48
+ 'kinds': 'kind'
49
+ };
50
+ const type = typeMap[typePlural] || typePlural;
51
+ const items = registry_1.SchemaRegistry.listItems(type);
52
+ const summaries = items.map((item) => ({
53
+ id: item.id,
54
+ name: item.name,
55
+ label: item.label,
56
+ type: item.type,
57
+ icon: item.icon,
58
+ description: item.description,
59
+ ...(type === 'object' ? { path: `/api/v1/data/${item.name}` } : {}),
60
+ self: `/api/v1/meta/${typePlural}/${item.name || item.id}`
61
+ }));
62
+ return { data: summaries };
63
+ }
64
+ // 4. Metadata: Get Single Item
65
+ getMetaItem(typePlural, name) {
66
+ const typeMap = {
67
+ 'objects': 'object',
68
+ 'apps': 'app',
69
+ 'flows': 'flow',
70
+ 'reports': 'report',
71
+ 'plugins': 'plugin',
72
+ 'kinds': 'kind'
73
+ };
74
+ const type = typeMap[typePlural] || typePlural;
75
+ const item = registry_1.SchemaRegistry.getItem(type, name);
76
+ if (!item)
77
+ throw new Error(`Metadata not found: ${type}/${name}`);
78
+ return item;
79
+ }
80
+ // 5. UI: View Definition
81
+ getUiView(objectName, type) {
82
+ const view = this.engine.getView(objectName, type);
83
+ if (!view)
84
+ throw new Error('View not generated');
85
+ return view;
86
+ }
87
+ // 6. Data: Find
88
+ async findData(objectName, query) {
89
+ return await this.engine.find(objectName, query);
90
+ }
91
+ // 7. Data: Query (Advanced AST)
92
+ async queryData(objectName, body) {
93
+ return await this.engine.find(objectName, body);
94
+ }
95
+ // 8. Data: Get
96
+ async getData(objectName, id) {
97
+ return await this.engine.get(objectName, id);
98
+ }
99
+ // 9. Data: Create
100
+ async createData(objectName, body) {
101
+ return await this.engine.create(objectName, body);
102
+ }
103
+ // 10. Data: Update
104
+ async updateData(objectName, id, body) {
105
+ return await this.engine.update(objectName, id, body);
106
+ }
107
+ // 11. Data: Delete
108
+ async deleteData(objectName, id) {
109
+ return await this.engine.delete(objectName, id);
110
+ }
111
+ }
112
+ exports.ObjectStackRuntimeProtocol = ObjectStackRuntimeProtocol;
@@ -0,0 +1,55 @@
1
+ import { ServiceObject, App, ObjectStackManifest } from '@objectstack/spec';
2
+ /**
3
+ * Global Schema Registry
4
+ * Unified storage for all metadata types (Objects, Apps, Flows, Layouts, etc.)
5
+ */
6
+ export declare class SchemaRegistry {
7
+ private static metadata;
8
+ /**
9
+ * Universal Register Method
10
+ * @param type The category of metadata (e.g., 'object', 'app', 'plugin')
11
+ * @param item The metadata item itself
12
+ * @param keyField The property to use as the unique key (default: 'name')
13
+ */
14
+ static registerItem<T>(type: string, item: T, keyField?: keyof T): void;
15
+ /**
16
+ * Universal Get Method
17
+ */
18
+ static getItem<T>(type: string, name: string): T | undefined;
19
+ /**
20
+ * Universal List Method
21
+ */
22
+ static listItems<T>(type: string): T[];
23
+ /**
24
+ * Get all registered metadata types (Kinds)
25
+ */
26
+ static getRegisteredTypes(): string[];
27
+ /**
28
+ * Object Helpers
29
+ */
30
+ static registerObject(schema: ServiceObject): void;
31
+ static getObject(name: string): ServiceObject | undefined;
32
+ static getAllObjects(): ServiceObject[];
33
+ /**
34
+ * App Helpers
35
+ */
36
+ static registerApp(app: App): void;
37
+ static getApp(name: string): App | undefined;
38
+ static getAllApps(): App[];
39
+ /**
40
+ * Plugin Helpers
41
+ */
42
+ static registerPlugin(manifest: ObjectStackManifest): void;
43
+ static getAllPlugins(): ObjectStackManifest[];
44
+ /**
45
+ * Kind (Metadata Type) Helpers
46
+ */
47
+ static registerKind(kind: {
48
+ id: string;
49
+ globs: string[];
50
+ }): void;
51
+ static getAllKinds(): {
52
+ id: string;
53
+ globs: string[];
54
+ }[];
55
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SchemaRegistry = void 0;
4
+ /**
5
+ * Global Schema Registry
6
+ * Unified storage for all metadata types (Objects, Apps, Flows, Layouts, etc.)
7
+ */
8
+ class SchemaRegistry {
9
+ /**
10
+ * Universal Register Method
11
+ * @param type The category of metadata (e.g., 'object', 'app', 'plugin')
12
+ * @param item The metadata item itself
13
+ * @param keyField The property to use as the unique key (default: 'name')
14
+ */
15
+ static registerItem(type, item, keyField = 'name') {
16
+ if (!this.metadata.has(type)) {
17
+ this.metadata.set(type, new Map());
18
+ }
19
+ const collection = this.metadata.get(type);
20
+ const key = String(item[keyField]);
21
+ if (collection.has(key)) {
22
+ console.warn(`[Registry] Overwriting ${type}: ${key}`);
23
+ }
24
+ collection.set(key, item);
25
+ console.log(`[Registry] Registered ${type}: ${key}`);
26
+ }
27
+ /**
28
+ * Universal Get Method
29
+ */
30
+ static getItem(type, name) {
31
+ return this.metadata.get(type)?.get(name);
32
+ }
33
+ /**
34
+ * Universal List Method
35
+ */
36
+ static listItems(type) {
37
+ return Array.from(this.metadata.get(type)?.values() || []);
38
+ }
39
+ /**
40
+ * Get all registered metadata types (Kinds)
41
+ */
42
+ static getRegisteredTypes() {
43
+ return Array.from(this.metadata.keys());
44
+ }
45
+ // ==========================================
46
+ // Typed Helper Methods (Shortcuts)
47
+ // ==========================================
48
+ /**
49
+ * Object Helpers
50
+ */
51
+ static registerObject(schema) {
52
+ this.registerItem('object', schema, 'name');
53
+ }
54
+ static getObject(name) {
55
+ return this.getItem('object', name);
56
+ }
57
+ static getAllObjects() {
58
+ return this.listItems('object');
59
+ }
60
+ /**
61
+ * App Helpers
62
+ */
63
+ static registerApp(app) {
64
+ this.registerItem('app', app, 'name');
65
+ }
66
+ static getApp(name) {
67
+ return this.getItem('app', name);
68
+ }
69
+ static getAllApps() {
70
+ return this.listItems('app');
71
+ }
72
+ /**
73
+ * Plugin Helpers
74
+ */
75
+ static registerPlugin(manifest) {
76
+ this.registerItem('plugin', manifest, 'id');
77
+ }
78
+ static getAllPlugins() {
79
+ return this.listItems('plugin');
80
+ }
81
+ /**
82
+ * Kind (Metadata Type) Helpers
83
+ */
84
+ static registerKind(kind) {
85
+ this.registerItem('kind', kind, 'id');
86
+ }
87
+ static getAllKinds() {
88
+ return this.listItems('kind');
89
+ }
90
+ }
91
+ exports.SchemaRegistry = SchemaRegistry;
92
+ // Nested Map: Type -> Name/ID -> MetadataItem
93
+ SchemaRegistry.metadata = new Map();
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@objectstack/runtime",
3
+ "version": "0.1.0",
4
+ "description": "ObjectStack Core Runtime & Query Engine",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "dependencies": {
8
+ "@objectstack/spec": "0.1.1"
9
+ },
10
+ "devDependencies": {
11
+ "typescript": "^5.0.0"
12
+ },
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsc -w"
16
+ }
17
+ }
@@ -0,0 +1,169 @@
1
+ import { ServiceObject } from '@objectstack/spec';
2
+ import { SchemaRegistry } from './registry';
3
+ import { ObjectQL } from './engine';
4
+
5
+ /**
6
+ * Server Data Engine Wrapper
7
+ *
8
+ * This class is now a thin wrapper that initializes the ObjectQL Engine
9
+ * with the appropriate Server-Side configuration (Registry, Drivers).
10
+ *
11
+ * The core logic has been moved to @objectstack/objectql.
12
+ */
13
+ export class DataEngine {
14
+ public ql: ObjectQL;
15
+ private plugins: any[];
16
+
17
+ constructor(plugins: any[] = []) {
18
+ // 1. Initialize Engine with Host Context (Simulated OS services)
19
+ this.ql = new ObjectQL({
20
+ env: process.env.NODE_ENV || 'development'
21
+ });
22
+ this.plugins = plugins;
23
+ }
24
+
25
+ async start() {
26
+ console.log('[DataEngine] Starting...');
27
+
28
+ // 0. Register Provided Plugins
29
+ this.plugins.forEach(p => {
30
+ console.log(`[DataEngine] Loading Plugin: ${p.id || p.name}`);
31
+ SchemaRegistry.registerPlugin(p);
32
+
33
+ // Register Objects from App/Plugin
34
+ if (p.objects) {
35
+ for (const obj of p.objects) {
36
+ SchemaRegistry.registerObject(obj);
37
+ console.log(`[DataEngine] Registered Object: ${obj.name}`);
38
+ }
39
+ }
40
+ });
41
+
42
+ // 1. Load Drivers (Default to Memory if none provided in plugins)
43
+ // TODO: Detect driver from plugins. For now, we still hard load memory driver if needed?
44
+ // In strict mode, user should pass driver in plugins array (DriverManifest).
45
+ // check if driver is registered
46
+
47
+ // For Backwards Compat / Easy Dev, try dynamic import of memory driver if installed
48
+ try {
49
+ // @ts-ignore
50
+ const { InMemoryDriver } = await import('@objectstack/driver-memory');
51
+ const driver = new InMemoryDriver();
52
+ this.ql.registerDriver(driver);
53
+ } catch (e) {
54
+ // Ignore if not present
55
+ }
56
+
57
+ // 2. Initialize Engine
58
+ await this.ql.init();
59
+
60
+
61
+ // 3. Seed Data
62
+ await this.seed();
63
+ }
64
+
65
+ async seed() {
66
+ // If no driver registered yet, this might fail or wait.
67
+ // In real world, we wait for 'ready' event.
68
+ try {
69
+ // Mock System Table
70
+ try {
71
+ // We don't have SystemStatus defined in schema usually, skipping for general engine
72
+ // await this.ql.insert('SystemStatus', { status: 'OK', uptime: 0 });
73
+ } catch {}
74
+
75
+ // Iterate over all registered plugins/apps and check for 'data' property in manifest
76
+
77
+ const plugins = SchemaRegistry.getRegisteredTypes(); // This returns types like 'plugin', 'app'
78
+
79
+ // This is a bit hacky because we don't have a direct "getAllManifests" API exposed easily
80
+ // We will iterate known apps for now, or improve Registry API later.
81
+ // Actually, SchemaRegistry.listItems('app') returns the manifests!
82
+
83
+ const apps = [...SchemaRegistry.listItems('app'), ...SchemaRegistry.listItems('plugin')];
84
+
85
+ for (const appItem of apps) {
86
+ const app = appItem as any; // Cast to access data prop safely
87
+ if (app.data && Array.isArray(app.data)) {
88
+ console.log(`[DataEngine] Seeding data for ${app.name || app.id}...`);
89
+ for (const seed of app.data) {
90
+ try {
91
+ // Check if data exists
92
+ const existing = await this.ql.find(seed.object, { top: 1 });
93
+ if (existing.length === 0) {
94
+ console.log(`[DataEngine] Inserting ${seed.records.length} records into ${seed.object}`);
95
+ for (const record of seed.records) {
96
+ await this.ql.insert(seed.object, record);
97
+ }
98
+ }
99
+ } catch (e) {
100
+ console.warn(`[DataEngine] Failed to seed ${seed.object}`, e);
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ } catch(e) {
107
+ console.warn('Seed failed (driver might not be ready):', e);
108
+ }
109
+ }
110
+
111
+ // Forward methods to ObjectQL
112
+ async find(objectName: string, query: any) {
113
+ this.ensureSchema(objectName);
114
+ const results = await this.ql.find(objectName, { top: 100 });
115
+ return { value: results, count: results.length };
116
+ }
117
+
118
+ async get(objectName: string, id: string) {
119
+ this.ensureSchema(objectName);
120
+ // Find One
121
+ const results = await this.ql.find(objectName, { top: 1 }); // Mock implementation
122
+ return results[0];
123
+ }
124
+
125
+ async create(objectName: string, data: any) {
126
+ this.ensureSchema(objectName);
127
+ return this.ql.insert(objectName, data);
128
+ }
129
+
130
+ async update(objectName: string, id: string, data: any) {
131
+ this.ensureSchema(objectName);
132
+ return this.ql.update(objectName, id, data);
133
+ }
134
+
135
+ async delete(objectName: string, id: string) {
136
+ this.ensureSchema(objectName);
137
+ return this.ql.delete(objectName, id);
138
+ }
139
+
140
+ // [New Methods for ObjectUI]
141
+ getMetadata(objectName: string) {
142
+ return this.ensureSchema(objectName);
143
+ }
144
+
145
+ getView(objectName: string, viewType: 'list' | 'form' = 'list') {
146
+ const schema = this.ensureSchema(objectName);
147
+
148
+ // Auto-Scaffold Default View
149
+ if (viewType === 'list') {
150
+ return {
151
+ type: 'datagrid',
152
+ title: `${schema.label || objectName} List`,
153
+ columns: Object.keys(schema.fields || {}).map(key => ({
154
+ field: key,
155
+ label: schema.fields?.[key]?.label || key,
156
+ width: 150
157
+ })),
158
+ actions: ['create', 'edit', 'delete']
159
+ };
160
+ }
161
+ return null;
162
+ }
163
+
164
+ private ensureSchema(name: string): ServiceObject {
165
+ const schema = SchemaRegistry.getObject(name);
166
+ if (!schema) throw new Error(`Unknown object: ${name}`);
167
+ return schema;
168
+ }
169
+ }