Skip to content

Commit 9fe9c74

Browse files
committed
Add HTML data grid, update docs and CLI, improve GraphQL UX
Introduces a new agrid.html file with a web component for ObjectQL data grids. Updates documentation to reference Scalar API docs instead of Swagger, clarifies OpenAPI endpoints, and improves CLI serve/dev commands to output type generation paths and serve Scalar docs at the root. Adds Apollo Sandbox as the default GraphQL playground. Adjusts .gitignore to exclude generated types. Refines package.json scripts for clarity and disables example modules in config.
1 parent 4a73cd1 commit 9fe9c74

File tree

8 files changed

+71
-30
lines changed

8 files changed

+71
-30
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ docs/.vitepress/dist
1515
*.db
1616

1717
# Example tutorials CHANGELOG (auto-generated, not tracked)
18-
examples/tutorials/*/CHANGELOG.md
18+
examples/tutorials/*/CHANGELOG.md
19+
# Generated Types
20+
generated/

docs/api/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,5 @@ curl -X POST http://localhost:3000/api/objectql \
8686
### Auto-Generated Specs
8787

8888
For automated tool ingestion, use the following endpoints:
89-
- **OpenAPI / Swagger**: `/api/docs/swagger.json`
89+
- **OpenAPI / Swagger**: `/openapi.json` (Used by `/docs` UI)
9090
- **GraphQL Schema**: `/api/graphql/schema`

docs/guide/server-integration.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,19 @@ interface ObjectQLRequest {
7373
}
7474
```
7575

76-
## 📖 OpenAPI / Swagger Support
76+
## 📖 OpenAPI Support
7777

7878
`@objectql/server` automatically generates an OpenAPI 3.0 specification from your registered schema.
79+
This powers the built-in **API Docs** (available at `/docs` in CLI mode).
7980

80-
You can access the spec by appending `/openapi.json` to your handler's mount path.
81+
You can access the raw spec by appending `/openapi.json` to your handler's mount path.
8182

8283
**Example URLs:**
8384
* **CLI Serve:** `http://localhost:3000/openapi.json`
8485
* **Express (mounted at /api/objectql):** `http://localhost:3000/api/objectql/openapi.json`
8586
* **Next.js (pages/api/objectql.ts):** `http://localhost:3000/api/objectql/openapi.json`
8687

87-
This JSON file describes your data objects as "Virtual REST" endpoints (`GET /user`, `POST /user`, etc.), allowing you to easily import them into **Swagger UI**, **Postman**, or other API tools for visualization and testing.
88+
This JSON file describes your data objects as "Virtual REST" endpoints (`GET /user`, `POST /user`, etc.), allowing you to easily import them into **Scalar**, **Swagger UI**, **Postman**, or other API tools for visualization and testing.
8889

