json-rest-api 1.0.0 → 1.0.2
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/.claude/settings.local.json +16 -0
- package/LICENSE +674 -0
- package/README.md +655 -309
- package/RELATIONSHIPS.md +1013 -0
- package/TODO_rest-api-plugin-extras.js +142 -0
- package/connectors_ideas/express-plugin.js +120 -0
- package/connectors_ideas/fastify.js +219 -0
- package/connectors_ideas/http-plugin.js +138 -0
- package/connectors_ideas/koa-plugin.js +113 -0
- package/docs/fileUploads.md +722 -0
- package/docs/mini-guides/file-uploads-complete.md +159 -0
- package/examples/.gitkeep +0 -0
- package/examples/express-example.js +262 -0
- package/examples/express-plugin.js +366 -0
- package/examples/file-upload-example.js +205 -0
- package/examples/filename-strategies.js +140 -0
- package/examples/knex-basic.js +66 -0
- package/index.js +31 -56
- package/lib/payload-validators.js +685 -0
- package/lib/rest-api-errors.js +58 -0
- package/package.json +41 -14
- package/payloadValdators.js +686 -0
- package/plugins/core/connectors/express-plugin.js +449 -0
- package/plugins/core/connectors/http-plugin.js +450 -0
- package/plugins/core/file-handling-plugin.js +338 -0
- package/plugins/core/lib/busboy-detector.js +167 -0
- package/plugins/core/lib/formidable-detector.js +130 -0
- package/plugins/core/rest-api-knex-plugin.js +200 -0
- package/plugins/core/rest-api-plugin.js +1419 -0
- package/plugins/storage/local-storage.js +218 -0
- package/plugins/storage/s3-storage.js +125 -0
- package/test-examples/package-lock.json +906 -0
- package/test-examples/package.json +15 -0
- package/test-examples/test-basic-api.js +11 -0
- package/test-examples/test-books-resource.js +116 -0
- package/test-examples/test-complete-file-upload.js +140 -0
- package/test-examples/test-debug.js +28 -0
- package/test-examples/test-express-plugin.js +177 -0
- package/test-examples/test-express-simple.js +28 -0
- package/test-examples/test-express-with-import.js +71 -0
- package/test-examples/test-file-upload.js +132 -0
- package/test-examples/test-http-plugin.js +159 -0
- package/test-examples/test-programmatic-api.js +188 -0
- package/test-examples/test-rest-api-plugin.js +13 -0
- package/TODO.todo +0 -113
- package/api.js +0 -628
- package/docs/API.md +0 -1306
- package/docs/FIXES_APPLIED.md +0 -59
- package/docs/GUIDES.md +0 -1792
- package/docs/RESOURCES_IN_EXPRESS.md +0 -456
- package/docs/example-versioning.js +0 -168
- package/docs/example.js +0 -195
- package/plugins/API guide.md +0 -364
- package/plugins/api-registry.js +0 -142
- package/plugins/api-versioning.md +0 -135
- package/plugins/http.js +0 -273
- package/plugins/logging.js +0 -255
- package/plugins/memory.js +0 -182
- package/plugins/mysql.js +0 -753
- package/plugins/openapi.js +0 -401
- package/plugins/positioning.js +0 -220
- package/plugins/security.js +0 -261
- package/plugins/validation.js +0 -173
- package/plugins/versioning.js +0 -288
- package/resource-helper.js +0 -79
- package/schema.js +0 -481
- package/test-basic.js +0 -107
package/docs/example.js
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import {
|
|
3
|
-
createApi,
|
|
4
|
-
Api,
|
|
5
|
-
Schema,
|
|
6
|
-
MySQLPlugin,
|
|
7
|
-
HTTPPlugin,
|
|
8
|
-
ValidationPlugin,
|
|
9
|
-
PositioningPlugin,
|
|
10
|
-
VersioningPlugin
|
|
11
|
-
} from './index.js';
|
|
12
|
-
|
|
13
|
-
// Example 1: Simple API with memory storage
|
|
14
|
-
const simpleApi = createApi({
|
|
15
|
-
storage: 'memory',
|
|
16
|
-
http: {
|
|
17
|
-
basePath: '/api/v1',
|
|
18
|
-
typeOptions: {
|
|
19
|
-
users: {
|
|
20
|
-
searchFields: ['name', 'email']
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// Define a schema
|
|
27
|
-
const userSchema = new Schema({
|
|
28
|
-
id: { type: 'id' },
|
|
29
|
-
name: { type: 'string', required: true, min: 2, max: 100 },
|
|
30
|
-
email: { type: 'string', required: true, lowercase: true },
|
|
31
|
-
age: { type: 'number', min: 0, max: 150 },
|
|
32
|
-
active: { type: 'boolean', default: true },
|
|
33
|
-
createdAt: { type: 'timestamp', default: () => Date.now() }
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Register the schema
|
|
37
|
-
simpleApi.addResource('users', userSchema);
|
|
38
|
-
|
|
39
|
-
// Example 2: Advanced API with MySQL and all features
|
|
40
|
-
const advancedApi = new Api({ idProperty: 'id' });
|
|
41
|
-
|
|
42
|
-
// Configure plugins
|
|
43
|
-
advancedApi
|
|
44
|
-
.use(ValidationPlugin)
|
|
45
|
-
.use(MySQLPlugin, {
|
|
46
|
-
connections: [{
|
|
47
|
-
name: 'main',
|
|
48
|
-
config: {
|
|
49
|
-
host: 'localhost',
|
|
50
|
-
user: 'root',
|
|
51
|
-
password: 'password',
|
|
52
|
-
database: 'myapp'
|
|
53
|
-
}
|
|
54
|
-
}]
|
|
55
|
-
})
|
|
56
|
-
.use(PositioningPlugin, {
|
|
57
|
-
positionField: 'position',
|
|
58
|
-
beforeIdField: 'beforeId'
|
|
59
|
-
})
|
|
60
|
-
.use(VersioningPlugin, {
|
|
61
|
-
apiVersion: '2.0.0',
|
|
62
|
-
trackHistory: true,
|
|
63
|
-
optimisticLocking: true
|
|
64
|
-
})
|
|
65
|
-
.use(HTTPPlugin, {
|
|
66
|
-
basePath: '/api/v2'
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Define schemas
|
|
70
|
-
const productSchema = new Schema({
|
|
71
|
-
id: { type: 'id' },
|
|
72
|
-
name: { type: 'string', required: true, min: 1, max: 200 },
|
|
73
|
-
description: { type: 'string', max: 1000 },
|
|
74
|
-
price: { type: 'number', required: true, min: 0, currency: true },
|
|
75
|
-
stock: { type: 'number', default: 0, min: 0 },
|
|
76
|
-
category: { type: 'string', required: true },
|
|
77
|
-
tags: { type: 'array', default: [] },
|
|
78
|
-
metadata: { type: 'object' },
|
|
79
|
-
active: { type: 'boolean', default: true },
|
|
80
|
-
position: { type: 'number', default: 0 },
|
|
81
|
-
version: { type: 'number', default: 1 },
|
|
82
|
-
createdAt: { type: 'timestamp', default: () => Date.now() },
|
|
83
|
-
updatedAt: { type: 'timestamp' }
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// Create search schema from main schema
|
|
87
|
-
const productSearchSchema = advancedApi.createSearchSchema(productSchema, [
|
|
88
|
-
'name', 'category', 'active'
|
|
89
|
-
]);
|
|
90
|
-
|
|
91
|
-
// Register schemas
|
|
92
|
-
advancedApi.addResource('products', productSchema);
|
|
93
|
-
|
|
94
|
-
// Custom validation hook
|
|
95
|
-
advancedApi.hook('afterValidate', async (context) => {
|
|
96
|
-
if (context.method === 'insert' || context.method === 'update') {
|
|
97
|
-
const { data } = context;
|
|
98
|
-
|
|
99
|
-
// Custom business rule: expensive products must have description
|
|
100
|
-
if (data.price > 1000 && !data.description) {
|
|
101
|
-
context.errors.push({
|
|
102
|
-
field: 'description',
|
|
103
|
-
message: 'Products over $1000 must have a description',
|
|
104
|
-
code: 'EXPENSIVE_PRODUCT_NEEDS_DESCRIPTION'
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// Transform hook to add computed fields
|
|
111
|
-
advancedApi.hook('transformResult', async (context) => {
|
|
112
|
-
if (context.result && context.options.type === 'products') {
|
|
113
|
-
const product = context.result;
|
|
114
|
-
|
|
115
|
-
// Add computed field
|
|
116
|
-
product.displayPrice = `$${(product.price || 0).toFixed(2)}`;
|
|
117
|
-
product.inStock = product.stock > 0;
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Example 3: Using the API programmatically
|
|
122
|
-
async function exampleUsage() {
|
|
123
|
-
// Insert a product
|
|
124
|
-
const newProduct = await advancedApi.insert({
|
|
125
|
-
name: 'Premium Widget',
|
|
126
|
-
description: 'A high-quality widget for professionals',
|
|
127
|
-
price: 1299.99,
|
|
128
|
-
stock: 50,
|
|
129
|
-
category: 'widgets',
|
|
130
|
-
tags: ['premium', 'professional'],
|
|
131
|
-
beforeId: null // Will be placed at the end
|
|
132
|
-
}, {
|
|
133
|
-
type: 'products',
|
|
134
|
-
table: 'products',
|
|
135
|
-
positioning: { enabled: true }
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
console.log('Created product:', newProduct);
|
|
139
|
-
|
|
140
|
-
// Query products
|
|
141
|
-
const products = await advancedApi.query({
|
|
142
|
-
filter: { category: 'widgets', active: true },
|
|
143
|
-
sort: '-price,name',
|
|
144
|
-
page: { size: 10, number: 1 }
|
|
145
|
-
}, {
|
|
146
|
-
type: 'products',
|
|
147
|
-
table: 'products'
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
console.log('Found products:', products);
|
|
151
|
-
|
|
152
|
-
// Update with optimistic locking
|
|
153
|
-
const updated = await advancedApi.update(newProduct.data.id, {
|
|
154
|
-
stock: 45,
|
|
155
|
-
version: newProduct.data.attributes.version // For optimistic locking
|
|
156
|
-
}, {
|
|
157
|
-
type: 'products',
|
|
158
|
-
table: 'products'
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
console.log('Updated product:', updated);
|
|
162
|
-
|
|
163
|
-
// Get version history
|
|
164
|
-
const history = await advancedApi.getVersionHistory('products', newProduct.data.id);
|
|
165
|
-
console.log('Version history:', history);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Example 4: Express app setup
|
|
169
|
-
const app = express();
|
|
170
|
-
|
|
171
|
-
// Mount the APIs
|
|
172
|
-
simpleApi.mount(app);
|
|
173
|
-
advancedApi.mount(app);
|
|
174
|
-
|
|
175
|
-
// Add custom middleware
|
|
176
|
-
advancedApi.useMiddleware((req, res, next) => {
|
|
177
|
-
// Add user ID from session for versioning
|
|
178
|
-
req.options = { userId: req.session?.userId };
|
|
179
|
-
next();
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// Sync database schema
|
|
183
|
-
async function syncDatabase() {
|
|
184
|
-
await advancedApi.syncSchema(productSchema, 'products', {
|
|
185
|
-
connection: 'main'
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Start server
|
|
190
|
-
app.listen(3000, () => {
|
|
191
|
-
console.log('API server running on http://localhost:3000');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Export for use in other modules
|
|
195
|
-
export { simpleApi, advancedApi, userSchema, productSchema };
|
package/plugins/API guide.md
DELETED
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
● Programmatic API Usage Guide
|
|
2
|
-
|
|
3
|
-
Here's how to use the JSON REST API methods programmatically:
|
|
4
|
-
|
|
5
|
-
Basic Setup
|
|
6
|
-
|
|
7
|
-
import { createApi, Schema } from './jsonrestapi/index.js';
|
|
8
|
-
|
|
9
|
-
// Create API instance
|
|
10
|
-
const api = createApi({
|
|
11
|
-
storage: 'memory', // or 'mysql'
|
|
12
|
-
http: false // Disable HTTP if using programmatically only
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
// Define a schema
|
|
16
|
-
const userSchema = new Schema({
|
|
17
|
-
id: { type: 'id' },
|
|
18
|
-
name: { type: 'string', required: true },
|
|
19
|
-
email: { type: 'string', required: true, lowercase: true },
|
|
20
|
-
age: { type: 'number', min: 0 },
|
|
21
|
-
active: { type: 'boolean', default: true }
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// Register schema
|
|
25
|
-
api.addResource('users', userSchema);
|
|
26
|
-
|
|
27
|
-
CRUD Operations
|
|
28
|
-
|
|
29
|
-
1. Create (Insert)
|
|
30
|
-
|
|
31
|
-
// Simple insert
|
|
32
|
-
const newUser = await api.insert({
|
|
33
|
-
name: 'John Doe',
|
|
34
|
-
email: 'john@example.com',
|
|
35
|
-
age: 30
|
|
36
|
-
}, {
|
|
37
|
-
type: 'users'
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
console.log(newUser);
|
|
41
|
-
// Output: { data: { id: '1', type: 'users', attributes: { name: 'John
|
|
42
|
-
Doe', ... } } }
|
|
43
|
-
|
|
44
|
-
// Insert with positioning (if PositioningPlugin is used)
|
|
45
|
-
const secondUser = await api.insert({
|
|
46
|
-
name: 'Jane Smith',
|
|
47
|
-
email: 'jane@example.com',
|
|
48
|
-
beforeId: null // Place at end
|
|
49
|
-
}, {
|
|
50
|
-
type: 'users',
|
|
51
|
-
positioning: { enabled: true }
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
2. Read (Get)
|
|
55
|
-
|
|
56
|
-
// Get by ID
|
|
57
|
-
const user = await api.get('1', {
|
|
58
|
-
type: 'users'
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
console.log(user);
|
|
62
|
-
// Output: { data: { id: '1', type: 'users', attributes: { ... } } }
|
|
63
|
-
|
|
64
|
-
// Handle not found
|
|
65
|
-
try {
|
|
66
|
-
const notFound = await api.get('999', { type: 'users' });
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.log('User not found');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
3. Update
|
|
72
|
-
|
|
73
|
-
// Partial update
|
|
74
|
-
const updated = await api.update('1', {
|
|
75
|
-
age: 31,
|
|
76
|
-
active: false
|
|
77
|
-
}, {
|
|
78
|
-
type: 'users'
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Update with validation context
|
|
82
|
-
const updateWithContext = await api.update('1', {
|
|
83
|
-
email: 'newemail@example.com'
|
|
84
|
-
}, {
|
|
85
|
-
type: 'users',
|
|
86
|
-
userId: 'admin-123', // For audit logging
|
|
87
|
-
ipAddress: '192.168.1.1'
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
4. Delete
|
|
91
|
-
|
|
92
|
-
// Delete by ID
|
|
93
|
-
await api.delete('1', {
|
|
94
|
-
type: 'users'
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// Delete returns { data: null } on success
|
|
98
|
-
|
|
99
|
-
5. Query (List)
|
|
100
|
-
|
|
101
|
-
// Simple query
|
|
102
|
-
const allUsers = await api.query({}, {
|
|
103
|
-
type: 'users'
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Advanced query with filters
|
|
107
|
-
const activeAdults = await api.query({
|
|
108
|
-
filter: {
|
|
109
|
-
active: true,
|
|
110
|
-
age: { $gte: 18 } // If your storage plugin supports operators
|
|
111
|
-
},
|
|
112
|
-
sort: '-age,name', // Sort by age DESC, then name ASC
|
|
113
|
-
page: {
|
|
114
|
-
size: 20,
|
|
115
|
-
number: 1
|
|
116
|
-
}
|
|
117
|
-
}, {
|
|
118
|
-
type: 'users'
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
console.log(activeAdults);
|
|
122
|
-
// Output: {
|
|
123
|
-
// data: [ { id: '1', type: 'users', attributes: {...} }, ... ],
|
|
124
|
-
// meta: { total: 45, pageSize: 20, pageNumber: 1, totalPages: 3 },
|
|
125
|
-
// links: { self: '...', next: '...', last: '...' }
|
|
126
|
-
// }
|
|
127
|
-
|
|
128
|
-
// Search (if configured)
|
|
129
|
-
const searchResults = await api.query({
|
|
130
|
-
search: 'john',
|
|
131
|
-
filter: { active: true }
|
|
132
|
-
}, {
|
|
133
|
-
type: 'users',
|
|
134
|
-
searchFields: ['name', 'email']
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
Advanced Usage
|
|
138
|
-
|
|
139
|
-
With MySQL Storage
|
|
140
|
-
|
|
141
|
-
import { Api, MySQLPlugin, ValidationPlugin } from
|
|
142
|
-
'./jsonrestapi/index.js';
|
|
143
|
-
|
|
144
|
-
const api = new Api()
|
|
145
|
-
.use(ValidationPlugin)
|
|
146
|
-
.use(MySQLPlugin, {
|
|
147
|
-
connection: {
|
|
148
|
-
host: 'localhost',
|
|
149
|
-
user: 'root',
|
|
150
|
-
password: 'password',
|
|
151
|
-
database: 'myapp'
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// Sync schema with database
|
|
156
|
-
await api.syncSchema(userSchema, 'users');
|
|
157
|
-
|
|
158
|
-
// Use with table specification
|
|
159
|
-
const user = await api.insert({
|
|
160
|
-
name: 'Database User',
|
|
161
|
-
email: 'db@example.com'
|
|
162
|
-
}, {
|
|
163
|
-
type: 'users',
|
|
164
|
-
table: 'users', // MySQL table name
|
|
165
|
-
connection: 'default'
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
With Authentication Context
|
|
169
|
-
|
|
170
|
-
// Add security plugin
|
|
171
|
-
api.use(SecurityPlugin);
|
|
172
|
-
|
|
173
|
-
// All operations can include auth context
|
|
174
|
-
const result = await api.insert({
|
|
175
|
-
title: 'Secure Post',
|
|
176
|
-
content: 'This is secure'
|
|
177
|
-
}, {
|
|
178
|
-
type: 'posts',
|
|
179
|
-
userId: 'user-123', // From decoded JWT
|
|
180
|
-
authenticated: true,
|
|
181
|
-
permissions: ['create', 'read']
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
Batch Operations
|
|
185
|
-
|
|
186
|
-
// Insert multiple records
|
|
187
|
-
const users = [
|
|
188
|
-
{ name: 'User 1', email: 'user1@example.com' },
|
|
189
|
-
{ name: 'User 2', email: 'user2@example.com' },
|
|
190
|
-
{ name: 'User 3', email: 'user3@example.com' }
|
|
191
|
-
];
|
|
192
|
-
|
|
193
|
-
const created = await Promise.all(
|
|
194
|
-
users.map(user => api.insert(user, { type: 'users' }))
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
// Update multiple records
|
|
198
|
-
const updates = [
|
|
199
|
-
{ id: '1', data: { active: false } },
|
|
200
|
-
{ id: '2', data: { active: false } },
|
|
201
|
-
{ id: '3', data: { active: false } }
|
|
202
|
-
];
|
|
203
|
-
|
|
204
|
-
await Promise.all(
|
|
205
|
-
updates.map(({ id, data }) =>
|
|
206
|
-
api.update(id, data, { type: 'users' })
|
|
207
|
-
)
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
With Transactions (MySQL)
|
|
211
|
-
|
|
212
|
-
// Get connection pool
|
|
213
|
-
const { pool } = api.getConnection();
|
|
214
|
-
const connection = await pool.getConnection();
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
await connection.beginTransaction();
|
|
218
|
-
|
|
219
|
-
// Create user
|
|
220
|
-
const user = await api.insert({
|
|
221
|
-
name: 'Transaction User',
|
|
222
|
-
email: 'tx@example.com'
|
|
223
|
-
}, {
|
|
224
|
-
type: 'users',
|
|
225
|
-
connection // Pass connection for transaction
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// Create related profile
|
|
229
|
-
await api.insert({
|
|
230
|
-
userId: user.data.id,
|
|
231
|
-
bio: 'Created in transaction'
|
|
232
|
-
}, {
|
|
233
|
-
type: 'profiles',
|
|
234
|
-
connection
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
await connection.commit();
|
|
238
|
-
} catch (error) {
|
|
239
|
-
await connection.rollback();
|
|
240
|
-
throw error;
|
|
241
|
-
} finally {
|
|
242
|
-
connection.release();
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
With Custom Validation
|
|
246
|
-
|
|
247
|
-
// Add custom validation hook
|
|
248
|
-
api.hook('afterValidate', async (context) => {
|
|
249
|
-
if (context.options.type === 'users') {
|
|
250
|
-
const { data, method } = context;
|
|
251
|
-
|
|
252
|
-
// Check email uniqueness
|
|
253
|
-
if ((method === 'insert' || method === 'update') && data.email) {
|
|
254
|
-
const existing = await api.query({
|
|
255
|
-
filter: { email: data.email }
|
|
256
|
-
}, { type: 'users' });
|
|
257
|
-
|
|
258
|
-
if (existing.data.length > 0) {
|
|
259
|
-
context.errors.push({
|
|
260
|
-
field: 'email',
|
|
261
|
-
message: 'Email already exists',
|
|
262
|
-
code: 'DUPLICATE_EMAIL'
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
Error Handling
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
const user = await api.insert({
|
|
273
|
-
name: 'J', // Too short!
|
|
274
|
-
email: 'invalid-email'
|
|
275
|
-
}, { type: 'users' });
|
|
276
|
-
} catch (error) {
|
|
277
|
-
if (error.errors) {
|
|
278
|
-
// Validation errors
|
|
279
|
-
error.errors.forEach(err => {
|
|
280
|
-
console.log(`${err.field}: ${err.message}`);
|
|
281
|
-
});
|
|
282
|
-
} else {
|
|
283
|
-
// Other errors
|
|
284
|
-
console.error('Unexpected error:', error);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
Direct Storage Access
|
|
289
|
-
|
|
290
|
-
// If you need direct access to storage
|
|
291
|
-
if (api.memoryData) {
|
|
292
|
-
// Direct memory access
|
|
293
|
-
console.log('All data:', api.memoryData);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (api.mysqlPools) {
|
|
297
|
-
// Direct MySQL access
|
|
298
|
-
const { pool } = api.getConnection();
|
|
299
|
-
const [rows] = await pool.query('SELECT * FROM users WHERE active = ?',
|
|
300
|
-
[true]);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
Complete Example: Task Manager
|
|
304
|
-
|
|
305
|
-
import { createApi, Schema, PositioningPlugin } from
|
|
306
|
-
'./jsonrestapi/index.js';
|
|
307
|
-
|
|
308
|
-
// Create API
|
|
309
|
-
const api = createApi({ storage: 'memory' })
|
|
310
|
-
.use(PositioningPlugin);
|
|
311
|
-
|
|
312
|
-
// Define schema
|
|
313
|
-
const taskSchema = new Schema({
|
|
314
|
-
id: { type: 'id' },
|
|
315
|
-
title: { type: 'string', required: true, min: 1, max: 200 },
|
|
316
|
-
description: { type: 'string', max: 1000 },
|
|
317
|
-
completed: { type: 'boolean', default: false },
|
|
318
|
-
priority: { type: 'string', enum: ['low', 'medium', 'high'], default:
|
|
319
|
-
'medium' },
|
|
320
|
-
dueDate: { type: 'date' },
|
|
321
|
-
position: { type: 'number' },
|
|
322
|
-
createdAt: { type: 'timestamp', default: () => Date.now() }
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
api.addResource('tasks', taskSchema);
|
|
326
|
-
|
|
327
|
-
// Usage
|
|
328
|
-
async function taskManager() {
|
|
329
|
-
// Create tasks
|
|
330
|
-
const task1 = await api.insert({
|
|
331
|
-
title: 'Write documentation',
|
|
332
|
-
priority: 'high',
|
|
333
|
-
dueDate: '2024-12-31'
|
|
334
|
-
}, { type: 'tasks' });
|
|
335
|
-
|
|
336
|
-
const task2 = await api.insert({
|
|
337
|
-
title: 'Review PR',
|
|
338
|
-
priority: 'medium',
|
|
339
|
-
beforeId: task1.data.id // Position before first task
|
|
340
|
-
}, {
|
|
341
|
-
type: 'tasks',
|
|
342
|
-
positioning: { enabled: true }
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
// List high priority incomplete tasks
|
|
346
|
-
const urgent = await api.query({
|
|
347
|
-
filter: {
|
|
348
|
-
completed: false,
|
|
349
|
-
priority: 'high'
|
|
350
|
-
},
|
|
351
|
-
sort: 'position,dueDate'
|
|
352
|
-
}, { type: 'tasks' });
|
|
353
|
-
|
|
354
|
-
console.log(`Found ${urgent.meta.total} urgent tasks`);
|
|
355
|
-
|
|
356
|
-
// Complete a task
|
|
357
|
-
await api.update(task1.data.id, {
|
|
358
|
-
completed: true
|
|
359
|
-
}, { type: 'tasks' });
|
|
360
|
-
|
|
361
|
-
// Reorder tasks
|
|
362
|
-
await api.reposition('tasks', task2.data.id, null); // Move to end
|
|
363
|
-
}
|
|
364
|
-
|
package/plugins/api-registry.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import semver from 'semver';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* API Registry Plugin - Automatic version management
|
|
5
|
-
*/
|
|
6
|
-
export const ApiRegistryPlugin = {
|
|
7
|
-
install(api, options = {}) {
|
|
8
|
-
// Initialize the global registry if it doesn't exist
|
|
9
|
-
if (!globalThis.__jsonRestApiRegistry) {
|
|
10
|
-
globalThis.__jsonRestApiRegistry = new ApiRegistry();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const registry = globalThis.__jsonRestApiRegistry;
|
|
14
|
-
|
|
15
|
-
// Register this API instance
|
|
16
|
-
const apiName = options.name || api.options.name;
|
|
17
|
-
const apiVersion = options.version || api.options.version;
|
|
18
|
-
|
|
19
|
-
if (!apiName || !apiVersion) {
|
|
20
|
-
throw new Error('API name and version are required for registration');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
api.apiName = apiName;
|
|
24
|
-
api.apiVersion = apiVersion;
|
|
25
|
-
|
|
26
|
-
// Register in global registry
|
|
27
|
-
registry.register(apiName, apiVersion, api);
|
|
28
|
-
|
|
29
|
-
// Add version resolution to the API
|
|
30
|
-
api.apis = new Proxy({}, {
|
|
31
|
-
get(target, prop) {
|
|
32
|
-
const requestedVersion = api.apiVersion;
|
|
33
|
-
return registry.getCompatibleApi(prop, requestedVersion);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Override HTTP routes to handle version negotiation
|
|
38
|
-
if (api.router) {
|
|
39
|
-
const originalMount = api.mount;
|
|
40
|
-
api.mount = function(app, basePath = '/api') {
|
|
41
|
-
// Mount versioned routes
|
|
42
|
-
const versionPath = `${basePath}/${apiVersion}`;
|
|
43
|
-
originalMount.call(api, app, versionPath);
|
|
44
|
-
|
|
45
|
-
// Also mount on base path with version negotiation
|
|
46
|
-
app.use(`${basePath}/:apiName/*`, (req, res, next) => {
|
|
47
|
-
const requestedVersion =
|
|
48
|
-
req.headers['api-version'] ||
|
|
49
|
-
req.headers['x-api-version'] ||
|
|
50
|
-
req.query.v ||
|
|
51
|
-
'latest';
|
|
52
|
-
|
|
53
|
-
const compatibleApi = registry.getCompatibleApi(
|
|
54
|
-
req.params.apiName,
|
|
55
|
-
requestedVersion
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
if (!compatibleApi) {
|
|
59
|
-
return res.status(400).json({
|
|
60
|
-
errors: [{
|
|
61
|
-
status: '400',
|
|
62
|
-
title: 'Version Not Found',
|
|
63
|
-
detail: `No compatible version found for ${req.params.apiName} ${requestedVersion}`
|
|
64
|
-
}]
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Forward to the compatible API
|
|
69
|
-
req.url = req.url.replace(`/${req.params.apiName}`, '');
|
|
70
|
-
compatibleApi.router(req, res, next);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
return api;
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Global API Registry
|
|
81
|
-
*/
|
|
82
|
-
class ApiRegistry {
|
|
83
|
-
constructor() {
|
|
84
|
-
this.apis = new Map(); // name -> version -> api
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
register(name, version, api) {
|
|
88
|
-
if (!this.apis.has(name)) {
|
|
89
|
-
this.apis.set(name, new Map());
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const versions = this.apis.get(name);
|
|
93
|
-
versions.set(version, api);
|
|
94
|
-
|
|
95
|
-
// Sort versions for efficient lookup
|
|
96
|
-
const sortedVersions = Array.from(versions.entries())
|
|
97
|
-
.sort(([a], [b]) => semver.compare(b, a));
|
|
98
|
-
|
|
99
|
-
this.apis.set(name, new Map(sortedVersions));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
getCompatibleApi(name, versionSpec) {
|
|
103
|
-
const versions = this.apis.get(name);
|
|
104
|
-
if (!versions) return null;
|
|
105
|
-
|
|
106
|
-
// Special case: latest
|
|
107
|
-
if (versionSpec === 'latest') {
|
|
108
|
-
return versions.values().next().value;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Exact match
|
|
112
|
-
if (versions.has(versionSpec)) {
|
|
113
|
-
return versions.get(versionSpec);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Find compatible version
|
|
117
|
-
for (const [version, api] of versions) {
|
|
118
|
-
if (semver.satisfies(version, versionSpec)) {
|
|
119
|
-
return api;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Fallback: find any version >= requested
|
|
124
|
-
if (!versionSpec.includes('>') && !versionSpec.includes('<') && !versionSpec.includes('^') && !versionSpec.includes('~')) {
|
|
125
|
-
for (const [version, api] of versions) {
|
|
126
|
-
if (semver.gte(version, versionSpec)) {
|
|
127
|
-
return api;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
getAllVersions(name) {
|
|
136
|
-
const versions = this.apis.get(name);
|
|
137
|
-
return versions ? Array.from(versions.keys()) : [];
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Export the registry for direct access if needed
|
|
142
|
-
export const apiRegistry = globalThis.__jsonRestApiRegistry || new ApiRegistry();
|