Skip to content

Commit 83f4645

Browse files
committed
add wrap option
1 parent ac47170 commit 83f4645

3 files changed

Lines changed: 284 additions & 28 deletions

File tree

README.md

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ Creates a cleanup function to wrap existing values and remove expired ones.
4848
- `originalStorage` (Storage): The storage object (localStorage or sessionStorage)
4949
- `options` (object, optional):
5050
- `expiresInSeconds` (number, optional): Time in seconds after which stored items expire
51-
- `runWhenBrowserIsIdle` (boolean, optional): Whether to run cleanup when browser is idle (default: false)
51+
- `runWhenBrowserIsIdle` (boolean, optional): Whether to run cleanup when browser is idle (default: true)
52+
- `wrapUnwrappedItems` (boolean, optional): Whether to wrap non-wrapped items with expiration metadata (default: false)
5253

5354
**Returns:** An object with a `runCleanup()` method
5455

@@ -57,28 +58,57 @@ Creates a cleanup function to wrap existing values and remove expired ones.
5758
```javascript
5859
import { cleanupFactory } from "expirix";
5960

60-
// Create cleanup that runs when browser is idle
61+
// Create cleanup that runs when browser is idle (default behavior)
6162
const cleanup = cleanupFactory(localStorage, {
6263
expiresInSeconds: 3600, // 1 hour
63-
runWhenBrowserIsIdle: true,
6464
});
6565

6666
// Run cleanup (will use requestIdleCallback if available, setTimeout as fallback)
6767
cleanup.runCleanup();
68+
69+
// To run cleanup immediately instead:
70+
const immediateCleanup = cleanupFactory(localStorage, {
71+
expiresInSeconds: 3600,
72+
runWhenBrowserIsIdle: false,
73+
});
6874
```
6975

76+
## Wrapping Behavior Control
77+
78+
By default, cleanup operations only remove expired items but do not modify existing unwrapped values. You can control whether cleanup should wrap existing unwrapped items with the `wrapUnwrappedItems` option.
79+
80+
```javascript
81+
// Only clean up expired items, leave unwrapped items as-is (default)
82+
const cleanupOnly = cleanupFactory(localStorage, {
83+
expiresInSeconds: 3600,
84+
});
85+
86+
// Clean up expired items AND wrap unwrapped items
87+
const cleanupAndWrap = cleanupFactory(localStorage, {
88+
expiresInSeconds: 3600,
89+
wrapUnwrappedItems: true,
90+
});
91+
```
92+
93+
This gives you fine-grained control over when existing data gets wrapped with expiration metadata.
94+
7095
## Browser Idle Cleanup
7196

72-
The library includes support for running cleanup operations when the browser is idle, using the `requestIdleCallback` API when available, with a simple polyfill fallback for older browsers.
97+
By default, the library runs cleanup operations when the browser is idle, using the `requestIdleCallback` API when available, with a simple polyfill fallback for older browsers.
7398

7499
```javascript
75-
// Cleanup will run when browser is idle
100+
// Cleanup will run when browser is idle (default behavior)
76101
const idleCleanup = cleanupFactory(localStorage, {
77102
expiresInSeconds: 3600,
78-
runWhenBrowserIsIdle: true,
79103
});
80104

81105
idleCleanup.runCleanup(); // Schedules cleanup for when browser is idle
106+
107+
// To run cleanup immediately instead:
108+
const immediateCleanup = cleanupFactory(localStorage, {
109+
expiresInSeconds: 3600,
110+
runWhenBrowserIsIdle: false,
111+
});
82112
```
83113

84114
**requestIdleCallback Polyfill:**

src/cleanup.mjs

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,31 +35,29 @@ function getRequestIdleCallback() {
3535

3636
/**
3737
* @param {Storage} originalStorage - The storage object (localStorage or sessionStorage)
38-
* @param {{ expiresInSeconds?: number, runWhenBrowserIsIdle?: boolean }} [options={}] - Configuration options
38+
* @param {{ expiresInSeconds?: number, runWhenBrowserIsIdle?: boolean, wrapUnwrappedItems?: boolean }} [options={}] - Configuration options
3939
* @returns {{ runCleanup: () => void }}
4040
*/
4141
export function cleanupFactory(
4242
originalStorage,
43-
{ expiresInSeconds, runWhenBrowserIsIdle = false } = {}
43+
{
44+
expiresInSeconds,
45+
runWhenBrowserIsIdle = true,
46+
wrapUnwrappedItems = false,
47+
} = {}
4448
) {
4549
/**
4650
* @returns {void}
4751
*/
4852
function runCleanup() {
4953
const actualCleanup = () => {
50-
// Collect all keys first to avoid issues with storage mutation during iteration
51-
const keys = [];
52-
for (let i = 0; i < originalStorage.length; i++) {
54+
// Iterate backwards through storage to handle removals safely in a single pass
55+
for (let i = originalStorage.length - 1; i >= 0; i--) {
5356
const key = originalStorage.key(i);
54-
if (key !== null) {
55-
keys.push(key);
56-
}
57-
}
57+
if (key === null) continue;
5858

59-
// Process each key
60-
keys.forEach((key) => {
6159
const value = originalStorage.getItem(key);
62-
if (!value) return;
60+
if (!value) continue;
6361

6462
try {
6563
const parsed = JSON.parse(value);
@@ -70,21 +68,24 @@ export function cleanupFactory(
7068
originalStorage.removeItem(key);
7169
}
7270
// If not expired, leave it as is
73-
} else {
74-
// Not a wrapped value yet, wrap it
71+
} else if (wrapUnwrappedItems) {
72+
// Not a wrapped value yet, wrap it only if wrapUnwrappedItems is true
7573
originalStorage.setItem(
7674
key,
7775
JSON.stringify(createWrappedItem(value, expiresInSeconds))
7876
);
7977
}
78+
// If wrapUnwrappedItems is false, leave unwrapped items as-is
8079
} catch (e) {
81-
// Handle non-JSON values - treat as plain string and wrap
82-
originalStorage.setItem(
83-
key,
84-
JSON.stringify(createWrappedItem(value, expiresInSeconds))
85-
);
80+
// Handle non-JSON values - treat as plain string and wrap only if wrapUnwrappedItems is true
81+
if (wrapUnwrappedItems) {
82+
originalStorage.setItem(
83+
key,
84+
JSON.stringify(createWrappedItem(value, expiresInSeconds))
85+
);
86+
}
8687
}
87-
});
88+
}
8889
};
8990

9091
if (runWhenBrowserIsIdle) {

0 commit comments

Comments
 (0)