s3db.js 6.2.0 → 7.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.
Files changed (60) hide show
  1. package/PLUGINS.md +2724 -0
  2. package/README.md +372 -469
  3. package/UNLICENSE +24 -0
  4. package/dist/s3db.cjs.js +30057 -18387
  5. package/dist/s3db.cjs.min.js +1 -1
  6. package/dist/s3db.d.ts +373 -72
  7. package/dist/s3db.es.js +30043 -18384
  8. package/dist/s3db.es.min.js +1 -1
  9. package/dist/s3db.iife.js +29730 -18061
  10. package/dist/s3db.iife.min.js +1 -1
  11. package/package.json +44 -69
  12. package/src/behaviors/body-only.js +110 -0
  13. package/src/behaviors/body-overflow.js +153 -0
  14. package/src/behaviors/enforce-limits.js +195 -0
  15. package/src/behaviors/index.js +39 -0
  16. package/src/behaviors/truncate-data.js +204 -0
  17. package/src/behaviors/user-managed.js +147 -0
  18. package/src/client.class.js +515 -0
  19. package/src/concerns/base62.js +61 -0
  20. package/src/concerns/calculator.js +204 -0
  21. package/src/concerns/crypto.js +142 -0
  22. package/src/concerns/id.js +8 -0
  23. package/src/concerns/index.js +5 -0
  24. package/src/concerns/try-fn.js +151 -0
  25. package/src/connection-string.class.js +75 -0
  26. package/src/database.class.js +599 -0
  27. package/src/errors.js +261 -0
  28. package/src/index.js +17 -0
  29. package/src/plugins/audit.plugin.js +442 -0
  30. package/src/plugins/cache/cache.class.js +53 -0
  31. package/src/plugins/cache/index.js +6 -0
  32. package/src/plugins/cache/memory-cache.class.js +164 -0
  33. package/src/plugins/cache/s3-cache.class.js +189 -0
  34. package/src/plugins/cache.plugin.js +275 -0
  35. package/src/plugins/consumers/index.js +24 -0
  36. package/src/plugins/consumers/rabbitmq-consumer.js +56 -0
  37. package/src/plugins/consumers/sqs-consumer.js +102 -0
  38. package/src/plugins/costs.plugin.js +81 -0
  39. package/src/plugins/fulltext.plugin.js +473 -0
  40. package/src/plugins/index.js +12 -0
  41. package/src/plugins/metrics.plugin.js +603 -0
  42. package/src/plugins/plugin.class.js +210 -0
  43. package/src/plugins/plugin.obj.js +13 -0
  44. package/src/plugins/queue-consumer.plugin.js +134 -0
  45. package/src/plugins/replicator.plugin.js +769 -0
  46. package/src/plugins/replicators/base-replicator.class.js +85 -0
  47. package/src/plugins/replicators/bigquery-replicator.class.js +328 -0
  48. package/src/plugins/replicators/index.js +44 -0
  49. package/src/plugins/replicators/postgres-replicator.class.js +427 -0
  50. package/src/plugins/replicators/s3db-replicator.class.js +352 -0
  51. package/src/plugins/replicators/sqs-replicator.class.js +427 -0
  52. package/src/resource.class.js +2626 -0
  53. package/src/s3db.d.ts +1263 -0
  54. package/src/schema.class.js +706 -0
  55. package/src/stream/index.js +16 -0
  56. package/src/stream/resource-ids-page-reader.class.js +10 -0
  57. package/src/stream/resource-ids-reader.class.js +63 -0
  58. package/src/stream/resource-reader.class.js +81 -0
  59. package/src/stream/resource-writer.class.js +92 -0
  60. package/src/validator.class.js +97 -0
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Calculates the size in bytes of a string using UTF-8 encoding
3
+ * @param {string} str - The string to calculate size for
4
+ * @returns {number} - Size in bytes
5
+ */
6
+ export function calculateUTF8Bytes(str) {
7
+ if (typeof str !== 'string') {
8
+ str = String(str);
9
+ }
10
+
11
+ let bytes = 0;
12
+ for (let i = 0; i < str.length; i++) {
13
+ const codePoint = str.codePointAt(i);
14
+
15
+ if (codePoint <= 0x7F) {
16
+ // 1 byte: U+0000 to U+007F (ASCII characters)
17
+ bytes += 1;
18
+ } else if (codePoint <= 0x7FF) {
19
+ // 2 bytes: U+0080 to U+07FF
20
+ bytes += 2;
21
+ } else if (codePoint <= 0xFFFF) {
22
+ // 3 bytes: U+0800 to U+FFFF
23
+ bytes += 3;
24
+ } else if (codePoint <= 0x10FFFF) {
25
+ // 4 bytes: U+10000 to U+10FFFF
26
+ bytes += 4;
27
+ // Skip the next character if it's a surrogate pair
28
+ if (codePoint > 0xFFFF) {
29
+ i++;
30
+ }
31
+ }
32
+ }
33
+
34
+ return bytes;
35
+ }
36
+
37
+ /**
38
+ * Calculates the size in bytes of attribute names (mapped to digits)
39
+ * @param {Object} mappedObject - The object returned by schema.mapper()
40
+ * @returns {number} - Total size of attribute names in bytes
41
+ */
42
+ export function calculateAttributeNamesSize(mappedObject) {
43
+ let totalSize = 0;
44
+
45
+ for (const key of Object.keys(mappedObject)) {
46
+ totalSize += calculateUTF8Bytes(key);
47
+ }
48
+
49
+ return totalSize;
50
+ }
51
+
52
+ /**
53
+ * Transforms a value according to the schema mapper rules
54
+ * @param {any} value - The value to transform
55
+ * @returns {string} - The transformed value as string
56
+ */
57
+ export function transformValue(value) {
58
+ if (value === null || value === undefined) {
59
+ return '';
60
+ }
61
+
62
+ if (typeof value === 'boolean') {
63
+ return value ? '1' : '0';
64
+ }
65
+
66
+ if (typeof value === 'number') {
67
+ return String(value);
68
+ }
69
+
70
+ if (typeof value === 'string') {
71
+ return value;
72
+ }
73
+
74
+ if (Array.isArray(value)) {
75
+ // Handle arrays like in the schema mapper
76
+ if (value.length === 0) {
77
+ return '[]';
78
+ }
79
+ // For simplicity, join with | separator like in the schema
80
+ return value.map(item => String(item)).join('|');
81
+ }
82
+
83
+ if (typeof value === 'object') {
84
+ return JSON.stringify(value);
85
+ }
86
+
87
+ return String(value);
88
+ }
89
+
90
+ /**
91
+ * Calculates the size in bytes of each attribute in a mapped object
92
+ * @param {Object} mappedObject - The object returned by schema.mapper()
93
+ * @returns {Object} - Object with attribute names as keys and byte sizes as values
94
+ */
95
+ export function calculateAttributeSizes(mappedObject) {
96
+ const sizes = {};
97
+
98
+ for (const [key, value] of Object.entries(mappedObject)) {
99
+ const transformedValue = transformValue(value);
100
+ const byteSize = calculateUTF8Bytes(transformedValue);
101
+ sizes[key] = byteSize;
102
+ }
103
+
104
+ return sizes;
105
+ }
106
+
107
+ /**
108
+ * Calculates the total size in bytes of a mapped object (including attribute names)
109
+ * @param {Object} mappedObject - The object returned by schema.mapper()
110
+ * @returns {number} - Total size in bytes
111
+ */
112
+ export function calculateTotalSize(mappedObject) {
113
+ const valueSizes = calculateAttributeSizes(mappedObject);
114
+ const valueTotal = Object.values(valueSizes).reduce((total, size) => total + size, 0);
115
+
116
+ // Add the size of attribute names (digits)
117
+ const namesSize = calculateAttributeNamesSize(mappedObject);
118
+
119
+ return valueTotal + namesSize;
120
+ }
121
+
122
+ /**
123
+ * Gets detailed size information for a mapped object
124
+ * @param {Object} mappedObject - The object returned by schema.mapper()
125
+ * @returns {Object} - Object with sizes, total, and breakdown information
126
+ */
127
+ export function getSizeBreakdown(mappedObject) {
128
+ const valueSizes = calculateAttributeSizes(mappedObject);
129
+ const namesSize = calculateAttributeNamesSize(mappedObject);
130
+
131
+ const valueTotal = Object.values(valueSizes).reduce((sum, size) => sum + size, 0);
132
+ const total = valueTotal + namesSize;
133
+
134
+ // Sort attributes by size (largest first)
135
+ const sortedAttributes = Object.entries(valueSizes)
136
+ .sort(([, a], [, b]) => b - a)
137
+ .map(([key, size]) => ({
138
+ attribute: key,
139
+ size,
140
+ percentage: ((size / total) * 100).toFixed(2) + '%'
141
+ }));
142
+
143
+ return {
144
+ total,
145
+ valueSizes,
146
+ namesSize,
147
+ valueTotal,
148
+ breakdown: sortedAttributes,
149
+ // Add detailed breakdown including names
150
+ detailedBreakdown: {
151
+ values: valueTotal,
152
+ names: namesSize,
153
+ total: total
154
+ }
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Calculates the minimum overhead required for system fields
160
+ * @param {Object} config - Configuration object
161
+ * @param {string} [config.version='1'] - Resource version
162
+ * @param {boolean} [config.timestamps=false] - Whether timestamps are enabled
163
+ * @param {string} [config.id=''] - Resource ID (if known)
164
+ * @returns {number} - Minimum overhead in bytes
165
+ */
166
+ export function calculateSystemOverhead(config = {}) {
167
+ const { version = '1', timestamps = false, id = '' } = config;
168
+
169
+ // System fields that are always present
170
+ const systemFields = {
171
+ '_v': String(version), // Version field (e.g., "1", "10", "100")
172
+ };
173
+
174
+ // Optional system fields
175
+ if (timestamps) {
176
+ systemFields.createdAt = '2024-01-01T00:00:00.000Z'; // Example timestamp
177
+ systemFields.updatedAt = '2024-01-01T00:00:00.000Z'; // Example timestamp
178
+ }
179
+
180
+ if (id) {
181
+ systemFields.id = id;
182
+ }
183
+
184
+ // Calculate overhead for system fields
185
+ const overheadObject = {};
186
+ for (const [key, value] of Object.entries(systemFields)) {
187
+ overheadObject[key] = value;
188
+ }
189
+
190
+ return calculateTotalSize(overheadObject);
191
+ }
192
+
193
+ /**
194
+ * Calculates the effective metadata limit considering system overhead
195
+ * @param {Object} config - Configuration object
196
+ * @param {number} [config.s3Limit=2048] - S3 metadata limit in bytes
197
+ * @param {Object} [config.systemConfig] - System configuration for overhead calculation
198
+ * @returns {number} - Effective limit in bytes
199
+ */
200
+ export function calculateEffectiveLimit(config = {}) {
201
+ const { s3Limit = 2048, systemConfig = {} } = config;
202
+ const overhead = calculateSystemOverhead(systemConfig);
203
+ return s3Limit - overhead;
204
+ }
@@ -0,0 +1,142 @@
1
+ import { CryptoError } from "../errors.js";
2
+ import tryFn, { tryFnSync } from "./try-fn.js";
3
+
4
+ async function dynamicCrypto() {
5
+ let lib;
6
+
7
+ if (typeof process !== 'undefined') {
8
+ const [ok, err, result] = await tryFn(async () => {
9
+ const { webcrypto } = await import('crypto');
10
+ return webcrypto;
11
+ });
12
+ if (ok) {
13
+ lib = result;
14
+ } else {
15
+ throw new CryptoError('Crypto API not available', { original: err, context: 'dynamicCrypto' });
16
+ }
17
+ } else if (typeof window !== 'undefined') {
18
+ lib = window.crypto;
19
+ }
20
+
21
+ if (!lib) throw new CryptoError('Could not load any crypto library', { context: 'dynamicCrypto' });
22
+ return lib;
23
+ }
24
+
25
+ export async function sha256(message) {
26
+ const [okCrypto, errCrypto, cryptoLib] = await tryFn(dynamicCrypto);
27
+ if (!okCrypto) throw new CryptoError('Crypto API not available', { original: errCrypto });
28
+
29
+ const encoder = new TextEncoder();
30
+ const data = encoder.encode(message);
31
+ const [ok, err, hashBuffer] = await tryFn(() => cryptoLib.subtle.digest('SHA-256', data));
32
+ if (!ok) throw new CryptoError('SHA-256 digest failed', { original: err, input: message });
33
+
34
+ // Convert buffer to hex string
35
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
36
+ const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
37
+
38
+ return hashHex;
39
+ }
40
+
41
+ export async function encrypt(content, passphrase) {
42
+ const [okCrypto, errCrypto, cryptoLib] = await tryFn(dynamicCrypto);
43
+ if (!okCrypto) throw new CryptoError('Crypto API not available', { original: errCrypto });
44
+
45
+ const salt = cryptoLib.getRandomValues(new Uint8Array(16)); // Generate a random salt
46
+ const [okKey, errKey, key] = await tryFn(() => getKeyMaterial(passphrase, salt));
47
+ if (!okKey) throw new CryptoError('Key derivation failed', { original: errKey, passphrase, salt });
48
+
49
+ const iv = cryptoLib.getRandomValues(new Uint8Array(12)); // 12-byte IV for AES-GCM
50
+
51
+ const encoder = new TextEncoder();
52
+ const encodedContent = encoder.encode(content);
53
+
54
+ const [okEnc, errEnc, encryptedContent] = await tryFn(() => cryptoLib.subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, encodedContent));
55
+ if (!okEnc) throw new CryptoError('Encryption failed', { original: errEnc, content });
56
+
57
+ const encryptedData = new Uint8Array(salt.length + iv.length + encryptedContent.byteLength);
58
+ encryptedData.set(salt); // Prepend salt
59
+ encryptedData.set(iv, salt.length); // Prepend IV after salt
60
+ encryptedData.set(new Uint8Array(encryptedContent), salt.length + iv.length); // Append encrypted content
61
+
62
+ return arrayBufferToBase64(encryptedData);
63
+ }
64
+
65
+ export async function decrypt(encryptedBase64, passphrase) {
66
+ const [okCrypto, errCrypto, cryptoLib] = await tryFn(dynamicCrypto);
67
+ if (!okCrypto) throw new CryptoError('Crypto API not available', { original: errCrypto });
68
+
69
+ const encryptedData = base64ToArrayBuffer(encryptedBase64);
70
+
71
+ const salt = encryptedData.slice(0, 16); // Extract salt (first 16 bytes)
72
+ const iv = encryptedData.slice(16, 28); // Extract IV (next 12 bytes)
73
+ const encryptedContent = encryptedData.slice(28); // Remaining is the encrypted content
74
+
75
+ const [okKey, errKey, key] = await tryFn(() => getKeyMaterial(passphrase, salt));
76
+ if (!okKey) throw new CryptoError('Key derivation failed (decrypt)', { original: errKey, passphrase, salt });
77
+
78
+ const [okDec, errDec, decryptedContent] = await tryFn(() => cryptoLib.subtle.decrypt({ name: 'AES-GCM', iv: iv }, key, encryptedContent));
79
+ if (!okDec) throw new CryptoError('Decryption failed', { original: errDec, encryptedBase64 });
80
+
81
+ const decoder = new TextDecoder();
82
+ return decoder.decode(decryptedContent);
83
+ }
84
+
85
+ async function getKeyMaterial(passphrase, salt) {
86
+ const [okCrypto, errCrypto, cryptoLib] = await tryFn(dynamicCrypto);
87
+ if (!okCrypto) throw new CryptoError('Crypto API not available', { original: errCrypto });
88
+
89
+ const encoder = new TextEncoder();
90
+ const keyMaterial = encoder.encode(passphrase); // Convert passphrase to bytes
91
+
92
+ const [okImport, errImport, baseKey] = await tryFn(() => cryptoLib.subtle.importKey(
93
+ 'raw',
94
+ keyMaterial,
95
+ { name: 'PBKDF2' },
96
+ false,
97
+ ['deriveKey']
98
+ ));
99
+ if (!okImport) throw new CryptoError('importKey failed', { original: errImport, passphrase });
100
+
101
+ const [okDerive, errDerive, derivedKey] = await tryFn(() => cryptoLib.subtle.deriveKey(
102
+ {
103
+ name: 'PBKDF2',
104
+ salt: salt,
105
+ iterations: 100000,
106
+ hash: 'SHA-256'
107
+ },
108
+ baseKey,
109
+ { name: 'AES-GCM', length: 256 },
110
+ true,
111
+ ['encrypt', 'decrypt']
112
+ ));
113
+ if (!okDerive) throw new CryptoError('deriveKey failed', { original: errDerive, passphrase, salt });
114
+ return derivedKey;
115
+ }
116
+
117
+ function arrayBufferToBase64(buffer) {
118
+ if (typeof process !== 'undefined') {
119
+ // Node.js version
120
+ return Buffer.from(buffer).toString('base64');
121
+ } else {
122
+ // Browser version
123
+ const [ok, err, binary] = tryFnSync(() => String.fromCharCode.apply(null, new Uint8Array(buffer)));
124
+ if (!ok) throw new CryptoError('Failed to convert ArrayBuffer to base64 (browser)', { original: err });
125
+ return window.btoa(binary);
126
+ }
127
+ }
128
+
129
+ function base64ToArrayBuffer(base64) {
130
+ if (typeof process !== 'undefined') {
131
+ return new Uint8Array(Buffer.from(base64, 'base64'));
132
+ } else {
133
+ const [ok, err, binaryString] = tryFnSync(() => window.atob(base64));
134
+ if (!ok) throw new CryptoError('Failed to decode base64 (browser)', { original: err });
135
+ const len = binaryString.length;
136
+ const bytes = new Uint8Array(len);
137
+ for (let i = 0; i < len; i++) {
138
+ bytes[i] = binaryString.charCodeAt(i);
139
+ }
140
+ return bytes;
141
+ }
142
+ }
@@ -0,0 +1,8 @@
1
+ import { customAlphabet, urlAlphabet } from 'nanoid'
2
+
3
+ export const idGenerator = customAlphabet(urlAlphabet, 22)
4
+
5
+ // Password generator using nanoid with custom alphabet for better readability
6
+ // Excludes similar characters (0, O, 1, l, I) to avoid confusion
7
+ const passwordAlphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789'
8
+ export const passwordGenerator = customAlphabet(passwordAlphabet, 16)
@@ -0,0 +1,5 @@
1
+ export * from './base62.js';
2
+ export * from './calculator.js';
3
+ export * from './crypto.js';
4
+ export * from './id.js';
5
+ export * from './try-fn.js';
@@ -0,0 +1,151 @@
1
+ /**
2
+ * tryFn - A robust error handling utility for JavaScript functions and values.
3
+ *
4
+ * This utility provides a consistent way to handle errors and return values across different types:
5
+ * - Synchronous functions
6
+ * - Asynchronous functions (Promises)
7
+ * - Direct values
8
+ * - Promises
9
+ * - null/undefined values
10
+ *
11
+ * @param {Function|Promise|*} fnOrPromise - The input to process, can be:
12
+ * - A synchronous function that returns a value
13
+ * - An async function that returns a Promise
14
+ * - A Promise directly
15
+ * - Any direct value (number, string, object, etc)
16
+ *
17
+ * @returns {Array} A tuple containing:
18
+ * - [0] ok: boolean - Indicates if the operation succeeded
19
+ * - [1] err: Error|null - Error object if failed, null if succeeded
20
+ * - [2] data: any - The result data if succeeded, undefined if failed
21
+ *
22
+ * Key Features:
23
+ * - Unified error handling interface for all types of operations
24
+ * - Preserves and enhances error stack traces for better debugging
25
+ * - Zero dependencies
26
+ * - TypeScript friendly return tuple
27
+ * - Handles edge cases like null/undefined gracefully
28
+ * - Perfect for functional programming patterns
29
+ * - Ideal for Promise chains and async/await flows
30
+ * - Reduces try/catch boilerplate code
31
+ *
32
+ * Error Handling:
33
+ * - All errors maintain their original properties
34
+ * - Stack traces are automatically enhanced to show the tryFn call site
35
+ * - Errors from async operations are properly caught and formatted
36
+ *
37
+ * Common Use Cases:
38
+ * - API request wrappers
39
+ * - Database operations
40
+ * - File system operations
41
+ * - Data parsing and validation
42
+ * - Service integration points
43
+ *
44
+ * Examples:
45
+ * ```js
46
+ * // Handling synchronous operations
47
+ * const [ok, err, data] = tryFn(() => JSON.parse(jsonString));
48
+ *
49
+ * // Handling async operations
50
+ * const [ok, err, data] = await tryFn(async () => {
51
+ * const response = await fetch(url);
52
+ * return response.json();
53
+ * });
54
+ *
55
+ * // Direct promise handling
56
+ * const [ok, err, data] = await tryFn(fetch(url));
57
+ *
58
+ * // Value passthrough
59
+ * const [ok, err, data] = tryFn(42); // [true, null, 42]
60
+ * ```
61
+ */
62
+ export function tryFn(fnOrPromise) {
63
+ if (fnOrPromise == null) {
64
+ const err = new Error('fnOrPromise cannot be null or undefined');
65
+ err.stack = new Error().stack;
66
+ return [false, err, undefined];
67
+ }
68
+
69
+ if (typeof fnOrPromise === 'function') {
70
+ try {
71
+ const result = fnOrPromise();
72
+
73
+ if (result == null) {
74
+ return [true, null, result];
75
+ }
76
+
77
+ if (typeof result.then === 'function') {
78
+ return result
79
+ .then(data => [true, null, data])
80
+ .catch(error => {
81
+ if (
82
+ error instanceof Error &&
83
+ Object.isExtensible(error)
84
+ ) {
85
+ const desc = Object.getOwnPropertyDescriptor(error, 'stack');
86
+ if (
87
+ desc && desc.writable && desc.configurable && error.hasOwnProperty('stack')
88
+ ) {
89
+ try {
90
+ error.stack = new Error().stack;
91
+ } catch (_) {}
92
+ }
93
+ }
94
+ return [false, error, undefined];
95
+ });
96
+ }
97
+
98
+ return [true, null, result];
99
+
100
+ } catch (error) {
101
+ if (
102
+ error instanceof Error &&
103
+ Object.isExtensible(error)
104
+ ) {
105
+ const desc = Object.getOwnPropertyDescriptor(error, 'stack');
106
+ if (
107
+ desc && desc.writable && desc.configurable && error.hasOwnProperty('stack')
108
+ ) {
109
+ try {
110
+ error.stack = new Error().stack;
111
+ } catch (_) {}
112
+ }
113
+ }
114
+ return [false, error, undefined];
115
+ }
116
+ }
117
+
118
+ if (typeof fnOrPromise.then === 'function') {
119
+ return Promise.resolve(fnOrPromise)
120
+ .then(data => [true, null, data])
121
+ .catch(error => {
122
+ if (
123
+ error instanceof Error &&
124
+ Object.isExtensible(error)
125
+ ) {
126
+ const desc = Object.getOwnPropertyDescriptor(error, 'stack');
127
+ if (
128
+ desc && desc.writable && desc.configurable && error.hasOwnProperty('stack')
129
+ ) {
130
+ try {
131
+ error.stack = new Error().stack;
132
+ } catch (_) {}
133
+ }
134
+ }
135
+ return [false, error, undefined];
136
+ });
137
+ }
138
+
139
+ return [true, null, fnOrPromise];
140
+ }
141
+
142
+ export function tryFnSync(fn) {
143
+ try {
144
+ const result = fn();
145
+ return [true, null, result];
146
+ } catch (err) {
147
+ return [false, err, null];
148
+ }
149
+ }
150
+
151
+ export default tryFn;
@@ -0,0 +1,75 @@
1
+ export const S3_DEFAULT_REGION = "us-east-1";
2
+ export const S3_DEFAULT_ENDPOINT = "https://s3.us-east-1.amazonaws.com";
3
+
4
+ import tryFn, { tryFnSync } from "./concerns/try-fn.js";
5
+ import { ConnectionStringError } from "./errors.js";
6
+
7
+ export class ConnectionString {
8
+ constructor(connectionString) {
9
+ let uri;
10
+
11
+ const [ok, err, parsed] = tryFn(() => new URL(connectionString));
12
+ if (!ok) {
13
+ throw new ConnectionStringError("Invalid connection string: " + connectionString, { original: err, input: connectionString });
14
+ }
15
+ uri = parsed;
16
+ // defaults:
17
+ this.region = S3_DEFAULT_REGION;
18
+
19
+ // config:
20
+ if (uri.protocol === "s3:") this.defineFromS3(uri);
21
+ else this.defineFromCustomUri(uri);
22
+
23
+ for (const [k, v] of uri.searchParams.entries()) {
24
+ this[k] = v;
25
+ }
26
+ }
27
+
28
+ defineFromS3(uri) {
29
+ const [okBucket, errBucket, bucket] = tryFnSync(() => decodeURIComponent(uri.hostname));
30
+ if (!okBucket) throw new ConnectionStringError("Invalid bucket in connection string", { original: errBucket, input: uri.hostname });
31
+ this.bucket = bucket || 's3db';
32
+ const [okUser, errUser, user] = tryFnSync(() => decodeURIComponent(uri.username));
33
+ if (!okUser) throw new ConnectionStringError("Invalid accessKeyId in connection string", { original: errUser, input: uri.username });
34
+ this.accessKeyId = user;
35
+ const [okPass, errPass, pass] = tryFnSync(() => decodeURIComponent(uri.password));
36
+ if (!okPass) throw new ConnectionStringError("Invalid secretAccessKey in connection string", { original: errPass, input: uri.password });
37
+ this.secretAccessKey = pass;
38
+ this.endpoint = S3_DEFAULT_ENDPOINT;
39
+
40
+ if (["/", "", null].includes(uri.pathname)) {
41
+ this.keyPrefix = "";
42
+ } else {
43
+ let [, ...subpath] = uri.pathname.split("/");
44
+ this.keyPrefix = [...(subpath || [])].join("/");
45
+ }
46
+ }
47
+
48
+ defineFromCustomUri(uri) {
49
+ this.forcePathStyle = true;
50
+ this.endpoint = uri.origin;
51
+ const [okUser, errUser, user] = tryFnSync(() => decodeURIComponent(uri.username));
52
+ if (!okUser) throw new ConnectionStringError("Invalid accessKeyId in connection string", { original: errUser, input: uri.username });
53
+ this.accessKeyId = user;
54
+ const [okPass, errPass, pass] = tryFnSync(() => decodeURIComponent(uri.password));
55
+ if (!okPass) throw new ConnectionStringError("Invalid secretAccessKey in connection string", { original: errPass, input: uri.password });
56
+ this.secretAccessKey = pass;
57
+
58
+ if (["/", "", null].includes(uri.pathname)) {
59
+ this.bucket = "s3db";
60
+ this.keyPrefix = "";
61
+ } else {
62
+ let [, bucket, ...subpath] = uri.pathname.split("/");
63
+ if (!bucket) {
64
+ this.bucket = "s3db";
65
+ } else {
66
+ const [okBucket, errBucket, bucketDecoded] = tryFnSync(() => decodeURIComponent(bucket));
67
+ if (!okBucket) throw new ConnectionStringError("Invalid bucket in connection string", { original: errBucket, input: bucket });
68
+ this.bucket = bucketDecoded;
69
+ }
70
+ this.keyPrefix = [...(subpath || [])].join("/");
71
+ }
72
+ }
73
+ }
74
+
75
+ export default ConnectionString;