Skip to content

Refactor API to conform with new specifications#10

Merged
hotlong merged 13 commits into
mainfrom
copilot/refactor-api-according-to-spec
Jan 11, 2026
Merged

Refactor API to conform with new specifications#10
hotlong merged 13 commits into
mainfrom
copilot/refactor-api-according-to-spec

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 11, 2026

API Refactoring According to New Specification

Successfully completed the full refactoring of the ObjectQL server package to fully implement the new API specification documented in docs/api/README.md.

Implementation Status

  • ✅ JSON-RPC API endpoint (POST /api/objectql) - Fully implemented with enhanced error handling
  • ✅ REST-style API (/api/data/:object) - Fully implemented
  • ✅ Metadata API - Enhanced with field and action endpoints
  • ✅ Proper error handling with standardized error codes - Fully implemented
  • ✅ AI context support - Fully implemented
  • ✅ Field-level metadata endpoints - Fully implemented
  • ✅ Actions metadata endpoint - Fully implemented

Changes Made

1. REST API Implementation

Created a complete REST-style API adapter (packages/server/src/adapters/rest.ts) supporting:

  • GET /api/data/:object - List records with query parameters (filter, fields, sort, top, skip, expand)
  • GET /api/data/:object/:id - Get single record
  • POST /api/data/:object - Create record (returns HTTP 201)
  • PUT/PATCH /api/data/:object/:id - Update record
  • DELETE /api/data/:object/:id - Delete record

2. Enhanced Error Handling

  • Added ErrorCode enum with standardized codes: VALIDATION_ERROR, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT, INTERNAL_ERROR, DATABASE_ERROR, RATE_LIMIT_EXCEEDED
  • Proper HTTP status code mapping (400, 401, 403, 404, 409, 429, 500)
  • Detailed error responses with code, message, and optional details

3. AI Context Support

  • Extended ObjectQLRequest type with optional ai_context field
  • Added AIContext interface supporting intent, natural language descriptions, and use case tracking
  • Integrated logging for AI explainability

4. Metadata API Enhancements

  • Added GET /api/metadata/objects/:name/fields/:field - Get detailed field metadata
  • Added GET /api/metadata/objects/:name/actions - List available actions
  • Enhanced field metadata to include validation properties (min, max, regex, etc.)

5. Updated Example Application

  • Updated examples/starters/express-api to demonstrate all API styles (JSON-RPC, REST, Metadata)
  • Added comprehensive curl command examples
  • Clear console output showing all available endpoints

6. Comprehensive Testing

  • Added 7 new REST API tests (packages/server/test/rest.test.ts)
  • Improved test quality with dynamic ID generation and efficient server reuse
  • All 27 tests passing (17 core + 10 server)

Available API Endpoints

  1. JSON-RPC API: POST /api/objectql
  2. REST API: GET/POST/PUT/DELETE /api/data/:object
  3. Metadata API:
    • GET /api/metadata/objects - List all objects
    • GET /api/metadata/objects/:name - Get object schema
    • GET /api/metadata/objects/:name/fields/:field - Get field metadata
    • GET /api/metadata/objects/:name/actions - List actions
  4. OpenAPI: GET /api/objectql/openapi.json

Testing & Quality

  • ✅ All 27 tests passing
  • ✅ Zero TypeScript errors
  • ✅ All packages build successfully
  • ✅ Zero security vulnerabilities (CodeQL analysis)
  • ✅ Code review feedback addressed

See API_REFACTORING_SUMMARY.md for detailed usage examples and implementation details.

Original prompt

帮我根据新的API规范重构API


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI self-assigned this Jan 11, 2026
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong
Copy link
Copy Markdown
Contributor

hotlong commented Jan 11, 2026

@copilot 继续

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 11, 2026

@copilot 继续

已完成API重构。主要更新:

✅ 实现了完整的REST API适配器 (/api/data/:object)
✅ 增强了错误处理,支持标准化错误代码
✅ 添加了AI上下文支持
✅ 完善了元数据API端点(字段和动作)
✅ 所有测试通过 (10/10)

提交: 3c395a9

Copilot AI and others added 2 commits January 11, 2026 09:22
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor API to conform with new specifications Refactor API to conform with new specifications Jan 11, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request successfully implements a comprehensive API refactoring to conform with new specifications documented in docs/api/README.md. The refactoring introduces REST-style API endpoints, enhanced error handling with standardized error codes, AI context support for explainability, and expanded metadata API capabilities.

