s3db.js 12.4.0 → 13.1.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/dist/s3db.cjs.js +8066 -3562
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +8066 -3563
- package/dist/s3db.es.js.map +1 -1
- package/package.json +5 -1
- package/src/clients/memory-client.class.js +41 -24
- package/src/database.class.js +52 -17
- package/src/plugins/api/index.js +13 -16
- package/src/plugins/api/routes/resource-routes.js +81 -3
- package/src/plugins/api/server.js +29 -9
- package/src/plugins/audit.plugin.js +2 -4
- package/src/plugins/backup.plugin.js +2 -4
- package/src/plugins/cache.plugin.js +1 -3
- package/src/plugins/costs.plugin.js +0 -2
- package/src/plugins/eventual-consistency/index.js +1 -3
- package/src/plugins/fulltext.plugin.js +2 -4
- package/src/plugins/geo.plugin.js +1 -3
- package/src/plugins/importer/index.js +0 -2
- package/src/plugins/index.js +1 -1
- package/src/plugins/metrics.plugin.js +2 -4
- package/src/plugins/ml/base-model.class.js +459 -0
- package/src/plugins/ml/classification-model.class.js +338 -0
- package/src/plugins/ml/neural-network-model.class.js +312 -0
- package/src/plugins/ml/regression-model.class.js +159 -0
- package/src/plugins/ml/timeseries-model.class.js +346 -0
- package/src/plugins/ml.errors.js +130 -0
- package/src/plugins/ml.plugin.js +1417 -0
- package/src/plugins/plugin.class.js +1 -3
- package/src/plugins/queue-consumer.plugin.js +1 -3
- package/src/plugins/relation.plugin.js +1 -3
- package/src/plugins/replicator.plugin.js +2 -4
- package/src/plugins/s3-queue.plugin.js +1 -3
- package/src/plugins/scheduler.plugin.js +2 -4
- package/src/plugins/state-machine.plugin.js +2 -4
- package/src/plugins/tfstate/index.js +0 -2
- package/src/plugins/ttl.plugin.js +36 -21
- package/src/plugins/vector.plugin.js +0 -2
- package/src/resource.class.js +106 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "s3db.js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.1.0",
|
|
4
4
|
"description": "Use AWS S3, the world's most reliable document storage, as a database with this ORM.",
|
|
5
5
|
"main": "dist/s3db.cjs.js",
|
|
6
6
|
"module": "dist/s3db.es.js",
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"@google-cloud/bigquery": "^7.0.0",
|
|
89
89
|
"@hono/node-server": "^1.0.0",
|
|
90
90
|
"@hono/swagger-ui": "^0.5.0",
|
|
91
|
+
"@tensorflow/tfjs-node": "^4.0.0",
|
|
91
92
|
"amqplib": "^0.10.8",
|
|
92
93
|
"hono": "^4.0.0",
|
|
93
94
|
"node-cron": "^4.0.0",
|
|
@@ -106,6 +107,9 @@
|
|
|
106
107
|
"@hono/swagger-ui": {
|
|
107
108
|
"optional": true
|
|
108
109
|
},
|
|
110
|
+
"@tensorflow/tfjs-node": {
|
|
111
|
+
"optional": true
|
|
112
|
+
},
|
|
109
113
|
"pg": {
|
|
110
114
|
"optional": true
|
|
111
115
|
},
|
|
@@ -640,41 +640,58 @@ export class MemoryClient extends EventEmitter {
|
|
|
640
640
|
for (const [resourceName, keys] of resourceMap.entries()) {
|
|
641
641
|
const records = [];
|
|
642
642
|
|
|
643
|
-
|
|
644
|
-
|
|
643
|
+
// Get resource from database if available (for proper field decoding)
|
|
644
|
+
const resource = database && database.resources && database.resources[resourceName];
|
|
645
645
|
|
|
646
|
+
for (const key of keys) {
|
|
646
647
|
// Extract id from key (e.g., resource=products/id=pr1 -> pr1)
|
|
647
648
|
const idMatch = key.match(/\/id=([^/]+)/);
|
|
648
649
|
const recordId = idMatch ? idMatch[1] : null;
|
|
649
650
|
|
|
650
|
-
|
|
651
|
-
const record = { ...obj.Metadata };
|
|
651
|
+
let record;
|
|
652
652
|
|
|
653
|
-
//
|
|
654
|
-
if (
|
|
655
|
-
|
|
653
|
+
// If resource is available, use its get() method for proper field name decoding
|
|
654
|
+
if (resource && recordId) {
|
|
655
|
+
try {
|
|
656
|
+
record = await resource.get(recordId);
|
|
657
|
+
} catch (err) {
|
|
658
|
+
// Fallback to manual reconstruction if get() fails
|
|
659
|
+
console.warn(`Failed to get record ${recordId} from resource ${resourceName}, using fallback`);
|
|
660
|
+
record = null;
|
|
661
|
+
}
|
|
656
662
|
}
|
|
657
663
|
|
|
658
|
-
//
|
|
659
|
-
if (
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
664
|
+
// Fallback: manually reconstruct from metadata and body
|
|
665
|
+
if (!record) {
|
|
666
|
+
const obj = await this.getObject(key);
|
|
667
|
+
record = { ...obj.Metadata };
|
|
668
|
+
|
|
669
|
+
// Include id in record if extracted from key
|
|
670
|
+
if (recordId && !record.id) {
|
|
671
|
+
record.id = recordId;
|
|
663
672
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
673
|
+
|
|
674
|
+
// If body exists, parse it
|
|
675
|
+
if (obj.Body) {
|
|
676
|
+
const chunks = [];
|
|
677
|
+
for await (const chunk of obj.Body) {
|
|
678
|
+
chunks.push(chunk);
|
|
679
|
+
}
|
|
680
|
+
const bodyBuffer = Buffer.concat(chunks);
|
|
681
|
+
|
|
682
|
+
// Try to parse as JSON if it looks like JSON
|
|
683
|
+
const bodyStr = bodyBuffer.toString('utf-8');
|
|
684
|
+
if (bodyStr.startsWith('{') || bodyStr.startsWith('[')) {
|
|
685
|
+
try {
|
|
686
|
+
const bodyData = JSON.parse(bodyStr);
|
|
687
|
+
Object.assign(record, bodyData);
|
|
688
|
+
} catch {
|
|
689
|
+
// If not JSON, store as _body field
|
|
690
|
+
record._body = bodyStr;
|
|
691
|
+
}
|
|
692
|
+
} else if (bodyStr) {
|
|
674
693
|
record._body = bodyStr;
|
|
675
694
|
}
|
|
676
|
-
} else if (bodyStr) {
|
|
677
|
-
record._body = bodyStr;
|
|
678
695
|
}
|
|
679
696
|
}
|
|
680
697
|
|
package/src/database.class.js
CHANGED
|
@@ -14,7 +14,12 @@ export class Database extends EventEmitter {
|
|
|
14
14
|
constructor(options) {
|
|
15
15
|
super();
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// Generate database ID with fallback for reliability
|
|
18
|
+
this.id = (() => {
|
|
19
|
+
const [ok, err, id] = tryFn(() => idGenerator(7));
|
|
20
|
+
return ok && id ? id : `db-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
21
|
+
})();
|
|
22
|
+
|
|
18
23
|
this.version = "1";
|
|
19
24
|
// Version is injected during build, fallback to "latest" for development
|
|
20
25
|
this.s3dbVersion = (() => {
|
|
@@ -64,6 +69,7 @@ export class Database extends EventEmitter {
|
|
|
64
69
|
this.versioningEnabled = options.versioningEnabled || false;
|
|
65
70
|
this.persistHooks = options.persistHooks || false; // New configuration for hook persistence
|
|
66
71
|
this.strictValidation = options.strictValidation !== false; // Enable strict validation by default
|
|
72
|
+
this.strictHooks = options.strictHooks || false; // Throw on first hook error instead of continuing
|
|
67
73
|
|
|
68
74
|
// Initialize hooks system
|
|
69
75
|
this._initHooks();
|
|
@@ -110,21 +116,32 @@ export class Database extends EventEmitter {
|
|
|
110
116
|
this.bucket = this.client.bucket;
|
|
111
117
|
this.keyPrefix = this.client.keyPrefix;
|
|
112
118
|
|
|
113
|
-
//
|
|
114
|
-
|
|
119
|
+
// Register exit listener for cleanup
|
|
120
|
+
this._registerExitListener();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Register process exit listener for automatic cleanup
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
127
|
+
_registerExitListener() {
|
|
128
|
+
if (!this._exitListenerRegistered && typeof process !== 'undefined') {
|
|
115
129
|
this._exitListenerRegistered = true;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
// Store listener reference for cleanup
|
|
131
|
+
this._exitListener = async () => {
|
|
132
|
+
if (this.isConnected()) {
|
|
133
|
+
// Silently ignore errors on exit
|
|
134
|
+
await tryFn(() => this.disconnect());
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
process.on('exit', this._exitListener);
|
|
124
138
|
}
|
|
125
139
|
}
|
|
126
|
-
|
|
140
|
+
|
|
127
141
|
async connect() {
|
|
142
|
+
// Re-register exit listener if it was cleaned up
|
|
143
|
+
this._registerExitListener();
|
|
144
|
+
|
|
128
145
|
await this.startPlugins();
|
|
129
146
|
|
|
130
147
|
let metadata = null;
|
|
@@ -1271,15 +1288,24 @@ export class Database extends EventEmitter {
|
|
|
1271
1288
|
this.client.removeAllListeners();
|
|
1272
1289
|
}
|
|
1273
1290
|
|
|
1274
|
-
// 4.
|
|
1291
|
+
// 4. Emit disconnected event BEFORE removing database listeners (race condition fix)
|
|
1292
|
+
// This ensures listeners can actually receive the event
|
|
1293
|
+
await this.emit('disconnected', new Date());
|
|
1294
|
+
|
|
1295
|
+
// 5. Remove all listeners from the database itself
|
|
1275
1296
|
this.removeAllListeners();
|
|
1276
1297
|
|
|
1277
|
-
//
|
|
1298
|
+
// 6. Cleanup process exit listener (memory leak fix)
|
|
1299
|
+
if (this._exitListener && typeof process !== 'undefined') {
|
|
1300
|
+
process.off('exit', this._exitListener);
|
|
1301
|
+
this._exitListener = null;
|
|
1302
|
+
this._exitListenerRegistered = false;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// 7. Clear saved metadata and plugin lists
|
|
1278
1306
|
this.savedMetadata = null;
|
|
1279
1307
|
this.plugins = {};
|
|
1280
1308
|
this.pluginList = [];
|
|
1281
|
-
|
|
1282
|
-
this.emit('disconnected', new Date());
|
|
1283
1309
|
});
|
|
1284
1310
|
}
|
|
1285
1311
|
|
|
@@ -1400,8 +1426,17 @@ export class Database extends EventEmitter {
|
|
|
1400
1426
|
for (const hook of hooks) {
|
|
1401
1427
|
const [ok, error] = await tryFn(() => hook({ database: this, ...context }));
|
|
1402
1428
|
if (!ok) {
|
|
1403
|
-
// Emit error
|
|
1429
|
+
// Emit error event
|
|
1404
1430
|
this.emit('hookError', { event, error, context });
|
|
1431
|
+
|
|
1432
|
+
// In strict mode, throw on first error instead of continuing
|
|
1433
|
+
if (this.strictHooks) {
|
|
1434
|
+
throw new DatabaseError(`Hook execution failed for event '${event}': ${error.message}`, {
|
|
1435
|
+
event,
|
|
1436
|
+
originalError: error,
|
|
1437
|
+
context
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1405
1440
|
}
|
|
1406
1441
|
}
|
|
1407
1442
|
}
|
package/src/plugins/api/index.js
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
import { Plugin } from '../plugin.class.js';
|
|
36
36
|
import { requirePluginDependency } from '../concerns/plugin-dependencies.js';
|
|
37
37
|
import tryFn from '../../concerns/try-fn.js';
|
|
38
|
+
import { ApiServer } from './server.js';
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* API Plugin class
|
|
@@ -437,15 +438,18 @@ export class ApiPlugin extends Plugin {
|
|
|
437
438
|
return async (c, next) => {
|
|
438
439
|
await next();
|
|
439
440
|
|
|
440
|
-
//
|
|
441
|
-
// For now, this is a placeholder
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
441
|
+
// TODO: Implement actual compression using zlib
|
|
442
|
+
// For now, this is a no-op placeholder to avoid ERR_CONTENT_DECODING_FAILED errors
|
|
443
|
+
//
|
|
444
|
+
// WARNING: Do NOT set Content-Encoding headers without actually compressing!
|
|
445
|
+
// Setting these headers without compression causes browsers to fail with:
|
|
446
|
+
// net::ERR_CONTENT_DECODING_FAILED 200 (OK)
|
|
447
|
+
//
|
|
448
|
+
// Real implementation would require:
|
|
449
|
+
// 1. Check Accept-Encoding header
|
|
450
|
+
// 2. Compress response body with zlib.gzip() or zlib.deflate()
|
|
451
|
+
// 3. Set Content-Encoding header
|
|
452
|
+
// 4. Update Content-Length header
|
|
449
453
|
};
|
|
450
454
|
}
|
|
451
455
|
|
|
@@ -457,11 +461,6 @@ export class ApiPlugin extends Plugin {
|
|
|
457
461
|
console.log('[API Plugin] Starting server...');
|
|
458
462
|
}
|
|
459
463
|
|
|
460
|
-
// Dynamic import with path manipulation to prevent Rollup from inlining
|
|
461
|
-
// This ensures hono is only loaded when ApiPlugin is actually used
|
|
462
|
-
const serverPath = './server' + '.js';
|
|
463
|
-
const { ApiServer } = await import(/* @vite-ignore */ serverPath);
|
|
464
|
-
|
|
465
464
|
// Create server instance
|
|
466
465
|
this.server = new ApiServer({
|
|
467
466
|
port: this.config.port,
|
|
@@ -543,5 +542,3 @@ export class ApiPlugin extends Plugin {
|
|
|
543
542
|
return this.server ? this.server.getApp() : null;
|
|
544
543
|
}
|
|
545
544
|
}
|
|
546
|
-
|
|
547
|
-
export default ApiPlugin;
|
|
@@ -4,18 +4,56 @@
|
|
|
4
4
|
* Automatically generates REST endpoints for each resource
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Hono } from 'hono';
|
|
8
7
|
import { asyncHandler } from '../utils/error-handler.js';
|
|
9
8
|
import * as formatter from '../utils/response-formatter.js';
|
|
10
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Parse custom route definition (e.g., "GET /healthcheck" or "async POST /custom")
|
|
12
|
+
* @param {string} routeDef - Route definition string
|
|
13
|
+
* @returns {Object} Parsed route { method, path, isAsync }
|
|
14
|
+
*/
|
|
15
|
+
function parseCustomRoute(routeDef) {
|
|
16
|
+
// Remove "async" prefix if present
|
|
17
|
+
let def = routeDef.trim();
|
|
18
|
+
const isAsync = def.startsWith('async ');
|
|
19
|
+
|
|
20
|
+
if (isAsync) {
|
|
21
|
+
def = def.substring(6).trim(); // Remove "async "
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Split by space (e.g., "GET /path" -> ["GET", "/path"])
|
|
25
|
+
const parts = def.split(/\s+/);
|
|
26
|
+
|
|
27
|
+
if (parts.length < 2) {
|
|
28
|
+
throw new Error(`Invalid route definition: "${routeDef}". Expected format: "METHOD /path" or "async METHOD /path"`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const method = parts[0].toUpperCase();
|
|
32
|
+
const path = parts.slice(1).join(' ').trim(); // Join remaining parts in case path has spaces (unlikely but possible)
|
|
33
|
+
|
|
34
|
+
// Validate HTTP method
|
|
35
|
+
const validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
|
|
36
|
+
if (!validMethods.includes(method)) {
|
|
37
|
+
throw new Error(`Invalid HTTP method: "${method}". Must be one of: ${validMethods.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Validate path starts with /
|
|
41
|
+
if (!path.startsWith('/')) {
|
|
42
|
+
throw new Error(`Invalid route path: "${path}". Path must start with "/"`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { method, path, isAsync };
|
|
46
|
+
}
|
|
47
|
+
|
|
11
48
|
/**
|
|
12
49
|
* Create routes for a resource
|
|
13
50
|
* @param {Object} resource - s3db.js Resource instance
|
|
14
51
|
* @param {string} version - Resource version (e.g., 'v1', 'v1')
|
|
15
52
|
* @param {Object} config - Route configuration
|
|
53
|
+
* @param {Function} Hono - Hono constructor (passed from server.js)
|
|
16
54
|
* @returns {Hono} Hono app with resource routes
|
|
17
55
|
*/
|
|
18
|
-
export function createResourceRoutes(resource, version, config = {}) {
|
|
56
|
+
export function createResourceRoutes(resource, version, config = {}, Hono) {
|
|
19
57
|
const app = new Hono();
|
|
20
58
|
const {
|
|
21
59
|
methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
|
|
@@ -31,6 +69,46 @@ export function createResourceRoutes(resource, version, config = {}) {
|
|
|
31
69
|
app.use('*', middleware);
|
|
32
70
|
});
|
|
33
71
|
|
|
72
|
+
// Register custom routes from resource.config.api (if defined)
|
|
73
|
+
if (resource.config?.api && typeof resource.config.api === 'object') {
|
|
74
|
+
for (const [routeDef, handler] of Object.entries(resource.config.api)) {
|
|
75
|
+
try {
|
|
76
|
+
const { method, path } = parseCustomRoute(routeDef);
|
|
77
|
+
|
|
78
|
+
if (typeof handler !== 'function') {
|
|
79
|
+
throw new Error(`Handler for route "${routeDef}" must be a function`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Register the custom route
|
|
83
|
+
// The handler receives the full Hono context
|
|
84
|
+
app.on(method, path, asyncHandler(async (c) => {
|
|
85
|
+
// Call user's handler with Hono context
|
|
86
|
+
const result = await handler(c, { resource, database: resource.database });
|
|
87
|
+
|
|
88
|
+
// If handler already returned a response, use it
|
|
89
|
+
if (result && result.constructor && result.constructor.name === 'Response') {
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// If handler returned data, wrap in success formatter
|
|
94
|
+
if (result !== undefined && result !== null) {
|
|
95
|
+
return c.json(formatter.success(result));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// If no return value, return 204 No Content
|
|
99
|
+
return c.json(formatter.noContent(), 204);
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
if (config.verbose || resource.database?.verbose) {
|
|
103
|
+
console.log(`[API Plugin] Registered custom route for ${resourceName}: ${method} ${path}`);
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(`[API Plugin] Error registering custom route "${routeDef}" for ${resourceName}:`, error.message);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
34
112
|
// LIST - GET /{version}/{resource}
|
|
35
113
|
if (methods.includes('GET')) {
|
|
36
114
|
app.get('/', asyncHandler(async (c) => {
|
|
@@ -307,7 +385,7 @@ export function createResourceRoutes(resource, version, config = {}) {
|
|
|
307
385
|
* @param {string} version - Resource version (e.g., 'v1')
|
|
308
386
|
* @returns {Hono} Hono app with relational routes
|
|
309
387
|
*/
|
|
310
|
-
export function createRelationalRoutes(sourceResource, relationName, relationConfig, version) {
|
|
388
|
+
export function createRelationalRoutes(sourceResource, relationName, relationConfig, version, Hono) {
|
|
311
389
|
const app = new Hono();
|
|
312
390
|
const resourceName = sourceResource.name;
|
|
313
391
|
const relatedResourceName = relationConfig.resource;
|
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
* Manages HTTP server lifecycle and routing
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Hono } from 'hono';
|
|
8
|
-
import { serve } from '@hono/node-server';
|
|
9
|
-
import { swaggerUI } from '@hono/swagger-ui';
|
|
10
7
|
import { createResourceRoutes, createRelationalRoutes } from './routes/resource-routes.js';
|
|
11
8
|
import { errorHandler } from './utils/error-handler.js';
|
|
12
9
|
import * as formatter from './utils/response-formatter.js';
|
|
@@ -46,17 +43,18 @@ export class ApiServer {
|
|
|
46
43
|
}
|
|
47
44
|
};
|
|
48
45
|
|
|
49
|
-
this.app =
|
|
46
|
+
this.app = null; // Will be initialized in start() with dynamic import
|
|
50
47
|
this.server = null;
|
|
51
48
|
this.isRunning = false;
|
|
52
49
|
this.openAPISpec = null;
|
|
50
|
+
this.initialized = false;
|
|
53
51
|
|
|
54
52
|
// Detect if RelationPlugin is installed
|
|
55
53
|
this.relationsPlugin = this.options.database?.plugins?.relation ||
|
|
56
54
|
this.options.database?.plugins?.RelationPlugin ||
|
|
57
55
|
null;
|
|
58
56
|
|
|
59
|
-
|
|
57
|
+
// Routes will be setup in start() after dynamic import
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
/**
|
|
@@ -170,7 +168,7 @@ export class ApiServer {
|
|
|
170
168
|
|
|
171
169
|
// API Documentation UI endpoint
|
|
172
170
|
if (this.options.docsUI === 'swagger') {
|
|
173
|
-
this.app.get('/docs', swaggerUI({
|
|
171
|
+
this.app.get('/docs', this.swaggerUI({
|
|
174
172
|
url: '/openapi.json'
|
|
175
173
|
}));
|
|
176
174
|
} else {
|
|
@@ -254,7 +252,7 @@ export class ApiServer {
|
|
|
254
252
|
methods: config.methods,
|
|
255
253
|
customMiddleware: config.customMiddleware || [],
|
|
256
254
|
enableValidation: config.validation !== false
|
|
257
|
-
});
|
|
255
|
+
}, this.Hono);
|
|
258
256
|
|
|
259
257
|
// Mount resource routes
|
|
260
258
|
this.app.route(`/${version}/${name}`, resourceApp);
|
|
@@ -317,7 +315,8 @@ export class ApiServer {
|
|
|
317
315
|
resource,
|
|
318
316
|
relationName,
|
|
319
317
|
relationConfig,
|
|
320
|
-
version
|
|
318
|
+
version,
|
|
319
|
+
this.Hono
|
|
321
320
|
);
|
|
322
321
|
|
|
323
322
|
// Mount relational routes at /{version}/{resource}/:id/{relation}
|
|
@@ -343,11 +342,32 @@ export class ApiServer {
|
|
|
343
342
|
return;
|
|
344
343
|
}
|
|
345
344
|
|
|
345
|
+
// Dynamic import of Hono dependencies (peer dependencies)
|
|
346
|
+
// This ensures hono is only loaded when server actually starts
|
|
347
|
+
if (!this.initialized) {
|
|
348
|
+
const { Hono } = await import('hono');
|
|
349
|
+
const { serve } = await import('@hono/node-server');
|
|
350
|
+
const { swaggerUI } = await import('@hono/swagger-ui');
|
|
351
|
+
|
|
352
|
+
// Store for use in _setupRoutes
|
|
353
|
+
this.Hono = Hono;
|
|
354
|
+
this.serve = serve;
|
|
355
|
+
this.swaggerUI = swaggerUI;
|
|
356
|
+
|
|
357
|
+
// Initialize app
|
|
358
|
+
this.app = new Hono();
|
|
359
|
+
|
|
360
|
+
// Setup all routes
|
|
361
|
+
this._setupRoutes();
|
|
362
|
+
|
|
363
|
+
this.initialized = true;
|
|
364
|
+
}
|
|
365
|
+
|
|
346
366
|
const { port, host } = this.options;
|
|
347
367
|
|
|
348
368
|
return new Promise((resolve, reject) => {
|
|
349
369
|
try {
|
|
350
|
-
this.server = serve({
|
|
370
|
+
this.server = this.serve({
|
|
351
371
|
fetch: this.app.fetch,
|
|
352
372
|
port,
|
|
353
373
|
hostname: host
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Plugin from "./plugin.class.js";
|
|
1
|
+
import { Plugin } from "./plugin.class.js";
|
|
2
2
|
import tryFn from "../concerns/try-fn.js";
|
|
3
3
|
|
|
4
4
|
export class AuditPlugin extends Plugin {
|
|
@@ -420,6 +420,4 @@ export class AuditPlugin extends Plugin {
|
|
|
420
420
|
|
|
421
421
|
return deletedCount;
|
|
422
422
|
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
export default AuditPlugin;
|
|
423
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Plugin from "./plugin.class.js";
|
|
1
|
+
import { Plugin } from "./plugin.class.js";
|
|
2
2
|
import tryFn from "../concerns/try-fn.js";
|
|
3
3
|
import { createBackupDriver, validateBackupConfig } from "./backup/index.js";
|
|
4
4
|
import { StreamingExporter } from "./backup/streaming-exporter.js";
|
|
@@ -982,6 +982,4 @@ export class BackupPlugin extends Plugin {
|
|
|
982
982
|
await this.driver.cleanup();
|
|
983
983
|
}
|
|
984
984
|
}
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
export default BackupPlugin;
|
|
985
|
+
}
|
|
@@ -2,7 +2,7 @@ import { join } from "path";
|
|
|
2
2
|
import jsonStableStringify from "json-stable-stringify";
|
|
3
3
|
import crypto from 'crypto';
|
|
4
4
|
|
|
5
|
-
import Plugin from "./plugin.class.js";
|
|
5
|
+
import { Plugin } from "./plugin.class.js";
|
|
6
6
|
import S3Cache from "./cache/s3-cache.class.js";
|
|
7
7
|
import MemoryCache from "./cache/memory-cache.class.js";
|
|
8
8
|
import { FilesystemCache } from "./cache/filesystem-cache.class.js";
|
|
@@ -781,5 +781,3 @@ export class CachePlugin extends Plugin {
|
|
|
781
781
|
return parts.join(' ');
|
|
782
782
|
}
|
|
783
783
|
}
|
|
784
|
-
|
|
785
|
-
export default CachePlugin;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module eventual-consistency
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import Plugin from "../plugin.class.js";
|
|
7
|
+
import { Plugin } from "../plugin.class.js";
|
|
8
8
|
import { createConfig, validateResourcesConfig, logConfigWarnings, logInitialization } from "./config.js";
|
|
9
9
|
import { detectTimezone, getCohortInfo, createFieldHandler } from "./utils.js";
|
|
10
10
|
import { createPartitionConfig } from "./partitions.js";
|
|
@@ -733,5 +733,3 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
733
733
|
return diagnostics;
|
|
734
734
|
}
|
|
735
735
|
}
|
|
736
|
-
|
|
737
|
-
export default EventualConsistencyPlugin;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Plugin from "./plugin.class.js";
|
|
1
|
+
import { Plugin } from "./plugin.class.js";
|
|
2
2
|
import tryFn from "../concerns/try-fn.js";
|
|
3
3
|
import { FulltextError } from "./fulltext.errors.js";
|
|
4
4
|
|
|
@@ -554,6 +554,4 @@ export class FullTextPlugin extends Plugin {
|
|
|
554
554
|
// Save changes
|
|
555
555
|
await this.saveIndexes();
|
|
556
556
|
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
export default FullTextPlugin;
|
|
557
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Plugin from "./plugin.class.js";
|
|
1
|
+
import { Plugin } from "./plugin.class.js";
|
|
2
2
|
import tryFn from "../concerns/try-fn.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -868,5 +868,3 @@ export class GeoPlugin extends Plugin {
|
|
|
868
868
|
await super.uninstall();
|
|
869
869
|
}
|
|
870
870
|
}
|
|
871
|
-
|
|
872
|
-
export default GeoPlugin;
|
package/src/plugins/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export * from './plugin.class.js'
|
|
2
2
|
export * from './plugin.obj.js'
|
|
3
|
-
export { default as Plugin } from './plugin.class.js'
|
|
4
3
|
|
|
5
4
|
// plugins:
|
|
6
5
|
// ApiPlugin is exported separately to avoid bundling hono dependencies
|
|
@@ -13,6 +12,7 @@ export * from './eventual-consistency/index.js'
|
|
|
13
12
|
export * from './fulltext.plugin.js'
|
|
14
13
|
export * from './geo.plugin.js'
|
|
15
14
|
export * from './metrics.plugin.js'
|
|
15
|
+
export * from './ml.plugin.js'
|
|
16
16
|
export * from './queue-consumer.plugin.js'
|
|
17
17
|
export * from './relation.plugin.js'
|
|
18
18
|
export * from './replicator.plugin.js'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Plugin from "./plugin.class.js";
|
|
1
|
+
import { Plugin } from "./plugin.class.js";
|
|
2
2
|
import tryFn from "../concerns/try-fn.js";
|
|
3
3
|
|
|
4
4
|
export class MetricsPlugin extends Plugin {
|
|
@@ -831,6 +831,4 @@ export class MetricsPlugin extends Plugin {
|
|
|
831
831
|
console.error('[Metrics Plugin] Standalone metrics server error:', err);
|
|
832
832
|
});
|
|
833
833
|
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
export default MetricsPlugin;
|
|
834
|
+
}
|