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.
Files changed (38) hide show
  1. package/dist/s3db.cjs.js +8066 -3562
  2. package/dist/s3db.cjs.js.map +1 -1
  3. package/dist/s3db.es.js +8066 -3563
  4. package/dist/s3db.es.js.map +1 -1
  5. package/package.json +5 -1
  6. package/src/clients/memory-client.class.js +41 -24
  7. package/src/database.class.js +52 -17
  8. package/src/plugins/api/index.js +13 -16
  9. package/src/plugins/api/routes/resource-routes.js +81 -3
  10. package/src/plugins/api/server.js +29 -9
  11. package/src/plugins/audit.plugin.js +2 -4
  12. package/src/plugins/backup.plugin.js +2 -4
  13. package/src/plugins/cache.plugin.js +1 -3
  14. package/src/plugins/costs.plugin.js +0 -2
  15. package/src/plugins/eventual-consistency/index.js +1 -3
  16. package/src/plugins/fulltext.plugin.js +2 -4
  17. package/src/plugins/geo.plugin.js +1 -3
  18. package/src/plugins/importer/index.js +0 -2
  19. package/src/plugins/index.js +1 -1
  20. package/src/plugins/metrics.plugin.js +2 -4
  21. package/src/plugins/ml/base-model.class.js +459 -0
  22. package/src/plugins/ml/classification-model.class.js +338 -0
  23. package/src/plugins/ml/neural-network-model.class.js +312 -0
  24. package/src/plugins/ml/regression-model.class.js +159 -0
  25. package/src/plugins/ml/timeseries-model.class.js +346 -0
  26. package/src/plugins/ml.errors.js +130 -0
  27. package/src/plugins/ml.plugin.js +1417 -0
  28. package/src/plugins/plugin.class.js +1 -3
  29. package/src/plugins/queue-consumer.plugin.js +1 -3
  30. package/src/plugins/relation.plugin.js +1 -3
  31. package/src/plugins/replicator.plugin.js +2 -4
  32. package/src/plugins/s3-queue.plugin.js +1 -3
  33. package/src/plugins/scheduler.plugin.js +2 -4
  34. package/src/plugins/state-machine.plugin.js +2 -4
  35. package/src/plugins/tfstate/index.js +0 -2
  36. package/src/plugins/ttl.plugin.js +36 -21
  37. package/src/plugins/vector.plugin.js +0 -2
  38. package/src/resource.class.js +106 -34
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "s3db.js",
3
- "version": "12.4.0",
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
- for (const key of keys) {
644
- const obj = await this.getObject(key);
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
- // Reconstruct record from metadata and body
651
- const record = { ...obj.Metadata };
651
+ let record;
652
652
 
653
- // Include id in record if extracted from key
654
- if (recordId && !record.id) {
655
- record.id = recordId;
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
- // If body exists, parse it
659
- if (obj.Body) {
660
- const chunks = [];
661
- for await (const chunk of obj.Body) {
662
- chunks.push(chunk);
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
- const bodyBuffer = Buffer.concat(chunks);
665
-
666
- // Try to parse as JSON if it looks like JSON
667
- const bodyStr = bodyBuffer.toString('utf-8');
668
- if (bodyStr.startsWith('{') || bodyStr.startsWith('[')) {
669
- try {
670
- const bodyData = JSON.parse(bodyStr);
671
- Object.assign(record, bodyData);
672
- } catch {
673
- // If not JSON, store as _body field
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
 
@@ -14,7 +14,12 @@ export class Database extends EventEmitter {
14
14
  constructor(options) {
15
15
  super();
16
16
 
17
- this.id = idGenerator(7)
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
- // Add process exit listener for cleanup
114
- if (!this._exitListenerRegistered) {
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
- if (typeof process !== 'undefined') {
117
- process.on('exit', async () => {
118
- if (this.isConnected()) {
119
- // Silently ignore errors on exit
120
- await tryFn(() => this.disconnect());
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. Remove all listeners from the database itself
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
- // 5. Clear saved metadata and plugin lists
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 but don't stop hook execution
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
  }
@@ -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
- // Note: Actual compression would require proper streaming support
441
- // For now, this is a placeholder
442
- const acceptEncoding = c.req.header('accept-encoding') || '';
443
-
444
- if (acceptEncoding.includes('gzip')) {
445
- c.header('Content-Encoding', 'gzip');
446
- } else if (acceptEncoding.includes('deflate')) {
447
- c.header('Content-Encoding', 'deflate');
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 = new Hono();
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
- this._setupRoutes();
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;
@@ -299,5 +299,3 @@ export class CostsPlugin extends Plugin {
299
299
  }
300
300
  }
301
301
  }
302
-
303
- export default CostsPlugin;
@@ -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;
@@ -1016,5 +1016,3 @@ export const Transformers = {
1016
1016
  return String(value).trim();
1017
1017
  }
1018
1018
  };
1019
-
1020
- export default ImporterPlugin;
@@ -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
+ }