json-rest-api 1.0.0 → 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.
Files changed (67) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/LICENSE +674 -0
  3. package/README.md +655 -309
  4. package/RELATIONSHIPS.md +1013 -0
  5. package/TODO_rest-api-plugin-extras.js +142 -0
  6. package/connectors_ideas/express-plugin.js +120 -0
  7. package/connectors_ideas/fastify.js +219 -0
  8. package/connectors_ideas/http-plugin.js +138 -0
  9. package/connectors_ideas/koa-plugin.js +113 -0
  10. package/docs/fileUploads.md +722 -0
  11. package/docs/mini-guides/file-uploads-complete.md +159 -0
  12. package/examples/.gitkeep +0 -0
  13. package/examples/express-example.js +262 -0
  14. package/examples/express-plugin.js +366 -0
  15. package/examples/file-upload-example.js +205 -0
  16. package/examples/filename-strategies.js +140 -0
  17. package/examples/knex-basic.js +66 -0
  18. package/index.js +31 -56
  19. package/lib/payload-validators.js +685 -0
  20. package/lib/rest-api-errors.js +58 -0
  21. package/package.json +41 -14
  22. package/payloadValdators.js +686 -0
  23. package/plugins/core/connectors/express-plugin.js +449 -0
  24. package/plugins/core/connectors/http-plugin.js +450 -0
  25. package/plugins/core/file-handling-plugin.js +338 -0
  26. package/plugins/core/lib/busboy-detector.js +167 -0
  27. package/plugins/core/lib/formidable-detector.js +130 -0
  28. package/plugins/core/rest-api-knex-plugin.js +200 -0
  29. package/plugins/core/rest-api-plugin.js +1419 -0
  30. package/plugins/storage/local-storage.js +218 -0
  31. package/plugins/storage/s3-storage.js +125 -0
  32. package/test-examples/package-lock.json +906 -0
  33. package/test-examples/package.json +15 -0
  34. package/test-examples/test-basic-api.js +11 -0
  35. package/test-examples/test-books-resource.js +116 -0
  36. package/test-examples/test-complete-file-upload.js +140 -0
  37. package/test-examples/test-debug.js +28 -0
  38. package/test-examples/test-express-plugin.js +177 -0
  39. package/test-examples/test-express-simple.js +28 -0
  40. package/test-examples/test-express-with-import.js +71 -0
  41. package/test-examples/test-file-upload.js +132 -0
  42. package/test-examples/test-http-plugin.js +159 -0
  43. package/test-examples/test-programmatic-api.js +188 -0
  44. package/test-examples/test-rest-api-plugin.js +13 -0
  45. package/TODO.todo +0 -113
  46. package/api.js +0 -628
  47. package/docs/API.md +0 -1306
  48. package/docs/FIXES_APPLIED.md +0 -59
  49. package/docs/GUIDES.md +0 -1792
  50. package/docs/RESOURCES_IN_EXPRESS.md +0 -456
  51. package/docs/example-versioning.js +0 -168
  52. package/docs/example.js +0 -195
  53. package/plugins/API guide.md +0 -364
  54. package/plugins/api-registry.js +0 -142
  55. package/plugins/api-versioning.md +0 -135
  56. package/plugins/http.js +0 -273
  57. package/plugins/logging.js +0 -255
  58. package/plugins/memory.js +0 -182
  59. package/plugins/mysql.js +0 -753
  60. package/plugins/openapi.js +0 -401
  61. package/plugins/positioning.js +0 -220
  62. package/plugins/security.js +0 -261
  63. package/plugins/validation.js +0 -173
  64. package/plugins/versioning.js +0 -288
  65. package/resource-helper.js +0 -79
  66. package/schema.js +0 -481
  67. package/test-basic.js +0 -107
