json-rest-api 0.1.1 → 1.0.1
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 -20
- package/README.md +728 -120
- 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 +28 -202
- package/lib/payload-validators.js +685 -0
- package/lib/rest-api-errors.js +58 -0
- package/package.json +37 -20
- 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/.jshintrc +0 -48
- package/.npmignore +0 -15
- package/examples/client.js +0 -18
- package/examples/client2.js +0 -27
- package/examples/server.js +0 -23
- package/test.js +0 -101
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Api } from 'hooked-api';
|
|
2
|
+
import { RestApiPlugin, FileHandlingPlugin, ExpressPlugin } from 'json-rest-api';
|
|
3
|
+
import { LocalStorage } from 'json-rest-api';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
const api = new Api({
|
|
13
|
+
name: 'my-library-api',
|
|
14
|
+
version: '1.0.0'
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Create a storage adapter
|
|
18
|
+
const coverStorage = new LocalStorage({
|
|
19
|
+
directory: './uploads/covers',
|
|
20
|
+
baseUrl: 'http://localhost:3000/uploads/covers'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Add plugins (ORDER MATTERS!)
|
|
24
|
+
api.use(RestApiPlugin);
|
|
25
|
+
api.use(FileHandlingPlugin); // Must come after RestApiPlugin
|
|
26
|
+
api.use(ExpressPlugin); // Must come after FileHandlingPlugin
|
|
27
|
+
|
|
28
|
+
// Update the books schema to include a cover image
|
|
29
|
+
api.addResource('books', {
|
|
30
|
+
schema: {
|
|
31
|
+
title: { type: 'string', required: true },
|
|
32
|
+
author: { type: 'string', required: true },
|
|
33
|
+
year: { type: 'number' },
|
|
34
|
+
isbn: { type: 'string' },
|
|
35
|
+
cover: {
|
|
36
|
+
type: 'file',
|
|
37
|
+
storage: coverStorage,
|
|
38
|
+
accepts: ['image/jpeg', 'image/png', 'image/gif'],
|
|
39
|
+
maxSize: '5mb'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Add dataPost helper to handle the cover URL
|
|
45
|
+
api.customize({
|
|
46
|
+
vars: {
|
|
47
|
+
helpers: {
|
|
48
|
+
dataPost: async ({ scopeName, inputRecord }) => {
|
|
49
|
+
const newBook = {
|
|
50
|
+
id: String(Date.now()),
|
|
51
|
+
...inputRecord.data.attributes
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// The file has already been uploaded and replaced with a URL
|
|
55
|
+
console.log('Book cover URL:', newBook.cover);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
data: {
|
|
59
|
+
type: 'books',
|
|
60
|
+
id: newBook.id,
|
|
61
|
+
attributes: {
|
|
62
|
+
title: newBook.title,
|
|
63
|
+
author: newBook.author,
|
|
64
|
+
year: newBook.year,
|
|
65
|
+
cover: newBook.cover // This is now a URL!
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
dataQuery: async () => ({ data: [] })
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Wait for async plugin initialization
|
|
77
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
78
|
+
|
|
79
|
+
// Create Express app
|
|
80
|
+
const app = express();
|
|
81
|
+
|
|
82
|
+
// Serve uploaded files
|
|
83
|
+
app.use('/uploads', express.static('./uploads'));
|
|
84
|
+
|
|
85
|
+
// Mount the API
|
|
86
|
+
app.use(api.http.express.router);
|
|
87
|
+
|
|
88
|
+
const server = app.listen(3000, async () => {
|
|
89
|
+
console.log('✓ File upload API running at http://localhost:3000/api');
|
|
90
|
+
|
|
91
|
+
// Create a test image file
|
|
92
|
+
const testImagePath = './test-cover.png';
|
|
93
|
+
await fs.writeFile(testImagePath, Buffer.from('PNG_TEST_DATA'));
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Test file upload with curl
|
|
97
|
+
console.log('\nTesting file upload with multipart/form-data...\n');
|
|
98
|
+
|
|
99
|
+
const { stdout, stderr } = await execAsync(`curl -s -X POST http://localhost:3000/api/books \
|
|
100
|
+
-F "title=The Hobbit" \
|
|
101
|
+
-F "author=J.R.R. Tolkien" \
|
|
102
|
+
-F "year=1937" \
|
|
103
|
+
-F "cover=@${testImagePath}"`);
|
|
104
|
+
|
|
105
|
+
if (stderr) {
|
|
106
|
+
console.error('Curl error:', stderr);
|
|
107
|
+
} else {
|
|
108
|
+
console.log('Response:', stdout);
|
|
109
|
+
|
|
110
|
+
// Parse response to check if it worked
|
|
111
|
+
try {
|
|
112
|
+
const response = JSON.parse(stdout);
|
|
113
|
+
if (response.data && response.data.attributes && response.data.attributes.cover) {
|
|
114
|
+
console.log('\n✓ File upload successful!');
|
|
115
|
+
console.log(' Book ID:', response.data.id);
|
|
116
|
+
console.log(' Cover URL:', response.data.attributes.cover);
|
|
117
|
+
} else if (response.errors) {
|
|
118
|
+
console.log('\n✗ File upload failed with errors:', response.errors);
|
|
119
|
+
}
|
|
120
|
+
} catch (e) {
|
|
121
|
+
console.log('\n✗ Could not parse response');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('Test failed:', error.message);
|
|
126
|
+
} finally {
|
|
127
|
+
// Cleanup
|
|
128
|
+
await fs.unlink(testImagePath).catch(() => {});
|
|
129
|
+
server.close();
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Api } from 'hooked-api';
|
|
2
|
+
import { RestApiPlugin, FileHandlingPlugin, HttpPlugin } from 'json-rest-api';
|
|
3
|
+
|
|
4
|
+
const api = new Api({
|
|
5
|
+
name: 'my-library-api',
|
|
6
|
+
version: '1.0.0'
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// Add plugins
|
|
10
|
+
api.use(RestApiPlugin);
|
|
11
|
+
api.use(FileHandlingPlugin);
|
|
12
|
+
api.use(HttpPlugin, {
|
|
13
|
+
port: 3000, // Server port (default: 3000)
|
|
14
|
+
basePath: '/api', // API base path (default: '/api')
|
|
15
|
+
strictContentType: true, // Enforce JSON content types (default: true)
|
|
16
|
+
requestSizeLimit: '10mb' // Max request body size (default: '1mb')
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Add your books resource
|
|
20
|
+
api.addResource('books', {
|
|
21
|
+
schema: {
|
|
22
|
+
title: { type: 'string', required: true },
|
|
23
|
+
author: { type: 'string', required: true },
|
|
24
|
+
year: { type: 'number' },
|
|
25
|
+
isbn: { type: 'string' }
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Add simple data helpers
|
|
30
|
+
api.customize({
|
|
31
|
+
vars: {
|
|
32
|
+
helpers: {
|
|
33
|
+
dataQuery: async ({ scopeName, queryParams }) => {
|
|
34
|
+
const fakeBooks = [
|
|
35
|
+
{ id: '1', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
|
|
36
|
+
{ id: '2', title: '1984', author: 'George Orwell', year: 1949 },
|
|
37
|
+
{ id: '3', title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 }
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
data: fakeBooks.map(book => ({
|
|
42
|
+
type: 'books',
|
|
43
|
+
id: book.id,
|
|
44
|
+
attributes: {
|
|
45
|
+
title: book.title,
|
|
46
|
+
author: book.author,
|
|
47
|
+
year: book.year
|
|
48
|
+
}
|
|
49
|
+
}))
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
dataGet: async ({ scopeName, id }) => {
|
|
54
|
+
const book = { id, title: 'Example Book', author: 'Example Author', year: 2024 };
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
data: {
|
|
58
|
+
type: 'books',
|
|
59
|
+
id: book.id,
|
|
60
|
+
attributes: {
|
|
61
|
+
title: book.title,
|
|
62
|
+
author: book.author,
|
|
63
|
+
year: book.year
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
dataPost: async ({ scopeName, inputRecord }) => {
|
|
70
|
+
const newBook = {
|
|
71
|
+
id: String(Date.now()),
|
|
72
|
+
...inputRecord.data.attributes
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
data: {
|
|
77
|
+
type: 'books',
|
|
78
|
+
id: newBook.id,
|
|
79
|
+
attributes: {
|
|
80
|
+
title: newBook.title,
|
|
81
|
+
author: newBook.author,
|
|
82
|
+
year: newBook.year
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
dataPut: async ({ scopeName, id, inputRecord }) => {
|
|
89
|
+
return {
|
|
90
|
+
data: {
|
|
91
|
+
type: 'books',
|
|
92
|
+
id: id,
|
|
93
|
+
attributes: inputRecord.data.attributes
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
dataPatch: async ({ scopeName, id, inputRecord }) => {
|
|
99
|
+
return {
|
|
100
|
+
data: {
|
|
101
|
+
type: 'books',
|
|
102
|
+
id: id,
|
|
103
|
+
attributes: inputRecord.data.attributes
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
dataDelete: async ({ scopeName, id }) => {
|
|
109
|
+
return { success: true };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Wait for async plugin installation to complete
|
|
116
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
117
|
+
|
|
118
|
+
// Start the HTTP server - multiple options:
|
|
119
|
+
console.log('Testing HTTP plugin options...\n');
|
|
120
|
+
|
|
121
|
+
// Check if api.http namespace exists
|
|
122
|
+
console.log('api.http exists?', !!api.http);
|
|
123
|
+
console.log('api.http.startServer exists?', typeof api.http?.startServer);
|
|
124
|
+
|
|
125
|
+
// Option 1: Start on configured port (3000 by default)
|
|
126
|
+
api.http.startServer();
|
|
127
|
+
|
|
128
|
+
console.log('✓ HTTP server started successfully!');
|
|
129
|
+
console.log(' API running at http://localhost:3000/api');
|
|
130
|
+
|
|
131
|
+
// Test with curl commands after a short delay
|
|
132
|
+
import { exec } from 'child_process';
|
|
133
|
+
import { promisify } from 'util';
|
|
134
|
+
const execAsync = promisify(exec);
|
|
135
|
+
|
|
136
|
+
setTimeout(async () => {
|
|
137
|
+
try {
|
|
138
|
+
console.log('\nTesting endpoints...\n');
|
|
139
|
+
|
|
140
|
+
// Test GET all books
|
|
141
|
+
console.log('1. Testing GET /api/books:');
|
|
142
|
+
const { stdout: getAll } = await execAsync('curl -s http://localhost:3000/api/books');
|
|
143
|
+
console.log('✓ Response:', getAll.substring(0, 100) + '...');
|
|
144
|
+
|
|
145
|
+
// Test GET single book
|
|
146
|
+
console.log('\n2. Testing GET /api/books/1:');
|
|
147
|
+
const { stdout: getOne } = await execAsync('curl -s http://localhost:3000/api/books/1');
|
|
148
|
+
console.log('✓ Response:', getOne.substring(0, 100) + '...');
|
|
149
|
+
|
|
150
|
+
// Close server
|
|
151
|
+
console.log('\nClosing server...');
|
|
152
|
+
api.http.server.close();
|
|
153
|
+
process.exit(0);
|
|
154
|
+
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error('Test error:', error.message);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
}, 500);
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Api } from 'hooked-api';
|
|
2
|
+
import { RestApiPlugin } from 'json-rest-api';
|
|
3
|
+
|
|
4
|
+
const api = new Api({
|
|
5
|
+
name: 'my-library-api',
|
|
6
|
+
version: '1.0.0'
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// Use the REST API plugin
|
|
10
|
+
api.use(RestApiPlugin);
|
|
11
|
+
|
|
12
|
+
// Define a books resource
|
|
13
|
+
api.addResource('books', {
|
|
14
|
+
schema: {
|
|
15
|
+
title: { type: 'string', required: true },
|
|
16
|
+
author: { type: 'string', required: true },
|
|
17
|
+
year: { type: 'number' },
|
|
18
|
+
isbn: { type: 'string' }
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Add simple data helpers
|
|
23
|
+
api.customize({
|
|
24
|
+
vars: {
|
|
25
|
+
helpers: {
|
|
26
|
+
dataQuery: async ({ scopeName, queryParams }) => {
|
|
27
|
+
const fakeBooks = [
|
|
28
|
+
{ id: '1', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
|
|
29
|
+
{ id: '2', title: '1984', author: 'George Orwell', year: 1949 },
|
|
30
|
+
{ id: '3', title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 }
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
data: fakeBooks.map(book => ({
|
|
35
|
+
type: 'books',
|
|
36
|
+
id: book.id,
|
|
37
|
+
attributes: {
|
|
38
|
+
title: book.title,
|
|
39
|
+
author: book.author,
|
|
40
|
+
year: book.year
|
|
41
|
+
}
|
|
42
|
+
}))
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
dataGet: async ({ scopeName, id }) => {
|
|
47
|
+
const book = { id, title: 'Example Book', author: 'Example Author', year: 2024 };
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
data: {
|
|
51
|
+
type: 'books',
|
|
52
|
+
id: book.id,
|
|
53
|
+
attributes: {
|
|
54
|
+
title: book.title,
|
|
55
|
+
author: book.author,
|
|
56
|
+
year: book.year
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
dataPost: async ({ scopeName, inputRecord }) => {
|
|
63
|
+
const newBook = {
|
|
64
|
+
id: String(Date.now()),
|
|
65
|
+
...inputRecord.data.attributes
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
data: {
|
|
70
|
+
type: 'books',
|
|
71
|
+
id: newBook.id,
|
|
72
|
+
attributes: {
|
|
73
|
+
title: newBook.title,
|
|
74
|
+
author: newBook.author,
|
|
75
|
+
year: newBook.year
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
dataPut: async ({ scopeName, id, inputRecord }) => {
|
|
82
|
+
return {
|
|
83
|
+
data: {
|
|
84
|
+
type: 'books',
|
|
85
|
+
id: id,
|
|
86
|
+
attributes: inputRecord.data.attributes
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
dataPatch: async ({ scopeName, id, inputRecord }) => {
|
|
92
|
+
return {
|
|
93
|
+
data: {
|
|
94
|
+
type: 'books',
|
|
95
|
+
id: id,
|
|
96
|
+
attributes: inputRecord.data.attributes
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
dataDelete: async ({ scopeName, id }) => {
|
|
102
|
+
return { success: true };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Test programmatic API usage
|
|
109
|
+
console.log('Testing programmatic API usage...\n');
|
|
110
|
+
|
|
111
|
+
// First check what methods are available
|
|
112
|
+
console.log('Available methods on api.resources.books:');
|
|
113
|
+
if (api.resources && api.resources.books) {
|
|
114
|
+
const methods = Object.keys(api.resources.books).filter(k => typeof api.resources.books[k] === 'function');
|
|
115
|
+
console.log(' Methods:', methods.join(', '));
|
|
116
|
+
} else {
|
|
117
|
+
console.log(' ERROR: api.resources.books is not available');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
// Query all books
|
|
122
|
+
console.log('\n1. Testing query():');
|
|
123
|
+
const allBooks = await api.resources.books.query({});
|
|
124
|
+
console.log('✓ Query successful:', JSON.stringify(allBooks, null, 2));
|
|
125
|
+
|
|
126
|
+
// Get a single book
|
|
127
|
+
console.log('\n2. Testing get():');
|
|
128
|
+
const book = await api.resources.books.get({ id: '1' });
|
|
129
|
+
console.log('✓ Get successful:', JSON.stringify(book, null, 2));
|
|
130
|
+
|
|
131
|
+
// Create a new book
|
|
132
|
+
console.log('\n3. Testing post():');
|
|
133
|
+
const newBook = await api.resources.books.post({
|
|
134
|
+
inputRecord: {
|
|
135
|
+
data: {
|
|
136
|
+
type: 'books',
|
|
137
|
+
attributes: {
|
|
138
|
+
title: 'New Book',
|
|
139
|
+
author: 'New Author',
|
|
140
|
+
year: 2024
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
console.log('✓ Post successful:', JSON.stringify(newBook, null, 2));
|
|
146
|
+
|
|
147
|
+
// Update a book (replace all fields)
|
|
148
|
+
console.log('\n4. Testing put():');
|
|
149
|
+
const updatedBook = await api.resources.books.put({
|
|
150
|
+
id: '1',
|
|
151
|
+
inputRecord: {
|
|
152
|
+
data: {
|
|
153
|
+
type: 'books',
|
|
154
|
+
attributes: {
|
|
155
|
+
title: 'Updated Title',
|
|
156
|
+
author: 'Updated Author',
|
|
157
|
+
year: 2024
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
console.log('✓ Put successful:', JSON.stringify(updatedBook, null, 2));
|
|
163
|
+
|
|
164
|
+
// Partially update a book
|
|
165
|
+
console.log('\n5. Testing patch():');
|
|
166
|
+
const patchedBook = await api.resources.books.patch({
|
|
167
|
+
id: '1',
|
|
168
|
+
inputRecord: {
|
|
169
|
+
data: {
|
|
170
|
+
type: 'books',
|
|
171
|
+
attributes: {
|
|
172
|
+
title: 'New Title Only'
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
console.log('✓ Patch successful:', JSON.stringify(patchedBook, null, 2));
|
|
178
|
+
|
|
179
|
+
// Delete a book
|
|
180
|
+
console.log('\n6. Testing delete():');
|
|
181
|
+
const deleteResult = await api.resources.books.delete({ id: '1' });
|
|
182
|
+
console.log('✓ Delete successful:', JSON.stringify(deleteResult, null, 2));
|
|
183
|
+
|
|
184
|
+
console.log('\n✅ All programmatic API tests passed!');
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error('❌ Error:', error.message);
|
|
187
|
+
console.error(error.stack);
|
|
188
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Api } from 'hooked-api';
|
|
2
|
+
import { RestApiPlugin } from 'json-rest-api';
|
|
3
|
+
|
|
4
|
+
const api = new Api({
|
|
5
|
+
name: 'my-library-api',
|
|
6
|
+
version: '1.0.0'
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// Use the REST API plugin
|
|
10
|
+
api.use(RestApiPlugin);
|
|
11
|
+
|
|
12
|
+
console.log('✓ REST API Plugin loaded successfully!');
|
|
13
|
+
console.log(' Plugin registered:', api.plugins.has(RestApiPlugin));
|
package/.jshintrc
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"browser" : false,
|
|
3
|
-
"curly" : false,
|
|
4
|
-
"devel" : false,
|
|
5
|
-
"eqeqeq" : true,
|
|
6
|
-
"eqnull" : true,
|
|
7
|
-
"esnext" : true,
|
|
8
|
-
"expr" : true,
|
|
9
|
-
"forin" : false,
|
|
10
|
-
"immed" : false,
|
|
11
|
-
"indent" : 4,
|
|
12
|
-
"latedef" : false,
|
|
13
|
-
"laxcomma" : true,
|
|
14
|
-
"maxcomplexity" : 39,
|
|
15
|
-
"maxdepth" : 5,
|
|
16
|
-
"maxlen" : 110,
|
|
17
|
-
"maxparams" : 6,
|
|
18
|
-
"maxstatements" : 100,
|
|
19
|
-
"multistr" : true,
|
|
20
|
-
"newcap" : true,
|
|
21
|
-
"noarg" : true,
|
|
22
|
-
"noempty" : true,
|
|
23
|
-
"nonew" : false,
|
|
24
|
-
"node" : true,
|
|
25
|
-
"onevar" : false,
|
|
26
|
-
"plusplus" : false,
|
|
27
|
-
"quotmark" : "single",
|
|
28
|
-
"regexp" : false,
|
|
29
|
-
"undef" : true,
|
|
30
|
-
"unused" : true,
|
|
31
|
-
"strict" : true,
|
|
32
|
-
"sub" : true,
|
|
33
|
-
"trailing" : true,
|
|
34
|
-
"undef" : true,
|
|
35
|
-
"unused" : true,
|
|
36
|
-
"white" : false,
|
|
37
|
-
"validthis" : true,
|
|
38
|
-
|
|
39
|
-
"globals" : {
|
|
40
|
-
"console" : true,
|
|
41
|
-
"describe" : true,
|
|
42
|
-
"it" : true,
|
|
43
|
-
"before" : true,
|
|
44
|
-
"beforeEach": true,
|
|
45
|
-
"expect" : true,
|
|
46
|
-
"after" : true
|
|
47
|
-
}
|
|
48
|
-
}
|
package/.npmignore
DELETED
package/examples/client.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
var request = require('request');
|
|
3
|
-
var inspect = require('util').inspect;
|
|
4
|
-
var debug = require('debug')('json-rest-api:example:client');
|
|
5
|
-
var assert = require('assert');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
request({json:true, uri: 'http://localhost:8000/ping'}, function(err, res, json) {
|
|
9
|
-
if (err) {
|
|
10
|
-
debug('Error: '+inspect(err));
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
debug('typeof json: '+typeof json);
|
|
14
|
-
debug('json: '+inspect(json));
|
|
15
|
-
assert.ok(json.success === true);
|
|
16
|
-
assert.ok(json.pong === 'pong');
|
|
17
|
-
console.log('Success!');
|
|
18
|
-
});
|
package/examples/client2.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
var request = require('request');
|
|
3
|
-
var inspect = require('util').inspect;
|
|
4
|
-
var debug = require('debug')('json-rest-api:example:client');
|
|
5
|
-
var assert = require('assert');
|
|
6
|
-
|
|
7
|
-
var data = {
|
|
8
|
-
field1: 'Edmond',
|
|
9
|
-
field2: 100,
|
|
10
|
-
field3: {
|
|
11
|
-
alpha: true,
|
|
12
|
-
beta: [ 0, 1, 2 ],
|
|
13
|
-
gamma: 999
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
request({method:'POST', json:data, uri: 'http://localhost:8000/postData'}, function(err, res, json) {
|
|
18
|
-
if (err) {
|
|
19
|
-
debug('Error: '+inspect(err));
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
debug('typeof json: '+typeof json);
|
|
23
|
-
debug('json: '+inspect(json));
|
|
24
|
-
assert.ok(json.success === true);
|
|
25
|
-
assert.ok(json.pong === 'pong');
|
|
26
|
-
console.log('Success!');
|
|
27
|
-
});
|
package/examples/server.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
var JsonRestApi = require('../index');
|
|
3
|
-
var inspect = require('util').inspect;
|
|
4
|
-
var debug = require('debug')('json-rest-api:example:server');
|
|
5
|
-
|
|
6
|
-
var RestApi = new JsonRestApi({port: 8000}, function(err) {
|
|
7
|
-
if (err) {
|
|
8
|
-
debug('/ping error: '+inspect(err));
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
console.log('Listening on port 8000.');
|
|
12
|
-
|
|
13
|
-
// add a route
|
|
14
|
-
RestApi.addRoute('get', '/ping', function(req, res) {
|
|
15
|
-
res.json({success: true, pong: 'pong'});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
RestApi.addRoute('post', '/postData', function(req, res, json) {
|
|
19
|
-
console.log(req.body);
|
|
20
|
-
console.log(json);
|
|
21
|
-
res.json({success: true, pong: 'pong'});
|
|
22
|
-
});
|
|
23
|
-
});
|