@heyputer/puter.js 2.0.13 → 2.0.14

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.14",
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
  ],
@@ -27,7 +27,8 @@
27
27
  "start-server": "npx http-server --cors -c-1",
28
28
  "start-webpack": "webpack && webpack --output-filename puter.dev.js --watch --devtool source-map",
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
+ "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",
31
+ "prepublishOnly": "npm run build && mv dist/puter.js dist/puter.cjs && npm version patch && git add package.json && git commit -m \"chore: bump puter-js version and publish \" && git push"
31
32
  },
32
33
  "author": "Puter Technologies Inc.",
33
34
  "license": "Apache-2.0",
@@ -36,7 +37,7 @@
36
37
  "webpack-cli": "^5.1.4"
37
38
  },
38
39
  "dependencies": {
39
- "@heyputer/kv.js": "^0.1.92",
40
+ "@heyputer/kv.js": "^0.2.1",
40
41
  "@heyputer/putility": "^1.0.3"
41
42
  }
42
- }
43
+ }
package/src/index.js CHANGED
@@ -130,7 +130,7 @@ 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
134
 
135
135
  // "modules" in puter.js are external interfaces for the developer
136
136
  this.modules_ = [];
@@ -693,12 +693,8 @@ const puterInit = (function() {
693
693
  */
694
694
  purgeCache = function() {
695
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
- }
696
+ this._cache.flushall();
697
+ console.log('Cache purged successfully');
702
698
  } catch( error ) {
703
699
  console.error('Error purging cache:', error);
704
700
  }
@@ -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,75 @@ export class PuterJSFileSystemModule extends AdvancedBase {
103
112
  });
104
113
 
105
114
  this.bindSocketEvents();
106
-
107
115
  }
108
116
 
109
117
  bindSocketEvents() {
110
- this.socket.on('item.added', (item) => {
111
- // todo: NAIVE PURGE
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
+ // delete old item from cache
127
+ puter._cache.del('item:' + item.old_path);
128
+ // if a directory
129
+ if(item.is_dir){
130
+ // delete readdir
131
+ puter._cache.del('readdir:' + item.old_path);
132
+ // descendants items
133
+ const descendants = puter._cache.keys('item:' + item.old_path + '/*');
134
+ for(const descendant of descendants){
135
+ console.log('Deleting cache for:', descendant);
136
+ puter._cache.del(descendant);
137
+ }
138
+ // descendants readdirs
139
+ const descendants_readdir = puter._cache.keys('readdir:' + item.old_path + '/*');
140
+ for(const descendant of descendants_readdir){
141
+ console.log('Deleting cache for:', descendant);
142
+ puter._cache.del(descendant);
143
+ }
144
+ }
145
+ // parent readdir
146
+ puter._cache.del('readdir:' + path.dirname(item.old_path));
147
+ });
148
+
149
+ this.socket.on('item.removed', (item) => {
150
+ // check original_client_socket_id and if it matches this.socket.id, don't invalidate cache
112
151
  puter._cache.flushall();
152
+ console.log('Flushed cache for item.deleted');
113
153
  });
114
- this.socket.on('item.renamed', (item) => {
115
- // todo: NAIVE PURGE
154
+
155
+ this.socket.on('item.added', (item) => {
156
+ // delete item from cache
157
+ puter._cache.del('item:' + item.path);
158
+ // delete readdir from cache
159
+ puter._cache.del('readdir:' + item.path);
160
+ // delete descendant items from cache
161
+ const descendant_items = puter._cache.keys('item:' + item.path + '/*');
162
+ for(const descendant of descendant_items){
163
+ puter._cache.del(descendant);
164
+ }
165
+ // delete descendant readdirs from cache
166
+ const descendant_readdirs = puter._cache.keys('readdir:' + item.path + '/*');
167
+ for(const descendant of descendant_readdirs){
168
+ puter._cache.del(descendant);
169
+ }
170
+ // delete parent readdir from cache
171
+ puter._cache.del('readdir:' + path.dirname(item.path));
172
+ });
173
+
174
+ this.socket.on('item.updated', (item) => {
175
+ // check original_client_socket_id and if it matches this.socket.id, don't invalidate cache
116
176
  puter._cache.flushall();
177
+ console.log('Flushed cache for item.updated');
117
178
  });
179
+
118
180
  this.socket.on('item.moved', (item) => {
119
- // todo: NAIVE PURGE
181
+ // check original_client_socket_id and if it matches this.socket.id, don't invalidate cache
120
182
  puter._cache.flushall();
183
+ console.log('Flushed cache for item.moved');
121
184
  });
122
185
 
123
186
  this.socket.on('connect', () => {
@@ -132,10 +195,6 @@ export class PuterJSFileSystemModule extends AdvancedBase {
132
195
  {
133
196
  console.log('FileSystem Socket: Disconnected');
134
197
  }
135
-
136
- // todo: NAIVE PURGE
137
- // purge cache on disconnect since we may have become out of sync
138
- puter._cache.flushall();
139
198
  });
140
199
 
141
200
  this.socket.on('reconnect', (attempt) => {
@@ -183,6 +242,14 @@ export class PuterJSFileSystemModule extends AdvancedBase {
183
242
  */
184
243
  setAuthToken(authToken) {
185
244
  this.authToken = authToken;
245
+
246
+ // Check cache timestamp and purge if needed (only in GUI environment)
247
+ if (this.context.env === 'gui') {
248
+ this.checkCacheAndPurge();
249
+ // Start background task to update LAST_VALID_TS every 1 second
250
+ this.startCacheUpdateTimer();
251
+ }
252
+
186
253
  // reset socket
187
254
  this.initializeSocket();
188
255
  }
@@ -199,4 +266,100 @@ export class PuterJSFileSystemModule extends AdvancedBase {
199
266
  // reset socket
200
267
  this.initializeSocket();
201
268
  }
269
+
270
+ /**
271
+ * The cache-related actions after local and remote updates.
272
+ *
273
+ * @memberof PuterJSFileSystemModule
274
+ * @returns {void}
275
+ */
276
+ invalidateCache() {
277
+ // Action: Update last valid time
278
+ // Set to 0, which means the cache is not up to date.
279
+ localStorage.setItem(LAST_VALID_TS, '0');
280
+ puter._cache.flushall();
281
+ }
282
+
283
+ /**
284
+ * Calls the cache API to get the last change timestamp from the server.
285
+ *
286
+ * @memberof PuterJSFileSystemModule
287
+ * @returns {Promise<number>} The timestamp from the server
288
+ */
289
+ async getCacheTimestamp() {
290
+ return new Promise((resolve, reject) => {
291
+ const xhr = utils.initXhr('/cache/last-change-timestamp', this.APIOrigin, this.authToken, 'get', 'application/json');
292
+
293
+ // set up event handlers for load and error events
294
+ utils.setupXhrEventHandlers(xhr, undefined, undefined, async (result) => {
295
+ try {
296
+ const response = typeof result === 'string' ? JSON.parse(result) : result;
297
+ resolve(response.timestamp || Date.now());
298
+ } catch (e) {
299
+ reject(new Error('Failed to parse response'));
300
+ }
301
+ }, reject);
302
+
303
+ xhr.send();
304
+ });
305
+ }
306
+
307
+ /**
308
+ * Checks cache timestamp and purges cache if needed.
309
+ * Only runs in GUI environment.
310
+ *
311
+ * @memberof PuterJSFileSystemModule
312
+ * @returns {void}
313
+ */
314
+ async checkCacheAndPurge() {
315
+ try {
316
+ const serverTimestamp = await this.getCacheTimestamp();
317
+ const localValidTs = parseInt(localStorage.getItem(LAST_VALID_TS)) || 0;
318
+
319
+ if (serverTimestamp - localValidTs > 2000) {
320
+ console.log('Cache is not up to date, purging cache');
321
+ // Server has newer data, purge local cache
322
+ puter._cache.flushall();
323
+ localStorage.setItem(LAST_VALID_TS, '0');
324
+ }
325
+ } catch (error) {
326
+ // If we can't get the server timestamp, silently fail
327
+ // This ensures the socket initialization doesn't break
328
+ console.error('Error checking cache timestamp:', error);
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Starts the background task to update LAST_VALID_TS every 1 second.
334
+ * Only runs in GUI environment.
335
+ *
336
+ * @memberof PuterJSFileSystemModule
337
+ * @returns {void}
338
+ */
339
+ startCacheUpdateTimer() {
340
+ if (this.context.env !== 'gui') {
341
+ return;
342
+ }
343
+
344
+ // Clear any existing timer
345
+ // this.stopCacheUpdateTimer();
346
+
347
+ // Start new timer
348
+ this.cacheUpdateTimer = setInterval(() => {
349
+ localStorage.setItem(LAST_VALID_TS, Date.now().toString());
350
+ }, 1000);
351
+ }
352
+
353
+ /**
354
+ * Stops the background cache update timer.
355
+ *
356
+ * @memberof PuterJSFileSystemModule
357
+ * @returns {void}
358
+ */
359
+ stopCacheUpdateTimer() {
360
+ if (this.cacheUpdateTimer) {
361
+ clearInterval(this.cacheUpdateTimer);
362
+ this.cacheUpdateTimer = null;
363
+ }
364
+ }
202
365
  }
@@ -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
 
@@ -31,8 +31,6 @@ const readdir = async function (...args) {
31
31
  let cacheKey;
32
32
  if(options.path){
33
33
  cacheKey = 'readdir:' + options.path;
34
- }else if(options.uid){
35
- cacheKey = 'readdir:' + options.uid;
36
34
  }
37
35
 
38
36
  if(options.consistency === 'eventual'){
@@ -64,18 +62,16 @@ const readdir = async function (...args) {
64
62
  const resultSize = JSON.stringify(result).length;
65
63
 
66
64
  // 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
65
+ const MAX_CACHE_SIZE = 100 * 1024 * 1024;
69
66
 
70
67
  if(resultSize <= MAX_CACHE_SIZE){
71
68
  // UPSERT the cache
72
- await puter._cache.set(cacheKey, result, { EX: EXPIRE_TIME });
69
+ puter._cache.set(cacheKey, result);
73
70
  }
74
71
 
75
72
  // set each individual item's cache
76
73
  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 });
74
+ puter._cache.set('item:' + item.path, item);
79
75
  }
80
76
 
81
77
  resolve(result);
@@ -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
 
@@ -39,8 +39,6 @@ const stat = async function (...args) {
39
39
  let cacheKey;
40
40
  if(options.path){
41
41
  cacheKey = 'item:' + options.path;
42
- }else if(options.uid){
43
- cacheKey = 'item:' + options.uid;
44
42
  }
45
43
 
46
44
  if(options.consistency === 'eventual' && !options.returnSubdomains && !options.returnPermissions && !options.returnVersions && !options.returnSize){
@@ -62,12 +60,10 @@ const stat = async function (...args) {
62
60
 
63
61
  // Cache the result if it's not bigger than MAX_CACHE_SIZE
64
62
  const MAX_CACHE_SIZE = 20 * 1024 * 1024;
65
- const EXPIRE_TIME = 60 * 60; // 1 hour
66
63
 
67
64
  if(resultSize <= MAX_CACHE_SIZE){
68
65
  // UPSERT the cache
69
- await puter._cache.set('item:' + result.path, result, { EX: EXPIRE_TIME });
70
- await puter._cache.set('item:' + result.uid, result, { EX: EXPIRE_TIME });
66
+ puter._cache.set(cacheKey, result);
71
67
  }
72
68
 
73
69
  resolve(result);
@@ -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 upload = async function(items, dirPath, options = {}){
6
6
  return new Promise(async (resolve, reject) => {
@@ -429,9 +429,6 @@ const upload = async function(items, dirPath, options = {}){
429
429
  options.start();
430
430
  }
431
431
 
432
- // todo: EXTREMELY NAIVE CACHE PURGE
433
- puter._cache.flushall();
434
-
435
432
  // send request
436
433
  xhr.send(fd);
437
434
  })
@@ -1,4 +1,4 @@
1
- import path from "../../../lib/path.js"
1
+ import path from "../../../lib/path.js";
2
2
  import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
3
3
 
4
4
  const write = async function (targetPath, data, options = {}) {
@@ -55,9 +55,6 @@ const write = async function (targetPath, data, options = {}) {
55
55
  throw new Error({ code: 'field_invalid', message: 'write() data parameter is an invalid type' });
56
56
  }
57
57
 
58
- // todo: EXTREMELY NAIVE CACHE PURGE
59
- puter._cache.flushall();
60
-
61
58
  // perform upload
62
59
  return this.upload(data, parent, options);
63
60
  }