@objectql/server 1.3.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,37 +33,49 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.createConsoleHandler = createConsoleHandler;
36
+ exports.createStudioHandler = createStudioHandler;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  /**
40
- * Creates a handler to serve the console UI static files.
40
+ * Creates a handler to serve the Studio UI static files.
41
41
  */
42
- function createConsoleHandler() {
43
- // Try to find the built console files
44
- const possiblePaths = [
45
- path.join(__dirname, '../../console/dist'),
46
- path.join(process.cwd(), 'node_modules/@objectql/console/dist'),
47
- path.join(process.cwd(), 'packages/console/dist'),
48
- ];
42
+ function createStudioHandler() {
49
43
  let distPath = null;
50
- for (const p of possiblePaths) {
51
- if (fs.existsSync(p)) {
52
- distPath = p;
53
- break;
44
+ // 1. Try to resolve from installed package (Standard way)
45
+ try {
46
+ const studioPkg = require.resolve('@objectql/studio/package.json');
47
+ const candidate = path.join(path.dirname(studioPkg), 'dist');
48
+ if (fs.existsSync(candidate)) {
49
+ distPath = candidate;
50
+ }
51
+ }
52
+ catch (e) {
53
+ // @objectql/studio might not be installed
54
+ }
55
+ // 2. Fallback for local development (Monorepo)
56
+ if (!distPath) {
57
+ const possiblePaths = [
58
+ path.join(__dirname, '../../studio/dist'),
59
+ path.join(process.cwd(), 'packages/studio/dist'),
60
+ ];
61
+ for (const p of possiblePaths) {
62
+ if (fs.existsSync(p)) {
63
+ distPath = p;
64
+ break;
65
+ }
54
66
  }
55
67
  }
56
68
  return async (req, res) => {
57
69
  if (!distPath) {
58
- // Return placeholder page if console is not built
70
+ // Return placeholder page if studio is not built
59
71
  const html = getPlaceholderPage();
60
72
  res.setHeader('Content-Type', 'text/html');
61
73
  res.statusCode = 200;
62
74
  res.end(html);
63
75
  return;
64
76
  }
65
- // Parse the URL and remove /console prefix
66
- let urlPath = (req.url || '').replace(/^\/console/, '') || '/';
77
+ // Parse the URL and remove /studio prefix
78
+ let urlPath = (req.url || '').replace(/^\/studio/, '') || '/';
67
79
  // Default to index.html for SPA routing
68
80
  if (urlPath === '/' || !urlPath.includes('.')) {
69
81
  urlPath = '/index.html';
@@ -119,7 +131,7 @@ function getPlaceholderPage() {
119
131
  <head>
120
132
  <meta charset="UTF-8">
121
133
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
122
- <title>ObjectQL Console</title>
134
+ <title>ObjectQL Studio</title>
123
135
  <style>
124
136
  * { margin: 0; padding: 0; box-sizing: border-box; }
125
137
  body {
@@ -155,19 +167,19 @@ function getPlaceholderPage() {
155
167
  </head>
156
168
  <body>
157
169
  <div class="container">
158
- <h1>ObjectQL Console</h1>
159
- <p>Web-based admin console for database management</p>
170
+ <h1>ObjectQL Studio</h1>
171
+ <p>Web-based admin studio for database management</p>
160
172
  <div class="info">
161
173
  <p style="margin-bottom: 1rem;">
162
- The console is available but needs to be built separately.
174
+ The studio is available but needs to be built separately.
163
175
  </p>
164
176
  <p style="font-size: 1rem;">
165
- To use the full console UI, run:<br>
166
- <code>cd packages/console && pnpm run build</code>
177
+ To use the full studio UI, run:<br>
178
+ <code>cd packages/studio && pnpm run build</code>
167
179
  </p>
168
180
  </div>
169
181
  </div>
170
182
  </body>
171
183
  </html>`;
172
184
  }
173
- //# sourceMappingURL=console.js.map
185
+ //# sourceMappingURL=studio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"studio.js","sourceRoot":"","sources":["../src/studio.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,3 +1,29 @@
1
+ /**
2
+ * Standardized error codes for ObjectQL API
3
+ */
4
+ export declare enum ErrorCode {
5
+ INVALID_REQUEST = "INVALID_REQUEST",
6
+ VALIDATION_ERROR = "VALIDATION_ERROR",
7
+ UNAUTHORIZED = "UNAUTHORIZED",
8
+ FORBIDDEN = "FORBIDDEN",
9
+ NOT_FOUND = "NOT_FOUND",
10
+ CONFLICT = "CONFLICT",
11
+ INTERNAL_ERROR = "INTERNAL_ERROR",
12
+ DATABASE_ERROR = "DATABASE_ERROR",
13
+ RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED"
14
+ }
15
+ /**
16
+ * AI context for better logging, debugging, and AI processing
17
+ */
18
+ export interface AIContext {
19
+ intent?: string;
20
+ natural_language?: string;
21
+ use_case?: string;
22
+ [key: string]: unknown;
23
+ }
24
+ /**
25
+ * ObjectQL JSON-RPC style request
26
+ */
1
27
  export interface ObjectQLRequest {
2
28
  user?: {
3
29
  id: string;
@@ -7,11 +33,33 @@ export interface ObjectQLRequest {
7
33
  op: 'find' | 'findOne' | 'create' | 'update' | 'delete' | 'count' | 'action';
8
34
  object: string;
9
35
  args: any;
36
+ ai_context?: AIContext;
10
37
  }
38
+ /**
39
+ * Error details structure
40
+ */
41
+ export interface ErrorDetails {
42
+ field?: string;
43
+ reason?: string;
44
+ fields?: Record<string, string>;
45
+ required_permission?: string;
46
+ user_roles?: string[];
47
+ retry_after?: number;
48
+ [key: string]: unknown;
49
+ }
50
+ /**
51
+ * ObjectQL API response
52
+ */
11
53
  export interface ObjectQLResponse {
12
54
  data?: any;
13
55
  error?: {
14
- code: string;
56
+ code: ErrorCode | string;
15
57
  message: string;
58
+ details?: ErrorDetails;
59
+ };
60
+ meta?: {
61
+ total?: number;
62
+ page?: number;
63
+ per_page?: number;
16
64
  };
17
65
  }
package/dist/types.js CHANGED
@@ -1,3 +1,20 @@
1
1
  "use strict";
2
+ // src/types.ts
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ErrorCode = void 0;
5
+ /**
6
+ * Standardized error codes for ObjectQL API
7
+ */
8
+ var ErrorCode;
9
+ (function (ErrorCode) {
10
+ ErrorCode["INVALID_REQUEST"] = "INVALID_REQUEST";
11
+ ErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
12
+ ErrorCode["UNAUTHORIZED"] = "UNAUTHORIZED";
13
+ ErrorCode["FORBIDDEN"] = "FORBIDDEN";
14
+ ErrorCode["NOT_FOUND"] = "NOT_FOUND";
15
+ ErrorCode["CONFLICT"] = "CONFLICT";
16
+ ErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
17
+ ErrorCode["DATABASE_ERROR"] = "DATABASE_ERROR";
18
+ ErrorCode["RATE_LIMIT_EXCEEDED"] = "RATE_LIMIT_EXCEEDED";
19
+ })(ErrorCode || (exports.ErrorCode = ErrorCode = {}));
3
20
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,eAAe;;;AAEf;;GAEG;AACH,IAAY,SAUX;AAVD,WAAY,SAAS;IACjB,gDAAmC,CAAA;IACnC,kDAAqC,CAAA;IACrC,0CAA6B,CAAA;IAC7B,oCAAuB,CAAA;IACvB,oCAAuB,CAAA;IACvB,kCAAqB,CAAA;IACrB,8CAAiC,CAAA;IACjC,8CAAiC,CAAA;IACjC,wDAA2C,CAAA;AAC/C,CAAC,EAVW,SAAS,yBAAT,SAAS,QAUpB"}
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "@objectql/server",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "HTTP Server Adapter for ObjectQL",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "dependencies": {
8
- "@objectql/core": "1.3.1",
9
- "@objectql/types": "1.3.1"
8
+ "js-yaml": "^4.1.1",
9
+ "@objectql/core": "1.5.0",
10
+ "@objectql/types": "1.5.0"
10
11
  },
11
12
  "devDependencies": {
12
- "typescript": "^5.3.0",
13
- "@types/node": "^20.10.0"
13
+ "@types/js-yaml": "^4.0.9",
14
+ "@types/node": "^20.10.0",
15
+ "typescript": "^5.3.0"
14
16
  },
15
17
  "scripts": {
16
18
  "build": "tsc",
@@ -1,6 +1,6 @@
1
1
  import { IObjectQL } from '@objectql/types';
2
2
  import { ObjectQLServer } from '../server';
3
- import { ObjectQLRequest } from '../types';
3
+ import { ObjectQLRequest, ErrorCode } from '../types';
4
4
  import { IncomingMessage, ServerResponse } from 'http';
5
5
  import { generateOpenAPI } from '../openapi';
6
6
 
@@ -21,50 +21,190 @@ export function createNodeHandler(app: IObjectQL) {
21
21
  return;
22
22
  }
23
23
 
24
- if (req.method !== 'POST') {
25
- res.statusCode = 405;
26
- res.end('Method Not Allowed');
27
- return;
28
- }
29
-
30
24
  const handleRequest = async (json: any) => {
31
25
  try {
32
- // TODO: Parse user from header or request override
26
+ // Determine Operation based on JSON or previously derived info
33
27
  const qlReq: ObjectQLRequest = {
34
28
  op: json.op,
35
29
  object: json.object,
36
30
  args: json.args,
37
- user: json.user // For dev/testing, allowing user injection
31
+ user: json.user,
32
+ ai_context: json.ai_context
38
33
  };
39
34
 
40
35
  const result = await server.handle(qlReq);
41
36
 
37
+ // Determine HTTP status code based on error
38
+ let statusCode = 200;
39
+ if (result.error) {
40
+ switch (result.error.code) {
41
+ case ErrorCode.INVALID_REQUEST:
42
+ case ErrorCode.VALIDATION_ERROR:
43
+ statusCode = 400;
44
+ break;
45
+ case ErrorCode.UNAUTHORIZED:
46
+ statusCode = 401;
47
+ break;
48
+ case ErrorCode.FORBIDDEN:
49
+ statusCode = 403;
50
+ break;
51
+ case ErrorCode.NOT_FOUND:
52
+ statusCode = 404;
53
+ break;
54
+ case ErrorCode.CONFLICT:
55
+ statusCode = 409;
56
+ break;
57
+ case ErrorCode.RATE_LIMIT_EXCEEDED:
58
+ statusCode = 429;
59
+ break;
60
+ default:
61
+ statusCode = 500;
62
+ }
63
+ }
64
+
42
65
  res.setHeader('Content-Type', 'application/json');
43
- res.statusCode = result.error ? 500 : 200;
66
+ res.statusCode = statusCode;
44
67
  res.end(JSON.stringify(result));
45
68
  } catch (e) {
69
+ console.error(e);
46
70
  res.statusCode = 500;
47
- res.end(JSON.stringify({ error: { code: 'INTERNAL_ERROR', message: 'Internal Server Error' }}));
71
+ res.end(JSON.stringify({
72
+ error: {
73
+ code: ErrorCode.INTERNAL_ERROR,
74
+ message: 'Internal Server Error'
75
+ }
76
+ }));
48
77
  }
49
78
  };
50
79
 
51
- // 1. Check if body is already parsed (e.g. by express.json())
52
- if (req.body && typeof req.body === 'object') {
53
- await handleRequest(req.body);
54
- return;
80
+ // Parse URL
81
+ const urlObj = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
82
+ const pathName = urlObj.pathname;
83
+ const method = req.method;
84
+
85
+ // 1. JSON-RPC: POST /api/objectql
86
+ if (pathName === '/api/objectql' && method === 'POST') {
87
+ await processBody(req, async (json) => {
88
+ await handleRequest(json);
89
+ }, res);
90
+ return;
55
91
  }
56
92
 
57
- // 2. Parse Body from stream
58
- let body = '';
59
- req.on('data', chunk => body += chunk);
60
- req.on('end', async () => {
61
- try {
62
- const json = JSON.parse(body);
63
- await handleRequest(json);
64
- } catch (e) {
65
- res.statusCode = 400;
66
- res.end('Invalid JSON');
93
+ // 2. REST API: /api/data/:object and /api/data/:object/:id
94
+ // Regex to match /api/data/objectName(/id)?
95
+ const restMatch = pathName.match(/^\/api\/data\/([^/]+)(?:\/(.+))?$/);
96
+
97
+ if (restMatch) {
98
+ const objectName = restMatch[1];
99
+ const id = restMatch[2];
100
+ const query = Object.fromEntries(urlObj.searchParams.entries());
101
+
102
+ if (method === 'GET') {
103
+ // GET /api/data/:object/:id -> findOne
104
+ if (id) {
105
+ await handleRequest({
106
+ op: 'findOne',
107
+ object: objectName,
108
+ args: { filters: [['_id', '=', id]] } // Assuming _id or id mapping
109
+ });
110
+ }
111
+ // GET /api/data/:object -> find (List)
112
+ else {
113
+ // Parse standard params
114
+ const args: any = {};
115
+ if (query.fields) args.fields = (query.fields as string).split(',');
116
+ if (query.top) args.limit = parseInt(query.top as string);
117
+ if (query.skip) args.skip = parseInt(query.skip as string);
118
+ if (query.filter) {
119
+ try {
120
+ args.filters = JSON.parse(query.filter as string);
121
+ } catch (e) {
122
+ // ignore invalid filter json
123
+ }
124
+ }
125
+ await handleRequest({ op: 'find', object: objectName, args });
126
+ }
127
+ return;
128
+ }
129
+
130
+ if (method === 'POST' && !id) {
131
+ // POST /api/data/:object -> create
132
+ await processBody(req, async (body) => {
133
+ await handleRequest({
134
+ op: 'create',
135
+ object: objectName,
136
+ args: body.data || body // Support enclosed in data or flat
137
+ });
138
+ }, res);
139
+ return;
140
+ }
141
+
142
+ if (method === 'PATCH' && id) {
143
+ // PATCH /api/data/:object/:id -> update
144
+ await processBody(req, async (body) => {
145
+ await handleRequest({
146
+ op: 'update',
147
+ object: objectName,
148
+ args: {
149
+ id: id,
150
+ data: body.data || body
151
+ }
152
+ });
153
+ }, res);
154
+ return;
67
155
  }
68
- });
156
+
157
+ if (method === 'DELETE' && id) {
158
+ // DELETE /api/data/:object/:id -> delete
159
+ await handleRequest({
160
+ op: 'delete',
161
+ object: objectName,
162
+ args: { id: id }
163
+ });
164
+ return;
165
+ }
166
+ }
167
+
168
+ // Fallback or 404
169
+ if (req.method === 'POST') {
170
+ // Fallback for root POSTs if people forget /api/objectql but send to /api something
171
+ await processBody(req, handleRequest, res);
172
+ return;
173
+ }
174
+
175
+ // Special case for root: since we accept POST / (RPC), correct response for GET / is 405
176
+ if (pathName === '/') {
177
+ res.setHeader('Allow', 'POST');
178
+ res.statusCode = 405;
179
+ res.end(JSON.stringify({ error: { code: ErrorCode.INVALID_REQUEST, message: 'Method Not Allowed. Use POST for JSON-RPC.' } }));
180
+ return;
181
+ }
182
+
183
+ res.statusCode = 404;
184
+ res.end(JSON.stringify({ error: { code: ErrorCode.NOT_FOUND, message: 'Not Found' } }));
69
185
  };
70
186
  }
187
+
188
+ // Helper to process body
189
+ async function processBody(req: IncomingMessage & { body?: any }, callback: (json: any) => Promise<void>, res: ServerResponse) {
190
+ if (req.body && typeof req.body === 'object') {
191
+ return callback(req.body);
192
+ }
193
+
194
+ let body = '';
195
+ req.on('data', chunk => body += chunk);
196
+ req.on('end', async () => {
197
+ try {
198
+ const json = body ? JSON.parse(body) : {};
199
+ await callback(json);
200
+ } catch (e) {
201
+ res.statusCode = 400;
202
+ res.end(JSON.stringify({
203
+ error: {
204
+ code: 'INVALID_JSON',
205
+ message: 'Invalid JSON body'
206
+ }
207
+ }));
208
+ }
209
+ });
210
+ }