Skip to content

Commit d3635d3

Browse files
Copilothotlong
andcommitted
feat(plugin-dev): simulate all kernel services for full API dev environment
- Add AppPlugin integration to register project metadata (objects, views, dashboards) - Add REST API plugin via createRestApiPlugin (metadata read/write endpoints) - Add Dispatcher plugin (auth, GraphQL, analytics, packages, storage, automation) - Add `stack` option to DevPluginOptions for loading project metadata - Fix DevPluginConfigSchema doc examples per reviewer feedback - Add z.string().min(1) constraint on service record keys - Add test for empty service key rejection - Update README with full API endpoint documentation Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 4f5f0da commit d3635d3

6 files changed

Lines changed: 167 additions & 68 deletions

File tree

packages/plugins/plugin-dev/README.md

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
# @objectstack/plugin-dev
22

3-
> Development Mode Plugin for ObjectStack — auto-enables all services with in-memory implementations.
3+
> Development Mode Plugin for ObjectStack — auto-enables all kernel services for a full-featured API development environment.
44
55
## Overview
66

7-
Instead of manually wiring up ObjectQL, drivers, auth, HTTP server, and REST endpoints for local development, use `DevPlugin` to get a fully functional stack in one line.
7+
Instead of manually wiring up ObjectQL, drivers, auth, HTTP server, REST endpoints, dispatcher, and metadata for local development, use `DevPlugin` to get a fully functional stack in one line.
8+
9+
The dev environment simulates **all kernel services** so you can:
10+
- CRUD business objects via REST API
11+
- Read, modify, and save views/apps/dashboards via metadata API (`PUT /api/v1/meta/:type/:name`)
12+
- Use GraphQL, analytics, storage, and automation endpoints
13+
- Authenticate with dev credentials (no real auth provider needed)
814

915
## Usage
1016

