@objectql/server 1.7.2 → 1.8.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.
- package/CHANGELOG.md +21 -0
- package/LICENSE +1 -1
- package/README.md +93 -0
- package/dist/adapters/graphql.d.ts +17 -0
- package/dist/adapters/graphql.js +460 -0
- package/dist/adapters/graphql.js.map +1 -0
- package/dist/adapters/rest.d.ts +7 -5
- package/dist/adapters/rest.js +52 -15
- package/dist/adapters/rest.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/metadata.js +7 -7
- package/dist/metadata.js.map +1 -1
- package/dist/server.d.ts +4 -0
- package/dist/server.js +85 -10
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +15 -8
- package/package.json +6 -3
- package/src/adapters/graphql.ts +523 -0
- package/src/adapters/rest.ts +49 -15
- package/src/index.ts +2 -0
- package/src/metadata.ts +7 -7
- package/src/server.ts +100 -11
- package/src/types.ts +24 -8
- package/test/graphql.test.ts +439 -0
- package/test/integration-example.ts +247 -0
- package/test/node.test.ts +4 -2
- package/test/rest-advanced.test.ts +455 -0
- package/test/rest.test.ts +283 -7
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Example: Demonstrates the new standardized API response format
|
|
3
|
+
*
|
|
4
|
+
* This file is for documentation purposes and shows how the API responses
|
|
5
|
+
* look with the new standardized format.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ObjectQL } from '@objectql/core';
|
|
9
|
+
import { createRESTHandler } from '../src/adapters/rest';
|
|
10
|
+
import { Driver } from '@objectql/types';
|
|
11
|
+
|
|
12
|
+
// Example: Setting up ObjectQL with a simple in-memory driver
|
|
13
|
+
class InMemoryDriver implements Driver {
|
|
14
|
+
private data: Record<string, any[]> = {};
|
|
15
|
+
|
|
16
|
+
async init() {}
|
|
17
|
+
|
|
18
|
+
async find(objectName: string, query: any) {
|
|
19
|
+
let items = this.data[objectName] || [];
|
|
20
|
+
|
|
21
|
+
// Apply skip and limit for pagination
|
|
22
|
+
if (query) {
|
|
23
|
+
if (query.skip) {
|
|
24
|
+
items = items.slice(query.skip);
|
|
25
|
+
}
|
|
26
|
+
if (query.limit) {
|
|
27
|
+
items = items.slice(0, query.limit);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return items;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async findOne(objectName: string, id: string | number) {
|
|
35
|
+
const items = this.data[objectName] || [];
|
|
36
|
+
return items.find(item => item.id === String(id)) || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async create(objectName: string, data: any) {
|
|
40
|
+
if (!this.data[objectName]) {
|
|
41
|
+
this.data[objectName] = [];
|
|
42
|
+
}
|
|
43
|
+
const newItem = { ...data, id: String(Date.now()) };
|
|
44
|
+
this.data[objectName].push(newItem);
|
|
45
|
+
return newItem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async update(objectName: string, id: string | number, data: any) {
|
|
49
|
+
const items = this.data[objectName] || [];
|
|
50
|
+
const index = items.findIndex(item => item.id === String(id));
|
|
51
|
+
if (index >= 0) {
|
|
52
|
+
this.data[objectName][index] = { ...items[index], ...data };
|
|
53
|
+
return this.data[objectName][index];
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async delete(objectName: string, id: string | number) {
|
|
59
|
+
const items = this.data[objectName] || [];
|
|
60
|
+
const index = items.findIndex(item => item.id === String(id));
|
|
61
|
+
if (index >= 0) {
|
|
62
|
+
this.data[objectName].splice(index, 1);
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async count(objectName: string) {
|
|
69
|
+
return (this.data[objectName] || []).length;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Seed some sample data
|
|
73
|
+
seed(objectName: string, items: any[]) {
|
|
74
|
+
this.data[objectName] = items;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Initialize ObjectQL
|
|
79
|
+
const driver = new InMemoryDriver();
|
|
80
|
+
const app = new ObjectQL({
|
|
81
|
+
datasources: {
|
|
82
|
+
default: driver
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Register a simple object schema
|
|
87
|
+
app.metadata.register('object', {
|
|
88
|
+
type: 'object',
|
|
89
|
+
id: 'contract',
|
|
90
|
+
content: {
|
|
91
|
+
name: 'contract',
|
|
92
|
+
label: 'Contract',
|
|
93
|
+
fields: {
|
|
94
|
+
name: { type: 'text', label: 'Contract Name' },
|
|
95
|
+
amount: { type: 'number', label: 'Amount' },
|
|
96
|
+
status: { type: 'text', label: 'Status' }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Seed sample data
|
|
102
|
+
driver.seed('contract', [
|
|
103
|
+
{ id: '1', name: 'Contract A', amount: 5000, status: 'active' },
|
|
104
|
+
{ id: '2', name: 'Contract B', amount: 3000, status: 'pending' },
|
|
105
|
+
{ id: '3', name: 'Contract C', amount: 7500, status: 'active' },
|
|
106
|
+
{ id: '4', name: 'Contract D', amount: 2000, status: 'completed' },
|
|
107
|
+
{ id: '5', name: 'Contract E', amount: 9000, status: 'active' },
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Example 1: List all contracts (no pagination)
|
|
112
|
+
*
|
|
113
|
+
* Request: GET /api/data/contract
|
|
114
|
+
*
|
|
115
|
+
* Response:
|
|
116
|
+
* {
|
|
117
|
+
* "items": [
|
|
118
|
+
* { "id": "1", "name": "Contract A", "amount": 5000, "status": "active" },
|
|
119
|
+
* { "id": "2", "name": "Contract B", "amount": 3000, "status": "pending" },
|
|
120
|
+
* { "id": "3", "name": "Contract C", "amount": 7500, "status": "active" },
|
|
121
|
+
* { "id": "4", "name": "Contract D", "amount": 2000, "status": "completed" },
|
|
122
|
+
* { "id": "5", "name": "Contract E", "amount": 9000, "status": "active" }
|
|
123
|
+
* ]
|
|
124
|
+
* }
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Example 2: List contracts with pagination (first page)
|
|
129
|
+
*
|
|
130
|
+
* Request: GET /api/data/contract?limit=2&skip=0
|
|
131
|
+
*
|
|
132
|
+
* Response:
|
|
133
|
+
* {
|
|
134
|
+
* "items": [
|
|
135
|
+
* { "id": "1", "name": "Contract A", "amount": 5000, "status": "active" },
|
|
136
|
+
* { "id": "2", "name": "Contract B", "amount": 3000, "status": "pending" }
|
|
137
|
+
* ],
|
|
138
|
+
* "meta": {
|
|
139
|
+
* "total": 5,
|
|
140
|
+
* "page": 1,
|
|
141
|
+
* "size": 2,
|
|
142
|
+
* "pages": 3,
|
|
143
|
+
* "has_next": true
|
|
144
|
+
* }
|
|
145
|
+
* }
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Example 3: List contracts with pagination (second page)
|
|
150
|
+
*
|
|
151
|
+
* Request: GET /api/data/contract?limit=2&skip=2
|
|
152
|
+
*
|
|
153
|
+
* Response:
|
|
154
|
+
* {
|
|
155
|
+
* "items": [
|
|
156
|
+
* { "id": "3", "name": "Contract C", "amount": 7500, "status": "active" },
|
|
157
|
+
* { "id": "4", "name": "Contract D", "amount": 2000, "status": "completed" }
|
|
158
|
+
* ],
|
|
159
|
+
* "meta": {
|
|
160
|
+
* "total": 5,
|
|
161
|
+
* "page": 2,
|
|
162
|
+
* "size": 2,
|
|
163
|
+
* "pages": 3,
|
|
164
|
+
* "has_next": true
|
|
165
|
+
* }
|
|
166
|
+
* }
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Example 4: Get a single contract
|
|
171
|
+
*
|
|
172
|
+
* Request: GET /api/data/contract/1
|
|
173
|
+
*
|
|
174
|
+
* Response:
|
|
175
|
+
* {
|
|
176
|
+
* "data": {
|
|
177
|
+
* "id": "1",
|
|
178
|
+
* "name": "Contract A",
|
|
179
|
+
* "amount": 5000,
|
|
180
|
+
* "status": "active"
|
|
181
|
+
* }
|
|
182
|
+
* }
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Example 5: Create a new contract
|
|
187
|
+
*
|
|
188
|
+
* Request: POST /api/data/contract
|
|
189
|
+
* Body: { "name": "Contract F", "amount": 4500, "status": "pending" }
|
|
190
|
+
*
|
|
191
|
+
* Response:
|
|
192
|
+
* {
|
|
193
|
+
* "data": {
|
|
194
|
+
* "id": "6",
|
|
195
|
+
* "name": "Contract F",
|
|
196
|
+
* "amount": 4500,
|
|
197
|
+
* "status": "pending"
|
|
198
|
+
* }
|
|
199
|
+
* }
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Example 6: Update a contract
|
|
204
|
+
*
|
|
205
|
+
* Request: PUT /api/data/contract/1
|
|
206
|
+
* Body: { "status": "completed" }
|
|
207
|
+
*
|
|
208
|
+
* Response:
|
|
209
|
+
* {
|
|
210
|
+
* "data": {
|
|
211
|
+
* "id": "1",
|
|
212
|
+
* "name": "Contract A",
|
|
213
|
+
* "amount": 5000,
|
|
214
|
+
* "status": "completed"
|
|
215
|
+
* }
|
|
216
|
+
* }
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Example 7: Delete a contract
|
|
221
|
+
*
|
|
222
|
+
* Request: DELETE /api/data/contract/1
|
|
223
|
+
*
|
|
224
|
+
* Response:
|
|
225
|
+
* {
|
|
226
|
+
* "data": {
|
|
227
|
+
* "id": "1",
|
|
228
|
+
* "deleted": true
|
|
229
|
+
* }
|
|
230
|
+
* }
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Example 8: Error response (not found)
|
|
235
|
+
*
|
|
236
|
+
* Request: GET /api/data/contract/999
|
|
237
|
+
*
|
|
238
|
+
* Response:
|
|
239
|
+
* {
|
|
240
|
+
* "error": {
|
|
241
|
+
* "code": "NOT_FOUND",
|
|
242
|
+
* "message": "Record not found"
|
|
243
|
+
* }
|
|
244
|
+
* }
|
|
245
|
+
*/
|
|
246
|
+
|
|
247
|
+
export { app, driver };
|
package/test/node.test.ts
CHANGED
|
@@ -60,7 +60,7 @@ describe('Node Adapter', () => {
|
|
|
60
60
|
|
|
61
61
|
expect(response.status).toBe(200);
|
|
62
62
|
expect(response.body).toEqual({
|
|
63
|
-
|
|
63
|
+
items: [{ id: 1, name: 'Alice' }]
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
66
|
|
|
@@ -78,7 +78,9 @@ describe('Node Adapter', () => {
|
|
|
78
78
|
|
|
79
79
|
expect(response.status).toBe(200);
|
|
80
80
|
expect(response.body).toEqual({
|
|
81
|
-
|
|
81
|
+
id: 2,
|
|
82
|
+
name: 'Bob',
|
|
83
|
+
'@type': 'user'
|
|
82
84
|
});
|
|
83
85
|
});
|
|
84
86
|
|