s3db.js 11.3.2 → 12.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/README.md +102 -8
- package/dist/s3db.cjs.js +36664 -15480
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.d.ts +57 -0
- package/dist/s3db.es.js +36661 -15531
- package/dist/s3db.es.js.map +1 -1
- package/mcp/entrypoint.js +58 -0
- package/mcp/tools/documentation.js +434 -0
- package/mcp/tools/index.js +4 -0
- package/package.json +27 -6
- package/src/behaviors/user-managed.js +13 -6
- package/src/client.class.js +41 -46
- package/src/concerns/base62.js +85 -0
- package/src/concerns/dictionary-encoding.js +294 -0
- package/src/concerns/geo-encoding.js +256 -0
- package/src/concerns/high-performance-inserter.js +34 -30
- package/src/concerns/ip.js +325 -0
- package/src/concerns/metadata-encoding.js +345 -66
- package/src/concerns/money.js +193 -0
- package/src/concerns/partition-queue.js +7 -4
- package/src/concerns/plugin-storage.js +39 -19
- package/src/database.class.js +76 -74
- package/src/errors.js +0 -4
- package/src/plugins/api/auth/api-key-auth.js +88 -0
- package/src/plugins/api/auth/basic-auth.js +154 -0
- package/src/plugins/api/auth/index.js +112 -0
- package/src/plugins/api/auth/jwt-auth.js +169 -0
- package/src/plugins/api/index.js +539 -0
- package/src/plugins/api/middlewares/index.js +15 -0
- package/src/plugins/api/middlewares/validator.js +185 -0
- package/src/plugins/api/routes/auth-routes.js +241 -0
- package/src/plugins/api/routes/resource-routes.js +304 -0
- package/src/plugins/api/server.js +350 -0
- package/src/plugins/api/utils/error-handler.js +147 -0
- package/src/plugins/api/utils/openapi-generator.js +1240 -0
- package/src/plugins/api/utils/response-formatter.js +218 -0
- package/src/plugins/backup/streaming-exporter.js +132 -0
- package/src/plugins/backup.plugin.js +103 -50
- package/src/plugins/cache/s3-cache.class.js +95 -47
- package/src/plugins/cache.plugin.js +107 -9
- package/src/plugins/concerns/plugin-dependencies.js +313 -0
- package/src/plugins/concerns/prometheus-formatter.js +255 -0
- package/src/plugins/consumers/rabbitmq-consumer.js +4 -0
- package/src/plugins/consumers/sqs-consumer.js +4 -0
- package/src/plugins/costs.plugin.js +255 -39
- package/src/plugins/eventual-consistency/helpers.js +15 -1
- package/src/plugins/geo.plugin.js +873 -0
- package/src/plugins/importer/index.js +1020 -0
- package/src/plugins/index.js +11 -0
- package/src/plugins/metrics.plugin.js +163 -4
- package/src/plugins/queue-consumer.plugin.js +6 -27
- package/src/plugins/relation.errors.js +139 -0
- package/src/plugins/relation.plugin.js +1242 -0
- package/src/plugins/replicators/bigquery-replicator.class.js +180 -8
- package/src/plugins/replicators/dynamodb-replicator.class.js +383 -0
- package/src/plugins/replicators/index.js +28 -3
- package/src/plugins/replicators/mongodb-replicator.class.js +391 -0
- package/src/plugins/replicators/mysql-replicator.class.js +558 -0
- package/src/plugins/replicators/planetscale-replicator.class.js +409 -0
- package/src/plugins/replicators/postgres-replicator.class.js +182 -7
- package/src/plugins/replicators/s3db-replicator.class.js +1 -12
- package/src/plugins/replicators/schema-sync.helper.js +601 -0
- package/src/plugins/replicators/sqs-replicator.class.js +11 -9
- package/src/plugins/replicators/turso-replicator.class.js +416 -0
- package/src/plugins/replicators/webhook-replicator.class.js +612 -0
- package/src/plugins/state-machine.plugin.js +122 -68
- package/src/plugins/tfstate/README.md +745 -0
- package/src/plugins/tfstate/base-driver.js +80 -0
- package/src/plugins/tfstate/errors.js +112 -0
- package/src/plugins/tfstate/filesystem-driver.js +129 -0
- package/src/plugins/tfstate/index.js +2660 -0
- package/src/plugins/tfstate/s3-driver.js +192 -0
- package/src/plugins/ttl.plugin.js +536 -0
- package/src/resource.class.js +14 -10
- package/src/s3db.d.ts +57 -0
- package/src/schema.class.js +366 -32
- package/SECURITY.md +0 -76
- package/src/partition-drivers/base-partition-driver.js +0 -106
- package/src/partition-drivers/index.js +0 -66
- package/src/partition-drivers/memory-partition-driver.js +0 -289
- package/src/partition-drivers/sqs-partition-driver.js +0 -337
- package/src/partition-drivers/sync-partition-driver.js +0 -38
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Driver Class for TfState Plugin
|
|
3
|
+
*
|
|
4
|
+
* All tfstate drivers must extend this class and implement the required methods.
|
|
5
|
+
*/
|
|
6
|
+
export class TfStateDriver {
|
|
7
|
+
constructor(config = {}) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.selector = config.selector || '**/*.tfstate';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Initialize the driver
|
|
14
|
+
* Called during plugin installation
|
|
15
|
+
*/
|
|
16
|
+
async initialize() {
|
|
17
|
+
throw new Error('Driver must implement initialize()');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* List all state files matching the selector
|
|
22
|
+
* @returns {Promise<Array>} Array of state file metadata { path, lastModified, size }
|
|
23
|
+
*/
|
|
24
|
+
async listStateFiles() {
|
|
25
|
+
throw new Error('Driver must implement listStateFiles()');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read a state file content
|
|
30
|
+
* @param {string} path - Path to the state file
|
|
31
|
+
* @returns {Promise<Object>} Parsed state file content
|
|
32
|
+
*/
|
|
33
|
+
async readStateFile(path) {
|
|
34
|
+
throw new Error('Driver must implement readStateFile()');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get state file metadata
|
|
39
|
+
* @param {string} path - Path to the state file
|
|
40
|
+
* @returns {Promise<Object>} Metadata { path, lastModified, size, etag }
|
|
41
|
+
*/
|
|
42
|
+
async getStateFileMetadata(path) {
|
|
43
|
+
throw new Error('Driver must implement getStateFileMetadata()');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if a state file has been modified since last check
|
|
48
|
+
* @param {string} path - Path to the state file
|
|
49
|
+
* @param {Date} since - Check modifications since this date
|
|
50
|
+
* @returns {Promise<boolean>} True if modified
|
|
51
|
+
*/
|
|
52
|
+
async hasBeenModified(path, since) {
|
|
53
|
+
const metadata = await this.getStateFileMetadata(path);
|
|
54
|
+
return new Date(metadata.lastModified) > new Date(since);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Match a path against the selector pattern
|
|
59
|
+
* @param {string} path - Path to check
|
|
60
|
+
* @returns {boolean} True if matches
|
|
61
|
+
*/
|
|
62
|
+
matchesSelector(path) {
|
|
63
|
+
const pattern = this.selector
|
|
64
|
+
.replace(/\*\*/g, '__DOUBLE_STAR__')
|
|
65
|
+
.replace(/\*/g, '[^/]*')
|
|
66
|
+
.replace(/__DOUBLE_STAR__/g, '.*')
|
|
67
|
+
.replace(/\?/g, '.')
|
|
68
|
+
.replace(/\[([^\]]+)\]/g, '[$1]');
|
|
69
|
+
|
|
70
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
71
|
+
return regex.test(path);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Close/cleanup driver resources
|
|
76
|
+
*/
|
|
77
|
+
async close() {
|
|
78
|
+
// Optional cleanup, override if needed
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TfStatePlugin Error Classes
|
|
3
|
+
* Custom errors for Terraform/OpenTofu state operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base error for all Terraform/OpenTofu state operations
|
|
8
|
+
*/
|
|
9
|
+
export class TfStateError extends Error {
|
|
10
|
+
constructor(message, context = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'TfStateError';
|
|
13
|
+
this.context = context;
|
|
14
|
+
Error.captureStackTrace(this, this.constructor);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Thrown when state file is invalid or corrupted
|
|
20
|
+
*/
|
|
21
|
+
export class InvalidStateFileError extends TfStateError {
|
|
22
|
+
constructor(filePath, reason, context = {}) {
|
|
23
|
+
super(`Invalid Terraform state file "${filePath}": ${reason}`, context);
|
|
24
|
+
this.name = 'InvalidStateFileError';
|
|
25
|
+
this.filePath = filePath;
|
|
26
|
+
this.reason = reason;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Thrown when state file version is not supported
|
|
32
|
+
*/
|
|
33
|
+
export class UnsupportedStateVersionError extends TfStateError {
|
|
34
|
+
constructor(version, supportedVersions, context = {}) {
|
|
35
|
+
super(
|
|
36
|
+
`Terraform state version ${version} is not supported. Supported versions: ${supportedVersions.join(', ')}`,
|
|
37
|
+
context
|
|
38
|
+
);
|
|
39
|
+
this.name = 'UnsupportedStateVersionError';
|
|
40
|
+
this.version = version;
|
|
41
|
+
this.supportedVersions = supportedVersions;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Thrown when state file cannot be read
|
|
47
|
+
*/
|
|
48
|
+
export class StateFileNotFoundError extends TfStateError {
|
|
49
|
+
constructor(filePath, context = {}) {
|
|
50
|
+
super(`Terraform state file not found: ${filePath}`, context);
|
|
51
|
+
this.name = 'StateFileNotFoundError';
|
|
52
|
+
this.filePath = filePath;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Thrown when resource extraction fails
|
|
58
|
+
*/
|
|
59
|
+
export class ResourceExtractionError extends TfStateError {
|
|
60
|
+
constructor(resourceAddress, originalError, context = {}) {
|
|
61
|
+
super(
|
|
62
|
+
`Failed to extract resource "${resourceAddress}": ${originalError.message}`,
|
|
63
|
+
context
|
|
64
|
+
);
|
|
65
|
+
this.name = 'ResourceExtractionError';
|
|
66
|
+
this.resourceAddress = resourceAddress;
|
|
67
|
+
this.originalError = originalError;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Thrown when state diff calculation fails
|
|
73
|
+
*/
|
|
74
|
+
export class StateDiffError extends TfStateError {
|
|
75
|
+
constructor(oldSerial, newSerial, originalError, context = {}) {
|
|
76
|
+
super(
|
|
77
|
+
`Failed to calculate diff between state serials ${oldSerial} and ${newSerial}: ${originalError.message}`,
|
|
78
|
+
context
|
|
79
|
+
);
|
|
80
|
+
this.name = 'StateDiffError';
|
|
81
|
+
this.oldSerial = oldSerial;
|
|
82
|
+
this.newSerial = newSerial;
|
|
83
|
+
this.originalError = originalError;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Thrown when file watching setup fails
|
|
89
|
+
*/
|
|
90
|
+
export class FileWatchError extends TfStateError {
|
|
91
|
+
constructor(path, originalError, context = {}) {
|
|
92
|
+
super(`Failed to watch path "${path}": ${originalError.message}`, context);
|
|
93
|
+
this.name = 'FileWatchError';
|
|
94
|
+
this.path = path;
|
|
95
|
+
this.originalError = originalError;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Thrown when resource filtering fails
|
|
101
|
+
*/
|
|
102
|
+
export class ResourceFilterError extends TfStateError {
|
|
103
|
+
constructor(filterExpression, originalError, context = {}) {
|
|
104
|
+
super(
|
|
105
|
+
`Failed to apply resource filter "${filterExpression}": ${originalError.message}`,
|
|
106
|
+
context
|
|
107
|
+
);
|
|
108
|
+
this.name = 'ResourceFilterError';
|
|
109
|
+
this.filterExpression = filterExpression;
|
|
110
|
+
this.originalError = originalError;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem Driver for TfState Plugin
|
|
3
|
+
*
|
|
4
|
+
* Reads Terraform/OpenTofu state files from local filesystem
|
|
5
|
+
* Useful for development and testing
|
|
6
|
+
*/
|
|
7
|
+
import { TfStateDriver } from './base-driver.js';
|
|
8
|
+
import { readFile, stat } from 'fs/promises';
|
|
9
|
+
import { join, relative } from 'path';
|
|
10
|
+
import { glob } from 'glob';
|
|
11
|
+
|
|
12
|
+
export class FilesystemTfStateDriver extends TfStateDriver {
|
|
13
|
+
constructor(config = {}) {
|
|
14
|
+
super(config);
|
|
15
|
+
this.basePath = config.basePath || config.path || process.cwd();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initialize filesystem driver
|
|
20
|
+
*/
|
|
21
|
+
async initialize() {
|
|
22
|
+
// Verify base path exists
|
|
23
|
+
try {
|
|
24
|
+
const stats = await stat(this.basePath);
|
|
25
|
+
if (!stats.isDirectory()) {
|
|
26
|
+
throw new Error(`Base path is not a directory: ${this.basePath}`);
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(`Invalid base path: ${this.basePath} - ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* List all state files matching the selector
|
|
35
|
+
*/
|
|
36
|
+
async listStateFiles() {
|
|
37
|
+
const pattern = join(this.basePath, this.selector);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const files = await glob(pattern, {
|
|
41
|
+
nodir: true,
|
|
42
|
+
absolute: false,
|
|
43
|
+
cwd: this.basePath
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const stateFiles = await Promise.all(
|
|
47
|
+
files.map(async (file) => {
|
|
48
|
+
const fullPath = join(this.basePath, file);
|
|
49
|
+
const stats = await stat(fullPath);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
path: file,
|
|
53
|
+
fullPath,
|
|
54
|
+
lastModified: stats.mtime,
|
|
55
|
+
size: stats.size,
|
|
56
|
+
etag: `${stats.mtime.getTime()}-${stats.size}` // Pseudo-etag
|
|
57
|
+
};
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return stateFiles;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
throw new Error(`Failed to list state files: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Read a state file from filesystem
|
|
69
|
+
*/
|
|
70
|
+
async readStateFile(path) {
|
|
71
|
+
const fullPath = path.startsWith(this.basePath)
|
|
72
|
+
? path
|
|
73
|
+
: join(this.basePath, path);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
77
|
+
return JSON.parse(content);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error.code === 'ENOENT') {
|
|
80
|
+
throw new Error(`State file not found: ${path}`);
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Failed to read state file ${path}: ${error.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get state file metadata from filesystem
|
|
88
|
+
*/
|
|
89
|
+
async getStateFileMetadata(path) {
|
|
90
|
+
const fullPath = path.startsWith(this.basePath)
|
|
91
|
+
? path
|
|
92
|
+
: join(this.basePath, path);
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const stats = await stat(fullPath);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
path,
|
|
99
|
+
fullPath,
|
|
100
|
+
lastModified: stats.mtime,
|
|
101
|
+
size: stats.size,
|
|
102
|
+
etag: `${stats.mtime.getTime()}-${stats.size}`
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error.code === 'ENOENT') {
|
|
106
|
+
throw new Error(`State file not found: ${path}`);
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`Failed to get metadata for ${path}: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if state file has been modified
|
|
114
|
+
*/
|
|
115
|
+
async hasBeenModified(path, since) {
|
|
116
|
+
const metadata = await this.getStateFileMetadata(path);
|
|
117
|
+
const lastModified = new Date(metadata.lastModified);
|
|
118
|
+
const sinceDate = new Date(since);
|
|
119
|
+
|
|
120
|
+
return lastModified > sinceDate;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Close filesystem driver (no-op)
|
|
125
|
+
*/
|
|
126
|
+
async close() {
|
|
127
|
+
// Nothing to close for filesystem
|
|
128
|
+
}
|
|
129
|
+
}
|