@@ -25,6 +31,19 @@ export default defineStack({
2531
});
2632
```
2733

34+
### Full-stack dev with project metadata
35+
36+
```typescript
37+
import config from './objectstack.config';
38+
import { DevPlugin } from '@objectstack/plugin-dev';
39+
40+
// Load all project metadata (objects, views, etc.) into the dev server
41+
export default defineStack({
42+
...config,
43+
plugins: [new DevPlugin({ stack: config })],
44+
});
45+
```
46+
2847
### With options
2948

3049
```typescript
@@ -33,7 +52,8 @@ plugins: [
3352
port: 4000,
3453
seedAdminUser: true,
3554
services: {
36-
auth: false, // Skip auth for quick prototyping
55+
auth: false, // Skip auth for quick prototyping
56+
dispatcher: false, // Skip extended API routes
3757
},
3858
}),
3959
]
@@ -43,14 +63,32 @@ plugins: [
4363

4464
| Service | Package | Description |
4565
|---------|---------|-------------|
46-
| ObjectQL | `@objectstack/objectql` | Data engine (query, CRUD, hooks) |
66+
| ObjectQL | `@objectstack/objectql` | Data engine (query, CRUD, hooks, metadata) |
4767
| InMemoryDriver | `@objectstack/driver-memory` | In-memory database (no DB install) |
68+
| App/Metadata | `@objectstack/runtime` | Project metadata (objects, views, apps, dashboards) |
4869
| Auth | `@objectstack/plugin-auth` | Authentication with dev credentials |
4970
| Hono Server | `@objectstack/plugin-hono-server` | HTTP server on configured port |
50-
| REST API | `@objectstack/rest` | Auto-generated CRUD endpoints |
71+
| REST API | `@objectstack/rest` | Auto-generated CRUD + metadata endpoints |
72+
| Dispatcher | `@objectstack/runtime` | Auth routes, GraphQL, analytics, packages, storage |
5173

5274
All services are **optional** — if a peer package isn't installed, it is silently skipped.
5375

76+
## API Endpoints (when all services enabled)
77+
78+
| Endpoint | Description |
79+
|----------|-------------|
80+
| `GET /api/v1/data/:object` | List records |
81+
| `POST /api/v1/data/:object` | Create record |
82+
| `GET /api/v1/data/:object/:id` | Get record |
83+
| `PUT /api/v1/data/:object/:id` | Update record |
84+
| `DELETE /api/v1/data/:object/:id` | Delete record |
85+
| `GET /api/v1/meta` | List metadata types |
86+
| `GET /api/v1/meta/:type` | List metadata of type |
87+
| `GET /api/v1/meta/:type/:name` | Get metadata item |
88+
| `PUT /api/v1/meta/:type/:name` | Save metadata item |
89+
| `POST /api/v1/graphql` | GraphQL endpoint |
90+
| `GET /.well-known/objectstack` | Service discovery |
91+
5492
## Options
5593

5694
| Option | Type | Default | Description |
@@ -62,6 +100,7 @@ All services are **optional** — if a peer package isn't installed, it is silen
62100
| `verbose` | `boolean` | `true` | Enable verbose logging |
63101
| `services` | `Record<string, boolean>` | all `true` | Enable/disable individual services |
64102
| `extraPlugins` | `Plugin[]` | `[]` | Additional plugins to load |
103+
| `stack` | `object` || Stack definition to load as project metadata |
65104

66105
## License
67106

packages/plugins/plugin-dev/src/dev-plugin.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ describe('DevPlugin', () => {
1414
expect(plugin).toBeDefined();
1515
});
1616

17-
it('should accept custom options', () => {
17+
it('should accept custom options including stack', () => {
1818
const plugin = new DevPlugin({
1919
port: 4000,
2020
seedAdminUser: false,
2121
verbose: false,
22-
services: { auth: false },
22+
services: { auth: false, dispatcher: false },
23+
stack: { manifest: { id: 'test', name: 'test', version: '1.0.0', type: 'app' } },
2324
});
2425
expect(plugin).toBeDefined();
2526
});
@@ -69,6 +70,7 @@ describe('DevPlugin', () => {
6970
auth: false,
7071
server: false,
7172
rest: false,
73+
dispatcher: false,
7274
},
7375
});
7476

packages/plugins/plugin-dev/src/dev-plugin.ts

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export interface DevPluginOptions {
4444
* Override which services to enable. By default all core services are enabled.
4545
* Set a service name to `false` to skip it.
4646
*
47-
* Available services: 'objectql', 'driver', 'auth', 'server', 'rest'
47+
* Available services: 'objectql', 'driver', 'auth', 'server', 'rest', 'dispatcher'
4848
*/
4949
services?: Partial<Record<string, boolean>>;
5050

@@ -53,21 +53,44 @@ export interface DevPluginOptions {
5353
* Useful for adding custom project plugins while still getting the dev defaults.
5454
*/
5555
extraPlugins?: Plugin[];
56+
57+
/**
58+
* Stack definition to load as a project.
59+
* When provided, the DevPlugin wraps it in an AppPlugin so that all
60+
* metadata (objects, views, apps, dashboards, etc.) is registered with
61+
* the kernel and exposed through the REST/metadata APIs.
62+
*
63+
* This is what makes `new DevPlugin({ stack: config })` equivalent to
64+
* a full `os serve --dev` environment: views can be read, modified, and
65+
* saved through the API.
66+
*
67+
* @example
68+
* ```ts
69+
* import config from './objectstack.config';
70+
* plugins: [new DevPlugin({ stack: config })]
71+
* ```
72+
*/
73+
stack?: Record<string, any>;
5674
}
5775

5876
/**
5977
* Development Mode Plugin for ObjectStack
6078
*
61-
* A convenience plugin that auto-configures the entire platform stack
62-
* for local development. Instead of manually wiring:
79+
* A convenience plugin that auto-configures the **entire** platform stack
80+
* for local development, simulating all kernel services so developers
81+
* can work in a full-featured API environment without external dependencies.
82+
*
83+
* Instead of manually wiring:
6384
*
6485
* ```ts
6586
* plugins: [
6687
* new ObjectQLPlugin(),
6788
* new DriverPlugin(new InMemoryDriver()),
6889
* new AuthPlugin({ secret: '...', baseUrl: '...' }),
6990
* new HonoServerPlugin({ port: 3000 }),
70-
* // ...
91+
* createRestApiPlugin(),
92+
* createDispatcherPlugin(),
93+
* new AppPlugin(config),
7194
* ]
7295
* ```
7396
*
@@ -77,13 +100,17 @@ export interface DevPluginOptions {
77100
* plugins: [new DevPlugin()]
78101
* ```
79102
*
80-
* The DevPlugin will:
81-
* 1. Register an ObjectQL engine (data layer)
82-
* 2. Register an InMemoryDriver (no database required)
83-
* 3. Register an Auth plugin (with dev credentials)
84-
* 4. Register a Hono HTTP server
85-
* 5. Register REST API endpoints
86-
* 6. Optionally seed a default admin user
103+
* ## Services enabled
104+
*
105+
* | Service | Package | Description |
106+
* |--------------|-----------------------------------|-------------------------------------------|
107+
* | ObjectQL | `@objectstack/objectql` | Data engine (query, CRUD, hooks) |
108+
* | Driver | `@objectstack/driver-memory` | In-memory database (no DB install) |
109+
* | Auth | `@objectstack/plugin-auth` | Authentication with dev credentials |
110+
* | HTTP Server | `@objectstack/plugin-hono-server` | HTTP server on configured port |
111+
* | REST API | `@objectstack/rest` | Auto-generated CRUD + metadata endpoints |
112+
* | Dispatcher | `@objectstack/runtime` | Auth, GraphQL, analytics, packages, etc. |
113+
* | App/Metadata | `@objectstack/runtime` | Project metadata (objects, views, apps) |
87114
*
88115
* All services can be individually disabled via `options.services`.
89116
* Peer packages are loaded via dynamic import and silently skipped if missing.
@@ -122,13 +149,13 @@ export class DevPlugin implements Plugin {
122149

123150
const enabled = (name: string) => this.options.services?.[name] !== false;
124151

125-
// 1. ObjectQL Engine (data layer)
152+
// 1. ObjectQL Engine (data layer + metadata service)
126153
if (enabled('objectql')) {
127154
try {
128155
const { ObjectQLPlugin } = await import('@objectstack/objectql');
129156
const qlPlugin = new ObjectQLPlugin();
130157
this.childPlugins.push(qlPlugin);
131-
ctx.logger.info(' ✔ ObjectQL engine enabled');
158+
ctx.logger.info(' ✔ ObjectQL engine enabled (data + metadata)');
132159
} catch {
133160
ctx.logger.warn(' ✘ @objectstack/objectql not installed — skipping data engine');
134161
}
@@ -148,7 +175,21 @@ export class DevPlugin implements Plugin {
148175
}
149176
}
150177

151-
// 3. Auth Plugin
178+
// 3. App Plugin — registers project metadata (objects, views, apps, dashboards, etc.)
179+
// This is the key piece that enables full API development:
180+
// once metadata is registered, REST endpoints can read/write views, etc.
181+
if (this.options.stack) {
182+
try {
183+
const { AppPlugin } = await import('@objectstack/runtime') as any;
184+
const appPlugin = new AppPlugin(this.options.stack);
185+
this.childPlugins.push(appPlugin);
186+
ctx.logger.info(' ✔ App metadata loaded from stack definition');
187+
} catch {
188+
ctx.logger.warn(' ✘ @objectstack/runtime not installed — skipping app metadata');
189+
}
190+
}
191+
192+
// 4. Auth Plugin
152193
if (enabled('auth')) {
153194
try {
154195
const { AuthPlugin } = await import('@objectstack/plugin-auth') as any;
@@ -163,7 +204,7 @@ export class DevPlugin implements Plugin {
163204
}
164205
}
165206

166-
// 4. Hono HTTP Server
207+
// 5. Hono HTTP Server
167208
if (enabled('server')) {
168209
try {
169210
const { HonoServerPlugin } = await import('@objectstack/plugin-hono-server') as any;
@@ -177,28 +218,30 @@ export class DevPlugin implements Plugin {
177218
}
178219
}
179220

180-
// 5. REST API endpoints
221+
// 6. REST API endpoints (CRUD + metadata read/write)
181222
if (enabled('rest')) {
182223
try {
183-
const restModule = await import('@objectstack/rest') as any;
184-
// REST may export a plugin class or a factory function
185-
const RestPlugin = (restModule as any).RestPlugin
186-
?? (restModule as any).createRestPlugin
187-
?? (restModule as any).default;
188-
if (typeof RestPlugin === 'function') {
189-
const restPlugin = RestPlugin.prototype?.init
190-
? new RestPlugin()
191-
: RestPlugin();
192-
if (restPlugin) {
193-
this.childPlugins.push(restPlugin);
194-
ctx.logger.info(' ✔ REST API endpoints enabled');
195-
}
196-
}
224+
const { createRestApiPlugin } = await import('@objectstack/rest') as any;
225+
const restPlugin = createRestApiPlugin();
226+
this.childPlugins.push(restPlugin);
227+
ctx.logger.info(' ✔ REST API endpoints enabled (CRUD + metadata)');
197228
} catch {
198229
ctx.logger.debug(' ℹ @objectstack/rest not installed — skipping REST endpoints');
199230
}
200231
}
201232

233+
// 7. Dispatcher (auth routes, GraphQL, analytics, packages, storage, automation)
234+
if (enabled('dispatcher')) {
235+
try {
236+
const { createDispatcherPlugin } = await import('@objectstack/runtime') as any;
237+
const dispatcherPlugin = createDispatcherPlugin();
238+
this.childPlugins.push(dispatcherPlugin);
239+
ctx.logger.info(' ✔ Dispatcher enabled (auth, GraphQL, analytics, packages, storage)');
240+
} catch {
241+
ctx.logger.debug(' ℹ Dispatcher not available — skipping extended API routes');
242+
}
243+
}
244+
202245
// Extra user-provided plugins
203246
if (this.options.extraPlugins) {
204247
this.childPlugins.push(...this.options.extraPlugins);
@@ -241,6 +284,10 @@ export class DevPlugin implements Plugin {
241284
ctx.logger.info('─────────────────────────────────────────');
242285
ctx.logger.info('🟢 ObjectStack Dev Server ready');
243286
ctx.logger.info(` http://localhost:${this.options.port}`);
287+
ctx.logger.info('');
288+
ctx.logger.info(' API: /api/v1/data/:object');
289+
ctx.logger.info(' Metadata: /api/v1/meta/:type/:name');
290+
ctx.logger.info(' Discovery: /.well-known/objectstack');
244291
ctx.logger.info('─────────────────────────────────────────');
245292
}
246293

packages/plugins/plugin-dev/src/index.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@
66
* Development Mode Plugin for ObjectStack.
77
*
88
* Auto-enables all platform services (ObjectQL, InMemoryDriver, Auth,
9-
* Hono HTTP server, REST API) with in-memory implementations so that
10-
* developers can run the full stack locally without external dependencies.
9+
* Hono HTTP server, REST API, Dispatcher, Metadata) with in-memory
10+
* implementations so that developers can run the full stack locally
11+
* without external dependencies.
12+
*
13+
* Provides a complete API development environment where you can:
14+
* - CRUD business objects
15+
* - Read/write metadata (views, apps, dashboards, etc.)
16+
* - Use GraphQL, analytics, and automation endpoints
17+
* - Authenticate with dev credentials
1118
*
1219
* @example Zero-config
1320
* ```typescript
@@ -20,15 +27,13 @@
2027
* });
2128
* ```
2229
*
23-
* @example With options
30+
* @example Full-stack dev with project metadata
2431
* ```typescript
25-
* plugins: [
26-
* new DevPlugin({
27-
* port: 4000,
28-
* seedAdminUser: true,
29-
* services: { auth: false }, // disable auth
30-
* }),
31-
* ]
32+
* import config from './objectstack.config';
33+
* import { DevPlugin } from '@objectstack/plugin-dev';
34+
*
35+
* // Loads all project metadata (objects, views, etc.) into the dev server
36+
* plugins: [new DevPlugin({ stack: config })]
3237
* ```
3338
*/
3439

packages/spec/src/kernel/dev-plugin.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,5 +223,12 @@ describe('Dev Mode Plugin Protocol', () => {
223223
DevPluginConfigSchema.safeParse({ simulatedLatency: -10 }).success,
224224
).toBe(false);
225225
});
226+
227+
it('should reject empty string as service key', () => {
228+
const result = DevPluginConfigSchema.safeParse({
229+
services: { '': { enabled: true } },
230+
});
231+
expect(result.success).toBe(false);
232+
});
226233
});
227234
});

0 commit comments

Comments
 (0)