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 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
- | `driver` | object | `null` | Custom cache driver instance |
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, MemoryCache, S3Cache, FilesystemCache } from 's3db.js';
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 global settings
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
- const s3dbWithCustomCache = new S3db({
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: customCache, // Custom driver instance
252
- includePartitions: true
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, 1800000); // 30 minutes
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
- const stats = users.cache.stats();
277
- console.log('Cache hit rate:', stats.hitRate);
278
- console.log('Total hits:', stats.hits);
279
- console.log('Total misses:', stats.misses);
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: this.config.includePartitions && this.getPartitionValues(data, resource) ? this.getPrimaryPartition(this.getPartitionValues(data, resource)) : null,
1322
- partitionValues: this.config.includePartitions && this.getPartitionValues(data, resource) ? JSON.stringify(this.getPartitionValues(data, resource)) : null
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: this.config.includePartitions && this.getPartitionValues(data, resource) ? this.getPrimaryPartition(this.getPartitionValues(data, resource)) : null,
1338
- partitionValues: this.config.includePartitions && this.getPartitionValues(data, resource) ? JSON.stringify(this.getPartitionValues(data, resource)) : null
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: oldData && this.config.includePartitions && this.getPartitionValues(oldData, resource) ? this.getPrimaryPartition(this.getPartitionValues(oldData, resource)) : null,
1354
- partitionValues: oldData && this.config.includePartitions && this.getPartitionValues(oldData, resource) ? JSON.stringify(this.getPartitionValues(oldData, resource)) : null
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 || !resource.partitions) return null;
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(resource.partitions)) {
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({ client, keyPrefix, ttl, prefix });
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: JSON.stringify(mappedData) };
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: JSON.stringify(data) };
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: JSON.stringify(mappedData) };
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
- const finalId = validatedId || id || this.idGenerator();
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 mapAwsError(errPut, {
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
- let insertedData = await this.composeFullObjectFromWrite({
11815
- id: finalId,
11816
- metadata: finalMetadata,
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.0" : "latest");
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 = {};