@frogfish/k2db 2.0.3 → 2.0.6

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/dist/db.d.ts CHANGED
@@ -47,6 +47,9 @@ export interface DropResult {
47
47
  export interface PurgeResult {
48
48
  id: string;
49
49
  }
50
+ export interface PurgeManyResult {
51
+ purged: number;
52
+ }
50
53
  export interface VersionedUpdateResult {
51
54
  updated: number;
52
55
  versionSaved: number;
@@ -150,6 +153,14 @@ export declare class K2DB {
150
153
  * @param id - UUID of the document.
151
154
  */
152
155
  purge(collectionName: string, id: string): Promise<PurgeResult>;
156
+ /**
157
+ * Permanently deletes all documents that are soft-deleted and whose _updated
158
+ * timestamp is older than the provided threshold (in milliseconds ago).
159
+ * @param collectionName - Name of the collection.
160
+ * @param olderThanMs - Age threshold in milliseconds; documents with
161
+ * `_updated <= (Date.now() - olderThanMs)` will be purged.
162
+ */
163
+ purgeDeletedOlderThan(collectionName: string, olderThanMs: number): Promise<PurgeManyResult>;
153
164
  /**
154
165
  * Restores a soft-deleted document.
155
166
  * @param collectionName - Name of the collection.
package/dist/db.js CHANGED
@@ -398,9 +398,19 @@ export class K2DB {
398
398
  this.validateCollectionName(collectionName);
399
399
  const collection = await this.getCollection(collectionName);
400
400
  debug(`Updating ${collectionName} with criteria: ${JSON.stringify(criteria)}`);
401
+ // Preserve intent to set _deleted during internal soft-delete operations.
402
+ // stripReservedFields removes underscore-prefixed keys (by design) to protect
403
+ // internal fields from user updates. However, deleteAll() legitimately passes
404
+ // {_deleted: true}. Capture and restore it here.
405
+ const deletedFlag = Object.prototype.hasOwnProperty.call(values, "_deleted")
406
+ ? values._deleted
407
+ : undefined;
401
408
  values = K2DB.stripReservedFields(values);
402
409
  values = this.applySchema(collectionName, values, /*partial*/ true);
403
410
  values._updated = Date.now();
411
+ if (deletedFlag !== undefined) {
412
+ values._deleted = deletedFlag;
413
+ }
404
414
  criteria = {
405
415
  ...criteria,
406
416
  _deleted: { $ne: true },
@@ -507,6 +517,10 @@ export class K2DB {
507
517
  }
508
518
  }
509
519
  catch (err) {
520
+ // Preserve existing K2Error classifications (e.g., NOT_FOUND)
521
+ if (err instanceof K2Error) {
522
+ throw err;
523
+ }
510
524
  throw new K2Error(ServiceError.SYSTEM_ERROR, "Error removing object from collection", "sys_mdb_remove_upd", this.normalizeError(err));
511
525
  }
512
526
  }
@@ -535,6 +549,31 @@ export class K2DB {
535
549
  throw new K2Error(ServiceError.SYSTEM_ERROR, `Error purging item with id: ${id}`, "sys_mdb_pg", this.normalizeError(err));
536
550
  }
537
551
  }
552
+ /**
553
+ * Permanently deletes all documents that are soft-deleted and whose _updated
554
+ * timestamp is older than the provided threshold (in milliseconds ago).
555
+ * @param collectionName - Name of the collection.
556
+ * @param olderThanMs - Age threshold in milliseconds; documents with
557
+ * `_updated <= (Date.now() - olderThanMs)` will be purged.
558
+ */
559
+ async purgeDeletedOlderThan(collectionName, olderThanMs) {
560
+ this.validateCollectionName(collectionName);
561
+ if (typeof olderThanMs !== 'number' || !isFinite(olderThanMs) || olderThanMs < 0) {
562
+ throw new K2Error(ServiceError.BAD_REQUEST, 'olderThanMs must be a non-negative number', 'sys_mdb_purge_older_invalid');
563
+ }
564
+ const collection = await this.getCollection(collectionName);
565
+ const cutoff = Date.now() - olderThanMs;
566
+ try {
567
+ const res = await this.runTimed('deleteMany', { collectionName, olderThanMs, cutoff }, async () => await collection.deleteMany({
568
+ _deleted: true,
569
+ _updated: { $lte: cutoff },
570
+ }));
571
+ return { purged: res.deletedCount ?? 0 };
572
+ }
573
+ catch (err) {
574
+ throw new K2Error(ServiceError.SYSTEM_ERROR, 'Error purging deleted items by age', 'sys_mdb_purge_older', this.normalizeError(err));
575
+ }
576
+ }
538
577
  /**
539
578
  * Restores a soft-deleted document.
540
579
  * @param collectionName - Name of the collection.
@@ -545,7 +584,8 @@ export class K2DB {
545
584
  const query = { ...(criteria || {}), _deleted: true };
546
585
  try {
547
586
  const res = await this.runTimed("updateMany", { collectionName, query }, async () => await collection.updateMany(query, {
548
- $set: { _deleted: false },
587
+ // Restoring is a data change: flip _deleted and bump _updated
588
+ $set: { _deleted: false, _updated: Date.now() },
549
589
  }));
550
590
  return { status: "restored", modified: res.modifiedCount };
551
591
  }
@@ -656,7 +696,9 @@ export class K2DB {
656
696
  const { uuidUnique = false, uuidPartialUnique = true, ownerIndex = true, deletedIndex = true, } = opts;
657
697
  const collection = await this.getCollection(collectionName);
658
698
  if (uuidPartialUnique) {
659
- await collection.createIndex({ _uuid: 1 }, { unique: true, partialFilterExpression: { _deleted: { $ne: true } } });
699
+ // Use a compound unique index to ensure at most one non-deleted document per _uuid
700
+ // without relying on partialFilterExpression (which may be limited in some environments).
701
+ await collection.createIndex({ _uuid: 1, _deleted: 1 }, { unique: true });
660
702
  }
661
703
  else if (uuidUnique) {
662
704
  await collection.createIndex({ _uuid: 1 }, { unique: true });
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frogfish/k2db",
3
- "version": "2.0.3",
3
+ "version": "2.0.6",
4
4
  "description": "A data handling library for K2 applications.",
5
5
  "type": "module",
6
6
  "main": "data.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frogfish/k2db",
3
- "version": "2.0.3",
3
+ "version": "2.0.6",
4
4
  "description": "A data handling library for K2 applications.",
5
5
  "main": "./dist/data.js",
6
6
  "types": "./dist/data.d.ts",