s3db.js 8.1.0 → 8.1.1
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 +95 -34
- package/dist/s3db.cjs.js +123 -53
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.es.js +123 -53
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +123 -53
- package/dist/s3db.iife.min.js +1 -1
- package/package.json +2 -2
- package/src/behaviors/body-overflow.js +0 -1
- package/src/behaviors/enforce-limits.js +3 -1
- package/src/behaviors/truncate-data.js +2 -1
- package/src/behaviors/user-managed.js +24 -3
- package/src/plugins/audit.plugin.js +61 -10
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +28 -0
- package/src/plugins/cache/s3-cache.class.js +1 -1
- package/src/resource.class.js +38 -38
package/PLUGINS.md
CHANGED
|
@@ -86,7 +86,10 @@ await users.list(); // Cached result
|
|
|
86
86
|
| `maxSize` | number | `1000` | Maximum number of items in cache - global setting |
|
|
87
87
|
| `config` | object | `{}` | Driver-specific configuration options (can override global settings) |
|
|
88
88
|
| `includePartitions` | boolean | `true` | Include partition values in cache keys |
|
|
89
|
-
| `
|
|
89
|
+
| `partitionAware` | boolean | `false` | Use partition-aware filesystem cache |
|
|
90
|
+
| `partitionStrategy` | string | `'hierarchical'` | Partition strategy: `'hierarchical'`, `'flat'`, `'temporal'` |
|
|
91
|
+
| `trackUsage` | boolean | `true` | Track partition usage statistics |
|
|
92
|
+
| `preloadRelated` | boolean | `false` | Preload related partition data |
|
|
90
93
|
|
|
91
94
|
**Configuration Priority:** Driver-specific `config` options override global plugin settings. For example, if you set `ttl: 600000` at the plugin level and `ttl: 1800000` in the driver config, the driver will use 1800000 (30 minutes) while the global setting serves as the default for any drivers that don't specify their own TTL.
|
|
92
95
|
|
|
@@ -100,6 +103,17 @@ The `config` object contains driver-specific options. Note that `ttl` and `maxSi
|
|
|
100
103
|
|-----------|------|---------|-------------|
|
|
101
104
|
| `ttl` | number | inherited | TTL override for memory cache (inherits from plugin level) |
|
|
102
105
|
| `maxSize` | number | inherited | Max items override for memory cache (inherits from plugin level) |
|
|
106
|
+
| `enableStats` | boolean | `false` | Whether to track cache statistics (hits, misses, etc.) |
|
|
107
|
+
| `evictionPolicy` | string | `'lru'` | Cache eviction policy: `'lru'` (Least Recently Used) or `'fifo'` (First In First Out) |
|
|
108
|
+
| `logEvictions` | boolean | `false` | Whether to log when items are evicted from cache |
|
|
109
|
+
| `cleanupInterval` | number | `60000` | Interval in milliseconds to run cleanup of expired items (1 minute default) |
|
|
110
|
+
| `caseSensitive` | boolean | `true` | Whether cache keys are case sensitive |
|
|
111
|
+
| `enableCompression` | boolean | `false` | Whether to compress values using gzip (requires zlib) |
|
|
112
|
+
| `compressionThreshold` | number | `1024` | Minimum size in bytes to trigger compression |
|
|
113
|
+
| `tags` | object | `{}` | Default tags to apply to all cached items |
|
|
114
|
+
| `persistent` | boolean | `false` | Whether to persist cache to disk (experimental) |
|
|
115
|
+
| `persistencePath` | string | `'./cache'` | Directory path for persistent cache storage |
|
|
116
|
+
| `persistenceInterval` | number | `300000` | Interval in milliseconds to save cache to disk (5 minutes default) |
|
|
103
117
|
|
|
104
118
|
#### S3 Driver (`driver: 's3'`)
|
|
105
119
|
|
|
@@ -129,6 +143,25 @@ The `config` object contains driver-specific options. Note that `ttl` and `maxSi
|
|
|
129
143
|
| `cleanupInterval` | number | `300000` | Interval in milliseconds to run cleanup (5 minutes) |
|
|
130
144
|
| `encoding` | string | `'utf8'` | File encoding to use |
|
|
131
145
|
| `fileMode` | number | `0o644` | File permissions in octal notation |
|
|
146
|
+
| `enableBackup` | boolean | `false` | Whether to create backup files before overwriting |
|
|
147
|
+
| `backupSuffix` | string | `'.bak'` | Suffix for backup files |
|
|
148
|
+
| `enableLocking` | boolean | `false` | Whether to use file locking to prevent concurrent access |
|
|
149
|
+
| `lockTimeout` | number | `5000` | Lock timeout in milliseconds |
|
|
150
|
+
| `enableJournal` | boolean | `false` | Whether to maintain a journal of operations |
|
|
151
|
+
| `journalFile` | string | `'cache.journal'` | Journal filename |
|
|
152
|
+
|
|
153
|
+
#### Partition-Aware Filesystem Driver (when `partitionAware: true`)
|
|
154
|
+
|
|
155
|
+
| Parameter | Type | Default | Description |
|
|
156
|
+
|-----------|------|---------|-------------|
|
|
157
|
+
| `partitionStrategy` | string | `'hierarchical'` | Partition strategy: `'hierarchical'`, `'flat'`, `'temporal'` |
|
|
158
|
+
| `trackUsage` | boolean | `true` | Track partition usage statistics |
|
|
159
|
+
| `preloadRelated` | boolean | `false` | Preload related partition data |
|
|
160
|
+
| `preloadThreshold` | number | `10` | Minimum usage count to trigger preloading |
|
|
161
|
+
| `maxCacheSize` | string/null | `null` | Maximum cache size (e.g., `'1GB'`, `'500MB'`) |
|
|
162
|
+
| `usageStatsFile` | string | `'partition-usage.json'` | File to store usage statistics |
|
|
163
|
+
|
|
164
|
+
**Note:** All Filesystem Driver parameters also apply to Partition-Aware Filesystem Driver.
|
|
132
165
|
|
|
133
166
|
### 🔧 Easy Example
|
|
134
167
|
|
|
@@ -141,7 +174,11 @@ const s3db = new S3db({
|
|
|
141
174
|
plugins: [new CachePlugin({
|
|
142
175
|
driver: 'memory',
|
|
143
176
|
ttl: 600000, // 10 minutes - applies to all cache operations
|
|
144
|
-
maxSize: 500 // 500 items max - applies to all cache operations
|
|
177
|
+
maxSize: 500, // 500 items max - applies to all cache operations
|
|
178
|
+
config: {
|
|
179
|
+
enableStats: true,
|
|
180
|
+
evictionPolicy: 'lru'
|
|
181
|
+
}
|
|
145
182
|
})]
|
|
146
183
|
});
|
|
147
184
|
|
|
@@ -156,7 +193,20 @@ const s3dbWithFileCache = new S3db({
|
|
|
156
193
|
directory: './cache',
|
|
157
194
|
ttl: 1800000, // 30 minutes - overrides global for filesystem only
|
|
158
195
|
enableCompression: true,
|
|
159
|
-
enableCleanup: true
|
|
196
|
+
enableCleanup: true,
|
|
197
|
+
enableMetadata: true
|
|
198
|
+
}
|
|
199
|
+
})]
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// S3 cache example
|
|
203
|
+
const s3dbWithS3Cache = new S3db({
|
|
204
|
+
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
205
|
+
plugins: [new CachePlugin({
|
|
206
|
+
driver: 's3',
|
|
207
|
+
ttl: 3600000, // 1 hour
|
|
208
|
+
config: {
|
|
209
|
+
keyPrefix: 'app-cache'
|
|
160
210
|
}
|
|
161
211
|
})]
|
|
162
212
|
});
|
|
@@ -185,15 +235,9 @@ const result3 = await products.count(); // Fresh data
|
|
|
185
235
|
### 🚀 Advanced Configuration Example
|
|
186
236
|
|
|
187
237
|
```javascript
|
|
188
|
-
import { S3db, CachePlugin
|
|
189
|
-
|
|
190
|
-
// Custom cache driver with advanced configuration
|
|
191
|
-
const customCache = new MemoryCache({
|
|
192
|
-
maxSize: 2000,
|
|
193
|
-
ttl: 900000 // 15 minutes
|
|
194
|
-
});
|
|
238
|
+
import { S3db, CachePlugin } from 's3db.js';
|
|
195
239
|
|
|
196
|
-
// Advanced cache configuration with
|
|
240
|
+
// Advanced cache configuration with partition-aware filesystem cache
|
|
197
241
|
const s3dbWithAdvancedCache = new S3db({
|
|
198
242
|
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
199
243
|
plugins: [new CachePlugin({
|
|
@@ -203,6 +247,10 @@ const s3dbWithAdvancedCache = new S3db({
|
|
|
203
247
|
ttl: 3600000, // 1 hour default
|
|
204
248
|
maxSize: 5000, // 5000 items max
|
|
205
249
|
includePartitions: true,
|
|
250
|
+
partitionAware: true, // Enable partition-aware caching
|
|
251
|
+
partitionStrategy: 'hierarchical',
|
|
252
|
+
trackUsage: true,
|
|
253
|
+
preloadRelated: true,
|
|
206
254
|
|
|
207
255
|
// Driver-specific configuration
|
|
208
256
|
config: {
|
|
@@ -217,39 +265,43 @@ const s3dbWithAdvancedCache = new S3db({
|
|
|
217
265
|
maxFileSize: 5242880, // 5MB per file
|
|
218
266
|
enableStats: true,
|
|
219
267
|
fileMode: 0o644,
|
|
220
|
-
encoding: 'utf8'
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// Multiple cache configuration examples
|
|
226
|
-
const s3dbWithS3Cache = new S3db({
|
|
227
|
-
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
228
|
-
plugins: [new CachePlugin({
|
|
229
|
-
driver: 's3',
|
|
230
|
-
ttl: 3600000, // 1 hour - global TTL
|
|
231
|
-
includePartitions: true,
|
|
232
|
-
config: {
|
|
233
|
-
keyPrefix: 'app-cache'
|
|
268
|
+
encoding: 'utf8',
|
|
269
|
+
enableBackup: true,
|
|
270
|
+
enableLocking: true,
|
|
271
|
+
lockTimeout: 3000
|
|
234
272
|
}
|
|
235
273
|
})]
|
|
236
274
|
});
|
|
237
275
|
|
|
276
|
+
// Memory cache with advanced features
|
|
238
277
|
const s3dbWithMemoryCache = new S3db({
|
|
239
278
|
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
240
279
|
plugins: [new CachePlugin({
|
|
241
280
|
driver: 'memory',
|
|
242
281
|
ttl: 600000, // 10 minutes - global TTL
|
|
243
282
|
maxSize: 5000, // 5000 items max - global limit
|
|
244
|
-
includePartitions: true
|
|
283
|
+
includePartitions: true,
|
|
284
|
+
config: {
|
|
285
|
+
enableStats: true,
|
|
286
|
+
evictionPolicy: 'lru',
|
|
287
|
+
logEvictions: true,
|
|
288
|
+
enableCompression: true,
|
|
289
|
+
compressionThreshold: 1024,
|
|
290
|
+
tags: { environment: 'production' }
|
|
291
|
+
}
|
|
245
292
|
})]
|
|
246
293
|
});
|
|
247
294
|
|
|
248
|
-
|
|
295
|
+
// S3 cache with custom prefix
|
|
296
|
+
const s3dbWithS3Cache = new S3db({
|
|
249
297
|
connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
|
|
250
298
|
plugins: [new CachePlugin({
|
|
251
|
-
driver:
|
|
252
|
-
|
|
299
|
+
driver: 's3',
|
|
300
|
+
ttl: 3600000, // 1 hour - global TTL
|
|
301
|
+
includePartitions: true,
|
|
302
|
+
config: {
|
|
303
|
+
keyPrefix: 'app-cache'
|
|
304
|
+
}
|
|
253
305
|
})]
|
|
254
306
|
});
|
|
255
307
|
|
|
@@ -267,16 +319,25 @@ const cacheKey = await users.cacheKeyFor({
|
|
|
267
319
|
});
|
|
268
320
|
|
|
269
321
|
// Manual cache operations
|
|
270
|
-
await users.cache.set(cacheKey, data
|
|
322
|
+
await users.cache.set(cacheKey, data);
|
|
271
323
|
const cached = await users.cache.get(cacheKey);
|
|
272
324
|
await users.cache.delete(cacheKey);
|
|
273
325
|
await users.cache.clear(); // Clear all cache
|
|
274
326
|
|
|
327
|
+
// Partition-aware cache operations (if using partition-aware cache)
|
|
328
|
+
if (users.cache.clearPartition) {
|
|
329
|
+
await users.cache.clearPartition('byStatus', { status: 'active' });
|
|
330
|
+
const stats = await users.cache.getPartitionStats('byStatus');
|
|
331
|
+
console.log('Partition stats:', stats);
|
|
332
|
+
}
|
|
333
|
+
|
|
275
334
|
// Cache statistics (if enabled)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
console.log('
|
|
279
|
-
console.log('Total
|
|
335
|
+
if (users.cache.stats) {
|
|
336
|
+
const stats = users.cache.stats();
|
|
337
|
+
console.log('Cache hit rate:', stats.hitRate);
|
|
338
|
+
console.log('Total hits:', stats.hits);
|
|
339
|
+
console.log('Total misses:', stats.misses);
|
|
340
|
+
}
|
|
280
341
|
```
|
|
281
342
|
|
|
282
343
|
---
|
package/dist/s3db.cjs.js
CHANGED
|
@@ -619,6 +619,12 @@ const idGenerator = nanoid.customAlphabet(nanoid.urlAlphabet, 22);
|
|
|
619
619
|
const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
|
|
620
620
|
const passwordGenerator = nanoid.customAlphabet(passwordAlphabet, 16);
|
|
621
621
|
|
|
622
|
+
var id = /*#__PURE__*/Object.freeze({
|
|
623
|
+
__proto__: null,
|
|
624
|
+
idGenerator: idGenerator,
|
|
625
|
+
passwordGenerator: passwordGenerator
|
|
626
|
+
});
|
|
627
|
+
|
|
622
628
|
var domain;
|
|
623
629
|
|
|
624
630
|
// This constructor is used to store event handlers. Instantiating this is
|
|
@@ -1312,14 +1318,15 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1312
1318
|
}
|
|
1313
1319
|
setupResourceAuditing(resource) {
|
|
1314
1320
|
resource.on("insert", async (data) => {
|
|
1321
|
+
const partitionValues = this.config.includePartitions ? this.getPartitionValues(data, resource) : null;
|
|
1315
1322
|
await this.logAudit({
|
|
1316
1323
|
resourceName: resource.name,
|
|
1317
1324
|
operation: "insert",
|
|
1318
1325
|
recordId: data.id || "auto-generated",
|
|
1319
1326
|
oldData: null,
|
|
1320
1327
|
newData: this.config.includeData ? JSON.stringify(this.truncateData(data)) : null,
|
|
1321
|
-
partition:
|
|
1322
|
-
partitionValues:
|
|
1328
|
+
partition: partitionValues ? this.getPrimaryPartition(partitionValues) : null,
|
|
1329
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1323
1330
|
});
|
|
1324
1331
|
});
|
|
1325
1332
|
resource.on("update", async (data) => {
|
|
@@ -1328,14 +1335,15 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1328
1335
|
const [ok, err, fetched] = await try_fn_default(() => resource.get(data.id));
|
|
1329
1336
|
if (ok) oldData = fetched;
|
|
1330
1337
|
}
|
|
1338
|
+
const partitionValues = this.config.includePartitions ? this.getPartitionValues(data, resource) : null;
|
|
1331
1339
|
await this.logAudit({
|
|
1332
1340
|
resourceName: resource.name,
|
|
1333
1341
|
operation: "update",
|
|
1334
1342
|
recordId: data.id,
|
|
1335
1343
|
oldData: oldData && this.config.includeData ? JSON.stringify(this.truncateData(oldData)) : null,
|
|
1336
1344
|
newData: this.config.includeData ? JSON.stringify(this.truncateData(data)) : null,
|
|
1337
|
-
partition:
|
|
1338
|
-
partitionValues:
|
|
1345
|
+
partition: partitionValues ? this.getPrimaryPartition(partitionValues) : null,
|
|
1346
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1339
1347
|
});
|
|
1340
1348
|
});
|
|
1341
1349
|
resource.on("delete", async (data) => {
|
|
@@ -1344,16 +1352,49 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1344
1352
|
const [ok, err, fetched] = await try_fn_default(() => resource.get(data.id));
|
|
1345
1353
|
if (ok) oldData = fetched;
|
|
1346
1354
|
}
|
|
1355
|
+
const partitionValues = oldData && this.config.includePartitions ? this.getPartitionValues(oldData, resource) : null;
|
|
1347
1356
|
await this.logAudit({
|
|
1348
1357
|
resourceName: resource.name,
|
|
1349
1358
|
operation: "delete",
|
|
1350
1359
|
recordId: data.id,
|
|
1351
1360
|
oldData: oldData && this.config.includeData ? JSON.stringify(this.truncateData(oldData)) : null,
|
|
1352
1361
|
newData: null,
|
|
1353
|
-
partition:
|
|
1354
|
-
partitionValues:
|
|
1362
|
+
partition: partitionValues ? this.getPrimaryPartition(partitionValues) : null,
|
|
1363
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1355
1364
|
});
|
|
1356
1365
|
});
|
|
1366
|
+
const originalDeleteMany = resource.deleteMany.bind(resource);
|
|
1367
|
+
const plugin = this;
|
|
1368
|
+
resource.deleteMany = async function(ids) {
|
|
1369
|
+
const objectsToDelete = [];
|
|
1370
|
+
if (plugin.config.includeData) {
|
|
1371
|
+
for (const id of ids) {
|
|
1372
|
+
const [ok, err, fetched] = await try_fn_default(() => resource.get(id));
|
|
1373
|
+
if (ok) {
|
|
1374
|
+
objectsToDelete.push(fetched);
|
|
1375
|
+
} else {
|
|
1376
|
+
objectsToDelete.push({ id });
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
} else {
|
|
1380
|
+
objectsToDelete.push(...ids.map((id) => ({ id })));
|
|
1381
|
+
}
|
|
1382
|
+
const result = await originalDeleteMany(ids);
|
|
1383
|
+
for (const oldData of objectsToDelete) {
|
|
1384
|
+
const partitionValues = oldData && plugin.config.includePartitions ? plugin.getPartitionValues(oldData, resource) : null;
|
|
1385
|
+
await plugin.logAudit({
|
|
1386
|
+
resourceName: resource.name,
|
|
1387
|
+
operation: "deleteMany",
|
|
1388
|
+
recordId: oldData.id,
|
|
1389
|
+
oldData: oldData && plugin.config.includeData ? JSON.stringify(plugin.truncateData(oldData)) : null,
|
|
1390
|
+
newData: null,
|
|
1391
|
+
partition: partitionValues ? plugin.getPrimaryPartition(partitionValues) : null,
|
|
1392
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
return result;
|
|
1396
|
+
};
|
|
1397
|
+
resource._originalDeleteMany = originalDeleteMany;
|
|
1357
1398
|
}
|
|
1358
1399
|
// Backward compatibility for tests
|
|
1359
1400
|
installEventListenersForResource(resource) {
|
|
@@ -1391,9 +1432,13 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1391
1432
|
}
|
|
1392
1433
|
}
|
|
1393
1434
|
getPartitionValues(data, resource) {
|
|
1394
|
-
if (!this.config.includePartitions
|
|
1435
|
+
if (!this.config.includePartitions) return null;
|
|
1436
|
+
const partitions = resource.config?.partitions || resource.partitions;
|
|
1437
|
+
if (!partitions) {
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1395
1440
|
const partitionValues = {};
|
|
1396
|
-
for (const [partitionName, partitionConfig] of Object.entries(
|
|
1441
|
+
for (const [partitionName, partitionConfig] of Object.entries(partitions)) {
|
|
1397
1442
|
const values = {};
|
|
1398
1443
|
for (const field of Object.keys(partitionConfig.fields)) {
|
|
1399
1444
|
values[field] = this.getNestedFieldValue(data, field);
|
|
@@ -1436,7 +1481,7 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1436
1481
|
}
|
|
1437
1482
|
async getAuditLogs(options = {}) {
|
|
1438
1483
|
if (!this.auditResource) return [];
|
|
1439
|
-
const { resourceName, operation, recordId, partition, startDate, endDate, limit = 100 } = options;
|
|
1484
|
+
const { resourceName, operation, recordId, partition, startDate, endDate, limit = 100, offset = 0 } = options;
|
|
1440
1485
|
let query = {};
|
|
1441
1486
|
if (resourceName) query.resourceName = resourceName;
|
|
1442
1487
|
if (operation) query.operation = operation;
|
|
@@ -1447,7 +1492,7 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1447
1492
|
if (startDate) query.timestamp.$gte = startDate;
|
|
1448
1493
|
if (endDate) query.timestamp.$lte = endDate;
|
|
1449
1494
|
}
|
|
1450
|
-
const result = await this.auditResource.page({ query, limit });
|
|
1495
|
+
const result = await this.auditResource.page({ query, limit, offset });
|
|
1451
1496
|
return result.items || [];
|
|
1452
1497
|
}
|
|
1453
1498
|
async getRecordHistory(resourceName, recordId) {
|
|
@@ -6562,7 +6607,7 @@ class S3Cache extends Cache {
|
|
|
6562
6607
|
ttl = 0,
|
|
6563
6608
|
prefix = void 0
|
|
6564
6609
|
}) {
|
|
6565
|
-
super(
|
|
6610
|
+
super();
|
|
6566
6611
|
this.client = client;
|
|
6567
6612
|
this.keyPrefix = keyPrefix;
|
|
6568
6613
|
this.config.ttl = ttl;
|
|
@@ -7243,6 +7288,26 @@ class PartitionAwareFilesystemCache extends FilesystemCache {
|
|
|
7243
7288
|
}
|
|
7244
7289
|
return super._set(key, data);
|
|
7245
7290
|
}
|
|
7291
|
+
/**
|
|
7292
|
+
* Public set method with partition support
|
|
7293
|
+
*/
|
|
7294
|
+
async set(resource, action, data, options = {}) {
|
|
7295
|
+
if (typeof resource === "string" && typeof action === "string" && options.partition) {
|
|
7296
|
+
const key = this._getPartitionCacheKey(resource, action, options.partition, options.partitionValues, options.params);
|
|
7297
|
+
return this._set(key, data, { resource, action, ...options });
|
|
7298
|
+
}
|
|
7299
|
+
return super.set(resource, action);
|
|
7300
|
+
}
|
|
7301
|
+
/**
|
|
7302
|
+
* Public get method with partition support
|
|
7303
|
+
*/
|
|
7304
|
+
async get(resource, action, options = {}) {
|
|
7305
|
+
if (typeof resource === "string" && typeof action === "string" && options.partition) {
|
|
7306
|
+
const key = this._getPartitionCacheKey(resource, action, options.partition, options.partitionValues, options.params);
|
|
7307
|
+
return this._get(key, { resource, action, ...options });
|
|
7308
|
+
}
|
|
7309
|
+
return super.get(resource);
|
|
7310
|
+
}
|
|
7246
7311
|
/**
|
|
7247
7312
|
* Enhanced get method with partition awareness
|
|
7248
7313
|
*/
|
|
@@ -10805,7 +10870,7 @@ async function handleInsert$4({ resource, data, mappedData, originalData }) {
|
|
|
10805
10870
|
if (totalSize > effectiveLimit) {
|
|
10806
10871
|
throw new Error(`S3 metadata size exceeds 2KB limit. Current size: ${totalSize} bytes, effective limit: ${effectiveLimit} bytes, absolute limit: ${S3_METADATA_LIMIT_BYTES} bytes`);
|
|
10807
10872
|
}
|
|
10808
|
-
return { mappedData, body:
|
|
10873
|
+
return { mappedData, body: "" };
|
|
10809
10874
|
}
|
|
10810
10875
|
async function handleUpdate$4({ resource, id, data, mappedData, originalData }) {
|
|
10811
10876
|
const totalSize = calculateTotalSize(mappedData);
|
|
@@ -10868,8 +10933,9 @@ async function handleInsert$3({ resource, data, mappedData, originalData }) {
|
|
|
10868
10933
|
excess: totalSize - 2047,
|
|
10869
10934
|
data: originalData || data
|
|
10870
10935
|
});
|
|
10936
|
+
return { mappedData: { _v: mappedData._v }, body: JSON.stringify(mappedData) };
|
|
10871
10937
|
}
|
|
10872
|
-
return { mappedData, body:
|
|
10938
|
+
return { mappedData, body: "" };
|
|
10873
10939
|
}
|
|
10874
10940
|
async function handleUpdate$3({ resource, id, data, mappedData, originalData }) {
|
|
10875
10941
|
const totalSize = calculateTotalSize(mappedData);
|
|
@@ -10916,6 +10982,18 @@ async function handleUpsert$3({ resource, id, data, mappedData, originalData })
|
|
|
10916
10982
|
return { mappedData, body: JSON.stringify(data) };
|
|
10917
10983
|
}
|
|
10918
10984
|
async function handleGet$3({ resource, metadata, body }) {
|
|
10985
|
+
if (body && body.trim() !== "") {
|
|
10986
|
+
try {
|
|
10987
|
+
const bodyData = JSON.parse(body);
|
|
10988
|
+
const mergedData = {
|
|
10989
|
+
...bodyData,
|
|
10990
|
+
...metadata
|
|
10991
|
+
};
|
|
10992
|
+
return { metadata: mergedData, body };
|
|
10993
|
+
} catch (error) {
|
|
10994
|
+
return { metadata, body };
|
|
10995
|
+
}
|
|
10996
|
+
}
|
|
10919
10997
|
return { metadata, body };
|
|
10920
10998
|
}
|
|
10921
10999
|
|
|
@@ -10983,7 +11061,7 @@ async function handleInsert$2({ resource, data, mappedData, originalData }) {
|
|
|
10983
11061
|
if (truncated) {
|
|
10984
11062
|
resultFields[TRUNCATED_FLAG] = TRUNCATED_FLAG_VALUE;
|
|
10985
11063
|
}
|
|
10986
|
-
return { mappedData: resultFields, body:
|
|
11064
|
+
return { mappedData: resultFields, body: "" };
|
|
10987
11065
|
}
|
|
10988
11066
|
async function handleUpdate$2({ resource, id, data, mappedData, originalData }) {
|
|
10989
11067
|
return handleInsert$2({ resource, data, mappedData, originalData });
|
|
@@ -11073,7 +11151,6 @@ async function handleInsert$1({ resource, data, mappedData, originalData }) {
|
|
|
11073
11151
|
}
|
|
11074
11152
|
const hasOverflow = Object.keys(bodyFields).length > 0;
|
|
11075
11153
|
let body = hasOverflow ? JSON.stringify(bodyFields) : "";
|
|
11076
|
-
if (!hasOverflow) body = "{}";
|
|
11077
11154
|
return { mappedData: metadataFields, body };
|
|
11078
11155
|
}
|
|
11079
11156
|
async function handleUpdate$1({ resource, id, data, mappedData, originalData }) {
|
|
@@ -11725,16 +11802,16 @@ ${errorDetails}`,
|
|
|
11725
11802
|
* email: 'john@example.com'
|
|
11726
11803
|
* });
|
|
11727
11804
|
*/
|
|
11728
|
-
async insert({ id, ...attributes }) {
|
|
11729
|
-
const exists = await this.exists(id);
|
|
11730
|
-
if (exists) throw new Error(`Resource with id '${id}' already exists`);
|
|
11731
|
-
this.getResourceKey(id || "(auto)");
|
|
11805
|
+
async insert({ id: id$1, ...attributes }) {
|
|
11806
|
+
const exists = await this.exists(id$1);
|
|
11807
|
+
if (exists) throw new Error(`Resource with id '${id$1}' already exists`);
|
|
11808
|
+
this.getResourceKey(id$1 || "(auto)");
|
|
11732
11809
|
if (this.options.timestamps) {
|
|
11733
11810
|
attributes.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11734
11811
|
attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11735
11812
|
}
|
|
11736
11813
|
const attributesWithDefaults = this.applyDefaults(attributes);
|
|
11737
|
-
const completeData = { id, ...attributesWithDefaults };
|
|
11814
|
+
const completeData = { id: id$1, ...attributesWithDefaults };
|
|
11738
11815
|
const preProcessedData = await this.executeHooks("beforeInsert", completeData);
|
|
11739
11816
|
const extraProps = Object.keys(preProcessedData).filter(
|
|
11740
11817
|
(k) => !(k in completeData) || preProcessedData[k] !== completeData[k]
|
|
@@ -11758,7 +11835,14 @@ ${errorDetails}`,
|
|
|
11758
11835
|
}
|
|
11759
11836
|
const { id: validatedId, ...validatedAttributes } = validated;
|
|
11760
11837
|
Object.assign(validatedAttributes, extraData);
|
|
11761
|
-
|
|
11838
|
+
let finalId = validatedId || id$1;
|
|
11839
|
+
if (!finalId) {
|
|
11840
|
+
finalId = this.idGenerator();
|
|
11841
|
+
if (!finalId || finalId.trim() === "") {
|
|
11842
|
+
const { idGenerator } = await Promise.resolve().then(function () { return id; });
|
|
11843
|
+
finalId = idGenerator();
|
|
11844
|
+
}
|
|
11845
|
+
}
|
|
11762
11846
|
const mappedData = await this.schema.mapper(validatedAttributes);
|
|
11763
11847
|
mappedData._v = String(this.version);
|
|
11764
11848
|
const behaviorImpl = getBehavior(this.behavior);
|
|
@@ -11803,26 +11887,11 @@ ${errorDetails}`,
|
|
|
11803
11887
|
errPut.excess = excess;
|
|
11804
11888
|
throw new ResourceError("metadata headers exceed", { resourceName: this.name, operation: "insert", id: finalId, totalSize, effectiveLimit, excess, suggestion: "Reduce metadata size or number of fields." });
|
|
11805
11889
|
}
|
|
11806
|
-
throw
|
|
11807
|
-
bucket: this.client.config.bucket,
|
|
11808
|
-
key,
|
|
11809
|
-
resourceName: this.name,
|
|
11810
|
-
operation: "insert",
|
|
11811
|
-
id: finalId
|
|
11812
|
-
});
|
|
11890
|
+
throw errPut;
|
|
11813
11891
|
}
|
|
11814
|
-
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
body,
|
|
11818
|
-
behavior: this.behavior
|
|
11819
|
-
});
|
|
11820
|
-
const finalResult = await this.executeHooks("afterInsert", insertedData);
|
|
11821
|
-
this.emit("insert", {
|
|
11822
|
-
...insertedData,
|
|
11823
|
-
$before: { ...completeData },
|
|
11824
|
-
$after: { ...finalResult }
|
|
11825
|
-
});
|
|
11892
|
+
const insertedObject = await this.get(finalId);
|
|
11893
|
+
const finalResult = await this.executeHooks("afterInsert", insertedObject);
|
|
11894
|
+
this.emit("insert", finalResult);
|
|
11826
11895
|
return finalResult;
|
|
11827
11896
|
}
|
|
11828
11897
|
/**
|
|
@@ -11831,7 +11900,7 @@ ${errorDetails}`,
|
|
|
11831
11900
|
* @returns {Promise<Object>} The resource object with all attributes and metadata
|
|
11832
11901
|
* @example
|
|
11833
11902
|
* const user = await resource.get('user-123');
|
|
11834
|
-
|
|
11903
|
+
*/
|
|
11835
11904
|
async get(id) {
|
|
11836
11905
|
if (lodashEs.isObject(id)) throw new Error(`id cannot be an object`);
|
|
11837
11906
|
if (lodashEs.isEmpty(id)) throw new Error("id cannot be empty");
|
|
@@ -11846,17 +11915,6 @@ ${errorDetails}`,
|
|
|
11846
11915
|
id
|
|
11847
11916
|
});
|
|
11848
11917
|
}
|
|
11849
|
-
if (request.ContentLength === 0) {
|
|
11850
|
-
const noContentErr = new Error(`No such key: ${key} [bucket:${this.client.config.bucket}]`);
|
|
11851
|
-
noContentErr.name = "NoSuchKey";
|
|
11852
|
-
throw mapAwsError(noContentErr, {
|
|
11853
|
-
bucket: this.client.config.bucket,
|
|
11854
|
-
key,
|
|
11855
|
-
resourceName: this.name,
|
|
11856
|
-
operation: "get",
|
|
11857
|
-
id
|
|
11858
|
-
});
|
|
11859
|
-
}
|
|
11860
11918
|
const objectVersionRaw = request.Metadata?._v || this.version;
|
|
11861
11919
|
const objectVersion = typeof objectVersionRaw === "string" && objectVersionRaw.startsWith("v") ? objectVersionRaw.slice(1) : objectVersionRaw;
|
|
11862
11920
|
const schema = await this.getSchemaForVersion(objectVersion);
|
|
@@ -13195,6 +13253,18 @@ ${errorDetails}`,
|
|
|
13195
13253
|
});
|
|
13196
13254
|
return result2;
|
|
13197
13255
|
}
|
|
13256
|
+
if (behavior === "user-managed" && body && body.trim() !== "") {
|
|
13257
|
+
const [okBody, errBody, parsedBody] = await try_fn_default(() => Promise.resolve(JSON.parse(body)));
|
|
13258
|
+
if (okBody) {
|
|
13259
|
+
const [okUnmap, errUnmap, unmappedBody] = await try_fn_default(() => this.schema.unmapper(parsedBody));
|
|
13260
|
+
const bodyData = okUnmap ? unmappedBody : {};
|
|
13261
|
+
const merged = { ...bodyData, ...unmappedMetadata, id };
|
|
13262
|
+
Object.keys(merged).forEach((k) => {
|
|
13263
|
+
merged[k] = fixValue(merged[k]);
|
|
13264
|
+
});
|
|
13265
|
+
return filterInternalFields(merged);
|
|
13266
|
+
}
|
|
13267
|
+
}
|
|
13198
13268
|
const result = { ...unmappedMetadata, id };
|
|
13199
13269
|
Object.keys(result).forEach((k) => {
|
|
13200
13270
|
result[k] = fixValue(result[k]);
|
|
@@ -13437,7 +13507,7 @@ class Database extends EventEmitter {
|
|
|
13437
13507
|
this.id = idGenerator(7);
|
|
13438
13508
|
this.version = "1";
|
|
13439
13509
|
this.s3dbVersion = (() => {
|
|
13440
|
-
const [ok, err, version] = try_fn_default(() => true ? "8.1.
|
|
13510
|
+
const [ok, err, version] = try_fn_default(() => true ? "8.1.1" : "latest");
|
|
13441
13511
|
return ok ? version : "latest";
|
|
13442
13512
|
})();
|
|
13443
13513
|
this.resources = {};
|