offline-cloudinary 2.0.0 → 2.1.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.
- package/CHANGELOG.md +25 -0
- package/index.js +6 -1
- package/package.json +1 -1
- package/src/utils/offline-cloudinary.js +35 -12
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.1.0] - 2025-12-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **In-memory caching system**: File mappings are now cached in memory for dramatically faster operations
|
|
13
|
+
- **Dirty flag tracking**: Efficient cache invalidation system that only syncs to disk when changes occur
|
|
14
|
+
- **Automatic periodic sync**: Background task (500ms intervals) automatically persists cache changes to disk
|
|
15
|
+
- **Graceful shutdown**: Process cleanup handler that ensures all in-memory changes are synced to disk on exit
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- `upload()` and `destroy()` operations now use in-memory cache for improved performance
|
|
20
|
+
- `initialise()` method now handles lazy initialization and cache management
|
|
21
|
+
- Improved resource management with automatic interval cleanup
|
|
22
|
+
|
|
23
|
+
### Performance
|
|
24
|
+
|
|
25
|
+
- Significantly reduced disk I/O operations
|
|
26
|
+
- Faster file operations through in-memory lookups
|
|
27
|
+
- Minimal overhead with periodic batch writes to disk
|
|
28
|
+
|
|
29
|
+
### Fully Backward Compatible
|
|
30
|
+
|
|
31
|
+
All v2.0.0 APIs remain unchanged. The caching system is transparent to end users.
|
|
32
|
+
|
|
8
33
|
## [2.0.0] - 2025-12-15
|
|
9
34
|
|
|
10
35
|
### Breaking Changes
|
package/index.js
CHANGED
|
@@ -13,8 +13,13 @@ const startEmulator = () => {
|
|
|
13
13
|
const portNumber = process.env.CLOUDINARY_OFFLINE_PORT;
|
|
14
14
|
if (!portNumber)
|
|
15
15
|
throw new Error("Please set CLOUDINARY_OFFLINE_PORT in your .env file");
|
|
16
|
-
app.listen(portNumber, () =>
|
|
16
|
+
app.listen(portNumber, () =>{
|
|
17
17
|
console.log("Offline Cloudinary running on port", portNumber)
|
|
18
|
+
process.on("SIGINT", async()=>{
|
|
19
|
+
if (offlineCloudinary.syncActive) clearInterval(offlineCloudinary.syncActive)
|
|
20
|
+
await offlineCloudinary.syncToDisk()
|
|
21
|
+
})
|
|
22
|
+
}
|
|
18
23
|
);
|
|
19
24
|
};
|
|
20
25
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "offline-cloudinary",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "An offline Cloudinary-like file manager with HTTP server emulator for local uploads, deletions, and testing without internet access.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,6 +8,29 @@ class OfflineCloudinary {
|
|
|
8
8
|
throw new Error("Please set CLOUDINARY_OFFLINE_PATH in your .env file");
|
|
9
9
|
}
|
|
10
10
|
this.rootPath = process.env.CLOUDINARY_OFFLINE_PATH;
|
|
11
|
+
this.initialised = false;
|
|
12
|
+
this.mappingsInMemory = { isDirty: false };
|
|
13
|
+
this.syncActive = false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async initialise() {
|
|
17
|
+
if (this.initialised) return;
|
|
18
|
+
const filePath = path.join(this.rootPath, "uploads.json");
|
|
19
|
+
await fs.access(filePath).catch(() => fs.writeFile(filePath, "{}"));
|
|
20
|
+
const data = await fs.readFile(filePath, "utf-8");
|
|
21
|
+
this.mappingsInMemory = { ...JSON.parse(data), isDirty: false };
|
|
22
|
+
this.initialised = true;
|
|
23
|
+
this.syncActive = setInterval(() => this.syncToDisk(), 500);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async syncToDisk() {
|
|
27
|
+
if (!this.mappingsInMemory.isDirty) return;
|
|
28
|
+
const mappingsCopy = { ...this.mappingsInMemory, isDirty: false };
|
|
29
|
+
await fs.writeFile(
|
|
30
|
+
path.join(this.rootPath, "uploads.json"),
|
|
31
|
+
JSON.stringify(mappingsCopy)
|
|
32
|
+
);
|
|
33
|
+
this.mappingsInMemory.isDirty = false;
|
|
11
34
|
}
|
|
12
35
|
|
|
13
36
|
/**
|
|
@@ -17,9 +40,8 @@ class OfflineCloudinary {
|
|
|
17
40
|
* @returns Cloudinary-like response
|
|
18
41
|
*/
|
|
19
42
|
async upload(tempFilePath, options = {}) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
throw new Error("Please set CLOUDINARY_OFFLINE_PORT in your .env file");
|
|
43
|
+
await this.initialise();
|
|
44
|
+
const portNumber = process.env.CLOUDINARY_OFFLINE_PORT || 3500;
|
|
23
45
|
await fs.access(tempFilePath).catch(() => {
|
|
24
46
|
throw new Error(`File not found: ${tempFilePath}`);
|
|
25
47
|
});
|
|
@@ -47,12 +69,8 @@ class OfflineCloudinary {
|
|
|
47
69
|
|
|
48
70
|
const uploadId = crypto.randomUUID();
|
|
49
71
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const mappings = JSON.parse(data);
|
|
53
|
-
mappings[uploadId] = finalPath;
|
|
54
|
-
|
|
55
|
-
await fs.writeFile("uploads.json", JSON.stringify(mappings));
|
|
72
|
+
this.mappingsInMemory[uploadId] = finalPath;
|
|
73
|
+
this.mappingsInMemory.isDirty = true;
|
|
56
74
|
|
|
57
75
|
// Return Cloudinary-like response
|
|
58
76
|
return {
|
|
@@ -83,10 +101,14 @@ class OfflineCloudinary {
|
|
|
83
101
|
* @returns {object} { result: "ok" } if deleted or { result: "not found" }
|
|
84
102
|
*/
|
|
85
103
|
async destroy(public_id) {
|
|
104
|
+
await this.initialise();
|
|
86
105
|
const uploadId = public_id;
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
106
|
+
const filePath = this.mappingsInMemory[uploadId];
|
|
107
|
+
if (filePath) {
|
|
108
|
+
await fs.unlink(filePath);
|
|
109
|
+
delete this.mappingsInMemory[uploadId];
|
|
110
|
+
this.mappingsInMemory.isDirty = true;
|
|
111
|
+
}
|
|
90
112
|
return { result: "ok" };
|
|
91
113
|
}
|
|
92
114
|
|
|
@@ -97,6 +119,7 @@ class OfflineCloudinary {
|
|
|
97
119
|
async clearStorage() {
|
|
98
120
|
await fs.rm(this.rootPath, { recursive: true, force: true });
|
|
99
121
|
await fs.mkdir(this.rootPath);
|
|
122
|
+
this.mappingsInMemory = { isDirty: false };
|
|
100
123
|
return { result: "ok" };
|
|
101
124
|
}
|
|
102
125
|
}
|