Skip to content

Commit b3304ad

Browse files
Copilothotlong
andcommitted
feat(plugin-dev): add @objectstack/plugin-dev package
Create the actual DevPlugin implementation that auto-configures all platform services (ObjectQL, InMemoryDriver, Auth, Hono HTTP server, REST API) for development mode. One-line setup replaces manual multi-plugin wiring. Usage: plugins: [new DevPlugin()] Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent ff4c899 commit b3304ad

8 files changed

Lines changed: 589 additions & 0 deletions

File tree

.changeset/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@objectstack/plugin-auth",
2020
"@objectstack/plugin-hono-server",
2121
"@objectstack/plugin-msw",
22+
"@objectstack/plugin-dev",
2223
"@objectstack/plugin-security",
2324
"@objectstack/hono",
2425
"@objectstack/nestjs",
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# @objectstack/plugin-dev
2+
3+
> Development Mode Plugin for ObjectStack — auto-enables all services with in-memory implementations.
4+
5+
## Overview
6+
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.
8+
9+
## Usage
10+
11+
### Zero-config
12+
13+
```typescript
14+
import { defineStack } from '@objectstack/spec';
15+
import { DevPlugin } from '@objectstack/plugin-dev';
16+
17+
export default defineStack({
18+
manifest: {
19+
id: 'com.example.myapp',
20+
name: 'My App',
21+
version: '0.1.0',
22+
type: 'app',
23+
},
24+
plugins: [new DevPlugin()],
25+
});
26+
```
27+
28+
### With options
29+
30+
```typescript
31+
plugins: [
32+
new DevPlugin({
33+
port: 4000,
34+
seedAdminUser: true,
35+
services: {
36+
auth: false, // Skip auth for quick prototyping
37+
},
38+
}),
39+
]
40+
```
41+
42+
## What it auto-configures
43+
44+
| Service | Package | Description |
45+
|---------|---------|-------------|
46+
| ObjectQL | `@objectstack/objectql` | Data engine (query, CRUD, hooks) |
47+
| InMemoryDriver | `@objectstack/driver-memory` | In-memory database (no DB install) |
48+
| Auth | `@objectstack/plugin-auth` | Authentication with dev credentials |
49+
| Hono Server | `@objectstack/plugin-hono-server` | HTTP server on configured port |
50+
| REST API | `@objectstack/rest` | Auto-generated CRUD endpoints |
51+
52+
All services are **optional** — if a peer package isn't installed, it is silently skipped.
53+
54+
## Options
55+
56+
| Option | Type | Default | Description |
57+
|--------|------|---------|-------------|
58+
| `port` | `number` | `3000` | HTTP server port |
59+
| `seedAdminUser` | `boolean` | `true` | Create `admin@dev.local` on startup |
60+
| `authSecret` | `string` | dev default | JWT secret for auth sessions |
61+
| `authBaseUrl` | `string` | `http://localhost:{port}` | Auth callback URL |
62+
| `verbose` | `boolean` | `true` | Enable verbose logging |
63+
| `services` | `Record<string, boolean>` | all `true` | Enable/disable individual services |
64+
| `extraPlugins` | `Plugin[]` | `[]` | Additional plugins to load |
65+
66+
## License
67+
68+
Apache-2.0
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "@objectstack/plugin-dev",
3+
"version": "2.0.6",
4+
"license": "Apache-2.0",
5+
"description": "Development Mode Plugin for ObjectStack — auto-enables all services with in-memory implementations",
6+
"main": "dist/index.js",
7+
"types": "dist/index.d.ts",
8+
"exports": {
9+
".": {
10+
"types": "./dist/index.d.ts",
11+
"import": "./dist/index.mjs",
12+
"require": "./dist/index.js"
13+
}
14+
},
15+
"scripts": {
16+
"build": "tsup --config ../../../tsup.config.ts",
17+
"test": "vitest run"
18+
},
19+
"dependencies": {
20+
"@objectstack/core": "workspace:*",
21+
"@objectstack/spec": "workspace:*"
22+
},
23+
"peerDependencies": {
24+
"@objectstack/driver-memory": "workspace:*",
25+
"@objectstack/objectql": "workspace:*",
26+
"@objectstack/runtime": "workspace:*",
27+
"@objectstack/plugin-auth": "workspace:*",
28+
"@objectstack/plugin-hono-server": "workspace:*",
29+
"@objectstack/rest": "workspace:*"
30+
},
31+
"peerDependenciesMeta": {
32+
"@objectstack/driver-memory": { "optional": true },
33+
"@objectstack/objectql": { "optional": true },
34+
"@objectstack/runtime": { "optional": true },
35+
"@objectstack/plugin-auth": { "optional": true },
36+
"@objectstack/plugin-hono-server": { "optional": true },
37+
"@objectstack/rest": { "optional": true }
38+
},
39+
"devDependencies": {
40+
"@objectstack/driver-memory": "workspace:*",
41+
"@objectstack/objectql": "workspace:*",
42+
"@objectstack/runtime": "workspace:*",
43+
"@objectstack/plugin-auth": "workspace:*",
44+
"@objectstack/plugin-hono-server": "workspace:*",
45+
"@types/node": "^25.2.2",
46+
"typescript": "^5.0.0",
47+
"vitest": "^4.0.18"
48+
}
49+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { DevPlugin } from './dev-plugin';
3+
4+
describe('DevPlugin', () => {
5+
it('should have correct metadata', () => {
6+
const plugin = new DevPlugin();
7+
expect(plugin.name).toBe('com.objectstack.plugin.dev');
8+
expect(plugin.type).toBe('standard');
9+
expect(plugin.version).toBe('1.0.0');
10+
});
11+
12+
it('should accept default options', () => {
13+
const plugin = new DevPlugin();
14+
expect(plugin).toBeDefined();
15+
});
16+
17+
it('should accept custom options', () => {
18+
const plugin = new DevPlugin({
19+
port: 4000,
20+
seedAdminUser: false,
21+
verbose: false,
22+
services: { auth: false },
23+
});
24+
expect(plugin).toBeDefined();
25+
});
26+
27+
it('should init with mocked context and handle missing deps gracefully', async () => {
28+
const ctx: any = {
29+
logger: {
30+
info: vi.fn(),
31+
debug: vi.fn(),
32+
warn: vi.fn(),
33+
error: vi.fn(),
34+
},
35+
getService: vi.fn().mockImplementation(() => { throw new Error('not found'); }),
36+
getServices: vi.fn().mockReturnValue(new Map()),
37+
registerService: vi.fn(),
38+
hook: vi.fn(),
39+
trigger: vi.fn(),
40+
getKernel: vi.fn(),
41+
};
42+
43+
// DevPlugin should not throw even if peer dependencies are missing
44+
const plugin = new DevPlugin({ seedAdminUser: false });
45+
await expect(plugin.init(ctx)).resolves.not.toThrow();
46+
});
47+
48+
it('should skip disabled services', async () => {
49+
const ctx: any = {
50+
logger: {
51+
info: vi.fn(),
52+
debug: vi.fn(),
53+
warn: vi.fn(),
54+
error: vi.fn(),
55+
},
56+
getService: vi.fn().mockImplementation(() => { throw new Error('not found'); }),
57+
getServices: vi.fn().mockReturnValue(new Map()),
58+
registerService: vi.fn(),
59+
hook: vi.fn(),
60+
trigger: vi.fn(),
61+
getKernel: vi.fn(),
62+
};
63+
64+
const plugin = new DevPlugin({
65+
seedAdminUser: false,
66+
services: {
67+
objectql: false,
68+
driver: false,
69+
auth: false,
70+
server: false,
71+
rest: false,
72+
},
73+
});
74+
75+
await plugin.init(ctx);
76+
77+
// No child plugins should be registered when all disabled
78+
// Logger should show init with 0 services
79+
const lastInfoCall = ctx.logger.info.mock.calls.find(
80+
(call: any[]) => typeof call[0] === 'string' && call[0].includes('initialized'),
81+
);
82+
expect(lastInfoCall).toBeDefined();
83+
expect(lastInfoCall[0]).toContain('0 service');
84+
});
85+
86+
it('should destroy without errors', async () => {
87+
const plugin = new DevPlugin();
88+
await expect(plugin.destroy()).resolves.not.toThrow();
89+
});
90+
});

0 commit comments

Comments
 (0)