Changes:

  • Implements complete REST API adapter with CRUD operations mapped to HTTP methods
  • Adds standardized error codes and HTTP status code mapping across all adapters
  • Introduces AI context support for request tracking and debugging
  • Enhances metadata API with field-level and actions endpoints
  • Updates example application to demonstrate all API styles

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
packages/server/src/types.ts Adds ErrorCode enum, AIContext and ErrorDetails interfaces, extends ObjectQLRequest and ObjectQLResponse
packages/server/src/server.ts Implements AI context logging, enhanced error handling, improved findOne with string ID support, standardized delete responses
packages/server/src/adapters/rest.ts NEW: Complete REST adapter supporting GET/POST/PUT/DELETE with query parameter parsing and error mapping
packages/server/src/adapters/node.ts Enhances JSON-RPC handler with AI context support and proper HTTP status codes
packages/server/src/metadata.ts Adds field-level metadata endpoint and actions listing endpoint with enhanced error responses
packages/server/src/index.ts Exports REST adapter for public use
packages/server/test/rest.test.ts NEW: 7 REST API tests covering CRUD operations and error cases
examples/starters/express-api/src/index.ts Integrates REST handler and updates console output with all endpoint examples
API_REFACTORING_SUMMARY.md NEW: Comprehensive documentation of refactoring changes with usage examples

Comment thread packages/server/test/rest.test.ts Outdated
Comment thread packages/server/src/adapters/rest.ts Outdated
Comment on lines +24 to +30
// Try to parse JSON values
try {
params[decodedKey] = JSON.parse(decodedValue);
} catch {
params[decodedKey] = decodedValue;
}
}
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential security vulnerability: The parseQueryParams function uses JSON.parse on user-supplied query parameter values without proper error handling or validation. A malicious user could potentially supply crafted JSON that could cause issues. While there's a try-catch that falls back to the raw string, there's no validation of what the parsed JSON contains (e.g., it could be an object with prototype pollution attempts).

