@heyputer/puter.js 2.0.13 → 2.0.15

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/index.d.ts CHANGED
@@ -32,11 +32,19 @@ declare class Puter {
32
32
 
33
33
  // AI Module
34
34
  interface AI {
35
- chat(prompt: string, options?: ChatOptions): Promise<ChatResponse>;
36
- chat(prompt: string, testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
37
- chat(prompt: string, imageURL?: string, testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
38
- chat(prompt: string, imageURLArray?: string[], testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
39
- chat(messages: ChatMessage[], testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
35
+ // Streaming overloads
36
+ chat(prompt: string, options: StreamingChatOptions): AsyncIterable<ChatResponseChunk>;
37
+ chat(prompt: string, testMode: boolean, options: StreamingChatOptions): AsyncIterable<ChatResponseChunk>;
38
+ chat(prompt: string, imageURL: string, testMode: boolean, options: StreamingChatOptions): AsyncIterable<ChatResponseChunk>;
39
+ chat(prompt: string, imageURLArray: string[], testMode: boolean, options: StreamingChatOptions): AsyncIterable<ChatResponseChunk>;
40
+ chat(messages: ChatMessage[], testMode: boolean, options: StreamingChatOptions): AsyncIterable<ChatResponseChunk>;
41
+
42
+ // Non-streaming overloads
43
+ chat(prompt: string, options?: NonStreamingChatOptions): Promise<ChatResponse>;
44
+ chat(prompt: string, testMode?: boolean, options?: NonStreamingChatOptions): Promise<ChatResponse>;
45
+ chat(prompt: string, imageURL?: string, testMode?: boolean, options?: NonStreamingChatOptions): Promise<ChatResponse>;
46
+ chat(prompt: string, imageURLArray?: string[], testMode?: boolean, options?: NonStreamingChatOptions): Promise<ChatResponse>;
47
+ chat(messages: ChatMessage[], testMode?: boolean, options?: NonStreamingChatOptions): Promise<ChatResponse>;
40
48
 
41
49
  img2txt(image: string | File | Blob, testMode?: boolean): Promise<string>;
42
50
 
@@ -50,6 +58,9 @@ interface AI {
50
58
  txt2speech(text: string, language?: string, voice?: string, engine?: string): Promise<HTMLAudioElement>;
51
59
  }
52
60
 
61
+ type StreamingChatOptions = Omit<ChatOptions, "stream"> & { stream: true };
62
+ type NonStreamingChatOptions = Omit<ChatOptions, "stream"> & { stream?: false | undefined };
63
+
53
64
  interface ChatOptions {
54
65
  model?: string;
55
66
  stream?: boolean;
@@ -109,6 +120,11 @@ interface Txt2SpeechOptions {
109
120
  engine?: 'standard' | 'neural' | 'generative';
110
121
  }
111
122
 
123
+ interface ChatResponseChunk {
124
+ text?: string;
125
+ [key: string]: any;
126
+ }
127
+
112
128
  // Apps Module
113
129
  interface Apps {
114
130
  create(name: string, indexURL: string): Promise<App>;
@@ -281,7 +297,9 @@ interface KV {
281
297
  set(key: string, value: string | number | boolean | object | any[]): Promise<boolean>;
282
298
  get(key: string): Promise<any>;
283
299
  del(key: string): Promise<boolean>;
300
+ incr(key: string, pathAndAmount: { [key: string]: number }): Promise<number>;
284
301
  incr(key: string, amount?: number): Promise<number>;
302
+ decr(key: string, pathAndAmount: { [key: string]: number }): Promise<number>;
285
303
  decr(key: string, amount?: number): Promise<number>;
286
304
  list(pattern?: string, returnValues?: boolean): Promise<string[] | KVPair[]>;
287
305
  list(returnValues?: boolean): Promise<string[] | KVPair[]>;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@heyputer/puter.js",
3
- "version": "2.0.13",
3
+ "version": "2.0.15",
4
4
  "description": "Puter.js - A JavaScript library for interacting with Puter services.",
5
5
  "main": "src/index.js",
6
6
  "types": "index.d.ts",
7
7
  "typings": "index.d.ts",
8
8
  "type": "module",
9
9
  "files": [
10
- "dist/puter.js",
10
+ "dist/puter.cjs",
11
11
  "src/",
12
12
  "index.d.ts"
13
13
  ],
@@ -25,18 +25,21 @@
25
25
  ],
26
26
  "scripts": {
27
27
  "start-server": "npx http-server --cors -c-1",
28
- "start-webpack": "webpack && webpack --output-filename puter.dev.js --watch --devtool source-map",
28
+ "start-webpack": "webpack --stats=errors-only && webpack --output-filename puter.dev.js --watch --devtool source-map --stats=errors-only",
29
29
  "start": "concurrently \"npm run start-server\" \"npm run start-webpack\"",
30
- "build": "webpack && { echo \"// Copyright 2024-present Puter Technologies Inc. All rights reserved.\"; echo \"// Generated on $(date '+%Y-%m-%d %H:%M')\n\"; cat ./dist/puter.js; } > temp && mv temp ./dist/puter.js"
30
+ "test": "npx http-server --cors -c-1 -o /test",
31
+ "build": "webpack && { echo \"// Copyright 2024-present Puter Technologies Inc. All rights reserved.\"; echo \"// Generated on $(date '+%Y-%m-%d %H:%M')\n\"; cat ./dist/puter.js; } > temp && mv temp ./dist/puter.js",
32
+ "prepublishOnly": "npm run build && mv dist/puter.js dist/puter.cjs && npm version patch"
31
33
  },
32
34
  "author": "Puter Technologies Inc.",
33
35
  "license": "Apache-2.0",
34
36
  "devDependencies": {
35
37
  "concurrently": "^8.2.2",
38
+ "http-server": "^14.1.1",
36
39
  "webpack-cli": "^5.1.4"
37
40
  },
38
41
  "dependencies": {
39
- "@heyputer/kv.js": "^0.1.92",
42
+ "@heyputer/kv.js": "^0.2.1",
40
43
  "@heyputer/putility": "^1.0.3"
41
44
  }
42
- }
45
+ }
package/src/index.js CHANGED
@@ -130,7 +130,8 @@ const puterInit = (function() {
130
130
  constructor() {
131
131
 
132
132
  // Initialize the cache using kv.js
133
- this._cache = new kvjs();
133
+ this._cache = new kvjs({dbName: 'puter_cache'});
134
+ this._opscache = new kvjs();
134
135
 
135
136
  // "modules" in puter.js are external interfaces for the developer
136
137
  this.modules_ = [];
@@ -493,10 +494,21 @@ const puterInit = (function() {
493
494
  console.error('Error accessing localStorage:', error);
494
495
  }
495
496
  }
497
+ // initialize loop for updating caches for major directories
498
+ if(this.env === 'gui'){
499
+ // check and update gui fs cache regularly
500
+ setInterval(puter.checkAndUpdateGUIFScache, 10000);
501
+ }
496
502
  // reinitialize submodules
497
503
  this.updateSubmodules();
504
+
498
505
  // rao
499
506
  this.request_rao_();
507
+
508
+ // perform whoami and cache results
509
+ puter.getUser().then((user) => {
510
+ this.whoami = user;
511
+ });
500
512
  };
501
513
 
502
514
  setAPIOrigin = function(APIOrigin) {
@@ -666,7 +678,12 @@ const puterInit = (function() {
666
678
  // If we went from online to offline, purge the cache
667
679
  if ( wasOnline && !isOnline ) {
668
680
  console.log('Network connection lost - purging cache');
669
- this.purgeCache();
681
+ try {
682
+ this._cache.flushall();
683
+ console.log('Cache purged successfully');
684
+ } catch( error ) {
685
+ console.error('Error purging cache:', error);
686
+ }
670
687
  }
671
688
 
672
689
  // Update the previous state
@@ -688,22 +705,48 @@ const puterInit = (function() {
688
705
  };
689
706
 
690
707
  /**
691
- * Purges all cached data
692
- * @public
708
+ * Checks and updates the GUI FS cache for most-commonly used paths
709
+ * @private
693
710
  */
694
- purgeCache = function() {
695
- try {
696
- if ( this._cache && typeof this._cache.flushall === 'function' ) {
697
- this._cache.flushall();
698
- console.log('Cache purged successfully');
699
- } else {
700
- console.warn('Cache purge failed: cache instance not available');
701
- }
702
- } catch( error ) {
703
- console.error('Error purging cache:', error);
711
+ checkAndUpdateGUIFScache = function(){
712
+ // only run in gui environment
713
+ if(puter.env !== 'gui') return;
714
+ // only run if user is authenticated
715
+ if(!puter.whoami) return;
716
+
717
+ let username = puter.whoami.username;
718
+
719
+ // common paths
720
+ let home_path = `/${username}`;
721
+ let desktop_path = `/${username}/Desktop`;
722
+ let documents_path = `/${username}/Documents`;
723
+ let public_path = `/${username}/Public`;
724
+
725
+ // Home
726
+ if(!puter._cache.get('readdir:' + home_path)){
727
+ console.log(`/${username} is not cached, refetching cache`);
728
+ // fetch home
729
+ puter.fs.readdir(home_path);
730
+ }
731
+ // Desktop
732
+ if(!puter._cache.get('readdir:' + desktop_path)){
733
+ console.log(`/${username}/Desktop is not cached, refetching cache`);
734
+ // fetch desktop
735
+ puter.fs.readdir(desktop_path);
736
+ }
737
+ // Documents
738
+ if(!puter._cache.get('readdir:' + documents_path)){
739
+ console.log(`/${username}/Documents is not cached, refetching cache`);
740
+ // fetch documents
741
+ puter.fs.readdir(documents_path);
742
+ }
743
+ // Public
744
+ if(!puter._cache.get('readdir:' + public_path)){
745
+ console.log(`/${username}/Public is not cached, refetching cache`);
746
+ // fetch public
747
+ puter.fs.readdir(public_path);
704
748
  }
705
- };
706
-
749
+ }
707
750
  }
708
751
 
709
752
  // Create a new Puter object and return it
package/src/init.cjs CHANGED
@@ -19,7 +19,7 @@ const init = (authToken) => {
19
19
  }
20
20
  });
21
21
  goodContext.globalThis = goodContext;
22
- const code = readFileSync(`${resolve(__filename, '..')}/../dist/puter.js`, 'utf8');
22
+ const code = readFileSync(`${resolve(__filename, '..')}/../dist/puter.cjs`, 'utf8');
23
23
  const context = vm.createContext(goodContext);
24
24
  vm.runInNewContext(code, context);
25
25
  if ( authToken ) {
@@ -1,4 +1,11 @@
1
1
  import io from '../../lib/socket.io/socket.io.esm.min.js';
2
+ import * as utils from '../../lib/utils.js';
3
+ import path from '../../lib/path.js';
4
+
5
+ // Constants
6
+ //
7
+ // The last valid time of the local cache.
8
+ const LAST_VALID_TS = 'last_valid_ts';
2
9
 
3
10
  // Operations
4
11
  import copy from './operations/copy.js';
@@ -20,6 +27,7 @@ import FSItem from '../FSItem.js';
20
27
  import deleteFSEntry from './operations/deleteFSEntry.js';
21
28
  import getReadURL from './operations/getReadUrl.js';
22
29
 
30
+
23
31
  export class PuterJSFileSystemModule extends AdvancedBase {
24
32
 
25
33
  space = space;
@@ -67,6 +75,7 @@ export class PuterJSFileSystemModule extends AdvancedBase {
67
75
  this.APIOrigin = context.APIOrigin;
68
76
  this.appID = context.appID;
69
77
  this.context = context;
78
+ this.cacheUpdateTimer = null;
70
79
  // Connect socket.
71
80
  this.initializeSocket();
72
81
 
@@ -103,21 +112,40 @@ export class PuterJSFileSystemModule extends AdvancedBase {
103
112
  });
104
113
 
105
114
  this.bindSocketEvents();
106
-
107
115
  }
108
116
 
109
117
  bindSocketEvents() {
118
+ // this.socket.on('cache.updated', (msg) => {
119
+ // // check original_client_socket_id and if it matches this.socket.id, don't post update
120
+ // if (msg.original_client_socket_id !== this.socket.id) {
121
+ // this.invalidateCache();
122
+ // }
123
+ // });
124
+
125
+ this.socket.on('item.renamed', (item) => {
126
+ puter._cache.flushall();
127
+ console.log('Flushed cache for item.renamed');
128
+ });
129
+
130
+ this.socket.on('item.removed', (item) => {
131
+ // check original_client_socket_id and if it matches this.socket.id, don't invalidate cache
132
+ puter._cache.flushall();
133
+ console.log('Flushed cache for item.removed');
134
+ });
135
+
110
136
  this.socket.on('item.added', (item) => {
111
- // todo: NAIVE PURGE
112
137
  puter._cache.flushall();
138
+ console.log('Flushed cache for item.added');
113
139
  });
114
- this.socket.on('item.renamed', (item) => {
115
- // todo: NAIVE PURGE
140
+
141
+ this.socket.on('item.updated', (item) => {
116
142
  puter._cache.flushall();
143
+ console.log('Flushed cache for item.updated');
117
144
  });
145
+
118
146
  this.socket.on('item.moved', (item) => {
119
- // todo: NAIVE PURGE
120
147
  puter._cache.flushall();
148
+ console.log('Flushed cache for item.moved');
121
149
  });
122
150
 
123
151
  this.socket.on('connect', () => {
@@ -132,10 +160,6 @@ export class PuterJSFileSystemModule extends AdvancedBase {
132
160
  {
133
161
  console.log('FileSystem Socket: Disconnected');
134
162
  }
135
-
136
- // todo: NAIVE PURGE
137
- // purge cache on disconnect since we may have become out of sync
138
- puter._cache.flushall();
139
163
  });
140
164
 
141
165
  this.socket.on('reconnect', (attempt) => {
@@ -183,6 +207,14 @@ export class PuterJSFileSystemModule extends AdvancedBase {
183
207
  */
184
208
  setAuthToken(authToken) {
185
209
  this.authToken = authToken;
210
+
211
+ // Check cache timestamp and purge if needed (only in GUI environment)
212
+ if (this.context.env === 'gui') {
213
+ this.checkCacheAndPurge();
214
+ // Start background task to update LAST_VALID_TS every 1 second
215
+ this.startCacheUpdateTimer();
216
+ }
217
+
186
218
  // reset socket
187
219
  this.initializeSocket();
188
220
  }
@@ -199,4 +231,100 @@ export class PuterJSFileSystemModule extends AdvancedBase {
199
231
  // reset socket
200
232
  this.initializeSocket();
201
233
  }
234
+
235
+ /**
236
+ * The cache-related actions after local and remote updates.
237
+ *
238
+ * @memberof PuterJSFileSystemModule
239
+ * @returns {void}
240
+ */
241
+ invalidateCache() {
242
+ // Action: Update last valid time
243
+ // Set to 0, which means the cache is not up to date.
244
+ localStorage.setItem(LAST_VALID_TS, '0');
245
+ puter._cache.flushall();
246
+ }
247
+
248
+ /**
249
+ * Calls the cache API to get the last change timestamp from the server.
250
+ *
251
+ * @memberof PuterJSFileSystemModule
252
+ * @returns {Promise<number>} The timestamp from the server
253
+ */
254
+ async getCacheTimestamp() {
255
+ return new Promise((resolve, reject) => {
256
+ const xhr = utils.initXhr('/cache/last-change-timestamp', this.APIOrigin, this.authToken, 'get', 'application/json');
257
+
258
+ // set up event handlers for load and error events
259
+ utils.setupXhrEventHandlers(xhr, undefined, undefined, async (result) => {
260
+ try {
261
+ const response = typeof result === 'string' ? JSON.parse(result) : result;
262
+ resolve(response.timestamp || Date.now());
263
+ } catch (e) {
264
+ reject(new Error('Failed to parse response'));
265
+ }
266
+ }, reject);
267
+
268
+ xhr.send();
269
+ });
270
+ }
271
+
272
+ /**
273
+ * Checks cache timestamp and purges cache if needed.
274
+ * Only runs in GUI environment.
275
+ *
276
+ * @memberof PuterJSFileSystemModule
277
+ * @returns {void}
278
+ */
279
+ async checkCacheAndPurge() {
280
+ try {
281
+ const serverTimestamp = await this.getCacheTimestamp();
282
+ const localValidTs = parseInt(localStorage.getItem(LAST_VALID_TS)) || 0;
283
+
284
+ if (serverTimestamp - localValidTs > 2000) {
285
+ console.log('Cache is not up to date, purging cache');
286
+ // Server has newer data, purge local cache
287
+ puter._cache.flushall();
288
+ localStorage.setItem(LAST_VALID_TS, '0');
289
+ }
290
+ } catch (error) {
291
+ // If we can't get the server timestamp, silently fail
292
+ // This ensures the socket initialization doesn't break
293
+ console.error('Error checking cache timestamp:', error);
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Starts the background task to update LAST_VALID_TS every 1 second.
299
+ * Only runs in GUI environment.
300
+ *
301
+ * @memberof PuterJSFileSystemModule
302
+ * @returns {void}
303
+ */
304
+ startCacheUpdateTimer() {
305
+ if (this.context.env !== 'gui') {
306
+ return;
307
+ }
308
+
309
+ // Clear any existing timer
310
+ // this.stopCacheUpdateTimer();
311
+
312
+ // Start new timer
313
+ this.cacheUpdateTimer = setInterval(() => {
314
+ localStorage.setItem(LAST_VALID_TS, Date.now().toString());
315
+ }, 1000);
316
+ }
317
+
318
+ /**
319
+ * Stops the background cache update timer.
320
+ *
321
+ * @memberof PuterJSFileSystemModule
322
+ * @returns {void}
323
+ */
324
+ stopCacheUpdateTimer() {
325
+ if (this.cacheUpdateTimer) {
326
+ clearInterval(this.cacheUpdateTimer);
327
+ this.cacheUpdateTimer = null;
328
+ }
329
+ }
202
330
  }
@@ -55,9 +55,6 @@ const copy = function (...args) {
55
55
  // if user is copying an item to where its source is, change the name so there is no conflict
56
56
  dedupe_name: (options.dedupe_name || options.dedupeName),
57
57
  }));
58
-
59
- // todo: EXTREMELY NAIVE CACHE PURGE
60
- puter._cache.flushall();
61
58
  })
62
59
  }
63
60
 
@@ -1,6 +1,6 @@
1
+ import path from "../../../lib/path.js";
1
2
  import * as utils from '../../../lib/utils.js';
2
3
  import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
3
- import path from "../../../lib/path.js"
4
4
 
5
5
  const mkdir = function (...args) {
6
6
  let options = {};
@@ -53,9 +53,6 @@ const mkdir = function (...args) {
53
53
  original_client_socket_id: this.socket.id,
54
54
  create_missing_parents: (options.recursive || options.createMissingParents) ?? false,
55
55
  }));
56
-
57
- // todo: EXTREMELY NAIVE CACHE PURGE
58
- puter._cache.flushall();
59
56
  })
60
57
  }
61
58
 
@@ -1,7 +1,7 @@
1
+ import path from "../../../lib/path.js";
1
2
  import * as utils from '../../../lib/utils.js';
2
3
  import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
3
- import stat from "./stat.js"
4
- import path from "../../../lib/path.js"
4
+ import stat from "./stat.js";
5
5
 
6
6
  const move = function (...args) {
7
7
  let options;
@@ -65,10 +65,6 @@ const move = function (...args) {
65
65
  new_metadata: (options.new_metadata || options.newMetadata),
66
66
  original_client_socket_id: options.excludeSocketID,
67
67
  }));
68
-
69
- // todo: EXTREMELY NAIVE CACHE PURGE
70
- puter._cache.flushall();
71
-
72
68
  })
73
69
  }
74
70
 
@@ -1,6 +1,14 @@
1
1
  import * as utils from '../../../lib/utils.js';
2
2
  import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
3
3
 
4
+ // Track in-flight requests to avoid duplicate backend calls
5
+ // Each entry stores: { promise, timestamp }
6
+ const inflightRequests = new Map();
7
+
8
+ // Time window (in ms) to group duplicate requests together
9
+ // Requests made within this window will share the same backend call
10
+ const DEDUPLICATION_WINDOW_MS = 2000; // 2 seconds
11
+
4
12
  const readdir = async function (...args) {
5
13
  let options;
6
14
 
@@ -31,8 +39,6 @@ const readdir = async function (...args) {
31
39
  let cacheKey;
32
40
  if(options.path){
33
41
  cacheKey = 'readdir:' + options.path;
34
- }else if(options.uid){
35
- cacheKey = 'readdir:' + options.uid;
36
42
  }
37
43
 
38
44
  if(options.consistency === 'eventual'){
@@ -44,58 +50,108 @@ const readdir = async function (...args) {
44
50
  }
45
51
  }
46
52
 
47
- // If auth token is not provided and we are in the web environment,
48
- // try to authenticate with Puter
49
- if(!puter.authToken && puter.env === 'web'){
50
- try{
51
- await puter.ui.authenticateWithPuter();
52
- }catch(e){
53
- // if authentication fails, throw an error
54
- reject('Authentication failed.');
53
+ // Generate deduplication key based on all request parameters
54
+ const deduplicationKey = JSON.stringify({
55
+ path: options.path,
56
+ uid: options.uid,
57
+ no_thumbs: options.no_thumbs,
58
+ no_assocs: options.no_assocs,
59
+ consistency: options.consistency,
60
+ });
61
+
62
+ // Check if there's already an in-flight request for the same parameters
63
+ const existingEntry = inflightRequests.get(deduplicationKey);
64
+ const now = Date.now();
65
+
66
+ if (existingEntry) {
67
+ const timeSinceRequest = now - existingEntry.timestamp;
68
+
69
+ // Only reuse the request if it's within the deduplication window
70
+ if (timeSinceRequest < DEDUPLICATION_WINDOW_MS) {
71
+ // Wait for the existing request and return its result
72
+ try {
73
+ const result = await existingEntry.promise;
74
+ resolve(result);
75
+ } catch (error) {
76
+ reject(error);
77
+ }
78
+ return;
79
+ } else {
80
+ // Request is too old, remove it from the tracker
81
+ inflightRequests.delete(deduplicationKey);
55
82
  }
56
83
  }
57
84
 
58
- // create xhr object
59
- const xhr = utils.initXhr('/readdir', this.APIOrigin, undefined, "post", "text/plain;actually=json");
85
+ // Create a promise for this request and store it to deduplicate concurrent calls
86
+ const requestPromise = new Promise(async (resolveRequest, rejectRequest) => {
87
+ // If auth token is not provided and we are in the web environment,
88
+ // try to authenticate with Puter
89
+ if(!puter.authToken && puter.env === 'web'){
90
+ try{
91
+ await puter.ui.authenticateWithPuter();
92
+ }catch(e){
93
+ // if authentication fails, throw an error
94
+ rejectRequest('Authentication failed.');
95
+ return;
96
+ }
97
+ }
60
98
 
61
- // set up event handlers for load and error events
62
- utils.setupXhrEventHandlers(xhr, options.success, options.error, async (result) => {
63
- // Calculate the size of the result for cache eligibility check
64
- const resultSize = JSON.stringify(result).length;
65
-
66
- // Cache the result if it's not bigger than MAX_CACHE_SIZE
67
- const MAX_CACHE_SIZE = 20 * 1024 * 1024;
68
- const EXPIRE_TIME = 60 * 60; // 1 hour
99
+ // create xhr object
100
+ const xhr = utils.initXhr('/readdir', this.APIOrigin, undefined, "post", "text/plain;actually=json");
69
101
 
70
- if(resultSize <= MAX_CACHE_SIZE){
71
- // UPSERT the cache
72
- await puter._cache.set(cacheKey, result, { EX: EXPIRE_TIME });
73
- }
102
+ // set up event handlers for load and error events
103
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, async (result) => {
104
+ // Calculate the size of the result for cache eligibility check
105
+ const resultSize = JSON.stringify(result).length;
106
+
107
+ // Cache the result if it's not bigger than MAX_CACHE_SIZE
108
+ const MAX_CACHE_SIZE = 100 * 1024 * 1024;
109
+
110
+ if(resultSize <= MAX_CACHE_SIZE){
111
+ // UPSERT the cache
112
+ puter._cache.set(cacheKey, result);
113
+ }
74
114
 
75
- // set each individual item's cache
76
- for(const item of result){
77
- await puter._cache.set('item:' + item.id, item, { EX: EXPIRE_TIME });
78
- await puter._cache.set('item:' + item.path, item, { EX: EXPIRE_TIME });
115
+ // set each individual item's cache
116
+ for(const item of result){
117
+ puter._cache.set('item:' + item.path, item);
118
+ }
119
+
120
+ resolveRequest(result);
121
+ }, rejectRequest);
122
+
123
+ // Build request payload - support both path and uid parameters
124
+ const payload = {
125
+ no_thumbs: options.no_thumbs,
126
+ no_assocs: options.no_assocs,
127
+ auth_token: this.authToken
128
+ };
129
+
130
+ // Add either uid or path to the payload
131
+ if (options.uid) {
132
+ payload.uid = options.uid;
133
+ } else if (options.path) {
134
+ payload.path = getAbsolutePathForApp(options.path);
79
135
  }
80
-
81
- resolve(result);
82
- }, reject);
83
136
 
84
- // Build request payload - support both path and uid parameters
85
- const payload = {
86
- no_thumbs: options.no_thumbs,
87
- no_assocs: options.no_assocs,
88
- auth_token: this.authToken
89
- };
137
+ xhr.send(JSON.stringify(payload));
138
+ });
90
139
 
91
- // Add either uid or path to the payload
92
- if (options.uid) {
93
- payload.uid = options.uid;
94
- } else if (options.path) {
95
- payload.path = getAbsolutePathForApp(options.path);
96
- }
140
+ // Store the promise and timestamp in the in-flight tracker
141
+ inflightRequests.set(deduplicationKey, {
142
+ promise: requestPromise,
143
+ timestamp: now,
144
+ });
97
145
 
98
- xhr.send(JSON.stringify(payload));
146
+ // Wait for the request to complete and clean up
147
+ try {
148
+ const result = await requestPromise;
149
+ inflightRequests.delete(deduplicationKey);
150
+ resolve(result);
151
+ } catch (error) {
152
+ inflightRequests.delete(deduplicationKey);
153
+ reject(error);
154
+ }
99
155
  })
100
156
  }
101
157
 
@@ -50,8 +50,7 @@ const rename = function (...args) {
50
50
  }
51
51
 
52
52
  xhr.send(JSON.stringify(dataToSend));
53
- // todo: EXTREMELY NAIVE CACHE PURGE
54
- puter._cache.flushall();
53
+
55
54
  })
56
55
  }
57
56