json-rest-api 0.1.0 → 1.0.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/README.md +392 -130
- package/TODO.todo +113 -0
- package/api.js +628 -0
- package/docs/API.md +1306 -0
- package/docs/FIXES_APPLIED.md +59 -0
- package/docs/GUIDES.md +1792 -0
- package/docs/RESOURCES_IN_EXPRESS.md +456 -0
- package/docs/example-versioning.js +168 -0
- package/docs/example.js +195 -0
- package/index.js +58 -207
- package/package.json +15 -25
- package/plugins/API guide.md +364 -0
- package/plugins/api-registry.js +142 -0
- package/plugins/api-versioning.md +135 -0
- package/plugins/http.js +273 -0
- package/plugins/logging.js +255 -0
- package/plugins/memory.js +182 -0
- package/plugins/mysql.js +753 -0
- package/plugins/openapi.js +401 -0
- package/plugins/positioning.js +220 -0
- package/plugins/security.js +261 -0
- package/plugins/validation.js +173 -0
- package/plugins/versioning.js +288 -0
- package/resource-helper.js +79 -0
- package/schema.js +481 -0
- package/test-basic.js +107 -0
- package/.jshintrc +0 -48
- package/.npmignore +0 -15
- package/LICENSE +0 -20
- package/examples/client.js +0 -18
- package/examples/client2.js +0 -27
- package/examples/server.js +0 -23
- package/test.js +0 -101
package/README.md
CHANGED
|
@@ -1,165 +1,427 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (err) {
|
|
27
|
-
debug('/ping error: '+inspect(err));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
console.log('Listening on port 8000.');
|
|
31
|
-
|
|
32
|
-
// add a "GET" route
|
|
33
|
-
RestApi.addRoute('get', '/ping', function(req, res, json, qs) {
|
|
34
|
-
var qsExp = {'alpha': 'one', 'beta': '2', 'gamma': 'true'};
|
|
35
|
-
assert.deepEqual(qsExp, qs);
|
|
36
|
-
res.json({success: true, pong: 'pong'});
|
|
37
|
-
});
|
|
1
|
+
# JSON REST API
|
|
2
|
+
|
|
3
|
+
A modern, plugin-based JSON REST API library with JSON:API compliance, schema validation, and extensible storage backends.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Plugin Architecture**: Extensible through plugins using `.use()` pattern
|
|
8
|
+
- **JSON:API Compliant**: Follows JSON:API specification for RESTful APIs
|
|
9
|
+
- **Schema Validation**: Built-in schema validation with extensible types and parameters
|
|
10
|
+
- **Multiple Storage Backends**: Memory and MySQL support out of the box
|
|
11
|
+
- **Automatic API Versioning**: Built-in version management with automatic routing
|
|
12
|
+
- **Positioning**: Manage record ordering with drag-and-drop support
|
|
13
|
+
- **Resource Versioning**: Track changes with version history
|
|
14
|
+
- **TypeScript-like Schema**: Define schemas with types, validation rules, and defaults
|
|
15
|
+
- **Hooks System**: Extensible through before/after hooks
|
|
16
|
+
- **Express Integration**: Easy integration with Express.js
|
|
17
|
+
- **Security**: Built-in security plugin with rate limiting, CORS, and authentication
|
|
18
|
+
- **Logging**: Structured logging with sensitive data redaction
|
|
19
|
+
- **OpenAPI**: Auto-generated API documentation
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install jsonrestapi
|
|
25
|
+
```
|
|
38
26
|
|
|
39
|
-
|
|
40
|
-
RestApi.addRoute('get', '/setName', function(req, res, json, qs) {
|
|
41
|
-
var qsExp = {'alpha': 'one', 'beta': '2', 'gamma': 'true'};
|
|
27
|
+
## Quick Start
|
|
42
28
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
29
|
+
```javascript
|
|
30
|
+
import { createApi, Schema } from 'jsonrestapi';
|
|
31
|
+
import express from 'express';
|
|
32
|
+
|
|
33
|
+
// Create an API with automatic versioning
|
|
34
|
+
const api = createApi({
|
|
35
|
+
name: 'myapp',
|
|
36
|
+
version: '1.0.0',
|
|
37
|
+
storage: 'memory',
|
|
38
|
+
http: { basePath: '/api' }
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Define a schema
|
|
42
|
+
const userSchema = new Schema({
|
|
43
|
+
id: { type: 'id' },
|
|
44
|
+
name: { type: 'string', required: true, min: 2, max: 100 },
|
|
45
|
+
email: { type: 'string', required: true, lowercase: true },
|
|
46
|
+
active: { type: 'boolean', default: true }
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Register the schema
|
|
50
|
+
api.addResource('users', userSchema);
|
|
51
|
+
|
|
52
|
+
// Create Express app and mount API
|
|
53
|
+
const app = express();
|
|
54
|
+
api.mount(app);
|
|
55
|
+
|
|
56
|
+
app.listen(3000);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Core Concepts
|
|
60
|
+
|
|
61
|
+
### Schema Definition
|
|
62
|
+
|
|
63
|
+
Schemas define the structure and validation rules for your data:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
const schema = new Schema({
|
|
67
|
+
// Numeric types
|
|
68
|
+
id: { type: 'id' },
|
|
69
|
+
age: { type: 'number', min: 0, max: 150 },
|
|
70
|
+
price: { type: 'number', currency: true },
|
|
71
|
+
|
|
72
|
+
// String types
|
|
73
|
+
name: { type: 'string', required: true, min: 2, max: 50 },
|
|
74
|
+
email: { type: 'string', lowercase: true },
|
|
75
|
+
description: { type: 'string', max: 1000 },
|
|
76
|
+
|
|
77
|
+
// Boolean type
|
|
78
|
+
active: { type: 'boolean', default: true },
|
|
79
|
+
|
|
80
|
+
// Date types
|
|
81
|
+
createdAt: { type: 'timestamp', default: () => Date.now() },
|
|
82
|
+
birthDate: { type: 'date' },
|
|
83
|
+
lastLogin: { type: 'dateTime' },
|
|
84
|
+
|
|
85
|
+
// Complex types
|
|
86
|
+
tags: { type: 'array', default: [] },
|
|
87
|
+
metadata: { type: 'object' },
|
|
88
|
+
|
|
89
|
+
// Special types
|
|
90
|
+
data: { type: 'serialize' }, // Circular JSON support
|
|
91
|
+
file: { type: 'blob' }
|
|
46
92
|
});
|
|
47
93
|
```
|
|
48
94
|
|
|
49
|
-
|
|
95
|
+
### Plugin System
|
|
96
|
+
|
|
97
|
+
Extend functionality through plugins:
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
const api = new Api();
|
|
101
|
+
|
|
102
|
+
api
|
|
103
|
+
.use(ValidationPlugin)
|
|
104
|
+
.use(MySQLPlugin, { connection: dbConfig })
|
|
105
|
+
.use(HTTPPlugin, { basePath: '/api' })
|
|
106
|
+
.use(PositioningPlugin)
|
|
107
|
+
.use(VersioningPlugin, { trackHistory: true });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Custom Plugins
|
|
50
111
|
|
|
51
|
-
|
|
52
|
-
'use strict';
|
|
53
|
-
var request = require('request');
|
|
54
|
-
var inspect = require('util').inspect;
|
|
55
|
-
var debug = require('debug')('json-rest-api:example:client');
|
|
56
|
-
var assert = require('assert');
|
|
112
|
+
Create your own plugins:
|
|
57
113
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
114
|
+
```javascript
|
|
115
|
+
const MyPlugin = {
|
|
116
|
+
install(api, options) {
|
|
117
|
+
// Add new methods
|
|
118
|
+
api.myMethod = () => { /* ... */ };
|
|
119
|
+
|
|
120
|
+
// Register hooks
|
|
121
|
+
api.hook('beforeInsert', async (context) => {
|
|
122
|
+
// Modify context before insert
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Add custom types
|
|
126
|
+
api.registerType('myType', ({ value }) => {
|
|
127
|
+
// Custom type logic
|
|
128
|
+
return processedValue;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
62
131
|
};
|
|
63
132
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
133
|
+
api.use(MyPlugin, { /* options */ });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## API Methods
|
|
137
|
+
|
|
138
|
+
### CRUD Operations
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// Get single resource
|
|
142
|
+
const user = await api.get('123', { type: 'users' });
|
|
143
|
+
|
|
144
|
+
// Query resources
|
|
145
|
+
const users = await api.query({
|
|
146
|
+
filter: { active: true },
|
|
147
|
+
sort: '-createdAt,name',
|
|
148
|
+
page: { size: 20, number: 1 }
|
|
149
|
+
}, { type: 'users' });
|
|
150
|
+
|
|
151
|
+
// Insert resource
|
|
152
|
+
const newUser = await api.insert({
|
|
153
|
+
name: 'John Doe',
|
|
154
|
+
email: 'john@example.com'
|
|
155
|
+
}, { type: 'users' });
|
|
156
|
+
|
|
157
|
+
// Update resource
|
|
158
|
+
const updated = await api.update('123', {
|
|
159
|
+
name: 'Jane Doe'
|
|
160
|
+
}, { type: 'users' });
|
|
161
|
+
|
|
162
|
+
// Delete resource
|
|
163
|
+
await api.delete('123', { type: 'users' });
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### HTTP Endpoints
|
|
167
|
+
|
|
168
|
+
When using HTTPPlugin, the following endpoints are available:
|
|
69
169
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
170
|
+
- `GET /api/{type}` - List resources
|
|
171
|
+
- `GET /api/{type}/{id}` - Get single resource
|
|
172
|
+
- `POST /api/{type}` - Create resource
|
|
173
|
+
- `PATCH /api/{type}/{id}` - Update resource
|
|
174
|
+
- `DELETE /api/{type}/{id}` - Delete resource
|
|
175
|
+
|
|
176
|
+
Query parameters:
|
|
177
|
+
- `filter[field]=value` - Filter by field
|
|
178
|
+
- `sort=-field1,field2` - Sort by fields
|
|
179
|
+
- `page[size]=10&page[number]=2` - Pagination
|
|
180
|
+
- `include=relation1,relation2` - Include relationships
|
|
181
|
+
|
|
182
|
+
## Storage Backends
|
|
183
|
+
|
|
184
|
+
### Memory Storage
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
api.use(MemoryPlugin, {
|
|
188
|
+
initialData: [
|
|
189
|
+
{ id: 1, name: 'Item 1' },
|
|
190
|
+
{ id: 2, name: 'Item 2' }
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### MySQL Storage
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
api.use(MySQLPlugin, {
|
|
199
|
+
connection: {
|
|
200
|
+
host: 'localhost',
|
|
201
|
+
user: 'root',
|
|
202
|
+
password: 'password',
|
|
203
|
+
database: 'myapp'
|
|
204
|
+
}
|
|
73
205
|
});
|
|
74
206
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
207
|
+
// Sync schema with database
|
|
208
|
+
await api.syncSchema(userSchema, 'users');
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Advanced Features
|
|
80
212
|
|
|
81
|
-
|
|
213
|
+
### Positioning
|
|
82
214
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
215
|
+
Manage record order:
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
api.use(PositioningPlugin);
|
|
219
|
+
|
|
220
|
+
// Insert at specific position
|
|
221
|
+
await api.insert({
|
|
222
|
+
name: 'Item',
|
|
223
|
+
beforeId: '123' // Place before item with ID 123
|
|
224
|
+
}, {
|
|
225
|
+
type: 'items',
|
|
226
|
+
positioning: { enabled: true }
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Reposition existing item
|
|
230
|
+
await api.reposition('items', '456', null); // Move to end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### API Versioning
|
|
234
|
+
|
|
235
|
+
Create multiple versions of your API with automatic version negotiation:
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
// Version 1.0.0
|
|
239
|
+
const apiV1 = createApi({
|
|
240
|
+
name: 'users',
|
|
241
|
+
version: '1.0.0',
|
|
242
|
+
storage: 'memory'
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
apiV1.addResource('users', new Schema({
|
|
246
|
+
id: { type: 'id' },
|
|
247
|
+
name: { type: 'string', required: true },
|
|
248
|
+
email: { type: 'string', required: true }
|
|
249
|
+
}));
|
|
250
|
+
|
|
251
|
+
// Version 2.0.0 with new fields
|
|
252
|
+
const apiV2 = createApi({
|
|
253
|
+
name: 'users',
|
|
254
|
+
version: '2.0.0',
|
|
255
|
+
storage: 'memory'
|
|
87
256
|
});
|
|
257
|
+
|
|
258
|
+
apiV2.addResource('users', new Schema({
|
|
259
|
+
id: { type: 'id' },
|
|
260
|
+
name: { type: 'string', required: true },
|
|
261
|
+
email: { type: 'string', required: true },
|
|
262
|
+
phone: { type: 'string' }, // New field
|
|
263
|
+
active: { type: 'boolean', default: true } // New field
|
|
264
|
+
}));
|
|
265
|
+
|
|
266
|
+
// Automatic version selection
|
|
267
|
+
const api = Api.get('users', 'latest'); // Gets v2.0.0
|
|
268
|
+
const apiV1Compat = Api.get('users', '1.0.0'); // Gets v1.0.0 or higher
|
|
269
|
+
|
|
270
|
+
// HTTP endpoints with automatic versioning
|
|
271
|
+
// GET /api/1.0.0/users
|
|
272
|
+
// GET /api/2.0.0/users
|
|
273
|
+
// GET /api/users?v=2.0.0
|
|
274
|
+
// GET /api/users (with API-Version: 2.0.0 header)
|
|
88
275
|
```
|
|
89
276
|
|
|
90
|
-
|
|
277
|
+
### Resource Versioning
|
|
91
278
|
|
|
92
|
-
|
|
93
|
-
RestApi constructor. Creates the HTTP server objects and starts listening on
|
|
94
|
-
the socket.
|
|
279
|
+
Track changes to individual resources:
|
|
95
280
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
* **Function** *[cb]* An optional callback. If there is a callback, the process
|
|
102
|
-
will be begin listening on the socket for HTTP requests. Without a callback,
|
|
103
|
-
you must call listen explicitly.
|
|
281
|
+
```javascript
|
|
282
|
+
api.use(VersioningPlugin, {
|
|
283
|
+
trackHistory: true,
|
|
284
|
+
optimisticLocking: true
|
|
285
|
+
});
|
|
104
286
|
|
|
105
|
-
|
|
106
|
-
|
|
287
|
+
// Automatic version tracking
|
|
288
|
+
const user = await api.insert({ name: 'John' }, { type: 'users' });
|
|
289
|
+
// user.version = 1
|
|
107
290
|
|
|
108
|
-
|
|
109
|
-
|
|
291
|
+
const updated = await api.update(user.id, { name: 'Jane' }, { type: 'users' });
|
|
292
|
+
// updated.version = 2
|
|
110
293
|
|
|
111
|
-
|
|
112
|
-
|
|
294
|
+
// Get version history
|
|
295
|
+
const history = await api.getVersionHistory('users', user.id);
|
|
113
296
|
|
|
114
|
-
|
|
115
|
-
|
|
297
|
+
// Restore previous version
|
|
298
|
+
await api.restoreVersion('users', user.id, 1);
|
|
116
299
|
|
|
117
|
-
|
|
118
|
-
|
|
300
|
+
// Compare versions
|
|
301
|
+
const diff = await api.diffVersions('users', user.id, 1, 2);
|
|
302
|
+
```
|
|
119
303
|
|
|
120
|
-
###
|
|
121
|
-
* **String** *verb* HTTP verb for route. e.g.: 'get', 'post'.
|
|
122
|
-
* **String** *path* A valid URI path.
|
|
123
|
-
* **Function** *cb* A function to run when the path executes. Which has the
|
|
124
|
-
following form: fn(req, res, json, qs):
|
|
125
|
-
* @param {Object} req - Node.js HTTP request object
|
|
126
|
-
* @param {Object} res - Node.js HTTP response object
|
|
127
|
-
* @param {Object} json - The JSON body of the request, if present.
|
|
128
|
-
* @param {Object} qs - The query string of the request, if present.
|
|
304
|
+
### Hooks
|
|
129
305
|
|
|
130
|
-
|
|
131
|
-
* **Boolean|Error** True, if no errors on the parameters, Error
|
|
132
|
-
* **Error|Boolean** true on success and Error on failure.
|
|
306
|
+
Extend behavior with hooks:
|
|
133
307
|
|
|
134
|
-
|
|
135
|
-
|
|
308
|
+
```javascript
|
|
309
|
+
// Validation hooks
|
|
310
|
+
api.hook('beforeValidate', async (context) => {
|
|
311
|
+
// Modify data before validation
|
|
312
|
+
});
|
|
136
313
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
314
|
+
api.hook('afterValidate', async (context) => {
|
|
315
|
+
// Add custom validation
|
|
316
|
+
if (context.data.price < 0) {
|
|
317
|
+
context.errors.push({
|
|
318
|
+
field: 'price',
|
|
319
|
+
message: 'Price cannot be negative'
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
});
|
|
142
323
|
|
|
143
|
-
|
|
324
|
+
// CRUD hooks
|
|
325
|
+
api.hook('beforeInsert', async (context) => {
|
|
326
|
+
context.data.createdBy = context.options.userId;
|
|
327
|
+
});
|
|
144
328
|
|
|
145
|
-
|
|
329
|
+
api.hook('transformResult', async (context) => {
|
|
330
|
+
// Add computed fields
|
|
331
|
+
if (context.result) {
|
|
332
|
+
context.result.displayName = context.result.name.toUpperCase();
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
```
|
|
146
336
|
|
|
147
|
-
|
|
337
|
+
## Schema Extensions
|
|
148
338
|
|
|
149
|
-
|
|
150
|
-
this software and associated documentation files (the "Software"), to deal in
|
|
151
|
-
the Software without restriction, including without limitation the rights to
|
|
152
|
-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
153
|
-
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
154
|
-
subject to the following conditions:
|
|
339
|
+
Add custom types and parameters:
|
|
155
340
|
|
|
156
|
-
|
|
157
|
-
|
|
341
|
+
```javascript
|
|
342
|
+
// Custom type
|
|
343
|
+
schema.registerType('phone', ({ value }) => {
|
|
344
|
+
const cleaned = value.replace(/\D/g, '');
|
|
345
|
+
if (cleaned.length !== 10) {
|
|
346
|
+
throw new Error('Invalid phone number');
|
|
347
|
+
}
|
|
348
|
+
return cleaned;
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Custom parameter
|
|
352
|
+
schema.registerParam('unique', async ({ value, fieldName, object }) => {
|
|
353
|
+
const exists = await checkUniqueness(fieldName, value);
|
|
354
|
+
if (exists) {
|
|
355
|
+
throw new Error(`${fieldName} must be unique`);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Use in schema
|
|
360
|
+
const schema = new Schema({
|
|
361
|
+
phone: { type: 'phone', required: true },
|
|
362
|
+
username: { type: 'string', unique: true }
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Cross-API Communication
|
|
367
|
+
|
|
368
|
+
APIs can access each other with automatic version compatibility:
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
// Orders API can access Users API
|
|
372
|
+
const ordersApi = createApi({
|
|
373
|
+
name: 'orders',
|
|
374
|
+
version: '1.0.0'
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
ordersApi.hook('afterInsert', async (context) => {
|
|
378
|
+
// Automatically gets a compatible users API
|
|
379
|
+
const usersApi = ordersApi.apis.users;
|
|
380
|
+
|
|
381
|
+
if (usersApi && context.data.userId) {
|
|
382
|
+
const user = await usersApi.get(context.data.userId, { type: 'users' });
|
|
383
|
+
console.log('Order created for:', user);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Security Best Practices
|
|
389
|
+
|
|
390
|
+
Enable comprehensive security features:
|
|
391
|
+
|
|
392
|
+
```javascript
|
|
393
|
+
import { SecurityPlugin, LoggingPlugin } from 'jsonrestapi';
|
|
394
|
+
|
|
395
|
+
api
|
|
396
|
+
.use(SecurityPlugin, {
|
|
397
|
+
rateLimit: { max: 100, windowMs: 15 * 60 * 1000 },
|
|
398
|
+
authentication: { type: 'bearer', required: true },
|
|
399
|
+
cors: { origin: 'https://app.example.com' }
|
|
400
|
+
})
|
|
401
|
+
.use(LoggingPlugin, {
|
|
402
|
+
level: 'info',
|
|
403
|
+
auditLog: true
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Error Handling
|
|
408
|
+
|
|
409
|
+
Errors follow JSON:API format:
|
|
410
|
+
|
|
411
|
+
```javascript
|
|
412
|
+
try {
|
|
413
|
+
await api.insert({ name: '' }, { type: 'users' });
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.log(error.errors);
|
|
416
|
+
// [{
|
|
417
|
+
// status: '422',
|
|
418
|
+
// title: 'Validation Error',
|
|
419
|
+
// detail: 'Field required',
|
|
420
|
+
// source: { pointer: '/data/attributes/name' }
|
|
421
|
+
// }]
|
|
422
|
+
}
|
|
423
|
+
```
|
|
158
424
|
|
|
159
|
-
|
|
160
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
161
|
-
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
162
|
-
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
163
|
-
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
164
|
-
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
425
|
+
## License
|
|
165
426
|
|
|
427
|
+
MIT
|
package/TODO.todo
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
TODO
|
|
2
|
+
|
|
3
|
+
10. Utility Features
|
|
4
|
+
|
|
5
|
+
- Missing from original:
|
|
6
|
+
- artificial delay for testing <-- ADD!!!
|
|
7
|
+
- Request decoration pattern <-- I DON'T KNOW WHAT THIS IS, BUT IF IT'S BEST PRACTICE/INDUSTRU STANDARD, TELL ME ABOUT IT
|
|
8
|
+
- Store inheritance/extension <--- THIS, I NEVER WANTED, NEVER USED IN 10 YEARS IN PRODUCTION
|
|
9
|
+
- Automatic timestamp fields <-- WOULD BE NICE
|
|
10
|
+
- Soft delete support <-- WOULD BE NICE BUT I DON'T THINK THE ORIGINAL HAD IT. I WILL IMPLEMENT IT IN THE FUTURE
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
5. Error Handling
|
|
14
|
+
|
|
15
|
+
- Missing structured error classes:
|
|
16
|
+
- BadRequest, NotFound, Conflict classes
|
|
17
|
+
- Error code standardization
|
|
18
|
+
- Multi-field validation errors
|
|
19
|
+
- Error context preservation
|
|
20
|
+
|
|
21
|
+
6. Hook System Gaps
|
|
22
|
+
|
|
23
|
+
- Missing hooks from original:
|
|
24
|
+
- beforeId, afterId
|
|
25
|
+
- beforeRetrieve, afterRetrieve
|
|
26
|
+
- beforeInsert, afterInsert (full lifecycle)
|
|
27
|
+
- queryFieldsAndJoins
|
|
28
|
+
- updateConditionsAndArgs
|
|
29
|
+
- deleteConditionsAndArgs
|
|
30
|
+
|
|
31
|
+
7. Store Templates
|
|
32
|
+
|
|
33
|
+
- No equivalent to storeTemplate.js:
|
|
34
|
+
- Automatic REST endpoint generation
|
|
35
|
+
- Built-in authentication handling
|
|
36
|
+
- Session management
|
|
37
|
+
- Request/response transformation
|
|
38
|
+
|
|
39
|
+
8. Memory Store Features
|
|
40
|
+
|
|
41
|
+
- Current memory plugin lacks:
|
|
42
|
+
- Query operators ($like, $in, $gt, etc.)
|
|
43
|
+
- Sorting by multiple fields
|
|
44
|
+
- Limit/offset with total count
|
|
45
|
+
- Deep object filtering
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
11. JSON:API Compliance Gaps
|
|
50
|
+
|
|
51
|
+
- Current HTTP plugin missing:
|
|
52
|
+
- Proper relationship handling
|
|
53
|
+
- Compound documents
|
|
54
|
+
- Sparse fieldsets
|
|
55
|
+
- Include parameter processing
|
|
56
|
+
- Meta information structure
|
|
57
|
+
|
|
58
|
+
12. Performance Features
|
|
59
|
+
|
|
60
|
+
- Not implemented:
|
|
61
|
+
- Connection pooling configuration
|
|
62
|
+
- Query result caching
|
|
63
|
+
- Batch operations
|
|
64
|
+
- Transaction support across operations
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
**************************************************************************
|
|
69
|
+
DONE
|
|
70
|
+
**************************************************************************
|
|
71
|
+
|
|
72
|
+
2. MySQL Schema Synchronization
|
|
73
|
+
|
|
74
|
+
- Current MySQL plugin is basic, missing:
|
|
75
|
+
- Automatic table creation/modification
|
|
76
|
+
- Column type mapping from schema
|
|
77
|
+
- Index creation for searchable fields
|
|
78
|
+
- Foreign key constraint management
|
|
79
|
+
- Schema migration tracking
|
|
80
|
+
|
|
81
|
+
9. Schema Features
|
|
82
|
+
|
|
83
|
+
- SimplSchema integration missing:
|
|
84
|
+
- onlyObjectValues for partial updates
|
|
85
|
+
- Conditional defaults based on other fields
|
|
86
|
+
- Array validation with item schemas
|
|
87
|
+
- Custom error messages per validator
|
|
88
|
+
|
|
89
|
+
1. Positioning System
|
|
90
|
+
|
|
91
|
+
- Current has basic positioning plugin but lacks:
|
|
92
|
+
- Filtering support during repositioning
|
|
93
|
+
- Gap-free positioning algorithm
|
|
94
|
+
- Complex position calculations with conditions
|
|
95
|
+
- Batch repositioning operations
|
|
96
|
+
|
|
97
|
+
3. Advanced Querying
|
|
98
|
+
|
|
99
|
+
- Missing query capabilities:
|
|
100
|
+
- Complex field joins (queryFieldsAndJoins hook)
|
|
101
|
+
- Nested object queries
|
|
102
|
+
- Array field queries
|
|
103
|
+
- Custom SQL conditions
|
|
104
|
+
- Aggregation support
|
|
105
|
+
|
|
106
|
+
4. Versioning Management
|
|
107
|
+
|
|
108
|
+
- Current versioning is rudimentary, missing:
|
|
109
|
+
- Version range negotiation
|
|
110
|
+
- Store registry with semver matching
|
|
111
|
+
- Version-specific schema transformations
|
|
112
|
+
- Backward compatibility helpers
|
|
113
|
+
|