s3db.js 7.4.2 → 8.0.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/PLUGINS.md +3 -3
- package/dist/s3db.cjs.js +516 -349
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.d.ts +10 -0
- package/dist/s3db.es.js +516 -349
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +517 -351
- package/dist/s3db.iife.min.js +1 -1
- package/mcp/README.md +1062 -0
- package/mcp/server.js +1 -1
- package/package.json +31 -29
- package/src/client.class.js +26 -5
- package/src/database.class.js +151 -0
- package/src/plugins/audit.plugin.js +143 -310
- package/src/plugins/cache/filesystem-cache.class.js +40 -11
- package/src/plugins/cache.plugin.js +95 -47
- package/src/plugins/fulltext.plugin.js +21 -0
- package/src/plugins/metrics.plugin.js +21 -6
- package/src/plugins/replicator.plugin.js +65 -70
- package/src/resource.class.js +3 -2
- package/src/s3db.d.ts +10 -0
package/mcp/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "s3db.js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.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",
|
|
@@ -49,8 +49,31 @@
|
|
|
49
49
|
"PLUGINS.md",
|
|
50
50
|
"UNLICENSE"
|
|
51
51
|
],
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "rollup -c",
|
|
54
|
+
"dev": "rollup -c -w",
|
|
55
|
+
"test": "npm run test:js && npm run test:ts",
|
|
56
|
+
"test:js": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand --testPathIgnorePatterns=\"plugin-audit.test.js|tests/typescript/\" --testTimeout=10000",
|
|
57
|
+
"test:ts": "tsc --noEmit --project tests/typescript/tsconfig.json",
|
|
58
|
+
"test:js-converage": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --detectOpenHandles --coverage --runInBand",
|
|
59
|
+
"test:js-ai": "node --max-old-space-size=4096 --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand --testPathIgnorePatterns=\"plugin-audit.test.js|tests/typescript/\"",
|
|
60
|
+
"test:full": "npm run test:js-ai && npm run test:ts",
|
|
61
|
+
"test:audit": "node --max-old-space-size=8192 --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/plugins/plugin-audit.test.js --runInBand --testTimeout=60000",
|
|
62
|
+
"test:cache": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/plugins/plugin-cache*.test.js --runInBand",
|
|
63
|
+
"test:quick": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand --testTimeout=10000",
|
|
64
|
+
"test:batch": "./test-batch.sh",
|
|
65
|
+
"test:plugins": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/plugins/ --runInBand --testTimeout=60000",
|
|
66
|
+
"test:plugins:fast": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/plugins/ --runInBand --testTimeout=15000 --testPathIgnorePatterns='plugin-audit.test.js|plugin-replicator-s3db.test.js|plugin-fulltext.test.js'",
|
|
67
|
+
"test:slow": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/plugins/plugin-audit.test.js tests/plugins/plugin-replicator-s3db.test.js tests/plugins/plugin-fulltext.test.js --runInBand --testTimeout=120000",
|
|
68
|
+
"test:types": "tsc --noEmit --project tests/typescript/tsconfig.json",
|
|
69
|
+
"test:types:basic": "tsc --noEmit tests/typescript/basic-usage.test.ts",
|
|
70
|
+
"test:types:direct": "tsc --noEmit tests/typescript/direct-type-test.ts",
|
|
71
|
+
"test:types:watch": "tsc --noEmit --watch --project tests/typescript/tsconfig.json",
|
|
72
|
+
"validate:types": "npm run test:types && echo 'TypeScript definitions are valid!'"
|
|
73
|
+
},
|
|
52
74
|
"dependencies": {
|
|
53
|
-
"@aws-sdk/client-s3": "^3.
|
|
75
|
+
"@aws-sdk/client-s3": "^3.850.0",
|
|
76
|
+
"@smithy/node-http-handler": "^4.1.0",
|
|
54
77
|
"@supercharge/promise-pool": "^3.2.0",
|
|
55
78
|
"fastest-validator": "^1.19.1",
|
|
56
79
|
"flat": "^6.0.1",
|
|
@@ -88,10 +111,10 @@
|
|
|
88
111
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
89
112
|
"@rollup/plugin-replace": "^6.0.2",
|
|
90
113
|
"@rollup/plugin-terser": "^0.4.4",
|
|
91
|
-
"@types/node": "24.0
|
|
92
|
-
"dotenv": "^17.2.
|
|
93
|
-
"jest": "^30.0.
|
|
94
|
-
"rollup": "^4.
|
|
114
|
+
"@types/node": "24.1.0",
|
|
115
|
+
"dotenv": "^17.2.1",
|
|
116
|
+
"jest": "^30.0.5",
|
|
117
|
+
"rollup": "^4.46.1",
|
|
95
118
|
"rollup-plugin-copy": "^3.5.0",
|
|
96
119
|
"rollup-plugin-esbuild": "^6.2.1",
|
|
97
120
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
|
@@ -100,26 +123,5 @@
|
|
|
100
123
|
},
|
|
101
124
|
"funding": [
|
|
102
125
|
"https://github.com/sponsors/forattini-dev"
|
|
103
|
-
]
|
|
104
|
-
|
|
105
|
-
"build": "rollup -c",
|
|
106
|
-
"dev": "rollup -c -w",
|
|
107
|
-
"test": "npm run test:js && npm run test:ts",
|
|
108
|
-
"test:js": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand",
|
|
109
|
-
"test:ts": "tsc --noEmit --project tests/typescript/tsconfig.json",
|
|
110
|
-
"test:js-converage": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --detectOpenHandles --coverage --runInBand",
|
|
111
|
-
"test:js-ai": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --detectOpenHandles --runInBand",
|
|
112
|
-
"test:full": "npm run test:js && npm run test:ts",
|
|
113
|
-
"test:cache": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/plugins/plugin-cache*.test.js --runInBand",
|
|
114
|
-
"test:quick": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand --testTimeout=10000",
|
|
115
|
-
"test:batch": "./test-batch.sh",
|
|
116
|
-
"test:plugins": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/plugins/ --runInBand --testTimeout=60000",
|
|
117
|
-
"test:plugins:fast": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/plugins/ --runInBand --testTimeout=15000 --testPathIgnorePatterns='plugin-audit.test.js|plugin-replicator-s3db.test.js|plugin-fulltext.test.js'",
|
|
118
|
-
"test:slow": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js tests/plugins/plugin-audit.test.js tests/plugins/plugin-replicator-s3db.test.js tests/plugins/plugin-fulltext.test.js --runInBand --testTimeout=120000",
|
|
119
|
-
"test:types": "tsc --noEmit --project tests/typescript/tsconfig.json",
|
|
120
|
-
"test:types:basic": "tsc --noEmit tests/typescript/basic-usage.test.ts",
|
|
121
|
-
"test:types:direct": "tsc --noEmit tests/typescript/direct-type-test.ts",
|
|
122
|
-
"test:types:watch": "tsc --noEmit --watch --project tests/typescript/tsconfig.json",
|
|
123
|
-
"validate:types": "npm run test:types && echo 'TypeScript definitions are valid!'"
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
+
]
|
|
127
|
+
}
|
package/src/client.class.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import EventEmitter from "events";
|
|
3
3
|
import { chunk } from "lodash-es";
|
|
4
|
+
import { Agent as HttpAgent } from 'http';
|
|
5
|
+
import { Agent as HttpsAgent } from 'https';
|
|
4
6
|
import { PromisePool } from "@supercharge/promise-pool";
|
|
5
|
-
|
|
6
|
-
import { idGenerator } from "./concerns/id.js";
|
|
7
|
-
import tryFn from "./concerns/try-fn.js";
|
|
7
|
+
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
S3Client,
|
|
@@ -16,10 +16,12 @@ import {
|
|
|
16
16
|
DeleteObjectsCommand,
|
|
17
17
|
ListObjectsV2Command,
|
|
18
18
|
} from '@aws-sdk/client-s3';
|
|
19
|
-
import { md5 } from "./concerns/crypto.js";
|
|
20
19
|
|
|
21
|
-
import
|
|
20
|
+
import tryFn from "./concerns/try-fn.js";
|
|
21
|
+
import { md5 } from "./concerns/crypto.js";
|
|
22
|
+
import { idGenerator } from "./concerns/id.js";
|
|
22
23
|
import { ConnectionString } from "./connection-string.class.js";
|
|
24
|
+
import { mapAwsError, UnknownError, NoSuchKey, NotFound } from "./errors.js";
|
|
23
25
|
|
|
24
26
|
export class Client extends EventEmitter {
|
|
25
27
|
constructor({
|
|
@@ -28,19 +30,38 @@ export class Client extends EventEmitter {
|
|
|
28
30
|
AwsS3Client,
|
|
29
31
|
connectionString,
|
|
30
32
|
parallelism = 10,
|
|
33
|
+
httpClientOptions = {},
|
|
31
34
|
}) {
|
|
32
35
|
super();
|
|
33
36
|
this.verbose = verbose;
|
|
34
37
|
this.id = id ?? idGenerator();
|
|
35
38
|
this.parallelism = parallelism;
|
|
36
39
|
this.config = new ConnectionString(connectionString);
|
|
40
|
+
this.httpClientOptions = {
|
|
41
|
+
keepAlive: false, // Disabled for maximum creation speed
|
|
42
|
+
maxSockets: 10, // Minimal sockets
|
|
43
|
+
maxFreeSockets: 2, // Minimal pool
|
|
44
|
+
timeout: 15000, // Short timeout
|
|
45
|
+
...httpClientOptions,
|
|
46
|
+
};
|
|
37
47
|
this.client = AwsS3Client || this.createClient()
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
createClient() {
|
|
51
|
+
// Create HTTP agents with keep-alive configuration
|
|
52
|
+
const httpAgent = new HttpAgent(this.httpClientOptions);
|
|
53
|
+
const httpsAgent = new HttpsAgent(this.httpClientOptions);
|
|
54
|
+
|
|
55
|
+
// Create HTTP handler with agents
|
|
56
|
+
const httpHandler = new NodeHttpHandler({
|
|
57
|
+
httpAgent,
|
|
58
|
+
httpsAgent,
|
|
59
|
+
});
|
|
60
|
+
|
|
41
61
|
let options = {
|
|
42
62
|
region: this.config.region,
|
|
43
63
|
endpoint: this.config.endpoint,
|
|
64
|
+
requestHandler: httpHandler,
|
|
44
65
|
}
|
|
45
66
|
|
|
46
67
|
if (this.config.forcePathStyle) options.forcePathStyle = true
|
package/src/database.class.js
CHANGED
|
@@ -32,6 +32,9 @@ export class Database extends EventEmitter {
|
|
|
32
32
|
this.passphrase = options.passphrase || "secret";
|
|
33
33
|
this.versioningEnabled = options.versioningEnabled || false;
|
|
34
34
|
|
|
35
|
+
// Initialize hooks system
|
|
36
|
+
this._initHooks();
|
|
37
|
+
|
|
35
38
|
// Handle both connection string and individual parameters
|
|
36
39
|
let connectionString = options.connectionString;
|
|
37
40
|
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
@@ -617,6 +620,154 @@ export class Database extends EventEmitter {
|
|
|
617
620
|
// Silently ignore errors on exit
|
|
618
621
|
}
|
|
619
622
|
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Initialize hooks system for database operations
|
|
626
|
+
* @private
|
|
627
|
+
*/
|
|
628
|
+
_initHooks() {
|
|
629
|
+
// Map of hook name -> array of hook functions
|
|
630
|
+
this._hooks = new Map();
|
|
631
|
+
|
|
632
|
+
// Define available hooks
|
|
633
|
+
this._hookEvents = [
|
|
634
|
+
'beforeConnect', 'afterConnect',
|
|
635
|
+
'beforeCreateResource', 'afterCreateResource',
|
|
636
|
+
'beforeUploadMetadata', 'afterUploadMetadata',
|
|
637
|
+
'beforeDisconnect', 'afterDisconnect',
|
|
638
|
+
'resourceCreated', 'resourceUpdated'
|
|
639
|
+
];
|
|
640
|
+
|
|
641
|
+
// Initialize hook arrays
|
|
642
|
+
for (const event of this._hookEvents) {
|
|
643
|
+
this._hooks.set(event, []);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Wrap hookable methods
|
|
647
|
+
this._wrapHookableMethods();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Wrap methods that can have hooks
|
|
652
|
+
* @private
|
|
653
|
+
*/
|
|
654
|
+
_wrapHookableMethods() {
|
|
655
|
+
if (this._hooksInstalled) return; // Already wrapped
|
|
656
|
+
|
|
657
|
+
// Store original methods
|
|
658
|
+
this._originalConnect = this.connect.bind(this);
|
|
659
|
+
this._originalCreateResource = this.createResource.bind(this);
|
|
660
|
+
this._originalUploadMetadataFile = this.uploadMetadataFile.bind(this);
|
|
661
|
+
this._originalDisconnect = this.disconnect.bind(this);
|
|
662
|
+
|
|
663
|
+
// Wrap connect
|
|
664
|
+
this.connect = async (...args) => {
|
|
665
|
+
await this._executeHooks('beforeConnect', { args });
|
|
666
|
+
const result = await this._originalConnect(...args);
|
|
667
|
+
await this._executeHooks('afterConnect', { result, args });
|
|
668
|
+
return result;
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
// Wrap createResource
|
|
672
|
+
this.createResource = async (config) => {
|
|
673
|
+
await this._executeHooks('beforeCreateResource', { config });
|
|
674
|
+
const resource = await this._originalCreateResource(config);
|
|
675
|
+
await this._executeHooks('afterCreateResource', { resource, config });
|
|
676
|
+
return resource;
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
// Wrap uploadMetadataFile
|
|
680
|
+
this.uploadMetadataFile = async (...args) => {
|
|
681
|
+
await this._executeHooks('beforeUploadMetadata', { args });
|
|
682
|
+
const result = await this._originalUploadMetadataFile(...args);
|
|
683
|
+
await this._executeHooks('afterUploadMetadata', { result, args });
|
|
684
|
+
return result;
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// Wrap disconnect
|
|
688
|
+
this.disconnect = async (...args) => {
|
|
689
|
+
await this._executeHooks('beforeDisconnect', { args });
|
|
690
|
+
const result = await this._originalDisconnect(...args);
|
|
691
|
+
await this._executeHooks('afterDisconnect', { result, args });
|
|
692
|
+
return result;
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
this._hooksInstalled = true;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Add a hook for a specific database event
|
|
700
|
+
* @param {string} event - Hook event name
|
|
701
|
+
* @param {Function} fn - Hook function
|
|
702
|
+
* @example
|
|
703
|
+
* database.addHook('afterCreateResource', async ({ resource }) => {
|
|
704
|
+
* console.log('Resource created:', resource.name);
|
|
705
|
+
* });
|
|
706
|
+
*/
|
|
707
|
+
addHook(event, fn) {
|
|
708
|
+
if (!this._hooks) this._initHooks();
|
|
709
|
+
if (!this._hooks.has(event)) {
|
|
710
|
+
throw new Error(`Unknown hook event: ${event}. Available events: ${this._hookEvents.join(', ')}`);
|
|
711
|
+
}
|
|
712
|
+
if (typeof fn !== 'function') {
|
|
713
|
+
throw new Error('Hook function must be a function');
|
|
714
|
+
}
|
|
715
|
+
this._hooks.get(event).push(fn);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Execute hooks for a specific event
|
|
720
|
+
* @param {string} event - Hook event name
|
|
721
|
+
* @param {Object} context - Context data to pass to hooks
|
|
722
|
+
* @private
|
|
723
|
+
*/
|
|
724
|
+
async _executeHooks(event, context = {}) {
|
|
725
|
+
if (!this._hooks || !this._hooks.has(event)) return;
|
|
726
|
+
|
|
727
|
+
const hooks = this._hooks.get(event);
|
|
728
|
+
for (const hook of hooks) {
|
|
729
|
+
try {
|
|
730
|
+
await hook({ database: this, ...context });
|
|
731
|
+
} catch (error) {
|
|
732
|
+
// Emit error but don't stop hook execution
|
|
733
|
+
this.emit('hookError', { event, error, context });
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Remove a hook for a specific event
|
|
740
|
+
* @param {string} event - Hook event name
|
|
741
|
+
* @param {Function} fn - Hook function to remove
|
|
742
|
+
*/
|
|
743
|
+
removeHook(event, fn) {
|
|
744
|
+
if (!this._hooks || !this._hooks.has(event)) return;
|
|
745
|
+
|
|
746
|
+
const hooks = this._hooks.get(event);
|
|
747
|
+
const index = hooks.indexOf(fn);
|
|
748
|
+
if (index > -1) {
|
|
749
|
+
hooks.splice(index, 1);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Get all hooks for a specific event
|
|
755
|
+
* @param {string} event - Hook event name
|
|
756
|
+
* @returns {Function[]} Array of hook functions
|
|
757
|
+
*/
|
|
758
|
+
getHooks(event) {
|
|
759
|
+
if (!this._hooks || !this._hooks.has(event)) return [];
|
|
760
|
+
return [...this._hooks.get(event)];
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Clear all hooks for a specific event
|
|
765
|
+
* @param {string} event - Hook event name
|
|
766
|
+
*/
|
|
767
|
+
clearHooks(event) {
|
|
768
|
+
if (!this._hooks || !this._hooks.has(event)) return;
|
|
769
|
+
this._hooks.get(event).length = 0;
|
|
770
|
+
}
|
|
620
771
|
}
|
|
621
772
|
|
|
622
773
|
export class S3db extends Database {}
|