@@ -1,288 +0,0 @@
1
- /**
2
- * Versioning plugin for API versioning and resource version management
3
- */
4
- export const VersioningPlugin = {
5
- install(api, options = {}) {
6
- const defaultOptions = {
7
- // API versioning
8
- apiVersion: '1.0.0',
9
- versionHeader: 'api-version',
10
- versionParam: 'v',
11
-
12
- // Resource versioning
13
- versionField: 'version',
14
- lastModifiedField: 'lastModified',
15
- modifiedByField: 'modifiedBy',
16
- trackHistory: false,
17
- historyTable: null,
18
-
19
- ...options
20
- };
21
-
22
- // Store versioning options
23
- api.versioningOptions = defaultOptions;
24
-
25
- // API Version Management
26
- if (defaultOptions.apiVersion) {
27
- // Add version info to all responses
28
- api.hook('beforeSend', async (context) => {
29
- if (!context.meta) context.meta = {};
30
- context.meta.apiVersion = defaultOptions.apiVersion;
31
- });
32
-
33
- // Version negotiation for HTTP
34
- if (api.router) {
35
- api.router.use((req, res, next) => {
36
- // Check version header
37
- const headerVersion = req.headers[defaultOptions.versionHeader];
38
- // Check version query param
39
- const paramVersion = req.query[defaultOptions.versionParam];
40
-
41
- const requestedVersion = headerVersion || paramVersion;
42
-
43
- if (requestedVersion && requestedVersion !== defaultOptions.apiVersion) {
44
- // Handle version mismatch
45
- if (defaultOptions.strict) {
46
- return res.status(400).json({
47
- errors: [{
48
- status: '400',
49
- title: 'Version Mismatch',
50
- detail: `API version ${requestedVersion} not supported. Current version is ${defaultOptions.apiVersion}`
51
- }]
52
- });
53
- }
54
- }
55
-
56
- // Add version to response headers
57
- res.set('API-Version', defaultOptions.apiVersion);
58
- next();
59
- });
60
- }
61
- }
62
-
63
- // Resource Version Management
64
- if (defaultOptions.versionField) {
65
- // Initialize version on insert
66
- api.hook('beforeInsert', async (context) => {
67
- const { data, options } = context;
68
- if (options.versioning === false) return;
69
-
70
- // Set initial version
71
- if (data[defaultOptions.versionField] === undefined) {
72
- data[defaultOptions.versionField] = 1;
73
- }
74
-
75
- // Set modified timestamp
76
- if (defaultOptions.lastModifiedField) {
77
- data[defaultOptions.lastModifiedField] = new Date().toISOString();
78
- }
79
-
80
- // Set modified by
81
- if (defaultOptions.modifiedByField && options.userId) {
82
- data[defaultOptions.modifiedByField] = options.userId;
83
- }
84
- });
85
-
86
- // Increment version on update
87
- api.hook('beforeUpdate', async (context) => {
88
- const { data, options, id } = context;
89
- if (options.versioning === false) return;
90
-
91
- // Check for optimistic locking
92
- if (defaultOptions.optimisticLocking && data[defaultOptions.versionField]) {
93
- const currentVersion = data[defaultOptions.versionField];
94
- delete data[defaultOptions.versionField]; // Remove from update data
95
-
96
- // Get current record
97
- const impl = api.implementers.get('get');
98
- const current = await impl({ id, options });
99
-
100
- if (current && current[defaultOptions.versionField] !== currentVersion) {
101
- const error = new Error('Version conflict - record has been modified');
102
- error.status = 409;
103
- error.code = 'VERSION_CONFLICT';
104
- throw error;
105
- }
106
- }
107
-
108
- // Save current version for history
109
- if (defaultOptions.trackHistory) {
110
- const impl = api.implementers.get('get');
111
- context.previousVersion = await impl({ id, options });
112
- }
113
-
114
- // Increment version
115
- if (!options.skipVersioning) {
116
- const impl = api.implementers.get('get');
117
- const current = await impl({ id, options });
118
-
119
- if (current) {
120
- data[defaultOptions.versionField] = (current[defaultOptions.versionField] || 0) + 1;
121
- }
122
- }
123
-
124
- // Update modified timestamp
125
- if (defaultOptions.lastModifiedField) {
126
- data[defaultOptions.lastModifiedField] = new Date().toISOString();
127
- }
128
-
129
- // Update modified by
130
- if (defaultOptions.modifiedByField && options.userId) {
131
- data[defaultOptions.modifiedByField] = options.userId;
132
- }
133
- });
134
-
135
- // Save version history after update
136
- api.hook('afterUpdate', async (context) => {
137
- if (!defaultOptions.trackHistory || !context.previousVersion) return;
138
-
139
- await saveVersionHistory(api, context, defaultOptions);
140
- });
141
- }
142
-
143
- // Version comparison helpers
144
- api.compareVersions = (v1, v2) => {
145
- if (typeof v1 === 'number' && typeof v2 === 'number') {
146
- return v1 - v2;
147
- }
148
-
149
- // Semantic version comparison
150
- const parseVersion = (v) => {
151
- const parts = String(v).split('.').map(Number);
152
- return {
153
- major: parts[0] || 0,
154
- minor: parts[1] || 0,
155
- patch: parts[2] || 0
156
- };
157
- };
158
-
159
- const ver1 = parseVersion(v1);
160
- const ver2 = parseVersion(v2);
161
-
162
- if (ver1.major !== ver2.major) return ver1.major - ver2.major;
163
- if (ver1.minor !== ver2.minor) return ver1.minor - ver2.minor;
164
- return ver1.patch - ver2.patch;
165
- };
166
-
167
- // Get version history
168
- api.getVersionHistory = async (type, id, options = {}) => {
169
- if (!defaultOptions.trackHistory) {
170
- throw new Error('Version history tracking is not enabled');
171
- }
172
-
173
- const historyTable = defaultOptions.historyTable || `${type}_history`;
174
-
175
- // This would need to be implemented based on the storage plugin
176
- const result = await api.query({
177
- filter: {
178
- resourceId: id,
179
- resourceType: type
180
- },
181
- sort: `-${defaultOptions.versionField}`
182
- }, {
183
- type: historyTable,
184
- ...options
185
- });
186
-
187
- return result;
188
- };
189
-
190
- // Restore a specific version
191
- api.restoreVersion = async (type, id, version, options = {}) => {
192
- if (!defaultOptions.trackHistory) {
193
- throw new Error('Version history tracking is not enabled');
194
- }
195
-
196
- // Get the historical version
197
- const history = await api.getVersionHistory(type, id, options);
198
- const versionData = history.data.find(h =>
199
- h.attributes[defaultOptions.versionField] === version
200
- );
201
-
202
- if (!versionData) {
203
- throw new Error(`Version ${version} not found`);
204
- }
205
-
206
- // Restore the version
207
- const { resourceId, resourceType, ...data } = versionData.attributes;
208
- delete data.id; // Remove history record ID
209
-
210
- return api.update(id, data, {
211
- type,
212
- ...options,
213
- skipVersioning: false // Ensure new version is created
214
- });
215
- };
216
-
217
- // Version diffing
218
- api.diffVersions = async (type, id, version1, version2, options = {}) => {
219
- const history = await api.getVersionHistory(type, id, options);
220
-
221
- const v1Data = history.data.find(h =>
222
- h.attributes[defaultOptions.versionField] === version1
223
- );
224
- const v2Data = history.data.find(h =>
225
- h.attributes[defaultOptions.versionField] === version2
226
- );
227
-
228
- if (!v1Data || !v2Data) {
229
- throw new Error('One or both versions not found');
230
- }
231
-
232
- // Simple diff implementation
233
- const diff = {
234
- version1,
235
- version2,
236
- changes: []
237
- };
238
-
239
- const v1Attrs = v1Data.attributes;
240
- const v2Attrs = v2Data.attributes;
241
-
242
- // Find changed fields
243
- const allKeys = new Set([...Object.keys(v1Attrs), ...Object.keys(v2Attrs)]);
244
-
245
- for (const key of allKeys) {
246
- if (v1Attrs[key] !== v2Attrs[key]) {
247
- diff.changes.push({
248
- field: key,
249
- oldValue: v1Attrs[key],
250
- newValue: v2Attrs[key]
251
- });
252
- }
253
- }
254
-
255
- return diff;
256
- };
257
- }
258
- };
259
-
260
- /**
261
- * Save version history
262
- */
263
- async function saveVersionHistory(api, context, options) {
264
- const { previousVersion, result, options: contextOptions } = context;
265
- const historyTable = options.historyTable || `${contextOptions.type}_history`;
266
-
267
- // Create history record
268
- const historyData = {
269
- ...previousVersion,
270
- resourceId: previousVersion[api.options.idProperty],
271
- resourceType: contextOptions.type,
272
- savedAt: new Date().toISOString()
273
- };
274
-
275
- // Remove the original ID to avoid conflicts
276
- delete historyData[api.options.idProperty];
277
-
278
- // Save to history table
279
- try {
280
- await api.insert(historyData, {
281
- type: historyTable,
282
- versioning: false // Don't version the history records
283
- });
284
- } catch (error) {
285
- console.error('Failed to save version history:', error);
286
- // Don't fail the main operation if history fails
287
- }
288
- }
@@ -1,79 +0,0 @@
1
- import { Api, MySQLPlugin, ValidationPlugin, HTTPPlugin } from './index.js';
2
-
3
- // Cache for API instances by version
4
- const apiCache = new Map();
5
-
6
- /**
7
- * Get or create a shared API instance for a version
8
- */
9
- export function getVersionApi(version, config = {}) {
10
- const cacheKey = `${config.name || 'app'}-${version}`;
11
-
12
- if (apiCache.has(cacheKey)) {
13
- return apiCache.get(cacheKey);
14
- }
15
-
16
- // Create new API instance for this version
17
- const api = new Api({
18
- name: config.name || 'app',
19
- version,
20
- ...config.apiOptions
21
- });
22
-
23
- // Add default plugins if not already added
24
- if (!api.hasPlugin(ValidationPlugin)) {
25
- api.use(ValidationPlugin);
26
- }
27
-
28
- if (config.mysql && !api.hasPlugin(MySQLPlugin)) {
29
- api.use(MySQLPlugin, config.mysql);
30
- }
31
-
32
- if (config.http && !api.hasPlugin(HTTPPlugin)) {
33
- api.use(HTTPPlugin, {
34
- basePath: `/api/${version}`,
35
- ...config.http
36
- });
37
- }
38
-
39
- // Add any additional plugins
40
- if (config.plugins) {
41
- for (const [plugin, options] of config.plugins) {
42
- if (!api.hasPlugin(plugin)) {
43
- api.use(plugin, options);
44
- }
45
- }
46
- }
47
-
48
- apiCache.set(cacheKey, api);
49
- return api;
50
- }
51
-
52
- /**
53
- * Define a resource with automatic API management
54
- */
55
- export function defineResource(version, name, definition) {
56
- const api = getVersionApi(version, definition.api || {});
57
-
58
- // Add the resource
59
- api.addResource(name, definition.schema, definition.hooks);
60
-
61
- // Configure storage options if needed
62
- if (definition.storage) {
63
- // Store metadata for storage plugins to use
64
- api.resourceOptions = api.resourceOptions || new Map();
65
- api.resourceOptions.set(name, definition.storage);
66
- }
67
-
68
- // Sync schema in development
69
- if (process.env.NODE_ENV === 'development' && definition.syncSchema !== false) {
70
- const storage = definition.storage || {};
71
- api.syncSchema(definition.schema, storage.table || name, {
72
- connection: storage.connection || 'main'
73
- }).catch(err => {
74
- console.error(`Failed to sync schema for ${name}:`, err);
75
- });
76
- }
77
-
78
- return api;
79
- }