Skip to content

Commit a666527

Browse files
Copilothotlong
andcommitted
docs: Add plugin protocol documentation and example
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent a2eb1db commit a666527

3 files changed

Lines changed: 352 additions & 4 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* ObjectQL
3+
* Copyright (c) 2026-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
import { ObjectQL } from '@objectql/core';
10+
import { createSqlDriverPlugin } from '@objectql/driver-sql';
11+
12+
async function main() {
13+
console.log("🚀 Starting ObjectQL Hello World with Plugin...");
14+
15+
// 1. Initialize Engine with Plugin
16+
const app = new ObjectQL({
17+
plugins: [
18+
createSqlDriverPlugin({
19+
name: 'default',
20+
config: {
21+
client: 'sqlite3',
22+
connection: { filename: ':memory:' },
23+
useNullAsDefault: true
24+
}
25+
})
26+
]
27+
});
28+
29+
// 2. Define Metadata Inline
30+
app.registerObject({
31+
name: 'deal',
32+
fields: {
33+
title: { type: 'text', required: true },
34+
amount: { type: 'currency' },
35+
stage: {
36+
type: 'select',
37+
options: [
38+
{ label: 'New', value: 'new' },
39+
{ label: 'Negotiation', value: 'negotiation' },
40+
{ label: 'Closed', value: 'closed' }
41+
],
42+
defaultValue: 'new'
43+
}
44+
}
45+
});
46+
47+
await app.init(); // Boot the engine and setup plugins
48+
49+
// 3. Run Business Logic
50+
const ctx = app.createContext({ isSystem: true });
51+
const repo = ctx.object('deal');
52+
53+
console.log("Creating a new Deal...");
54+
await repo.create({
55+
title: 'Enterprise Contract',
56+
amount: 50000,
57+
stage: 'new'
58+
});
59+
60+
const results = await repo.find({});
61+
console.log('✅ Deals found in database:', results);
62+
}
63+
64+
if (require.main === module) {
65+
main().catch(console.error);
66+
}
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
# Driver Plugin Protocol
2+
3+
## Overview
4+
5+
According to @objectstack/spec, all drivers in ObjectQL have been enhanced to support the plugin protocol. This allows drivers to be registered as plugins, providing a more flexible and consistent way to configure datasources.
6+
7+
## Plugin Protocol
8+
9+
Each driver now exports a plugin factory function following the pattern:
10+
11+
```typescript
12+
export function create{DriverName}Plugin(options: {
13+
name: string;
14+
config: DriverConfig;
15+
}): ObjectQLPlugin;
16+
```
17+
18+
## Available Driver Plugins
19+
20+
### 1. SQL Driver Plugin
21+
22+
```typescript
23+
import { ObjectQL } from '@objectql/core';
24+
import { createSqlDriverPlugin } from '@objectql/driver-sql';
25+
26+
const app = new ObjectQL({
27+
plugins: [
28+
createSqlDriverPlugin({
29+
name: 'default',
30+
config: {
31+
client: 'sqlite3',
32+
connection: { filename: ':memory:' },
33+
useNullAsDefault: true
34+
}
35+
})
36+
]
37+
});
38+
```
39+
40+
### 2. MongoDB Driver Plugin
41+
42+
```typescript
43+
import { createMongoDriverPlugin } from '@objectql/driver-mongo';
44+
45+
const app = new ObjectQL({
46+
plugins: [
47+
createMongoDriverPlugin({
48+
name: 'default',
49+
config: {
50+
url: 'mongodb://localhost:27017',
51+
dbName: 'mydb'
52+
}
53+
})
54+
]
55+
});
56+
```
57+
58+
### 3. Memory Driver Plugin
59+
60+
```typescript
61+
import { createMemoryDriverPlugin } from '@objectql/driver-memory';
62+
63+
const app = new ObjectQL({
64+
plugins: [
65+
createMemoryDriverPlugin({
66+
name: 'default',
67+
config: {
68+
strictMode: true,
69+
initialData: {
70+
users: [{ id: '1', name: 'Alice' }]
71+
}
72+
}
73+
})
74+
]
75+
});
76+
```
77+
78+
### 4. Redis Driver Plugin
79+
80+
```typescript
81+
import { createRedisDriverPlugin } from '@objectql/driver-redis';
82+
83+
const app = new ObjectQL({
84+
plugins: [
85+
createRedisDriverPlugin({
86+
name: 'cache',
87+
config: {
88+
url: 'redis://localhost:6379'
89+
}
90+
})
91+
]
92+
});
93+
```
94+
95+
### 5. LocalStorage Driver Plugin
96+
97+
```typescript
98+
import { createLocalStorageDriverPlugin } from '@objectql/driver-localstorage';
99+
100+
const app = new ObjectQL({
101+
plugins: [
102+
createLocalStorageDriverPlugin({
103+
name: 'default',
104+
config: {
105+
namespace: 'myapp',
106+
strictMode: true
107+
}
108+
})
109+
]
110+
});
111+
```
112+
113+
### 6. FileSystem Driver Plugin
114+
115+
```typescript
116+
import { createFileSystemDriverPlugin } from '@objectql/driver-fs';
117+
118+
const app = new ObjectQL({
119+
plugins: [
120+
createFileSystemDriverPlugin({
121+
name: 'default',
122+
config: {
123+
dataDir: './data',
124+
prettyPrint: true,
125+
enableBackup: true
126+
}
127+
})
128+
]
129+
});
130+
```
131+
132+
### 7. SDK Driver Plugin (Remote API)
133+
134+
```typescript
135+
import { createSdkDriverPlugin } from '@objectql/driver-sdk';
136+
137+
const app = new ObjectQL({
138+
plugins: [
139+
createSdkDriverPlugin({
140+
name: 'remote',
141+
config: {
142+
baseUrl: 'https://api.example.com',
143+
rpcPath: '/api/objectql'
144+
}
145+
})
146+
]
147+
});
148+
```
149+
150+
### 8. Excel Driver Plugin
151+
152+
```typescript
153+
import { createExcelDriverPlugin } from '@objectql/driver-excel';
154+
155+
const app = new ObjectQL({
156+
plugins: [
157+
createExcelDriverPlugin({
158+
name: 'default',
159+
config: {
160+
filePath: './data.xlsx',
161+
storageMode: 'single-file'
162+
}
163+
})
164+
]
165+
});
166+
```
167+
168+
## Multiple Datasources
169+
170+
You can register multiple datasources using different plugins:
171+
172+
```typescript
173+
import { ObjectQL } from '@objectql/core';
174+
import { createSqlDriverPlugin } from '@objectql/driver-sql';
175+
import { createRedisDriverPlugin } from '@objectql/driver-redis';
176+
177+
const app = new ObjectQL({
178+
plugins: [
179+
// Primary database
180+
createSqlDriverPlugin({
181+
name: 'default',
182+
config: {
183+
client: 'postgresql',
184+
connection: process.env.DATABASE_URL
185+
}
186+
}),
187+
188+
// Cache layer
189+
createRedisDriverPlugin({
190+
name: 'cache',
191+
config: {
192+
url: process.env.REDIS_URL
193+
}
194+
})
195+
]
196+
});
197+
198+
await app.init();
199+
200+
// Use different datasources
201+
const ctx = app.createContext({ isSystem: true });
202+
203+
// Default datasource
204+
const users = ctx.object('users');
205+
await users.find({});
206+
207+
// Access cache datasource
208+
const cacheDriver = app.datasource('cache');
209+
```
210+
211+
## Backward Compatibility
212+
213+
The old approach of passing drivers directly to `datasources` config is still supported:
214+
215+
```typescript
216+
import { ObjectQL } from '@objectql/core';
217+
import { SqlDriver } from '@objectql/driver-sql';
218+
219+
const driver = new SqlDriver({
220+
client: 'sqlite3',
221+
connection: { filename: ':memory:' },
222+
useNullAsDefault: true
223+
});
224+
225+
const app = new ObjectQL({
226+
datasources: {
227+
default: driver
228+
}
229+
});
230+
```
231+
232+
## Benefits of Plugin-Based Approach
233+
234+
1. **Consistency**: All drivers follow the same plugin protocol from @objectstack/spec
235+
2. **Flexibility**: Easy to swap drivers without changing application code
236+
3. **Composability**: Mix and match multiple drivers as plugins
237+
4. **Testability**: Easier to mock and test with plugin-based configuration
238+
5. **Type Safety**: Plugin configurations are fully typed
239+
240+
## Migration Guide
241+
242+
### Old Approach
243+
244+
```typescript
245+
import { ObjectQL } from '@objectql/core';
246+
import { SqlDriver } from '@objectql/driver-sql';
247+
248+
const driver = new SqlDriver(config);
249+
const app = new ObjectQL({ datasources: { default: driver } });
250+
```
251+
252+
### New Plugin-Based Approach
253+
254+
```typescript
255+
import { ObjectQL } from '@objectql/core';
256+
import { createSqlDriverPlugin } from '@objectql/driver-sql';
257+
258+
const app = new ObjectQL({
259+
plugins: [
260+
createSqlDriverPlugin({ name: 'default', config })
261+
]
262+
});
263+
```
264+
265+
## Implementation Details
266+
267+
Each driver plugin:
268+
269+
1. Implements the `ObjectQLPlugin` interface from `@objectql/types`
270+
2. Has a unique `name` in the format `{driver-type}-driver:{datasource-name}`
271+
3. Registers the driver using `app.registerDatasource(name, driver)` in its `setup` method
272+
4. Fully typed configuration options specific to each driver type
273+
274+
## See Also
275+
276+
- [ObjectQL Plugin Protocol](../../packages/foundation/types/src/plugin.ts)
277+
- [Driver Interface](../../packages/foundation/types/src/driver.ts)
278+
- [Example: Hello World with Plugins](../../examples/quickstart/hello-world/src/index-plugin.ts)