8990
## Example Usage
9091

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
"name": "objectql-monorepo",
33
"private": true,
44
"scripts": {
5-
"dev": "tsc -b -w",
5+
"dev": "pnpm example:tracker",
66
"build": "tsc -b && pnpm -r run build",
77
"clean": "rm -rf dist packages/*/dist packages/*/node_modules node_modules",
88
"test": "pnpm run check-versions && pnpm -r run test",
99
"lint": "eslint packages --ext .ts,.tsx",
1010
"format": "prettier --write \"packages/**/*.{ts,tsx,json,md}\"",
1111

12-
"objectql": "node packages/tools/cli/dist/index.js",
1312
"cli:dev": "ts-node packages/tools/cli/src/index.ts",
1413

15-
"example:tracker": "pnpm objectql dev -d examples/showcase/project-tracker/src",
16-
"example:erp": "pnpm objectql dev -d examples/showcase/enterprise-erp/src",
14+
"objectql": "pnpm objectql",
15+
"objectql:tracker": "pnpm objectql dev -d examples/showcase/project-tracker/src",
16+
"objectql:erp": "pnpm objectql dev -d examples/showcase/enterprise-erp/src",
1717

1818
"check-versions": "node scripts/check-versions.js",
1919
"docs:dev": "vitepress dev docs",

packages/runtime/server/src/adapters/graphql.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ import { ErrorCode } from '../types';
44
import { IncomingMessage, ServerResponse } from 'http';
55
import { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBoolean, GraphQLList, GraphQLNonNull, GraphQLInputObjectType, GraphQLFieldConfigMap, GraphQLOutputType, GraphQLInputType } from 'graphql';
66

7+
const APOLLO_SANDBOX_HTML = `
8+
<!DOCTYPE html>
9+
<html lang="en">
10+
<body style="margin: 0; overflow-x: hidden; overflow-y: hidden">
11+
<div id="sandbox" style="height:100vh; width:100vw;"></div>
12+
<script src="https://embeddable-sandbox.cdn.apollographql.com/_latest/embeddable-sandbox.umd.production.min.js"></script>
13+
<script>
14+
new window.EmbeddedSandbox({
15+
target: "#sandbox",
16+
initialEndpoint: window.location.href,
17+
includeCookies: false, // Set to true if you need auth cookies
18+
});
19+
</script>
20+
</body>
21+
</html>`;
22+
723
/**
824
* Normalize ObjectQL response to use 'id' instead of '_id'
925
*/
@@ -421,6 +437,18 @@ export function createGraphQLHandler(app: IObjectQL) {
421437
return;
422438
}
423439

440+
// HTML Playground Support (Apollo Sandbox)
441+
// If it's a browser GET request without query params, show the playground
442+
const acceptHeader = req.headers.accept || '';
443+
const urlObj = new URL(req.url || '', `http://${req.headers.host || 'localhost'}`);
444+
const hasQueryParams = urlObj.searchParams.has('query');
445+
446+
if (req.method === 'GET' && acceptHeader.includes('text/html') && !hasQueryParams) {
447+
res.writeHead(200, { 'Content-Type': 'text/html' });
448+
res.end(APOLLO_SANDBOX_HTML);
449+
return;
450+
}
451+
424452
const url = req.url || '';
425453
const method = req.method || 'POST';
426454

packages/tools/cli/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ objectql dev --no-watch
449449
```
450450

451451
The development server provides:
452-
- **Swagger UI**: `http://localhost:<port>/swagger` - Interactive API documentation
452+
- **API Docs (Scalar)**: `http://localhost:<port>/docs` - Interactive API documentation
453453
- **API Endpoint**: `http://localhost:<port>/` - Main API endpoint
454454
- **OpenAPI Spec**: `http://localhost:<port>/openapi.json` - Machine-readable API spec
455455

@@ -602,7 +602,7 @@ objectql serve --dir ./src/schema --port 8080
602602
```
603603

604604
The server exposes:
605-
- **Web Console (Swagger UI)**: `http://localhost:<port>/swagger` (GET) - Interactive API explorer
605+
- **Web Console (API Docs)**: `http://localhost:<port>/docs` (GET) - Interactive API explorer
606606
- **JSON API Endpoint**: `http://localhost:<port>/` (POST)
607607
- **OpenAPI Spec**: `http://localhost:<port>/openapi.json` (GET)
608608

packages/tools/cli/src/commands/dev.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ export async function dev(options: {
1818
console.log(chalk.cyan('🚀 Starting ObjectQL Development Server...'));
1919

2020
const sourceDir = path.resolve(process.cwd(), options.dir || '.');
21-
// Default output for types in dev mode
22-
const typeOutputDir = path.resolve(process.cwd(), 'src/generated');
21+
// Output types to the source directory under 'generated' folder
22+
const typeOutputDir = path.join(sourceDir, 'generated');
2323

2424
// 1. Initial Type Generation
2525
try {
@@ -35,6 +35,7 @@ export async function dev(options: {
3535
}
3636

3737
console.log(chalk.blue(`\n🌐 Server context: ${sourceDir}`));
38+
console.log(chalk.blue(`📁 Type output: ${typeOutputDir}`));
3839

3940
// 3. Start Server
4041
await serve({
@@ -51,11 +52,15 @@ function startWatcher(sourceDir: string, outputDir: string) {
5152
console.log(chalk.yellow('👀 Watching for metadata changes...'));
5253

5354
try {
54-
// Recursive watch if supported (macOS/Windows support strict recursive)
55-
// Linux might need fallback or libraries like chokidar, but we use fs.watch for zero-dep strictness
55+
// Recursive watch
5656
const watcher = fs.watch(sourceDir, { recursive: true }, (eventType, filename) => {
5757
if (!filename) return;
5858

59+
// Ignore generated directory to prevent loops if output is inside source
60+
if (filename.includes('generated') || filename.includes('node_modules') || filename.includes('.git')) {
61+
return;
62+
}
63+
5964
// Only care about YAML
6065
if (filename.endsWith('.yml') || filename.endsWith('.yaml')) {
6166
// Debounce
@@ -69,14 +74,13 @@ function startWatcher(sourceDir: string, outputDir: string) {
6974
} catch (e) {
7075
console.error(chalk.red('Type generation failed:'), e);
7176
}
72-
}, 500); // 500ms debounce
77+
}, 500);
7378
}
7479
});
7580

7681
watcher.on('error', (e) => console.error(chalk.red('Watcher error:'), e));
7782

7883
} catch (e) {
7984
console.warn(chalk.yellow('Native recursive watch not supported or failed. Watching root only.'));
80-
// Fallback logic could go here
8185
}
8286
}

packages/tools/cli/src/commands/serve.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { ObjectQL } from '@objectql/core';
22
import { SqlDriver } from '@objectql/driver-sql';
33
import { ObjectLoader, loadModules } from '@objectql/platform-node';
4-
import { createNodeHandler } from '@objectql/server';
4+
import { createNodeHandler, createGraphQLHandler } from '@objectql/server';
55
import { createServer } from 'http';
66
import * as path from 'path';
77
import * as fs from 'fs';
88
import chalk from 'chalk';
99

10-
const CONSOLE_HTML = `
10+
const SCALAR_HTML = `
1111
<!DOCTYPE html>
1212
<html>
1313
<head>
14-
<title>ObjectQL API Reference (Scalar)</title>
14+
<title>ObjectQL API Reference</title>
1515
<meta charset="utf-8" />
1616
<meta name="viewport" content="width=device-width, initial-scale=1" />
1717
<style>
@@ -122,20 +122,26 @@ export async function serve(options: {
122122

123123
// 3. Create Handler
124124
const internalHandler = createNodeHandler(app);
125+
const graphqlHandler = createGraphQLHandler(app);
125126

126127
// 4. Start Server
127128
const server = createServer(async (req, res) => {
128-
// Serve Swagger UI
129-
if (req.method === 'GET' && (req.url === '/swagger' || req.url === '/swagger/')) {
129+
// Serve API Docs at Root (Default)
130+
if (req.method === 'GET' && (req.url === '/' || req.url === '/docs' || req.url === '/docs/')) {
130131
res.writeHead(200, { 'Content-Type': 'text/html' });
131-
res.end(CONSOLE_HTML);
132+
res.end(SCALAR_HTML);
132133
return;
133134
}
134135

135-
// Redirect / to /swagger for better DX
136-
if (req.method === 'GET' && req.url === '/') {
137-
res.writeHead(302, { 'Location': '/swagger' });
138-
res.end();
136+
// GraphQL Endpoint & Playground (Keep for compatibility)
137+
if (req.url === '/graphql' || req.url === '/graphql/') {
138+
await graphqlHandler(req, res);
139+
return;
140+
}
141+
142+
// Keep /api/graphql as alias for compatibility
143+
if (req.url?.startsWith('/api/graphql')) {
144+
await graphqlHandler(req, res);
139145
return;
140146
}
141147

@@ -169,10 +175,10 @@ export async function serve(options: {
169175
server.listen(port, () => {
170176
server.removeListener('error', onError);
171177
console.log(chalk.green(`\n🚀 Server ready at http://localhost:${port}`));
172-
console.log(chalk.green(`📚 Swagger UI: http://localhost:${port}/swagger`));
173-
console.log(chalk.blue(`📖 OpenAPI Spec: http://localhost:${port}/openapi.json`));
178+
console.log(chalk.blue(`📖 API Docs (Scalar): http://localhost:${port}/`));
179+
174180
console.log(chalk.gray('\nTry a curl command:'));
175-
console.log(`curl -X POST http://localhost:${port} -H "Content-Type: application/json" -d '{"op": "find", "object": "YourObject", "args": {}}'`);
181+
console.log(`curl -X POST http://localhost:${port} -H "Content-Type: application/json" -d '{"op": "find", "object": "tasks", "args": {}}'`);
176182
});
177183
};
178184

0 commit comments

Comments
 (0)