json-rest-api 1.0.2 → 1.0.3

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.
@@ -1,132 +0,0 @@
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
- });
@@ -1,159 +0,0 @@
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);
@@ -1,188 +0,0 @@
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
- }
@@ -1,13 +0,0 @@
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));