packages/drivers/sdk/src/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,7 @@ export class MetadataApiClient implements IMetadataApiClient {
445445
* createSdkDriverPlugin({
446446
* name: 'remote',
447447
* config: {
448-
* baseUrl: 'https://api.example.com',
449-
* apiKey: 'your-api-key'
448+
* baseUrl: 'https://api.example.com'
450449
* }
451450
* })
452451
* ]
@@ -461,14 +460,19 @@ export interface SdkDriverPluginConfig {
461460
/** Name of the datasource (e.g., 'remote', 'api') */
462461
name: string;
463462
/** SDK driver configuration */
464-
config: DataApiClientConfig;
463+
config: {
464+
/** Base URL for the remote ObjectQL API */
465+
baseUrl: string;
466+
/** Optional RPC path (default: '/api/objectql') */
467+
rpcPath?: string;
468+
};
465469
}
466470

467471
export function createSdkDriverPlugin(options: SdkDriverPluginConfig): ObjectQLPlugin {
468472
return {
469473
name: `sdk-driver:${options.name}`,
470474
setup(app: IObjectQL) {
471-
const driver = new DataApiClient(options.config);
475+
const driver = new RemoteDriver(options.config.baseUrl, options.config.rpcPath);
472476
app.registerDatasource(options.name, driver);
473477
}
474478
};

0 commit comments

Comments
 (0)