@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/src/engine.ts ADDED
@@ -0,0 +1,189 @@
1
+ import { DriverInterface, DriverOptions, QueryAST, ObjectStackManifest, ManifestSchema } from '@objectstack/spec';
2
+ import { SchemaRegistry } from './registry';
3
+
4
+ /**
5
+ * Host Context provided to plugins
6
+ */
7
+ export interface PluginContext {
8
+ ql: ObjectQL;
9
+ logger: Console;
10
+ // Extensible map for host-specific globals (like HTTP Router, etc.)
11
+ [key: string]: any;
12
+ }
13
+
14
+ /**
15
+ * ObjectQL Engine
16
+ */
17
+ export class ObjectQL {
18
+ private drivers = new Map<string, DriverInterface>();
19
+ private defaultDriver: string | null = null;
20
+ private plugins = new Map<string, any>();
21
+
22
+ // Host provided context additions (e.g. Server router)
23
+ private hostContext: Record<string, any> = {};
24
+
25
+ constructor(hostContext: Record<string, any> = {}) {
26
+ this.hostContext = hostContext;
27
+ console.log(`[ObjectQL] Engine Instance Created`);
28
+ }
29
+
30
+ /**
31
+ * Load and Register a Plugin
32
+ */
33
+ async use(manifestPart: any, runtimePart?: any) {
34
+ // 1. Validate / Register Manifest
35
+ if (manifestPart) {
36
+ // 1. Handle Module Imports (commonjs/esm interop)
37
+ // If the passed object is a module namespace with a default export, use that.
38
+ const manifest = manifestPart.default || manifestPart;
39
+
40
+ // In a real scenario, we might strictly parse this using Zod
41
+ // For now, simple ID check
42
+ const id = manifest.id || manifest.name;
43
+ if (!id) {
44
+ console.warn(`[ObjectQL] Plugin manifest missing ID (keys: ${Object.keys(manifest)})`, manifest);
45
+ // Don't return, try to proceed if it looks like an App (Apps might use 'name' instead of 'id')
46
+ // return;
47
+ }
48
+
49
+ console.log(`[ObjectQL] Loading Plugin: ${id}`);
50
+ SchemaRegistry.registerPlugin(manifest as ObjectStackManifest);
51
+
52
+ // Register Objects from App/Plugin
53
+ if (manifest.objects) {
54
+ for (const obj of manifest.objects) {
55
+ // Ensure object name is registered globally
56
+ SchemaRegistry.registerObject(obj);
57
+ console.log(`[ObjectQL] Registered Object: ${obj.name}`);
58
+ }
59
+ }
60
+
61
+ // Register contributions
62
+ if (manifest.contributes?.kinds) {
63
+ for (const kind of manifest.contributes.kinds) {
64
+ SchemaRegistry.registerKind(kind);
65
+ }
66
+ }
67
+
68
+ // Register Data Seeding (Lazy execution or immediate?)
69
+ // We store it in a temporary registry or execute immediately if engine is ready.
70
+ // Since `use` is init time, we might need to store it and run later in `seed()`.
71
+ // For this MVP, let's attach it to the manifest object in registry so DataEngine can find it.
72
+ }
73
+
74
+ // 2. Execute Runtime
75
+ if (runtimePart) {
76
+ const pluginDef = (runtimePart as any).default || runtimePart;
77
+ if (pluginDef.onEnable) {
78
+ const context: PluginContext = {
79
+ ql: this,
80
+ logger: console,
81
+ // Expose the driver registry helper explicitly if needed
82
+ drivers: {
83
+ register: (driver: DriverInterface) => this.registerDriver(driver)
84
+ },
85
+ ...this.hostContext
86
+ };
87
+
88
+ await pluginDef.onEnable(context);
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Register a new storage driver
95
+ */
96
+ registerDriver(driver: DriverInterface, isDefault: boolean = false) {
97
+ if (this.drivers.has(driver.name)) {
98
+ console.warn(`[ObjectQL] Driver ${driver.name} is already registered. Skipping.`);
99
+ return;
100
+ }
101
+
102
+ this.drivers.set(driver.name, driver);
103
+ console.log(`[ObjectQL] Registered driver: ${driver.name} v${driver.version}`);
104
+
105
+ if (isDefault || this.drivers.size === 1) {
106
+ this.defaultDriver = driver.name;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Helper to get the target driver
112
+ */
113
+ private getDriver(object: string): DriverInterface {
114
+ // TODO: Look up Object definition to see if it specifies a specific datasource/driver
115
+ // For now, always return default
116
+ if (!this.defaultDriver) {
117
+ throw new Error('[ObjectQL] No drivers registered!');
118
+ }
119
+ return this.drivers.get(this.defaultDriver)!;
120
+ }
121
+
122
+ /**
123
+ * Initialize the engine and all registered drivers
124
+ */
125
+ async init() {
126
+ console.log('[ObjectQL] Initializing drivers...');
127
+ for (const [name, driver] of this.drivers) {
128
+ try {
129
+ await driver.connect();
130
+ } catch (e) {
131
+ console.error(`[ObjectQL] Failed to connect driver ${name}`, e);
132
+ }
133
+ }
134
+ // In a real app, we would sync schemas here
135
+ }
136
+
137
+ async destroy() {
138
+ for (const driver of this.drivers.values()) {
139
+ await driver.disconnect();
140
+ }
141
+ }
142
+
143
+ // ============================================
144
+ // Data Access Methods
145
+ // ============================================
146
+
147
+ async find(object: string, filters: any = {}, options?: DriverOptions) {
148
+ const driver = this.getDriver(object);
149
+ console.log(`[ObjectQL] Finding ${object} on ${driver.name}...`);
150
+
151
+ // Transform simplified filters to QueryAST
152
+ // This is a simplified "Mock" transform.
153
+ // Real implementation would parse complex JSON or FilterBuilders.
154
+ const ast: QueryAST = {
155
+ object, // Add missing required field
156
+ // Pass through if it looks like AST, otherwise empty
157
+ // In this demo, we assume the caller passes a simplified object or raw AST
158
+ filters: filters.filters || undefined,
159
+ top: filters.top || 100,
160
+ sort: filters.sort || []
161
+ };
162
+
163
+ return driver.find(object, ast, options);
164
+ }
165
+
166
+ async insert(object: string, data: Record<string, any>, options?: DriverOptions) {
167
+ const driver = this.getDriver(object);
168
+ console.log(`[ObjectQL] Creating ${object} on ${driver.name}...`);
169
+ // 1. Validate Schema
170
+ // 2. Run "Before Insert" Triggers
171
+
172
+ const result = await driver.create(object, data, options);
173
+
174
+ // 3. Run "After Insert" Triggers
175
+ return result;
176
+ }
177
+
178
+ async update(object: string, id: string, data: Record<string, any>, options?: DriverOptions) {
179
+ const driver = this.getDriver(object);
180
+ console.log(`[ObjectQL] Updating ${object} ${id}...`);
181
+ return driver.update(object, id, data, options);
182
+ }
183
+
184
+ async delete(object: string, id: string, options?: DriverOptions) {
185
+ const driver = this.getDriver(object);
186
+ console.log(`[ObjectQL] Deleting ${object} ${id}...`);
187
+ return driver.delete(object, id, options);
188
+ }
189
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ // Export core engine
2
+ export { ObjectQL } from './engine';
3
+ export { SchemaRegistry } from './registry';
4
+ export { DataEngine } from './data-engine';
5
+ export { ObjectStackRuntimeProtocol } from './protocol';
6
+
7
+
8
+
9
+ // Re-export common types from spec for convenience
10
+ export type { DriverInterface, DriverOptions, QueryAST } from '@objectstack/spec';
@@ -0,0 +1,129 @@
1
+ import { SchemaRegistry } from './registry';
2
+ import { DataEngine } from './data-engine';
3
+
4
+ export interface ApiRequest {
5
+ params: Record<string, string>;
6
+ query: Record<string, string | string[]>;
7
+ body?: any;
8
+ }
9
+
10
+ export class ObjectStackRuntimeProtocol {
11
+ private engine: DataEngine;
12
+
13
+ constructor(engine: DataEngine) {
14
+ this.engine = engine;
15
+ }
16
+
17
+ // 1. Discovery
18
+ getDiscovery() {
19
+ return {
20
+ name: 'ObjectOS Server',
21
+ version: '1.0.0',
22
+ environment: process.env.NODE_ENV || 'development',
23
+ routes: {
24
+ discovery: '/api/v1',
25
+ metadata: '/api/v1/meta',
26
+ data: '/api/v1/data',
27
+ auth: '/api/v1/auth',
28
+ ui: '/api/v1/ui'
29
+ },
30
+ capabilities: {
31
+ search: true,
32
+ files: true
33
+ }
34
+ };
35
+ }
36
+
37
+ // 2. Metadata: List Types
38
+ getMetaTypes() {
39
+ const types = SchemaRegistry.getRegisteredTypes();
40
+ return {
41
+ data: types.map(type => ({
42
+ type,
43
+ href: `/api/v1/meta/${type}s`,
44
+ count: SchemaRegistry.listItems(type).length
45
+ }))
46
+ };
47
+ }
48
+
49
+ // 3. Metadata: List Items by Type
50
+ getMetaItems(typePlural: string) {
51
+ // Simple Singularization Mapping
52
+ const typeMap: Record<string, string> = {
53
+ 'objects': 'object',
54
+ 'apps': 'app',
55
+ 'flows': 'flow',
56
+ 'reports': 'report',
57
+ 'plugins': 'plugin',
58
+ 'kinds': 'kind'
59
+ };
60
+ const type = typeMap[typePlural] || typePlural;
61
+ const items = SchemaRegistry.listItems(type);
62
+
63
+ const summaries = items.map((item: any) => ({
64
+ id: item.id,
65
+ name: item.name,
66
+ label: item.label,
67
+ type: item.type,
68
+ icon: item.icon,
69
+ description: item.description,
70
+ ...(type === 'object' ? { path: `/api/v1/data/${item.name}` } : {}),
71
+ self: `/api/v1/meta/${typePlural}/${item.name || item.id}`
72
+ }));
73
+
74
+ return { data: summaries };
75
+ }
76
+
77
+ // 4. Metadata: Get Single Item
78
+ getMetaItem(typePlural: string, name: string) {
79
+ const typeMap: Record<string, string> = {
80
+ 'objects': 'object',
81
+ 'apps': 'app',
82
+ 'flows': 'flow',
83
+ 'reports': 'report',
84
+ 'plugins': 'plugin',
85
+ 'kinds': 'kind'
86
+ };
87
+ const type = typeMap[typePlural] || typePlural;
88
+ const item = SchemaRegistry.getItem(type, name);
89
+ if (!item) throw new Error(`Metadata not found: ${type}/${name}`);
90
+ return item;
91
+ }
92
+
93
+ // 5. UI: View Definition
94
+ getUiView(objectName: string, type: 'list' | 'form') {
95
+ const view = this.engine.getView(objectName, type);
96
+ if (!view) throw new Error('View not generated');
97
+ return view;
98
+ }
99
+
100
+ // 6. Data: Find
101
+ async findData(objectName: string, query: any) {
102
+ return await this.engine.find(objectName, query);
103
+ }
104
+
105
+ // 7. Data: Query (Advanced AST)
106
+ async queryData(objectName: string, body: any) {
107
+ return await this.engine.find(objectName, body);
108
+ }
109
+
110
+ // 8. Data: Get
111
+ async getData(objectName: string, id: string) {
112
+ return await this.engine.get(objectName, id);
113
+ }
114
+
115
+ // 9. Data: Create
116
+ async createData(objectName: string, body: any) {
117
+ return await this.engine.create(objectName, body);
118
+ }
119
+
120
+ // 10. Data: Update
121
+ async updateData(objectName: string, id: string, body: any) {
122
+ return await this.engine.update(objectName, id, body);
123
+ }
124
+
125
+ // 11. Data: Delete
126
+ async deleteData(objectName: string, id: string) {
127
+ return await this.engine.delete(objectName, id);
128
+ }
129
+ }
@@ -0,0 +1,107 @@
1
+ import { ServiceObject, App, ObjectStackManifest } from '@objectstack/spec';
2
+
3
+ /**
4
+ * Global Schema Registry
5
+ * Unified storage for all metadata types (Objects, Apps, Flows, Layouts, etc.)
6
+ */
7
+ export class SchemaRegistry {
8
+ // Nested Map: Type -> Name/ID -> MetadataItem
9
+ private static metadata = new Map<string, Map<string, any>>();
10
+
11
+ /**
12
+ * Universal Register Method
13
+ * @param type The category of metadata (e.g., 'object', 'app', 'plugin')
14
+ * @param item The metadata item itself
15
+ * @param keyField The property to use as the unique key (default: 'name')
16
+ */
17
+ static registerItem<T>(type: string, item: T, keyField: keyof T = 'name' as keyof T) {
18
+ if (!this.metadata.has(type)) {
19
+ this.metadata.set(type, new Map());
20
+ }
21
+ const collection = this.metadata.get(type)!;
22
+ const key = String(item[keyField]);
23
+
24
+ if (collection.has(key)) {
25
+ console.warn(`[Registry] Overwriting ${type}: ${key}`);
26
+ }
27
+ collection.set(key, item);
28
+ console.log(`[Registry] Registered ${type}: ${key}`);
29
+ }
30
+
31
+ /**
32
+ * Universal Get Method
33
+ */
34
+ static getItem<T>(type: string, name: string): T | undefined {
35
+ return this.metadata.get(type)?.get(name) as T;
36
+ }
37
+
38
+ /**
39
+ * Universal List Method
40
+ */
41
+ static listItems<T>(type: string): T[] {
42
+ return Array.from(this.metadata.get(type)?.values() || []) as T[];
43
+ }
44
+
45
+ /**
46
+ * Get all registered metadata types (Kinds)
47
+ */
48
+ static getRegisteredTypes(): string[] {
49
+ return Array.from(this.metadata.keys());
50
+ }
51
+
52
+ // ==========================================
53
+ // Typed Helper Methods (Shortcuts)
54
+ // ==========================================
55
+
56
+ /**
57
+ * Object Helpers
58
+ */
59
+ static registerObject(schema: ServiceObject) {
60
+ this.registerItem('object', schema, 'name');
61
+ }
62
+
63
+ static getObject(name: string): ServiceObject | undefined {
64
+ return this.getItem<ServiceObject>('object', name);
65
+ }
66
+
67
+ static getAllObjects(): ServiceObject[] {
68
+ return this.listItems<ServiceObject>('object');
69
+ }
70
+
71
+ /**
72
+ * App Helpers
73
+ */
74
+ static registerApp(app: App) {
75
+ this.registerItem('app', app, 'name');
76
+ }
77
+
78
+ static getApp(name: string): App | undefined {
79
+ return this.getItem<App>('app', name);
80
+ }
81
+
82
+ static getAllApps(): App[] {
83
+ return this.listItems<App>('app');
84
+ }
85
+
86
+ /**
87
+ * Plugin Helpers
88
+ */
89
+ static registerPlugin(manifest: ObjectStackManifest) {
90
+ this.registerItem('plugin', manifest, 'id');
91
+ }
92
+
93
+ static getAllPlugins(): ObjectStackManifest[] {
94
+ return this.listItems<ObjectStackManifest>('plugin');
95
+ }
96
+
97
+ /**
98
+ * Kind (Metadata Type) Helpers
99
+ */
100
+ static registerKind(kind: { id: string, globs: string[] }) {
101
+ this.registerItem('kind', kind, 'id');
102
+ }
103
+
104
+ static getAllKinds(): { id: string, globs: string[] }[] {
105
+ return this.listItems('kind');
106
+ }
107
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "declaration": true,
6
+ "outDir": "./dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true
11
+ },
12
+ "include": ["src/**/*"]
13
+ }