Copilot uses AI. Check for mistakes.
Comment thread packages/server/src/types.ts Outdated
Comment thread packages/server/src/types.ts Outdated
Comment thread packages/server/src/metadata.ts Outdated
Comment on lines +111 to 156
private handleError(error: any): ObjectQLResponse {
console.error('[ObjectQL Server] Error:', error);

// Handle validation errors
if (error.name === 'ValidationError' || error.code === 'VALIDATION_ERROR') {
return this.errorResponse(
ErrorCode.VALIDATION_ERROR,
'Validation failed',
{ fields: error.fields || error.details }
);
}

// Handle permission errors
if (error.name === 'PermissionError' || error.code === 'FORBIDDEN') {
return this.errorResponse(
ErrorCode.FORBIDDEN,
error.message || 'You do not have permission to access this resource',
error.details
);
}

// Handle not found errors
if (error.name === 'NotFoundError' || error.code === 'NOT_FOUND') {
return this.errorResponse(
ErrorCode.NOT_FOUND,
error.message || 'Resource not found'
);
}

// Handle conflict errors (e.g., unique constraint violations)
if (error.name === 'ConflictError' || error.code === 'CONFLICT') {
return this.errorResponse(
ErrorCode.CONFLICT,
error.message || 'Resource conflict',
error.details
);
}

// Handle database errors
if (error.name === 'DatabaseError' || error.code?.startsWith('DB_')) {
return this.errorResponse(
ErrorCode.DATABASE_ERROR,
'Database operation failed',
{ originalError: error.message }
);
}
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling relies on string matching of error names and codes (e.g., error.name === 'ValidationError' or error.code === 'VALIDATION_ERROR'). This is fragile and could break if error sources change their error naming conventions. Consider using instanceof checks for error classes or a more robust error type system.

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +160
describe('REST API Adapter', () => {
let app: ObjectQL;
let server: any;
let handler: any;

beforeAll(async () => {
app = new ObjectQL({
datasources: {
default: new MockDriver()
}
});

// Manual schema registration
app.metadata.register('object', {
type: 'object',
id: 'user',
content: {
name: 'user',
fields: {
name: { type: 'text' },
email: { type: 'email' }
}
}
});

// Create handler and server once for all tests
handler = createRESTHandler(app);
server = createServer(handler);
});

it('should handle GET /api/data/:object - List records', async () => {
const response = await request(server)
.get('/api/data/user')
.set('Accept', 'application/json');

expect(response.status).toBe(200);
expect(response.body.data).toHaveLength(2);
expect(response.body.data[0].name).toBe('Alice');
});

it('should handle GET /api/data/:object/:id - Get single record', async () => {
const response = await request(server)
.get('/api/data/user/1')
.set('Accept', 'application/json');

expect(response.status).toBe(200);
expect(response.body.data.name).toBe('Alice');
});

it('should handle POST /api/data/:object - Create record', async () => {
const response = await request(server)
.post('/api/data/user')
.send({ name: 'Charlie', email: 'charlie@example.com' })
.set('Accept', 'application/json');

expect(response.status).toBe(201);
expect(response.body.data.name).toBe('Charlie');
expect(response.body.data._id).toBeDefined();
});

it('should handle PUT /api/data/:object/:id - Update record', async () => {
const response = await request(server)
.put('/api/data/user/1')
.send({ name: 'Alice Updated' })
.set('Accept', 'application/json');

expect(response.status).toBe(200);
});

it('should handle DELETE /api/data/:object/:id - Delete record', async () => {
const response = await request(server)
.delete('/api/data/user/1')
.set('Accept', 'application/json');

expect(response.status).toBe(200);
expect(response.body.data.deleted).toBe(true);
});

it('should return 404 for non-existent object', async () => {
const response = await request(server)
.get('/api/data/nonexistent')
.set('Accept', 'application/json');

expect(response.status).toBe(404);
expect(response.body.error.code).toBe('NOT_FOUND');
});

it('should return 400 for update without ID', async () => {
const response = await request(server)
.put('/api/data/user')
.send({ name: 'Test' })
.set('Accept', 'application/json');

expect(response.status).toBe(400);
expect(response.body.error.code).toBe('INVALID_REQUEST');
});
});
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test suite shares state through a single MockDriver instance that is modified across tests (e.g., the POST test creates a user with _id '3', the PUT test updates user '1', and the DELETE test deletes user '1'). This creates interdependencies between tests where later tests depend on the state created/modified by earlier tests. Tests should be independent and idempotent. Consider resetting the driver state in beforeEach or using separate data for each test.

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +181
// GET /api/metadata/objects/:name/fields/:field - Get field metadata
const fieldMatch = url.match(/^\/api\/metadata\/objects\/([^\/]+)\/fields\/([^\/\?]+)$/);
if (method === 'GET' && fieldMatch) {
const [, objectName, fieldName] = fieldMatch;
const metadata = app.getObject(objectName);

if (!metadata) {
res.statusCode = 404;
res.end(JSON.stringify({
error: {
code: ErrorCode.NOT_FOUND,
message: `Object '${objectName}' not found`
}
}));
return;
}

const field = metadata.fields?.[fieldName];
if (!field) {
res.statusCode = 404;
res.end(JSON.stringify({
error: {
code: ErrorCode.NOT_FOUND,
message: `Field '${fieldName}' not found in object '${objectName}'`
}
}));
return;
}

res.setHeader('Content-Type', 'application/json');
res.statusCode = 200;
res.end(JSON.stringify({
name: field.name || fieldName,
type: field.type,
label: field.label,
required: field.required,
unique: field.unique,
defaultValue: field.defaultValue,
options: field.options,
min: field.min,
max: field.max,
min_length: field.min_length,
max_length: field.max_length,
regex: field.regex
}));
return;
}

// GET /api/metadata/objects/:name/actions - List actions
const actionsMatch = url.match(/^\/api\/metadata\/objects\/([^\/]+)\/actions$/);
if (method === 'GET' && actionsMatch) {
const [, objectName] = actionsMatch;
const metadata = app.getObject(objectName);

if (!metadata) {
res.statusCode = 404;
res.end(JSON.stringify({
error: {
code: ErrorCode.NOT_FOUND,
message: `Object '${objectName}' not found`
}
}));
return;
}

const actions = metadata.actions || {};
const formattedActions = Object.entries(actions).map(([key, action]) => ({
name: key,
type: action.type || 'record',
label: action.label || key,
params: action.params || {},
description: action.description
}));

res.setHeader('Content-Type', 'application/json');
res.statusCode = 200;
res.end(JSON.stringify({ actions: formattedActions }));
return;
}
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new metadata endpoints added in this PR (field-level metadata at lines 104-149 and actions metadata at lines 152-181) lack test coverage. While other endpoints may have tests, these new features should have dedicated tests to ensure they work correctly and to prevent regressions.

Copilot uses AI. Check for mistakes.
Comment thread packages/server/test/rest.test.ts Outdated
hotlong and others added 8 commits January 11, 2026 18:25
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@hotlong hotlong marked this pull request as ready for review January 11, 2026 10:28
@hotlong hotlong merged commit 06a885b into main Jan 11, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants