@heyputer/puter.js 2.2.4 → 2.2.8
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/puter.cjs +2 -2
- package/package.json +3 -2
- package/src/index.js +6 -3
- package/src/init.cjs +21 -1
- package/src/modules/AI.js +15 -9
- package/src/modules/Apps.js +2 -2
- package/src/modules/FileSystem/operations/upload.js +124 -4
- package/src/modules/FileSystem/utils/getAbsolutePathForApp.js +14 -4
- package/src/modules/KV.js +77 -44
- package/types/modules/kv.d.ts +22 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyputer/puter.js",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.8",
|
|
4
4
|
"description": "Puter.js - A JavaScript library for interacting with Puter services.",
|
|
5
5
|
"homepage": "https://developer.puter.com",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"webpack-cli": "^5.1.4"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@heyputer/kv.js": "^0.2.1"
|
|
45
|
+
"@heyputer/kv.js": "^0.2.1",
|
|
46
|
+
"open": "^10.2.0"
|
|
46
47
|
}
|
|
47
48
|
}
|
package/src/index.js
CHANGED
|
@@ -54,7 +54,7 @@ class SimpleLogger {
|
|
|
54
54
|
|
|
55
55
|
_prefix () {
|
|
56
56
|
const entries = Object.entries(this.fieldsObj);
|
|
57
|
-
if ( !entries.length ) return [];
|
|
57
|
+
if ( ! entries.length ) return [];
|
|
58
58
|
return [`[${ entries.map(([k, v]) => `${k}=${v}`).join(' ')}]`];
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -66,7 +66,7 @@ class Lock {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
async acquire () {
|
|
69
|
-
if ( !this.locked ) {
|
|
69
|
+
if ( ! this.locked ) {
|
|
70
70
|
this.locked = true;
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
@@ -374,7 +374,7 @@ const puterInit = (function () {
|
|
|
374
374
|
}]`;
|
|
375
375
|
logger = logger.fields({ prefix });
|
|
376
376
|
this.logger = logger;
|
|
377
|
-
} catch (error) {
|
|
377
|
+
} catch ( error ) {
|
|
378
378
|
if ( this.debugMode ) {
|
|
379
379
|
console.error('Failed to initialize prefix logger', error);
|
|
380
380
|
}
|
|
@@ -536,6 +536,9 @@ const puterInit = (function () {
|
|
|
536
536
|
};
|
|
537
537
|
|
|
538
538
|
resetAuthToken = function () {
|
|
539
|
+
if ( this.env === 'worker' || this.env === 'service-worker' ) {
|
|
540
|
+
throw new Error('Sign out is not permitted from WebWorkers or ServiceWorkers');
|
|
541
|
+
}
|
|
539
542
|
this.authToken = null;
|
|
540
543
|
// If the SDK is running on a 3rd-party site or an app, then save the authToken in localStorage
|
|
541
544
|
if ( this.env === 'web' || this.env === 'app' ) {
|
package/src/init.cjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const { readFileSync } = require('node:fs');
|
|
2
2
|
const vm = require('node:vm');
|
|
3
3
|
const { resolve } = require('node:path');
|
|
4
|
+
const { IncomingMessage } = require('node:http');
|
|
5
|
+
const open = require('open');
|
|
4
6
|
/**
|
|
5
7
|
* Method for loading puter.js in Node.js environment with auth token
|
|
6
8
|
* @param {string} authToken - Optional auth token to initialize puter with
|
|
@@ -28,4 +30,22 @@ const init = (authToken) => {
|
|
|
28
30
|
return goodContext.puter;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
const getAuthToken = (guiOrigin = 'https://puter.com') => {
|
|
34
|
+
const http = require('http');
|
|
35
|
+
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
const requestListener = function (/**@type {IncomingMessage} */ req, res) {
|
|
38
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
39
|
+
res.end('Authentication Granted! You maty now close this window.');
|
|
40
|
+
|
|
41
|
+
resolve(new URL(req.url, 'http://localhost/').searchParams.get('token'));
|
|
42
|
+
};
|
|
43
|
+
const server = http.createServer(requestListener);
|
|
44
|
+
server.listen(0, function () {
|
|
45
|
+
const url = `${guiOrigin}/?action=authme&redirectURL=${encodeURIComponent('http://localhost:') + this.address().port}`;
|
|
46
|
+
open.default(url);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
module.exports = { init, getAuthToken };
|
package/src/modules/AI.js
CHANGED
|
@@ -70,7 +70,7 @@ class AI {
|
|
|
70
70
|
|
|
71
71
|
const tryFetchModels = async () => {
|
|
72
72
|
const resp = await fetch(`${this.APIOrigin }/puterai/chat/models/details`, { headers });
|
|
73
|
-
if ( !resp.ok ) return null;
|
|
73
|
+
if ( ! resp.ok ) return null;
|
|
74
74
|
const data = await resp.json();
|
|
75
75
|
const models = Array.isArray(data?.models) ? data.models : [];
|
|
76
76
|
return provider ? models.filter(model => model.provider === provider) : models;
|
|
@@ -298,13 +298,13 @@ class AI {
|
|
|
298
298
|
if ( ! options.voice ) {
|
|
299
299
|
options.voice = '21m00Tcm4TlvDq8ikWAM';
|
|
300
300
|
}
|
|
301
|
-
if ( !
|
|
301
|
+
if ( !options.model && typeof options.engine === 'string' ) {
|
|
302
302
|
options.model = options.engine;
|
|
303
303
|
}
|
|
304
304
|
if ( ! options.model ) {
|
|
305
305
|
options.model = 'eleven_multilingual_v2';
|
|
306
306
|
}
|
|
307
|
-
if ( !
|
|
307
|
+
if ( !options.output_format && !options.response_format ) {
|
|
308
308
|
options.output_format = 'mp3_44100_128';
|
|
309
309
|
}
|
|
310
310
|
if ( options.response_format && !options.output_format ) {
|
|
@@ -736,6 +736,10 @@ class AI {
|
|
|
736
736
|
requestParams.max_tokens = userParams.max_tokens;
|
|
737
737
|
}
|
|
738
738
|
|
|
739
|
+
if ( userParams.provider ) {
|
|
740
|
+
requestParams.provider = userParams.provider;
|
|
741
|
+
}
|
|
742
|
+
|
|
739
743
|
// convert undefined to empty string so that .startsWith works
|
|
740
744
|
requestParams.model = requestParams.model ?? '';
|
|
741
745
|
|
|
@@ -745,11 +749,11 @@ class AI {
|
|
|
745
749
|
}
|
|
746
750
|
|
|
747
751
|
if ( userParams.driver ) {
|
|
748
|
-
|
|
752
|
+
requestParams.provider = requestParams.provider || userParams.driver;
|
|
749
753
|
}
|
|
750
754
|
|
|
751
755
|
// Additional parameters to pass from userParams to requestParams
|
|
752
|
-
const PARAMS_TO_PASS = ['tools', 'response', 'reasoning', 'reasoning_effort', 'text', 'verbosity'];
|
|
756
|
+
const PARAMS_TO_PASS = ['tools', 'response', 'reasoning', 'reasoning_effort', 'text', 'verbosity', 'provider'];
|
|
753
757
|
for ( const name of PARAMS_TO_PASS ) {
|
|
754
758
|
if ( userParams[name] ) {
|
|
755
759
|
requestParams[name] = userParams[name];
|
|
@@ -835,8 +839,8 @@ class AI {
|
|
|
835
839
|
options.model = 'gemini-2.5-flash-image-preview';
|
|
836
840
|
}
|
|
837
841
|
|
|
838
|
-
if (options.model ===
|
|
839
|
-
options.model =
|
|
842
|
+
if ( options.model === 'nano-banana-pro' ) {
|
|
843
|
+
options.model = 'gemini-3-pro-image-preview';
|
|
840
844
|
}
|
|
841
845
|
|
|
842
846
|
const driverHint = typeof options.driver === 'string' ? options.driver : undefined;
|
|
@@ -912,6 +916,7 @@ class AI {
|
|
|
912
916
|
options.seconds = options.duration;
|
|
913
917
|
}
|
|
914
918
|
|
|
919
|
+
// This sucks, should be backend's job like we do for chat models now
|
|
915
920
|
let videoService = 'openai-video-generation';
|
|
916
921
|
const driverHint = typeof options.driver === 'string' ? options.driver : undefined;
|
|
917
922
|
const driverHintLower = driverHint ? driverHint.toLowerCase() : undefined;
|
|
@@ -922,7 +927,7 @@ class AI {
|
|
|
922
927
|
const modelLower = typeof options.model === 'string' ? options.model.toLowerCase() : '';
|
|
923
928
|
|
|
924
929
|
const looksLikeTogetherVideoModel = typeof options.model === 'string' &&
|
|
925
|
-
TOGETHER_VIDEO_MODEL_PREFIXES.some(prefix => modelLower.startsWith(prefix));
|
|
930
|
+
(TOGETHER_VIDEO_MODEL_PREFIXES.some(prefix => modelLower.startsWith(prefix)) || options.model.startsWith('togetherai:'));
|
|
926
931
|
|
|
927
932
|
if ( driverHintLower === 'together' || driverHintLower === 'together-ai' ) {
|
|
928
933
|
videoService = 'together-video-generation';
|
|
@@ -958,7 +963,8 @@ class AI {
|
|
|
958
963
|
return result;
|
|
959
964
|
}
|
|
960
965
|
|
|
961
|
-
const video = (globalThis.document?.createElement('video') || {setAttribute: ()=>{
|
|
966
|
+
const video = (globalThis.document?.createElement('video') || { setAttribute: () => {
|
|
967
|
+
} });
|
|
962
968
|
video.src = sourceUrl;
|
|
963
969
|
video.controls = true;
|
|
964
970
|
video.preload = 'metadata';
|
package/src/modules/Apps.js
CHANGED
|
@@ -217,7 +217,7 @@ class Apps {
|
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
return new Promise((
|
|
220
|
+
return new Promise((resUpper, rejUpper) => {
|
|
221
221
|
let options;
|
|
222
222
|
|
|
223
223
|
// If first argument is an object, it's the options
|
|
@@ -235,7 +235,7 @@ class Apps {
|
|
|
235
235
|
const xhr = utils.initXhr('/get-dev-profile', puter.APIOrigin, puter.authToken, 'get');
|
|
236
236
|
|
|
237
237
|
// set up event handlers for load and error events
|
|
238
|
-
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
|
238
|
+
utils.setupXhrEventHandlers(xhr, options.success ?? resUpper, options.error ?? rejUpper, resolve, reject);
|
|
239
239
|
|
|
240
240
|
xhr.send();
|
|
241
241
|
});
|
|
@@ -2,6 +2,98 @@ import path from '../../../lib/path.js';
|
|
|
2
2
|
import * as utils from '../../../lib/utils.js';
|
|
3
3
|
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
|
4
4
|
|
|
5
|
+
const MAX_THUMBNAIL_BYTES = 2 * 1024 * 1024;
|
|
6
|
+
const DEFAULT_THUMBNAIL_DIMENSION = 128;
|
|
7
|
+
const MIN_THUMBNAIL_DIMENSION = 32;
|
|
8
|
+
|
|
9
|
+
const isLikelyImageFile = (file) => {
|
|
10
|
+
if ( ! file ) return false;
|
|
11
|
+
if ( file.type && file.type.startsWith('image/') ) return true;
|
|
12
|
+
const name = (file.name || '').toLowerCase();
|
|
13
|
+
return ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.tiff', '.avif', '.jfif'].some(ext => name.endsWith(ext));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const estimateDataUrlSize = (dataUrl) => {
|
|
17
|
+
if ( ! dataUrl ) return 0;
|
|
18
|
+
const commaIndex = dataUrl.indexOf(',');
|
|
19
|
+
const base64 = commaIndex === -1 ? dataUrl : dataUrl.slice(commaIndex + 1);
|
|
20
|
+
return Math.ceil(base64.length * 3 / 4);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const scaleDimensions = (width, height, maxDim) => {
|
|
24
|
+
const base = Math.max(width, height) || 1;
|
|
25
|
+
const scale = Math.min(1, maxDim / base);
|
|
26
|
+
const w = Math.max(1, Math.round(width * scale));
|
|
27
|
+
const h = Math.max(1, Math.round(height * scale));
|
|
28
|
+
return { width: w, height: h };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const loadImageFromFile = (file) => new Promise((resolve, reject) => {
|
|
32
|
+
if ( typeof document === 'undefined' || typeof URL === 'undefined' || typeof Image === 'undefined' ) return resolve(null);
|
|
33
|
+
const url = URL.createObjectURL(file);
|
|
34
|
+
const img = new Image();
|
|
35
|
+
img.onload = () => {
|
|
36
|
+
URL.revokeObjectURL(url);
|
|
37
|
+
resolve(img);
|
|
38
|
+
};
|
|
39
|
+
img.onerror = (e) => {
|
|
40
|
+
URL.revokeObjectURL(url);
|
|
41
|
+
reject(e);
|
|
42
|
+
};
|
|
43
|
+
img.src = url;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const renderThumbnail = (img, maxDim, type, quality) => {
|
|
47
|
+
if ( !img || typeof document === 'undefined' ) return null;
|
|
48
|
+
const { width, height } = scaleDimensions(img.naturalWidth || img.width, img.naturalHeight || img.height, maxDim);
|
|
49
|
+
const canvas = document.createElement('canvas');
|
|
50
|
+
canvas.width = width;
|
|
51
|
+
canvas.height = height;
|
|
52
|
+
const ctx = canvas.getContext('2d');
|
|
53
|
+
if ( ! ctx ) return null;
|
|
54
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
55
|
+
try {
|
|
56
|
+
return canvas.toDataURL(type, quality);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const defaultThumbnailGenerator = async (file) => {
|
|
63
|
+
try {
|
|
64
|
+
if ( typeof document === 'undefined' ) return undefined;
|
|
65
|
+
if ( typeof File === 'undefined' || !(file instanceof File) ) return undefined;
|
|
66
|
+
if ( ! isLikelyImageFile(file) ) return undefined;
|
|
67
|
+
|
|
68
|
+
const img = await loadImageFromFile(file);
|
|
69
|
+
if ( ! img ) return undefined;
|
|
70
|
+
|
|
71
|
+
let dimension = DEFAULT_THUMBNAIL_DIMENSION;
|
|
72
|
+
const formats = [
|
|
73
|
+
{ type: 'image/webp', quality: 0.85 },
|
|
74
|
+
{ type: 'image/jpeg', quality: 0.8 },
|
|
75
|
+
{ type: 'image/png' },
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
while ( dimension >= MIN_THUMBNAIL_DIMENSION ) {
|
|
79
|
+
for ( const { type, quality } of formats ) {
|
|
80
|
+
const dataUrl = renderThumbnail(img, dimension, type, quality);
|
|
81
|
+
if ( ! dataUrl ) continue;
|
|
82
|
+
if ( estimateDataUrlSize(dataUrl) <= MAX_THUMBNAIL_BYTES ) {
|
|
83
|
+
return dataUrl;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
dimension = Math.floor(dimension / 2);
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
// Ignore thumbnail errors; upload should proceed without them.
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return undefined;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/* eslint-disable */
|
|
5
97
|
const upload = async function (items, dirPath, options = {}) {
|
|
6
98
|
return new Promise(async (resolve, reject) => {
|
|
7
99
|
const DataTransferItem = globalThis.DataTransfer || (class DataTransferItem {
|
|
@@ -199,6 +291,19 @@ const upload = async function (items, dirPath, options = {}) {
|
|
|
199
291
|
return error({ code: 'EMPTY_UPLOAD', message: 'No files or directories to upload.' });
|
|
200
292
|
}
|
|
201
293
|
|
|
294
|
+
let thumbnails = [];
|
|
295
|
+
const shouldGenerateThumbnails = options.generateThumbnails || options.thumbnailGenerator;
|
|
296
|
+
if ( files.length && shouldGenerateThumbnails ) {
|
|
297
|
+
const generator = options.thumbnailGenerator || defaultThumbnailGenerator;
|
|
298
|
+
thumbnails = await Promise.all(files.map(async (file) => {
|
|
299
|
+
try {
|
|
300
|
+
return await generator(file);
|
|
301
|
+
} catch (e) {
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
|
|
202
307
|
// Check storage capacity.
|
|
203
308
|
// We need to check the storage capacity before the upload starts because
|
|
204
309
|
// we want to avoid uploading files in case there is not enough storage space.
|
|
@@ -286,19 +391,28 @@ const upload = async function (items, dirPath, options = {}) {
|
|
|
286
391
|
// Append file metadata to upload request
|
|
287
392
|
if ( ! options.shortcutTo ) {
|
|
288
393
|
for ( let i = 0; i < files.length; i++ ) {
|
|
289
|
-
|
|
394
|
+
const thumbnail = thumbnails[i] ?? options.thumbnail ?? undefined;
|
|
395
|
+
const fileinfo_payload = {
|
|
290
396
|
name: files[i].name,
|
|
291
397
|
type: files[i].type,
|
|
292
398
|
size: files[i].size,
|
|
399
|
+
};
|
|
400
|
+
if ( thumbnail ) {
|
|
401
|
+
fileinfo_payload.thumbnail = thumbnail;
|
|
402
|
+
}
|
|
403
|
+
fd.append('fileinfo', JSON.stringify({
|
|
404
|
+
...fileinfo_payload,
|
|
293
405
|
}));
|
|
294
406
|
}
|
|
295
407
|
}
|
|
296
408
|
// Append write operations for each file
|
|
297
409
|
for ( let i = 0; i < files.length; i++ ) {
|
|
298
|
-
|
|
410
|
+
const thumbnail = thumbnails[i] ?? options.thumbnail ?? undefined;
|
|
411
|
+
const operation = {
|
|
299
412
|
op: options.shortcutTo ? 'shortcut' : 'write',
|
|
300
413
|
dedupe_name: options.dedupeName ?? true,
|
|
301
414
|
overwrite: options.overwrite ?? false,
|
|
415
|
+
thumbnail,
|
|
302
416
|
create_missing_ancestors: (options.createMissingAncestors || options.createMissingParents),
|
|
303
417
|
operation_id: operation_id,
|
|
304
418
|
path: (
|
|
@@ -313,7 +427,13 @@ const upload = async function (items, dirPath, options = {}) {
|
|
|
313
427
|
shortcut_to: options.shortcutTo,
|
|
314
428
|
shortcut_to_uid: options.shortcutTo,
|
|
315
429
|
app_uid: options.appUID,
|
|
316
|
-
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
if ( thumbnail === undefined ) {
|
|
433
|
+
delete operation.thumbnail;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
fd.append('operation', JSON.stringify(operation));
|
|
317
437
|
}
|
|
318
438
|
|
|
319
439
|
// Append files to upload
|
|
@@ -463,4 +583,4 @@ const upload = async function (items, dirPath, options = {}) {
|
|
|
463
583
|
});
|
|
464
584
|
};
|
|
465
585
|
|
|
466
|
-
export default upload;
|
|
586
|
+
export default upload;
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import path from '../../../lib/path.js';
|
|
2
2
|
|
|
3
3
|
const getAbsolutePathForApp = (relativePath) => {
|
|
4
|
-
//
|
|
5
|
-
if ( puter.env === 'gui' )
|
|
4
|
+
// preserve previous behavior for falsy values when env is gui
|
|
5
|
+
if ( puter.env === 'gui' && !relativePath )
|
|
6
6
|
{
|
|
7
7
|
return relativePath;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const reLooksLikeUUID = /^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
|
|
11
|
+
const isUUID = reLooksLikeUUID.test(relativePath);
|
|
12
|
+
if ( isUUID ) {
|
|
13
|
+
return relativePath;
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
// if no relative path is provided, use the current working directory
|
|
11
17
|
if ( ! relativePath )
|
|
12
18
|
{
|
|
@@ -15,8 +21,12 @@ const getAbsolutePathForApp = (relativePath) => {
|
|
|
15
21
|
|
|
16
22
|
// If relativePath is not provided, or it's not starting with a slash or tilde,
|
|
17
23
|
// it means it's a relative path. In that case, prepend the app's root directory.
|
|
18
|
-
if ( !relativePath || (!relativePath.startsWith('/') && !relativePath.startsWith('~')
|
|
19
|
-
|
|
24
|
+
if ( !relativePath || (!relativePath.startsWith('/') && !relativePath.startsWith('~')) ) {
|
|
25
|
+
if ( puter.appID ) {
|
|
26
|
+
relativePath = path.join('~/AppData', puter.appID, relativePath);
|
|
27
|
+
} else {
|
|
28
|
+
relativePath = path.join('~/', relativePath);
|
|
29
|
+
}
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
return relativePath;
|
package/src/modules/KV.js
CHANGED
|
@@ -233,6 +233,38 @@ class KV {
|
|
|
233
233
|
return utils.make_driver_method(['key'], 'puter-kvstore', undefined, 'add').call(this, options);
|
|
234
234
|
};
|
|
235
235
|
|
|
236
|
+
remove = async (...args) => {
|
|
237
|
+
if ( !args || args.length < 2 ) {
|
|
238
|
+
throw ({ message: 'At least one path is required', code: 'arguments_required' });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const key = args[0];
|
|
242
|
+
const paths = args.slice(1);
|
|
243
|
+
|
|
244
|
+
if ( Array.isArray(paths[0]) && paths.length === 1 ) {
|
|
245
|
+
throw ({ message: 'Paths must be provided as separate arguments', code: 'paths_invalid' });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if ( key === undefined || key === null ) {
|
|
249
|
+
throw ({ message: 'Key cannot be undefined', code: 'key_undefined' });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if ( key.length > this.MAX_KEY_SIZE ) {
|
|
253
|
+
throw ({ message: `Key size cannot be larger than ${this.MAX_KEY_SIZE}`, code: 'key_too_large' });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if ( paths.length === 0 ) {
|
|
257
|
+
throw ({ message: 'At least one path is required', code: 'arguments_required' });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if ( paths.some((path) => typeof path !== 'string') ) {
|
|
261
|
+
throw ({ message: 'All paths must be strings', code: 'paths_invalid' });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return utils.make_driver_method(['key', 'paths'], 'puter-kvstore', undefined, 'remove')
|
|
265
|
+
.call(this, { key, paths });
|
|
266
|
+
};
|
|
267
|
+
|
|
236
268
|
update = utils.make_driver_method(['key', 'pathAndValueMap', 'ttl'], 'puter-kvstore', undefined, 'update', {
|
|
237
269
|
preprocess: (args) => {
|
|
238
270
|
if ( args.key === undefined || args.key === null ) {
|
|
@@ -318,43 +350,43 @@ class KV {
|
|
|
318
350
|
let pattern;
|
|
319
351
|
let returnValues = false;
|
|
320
352
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
353
|
+
const isOptionsObject = args.length === 1 && args[0] && typeof args[0] === 'object' && !Array.isArray(args[0]);
|
|
354
|
+
|
|
355
|
+
if ( isOptionsObject ) {
|
|
356
|
+
const input = args[0];
|
|
357
|
+
if ( typeof input.pattern === 'string' ) {
|
|
358
|
+
pattern = input.pattern;
|
|
359
|
+
}
|
|
360
|
+
returnValues = !!input.returnValues;
|
|
361
|
+
if ( input.limit !== undefined ) {
|
|
362
|
+
options.limit = input.limit;
|
|
363
|
+
}
|
|
364
|
+
if ( input.cursor !== undefined ) {
|
|
365
|
+
options.cursor = input.cursor;
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
// list(true) or list(pattern, true) will return the key-value pairs
|
|
369
|
+
if ( (args && args.length === 1 && args[0] === true) || (args && args.length === 2 && args[1] === true) ) {
|
|
370
|
+
returnValues = true;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// list(pattern)
|
|
374
|
+
// list(pattern, true)
|
|
375
|
+
if ( (args && args.length === 1 && typeof args[0] === 'string') || (args && args.length === 2 && typeof args[0] === 'string' && args[1] === true) ) {
|
|
376
|
+
pattern = args[0];
|
|
377
|
+
}
|
|
329
378
|
}
|
|
330
379
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if ( (args && args.length === 1 && typeof args[0] === 'string') || (args && args.length === 2 && typeof args[0] === 'string' && args[1] === true) ) {
|
|
334
|
-
pattern = args[0];
|
|
380
|
+
if ( ! returnValues ) {
|
|
381
|
+
options.as = 'keys';
|
|
335
382
|
}
|
|
336
383
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
// consider both the key and the value
|
|
342
|
-
if ( ! returnValues ) {
|
|
343
|
-
let keys = res.filter((key) => {
|
|
344
|
-
return globMatch(pattern, key);
|
|
345
|
-
});
|
|
346
|
-
return keys;
|
|
347
|
-
} else {
|
|
348
|
-
let keys = res.filter((key_value_pair) => {
|
|
349
|
-
return globMatch(pattern, key_value_pair.key);
|
|
350
|
-
});
|
|
351
|
-
return keys;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
384
|
+
const normalizedPattern = normalizeListPattern(pattern);
|
|
385
|
+
if ( normalizedPattern ) {
|
|
386
|
+
options.pattern = normalizedPattern;
|
|
387
|
+
}
|
|
354
388
|
|
|
355
|
-
|
|
356
|
-
},
|
|
357
|
-
}).call(this, options);
|
|
389
|
+
return utils.make_driver_method([], 'puter-kvstore', undefined, 'list').call(this, options);
|
|
358
390
|
};
|
|
359
391
|
|
|
360
392
|
// resolve to 'true' on success, or rejects with an error on failure
|
|
@@ -365,18 +397,19 @@ class KV {
|
|
|
365
397
|
clear = this.flush;
|
|
366
398
|
}
|
|
367
399
|
|
|
368
|
-
function
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
400
|
+
function normalizeListPattern (pattern) {
|
|
401
|
+
if ( typeof pattern !== 'string' ) {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
const trimmed = pattern.trim();
|
|
405
|
+
if ( trimmed === '' ) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
if ( trimmed.endsWith('*') ) {
|
|
409
|
+
const prefix = trimmed.slice(0, -1);
|
|
410
|
+
return prefix === '' ? undefined : prefix;
|
|
411
|
+
}
|
|
412
|
+
return trimmed;
|
|
380
413
|
}
|
|
381
414
|
|
|
382
415
|
export default KV;
|
package/types/modules/kv.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
1
2
|
export type KVValue = string | number | boolean | object | unknown;
|
|
2
3
|
export type KVScalar = KVValue | KVValue[];
|
|
3
4
|
|
|
@@ -18,6 +19,22 @@ export interface KVAddPath {
|
|
|
18
19
|
[path: string]: KVValue | KVValue[];
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
export interface KVListOptions {
|
|
23
|
+
pattern?: string;
|
|
24
|
+
returnValues?: boolean;
|
|
25
|
+
limit?: number;
|
|
26
|
+
cursor?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type KVListPaginationOptions =
|
|
30
|
+
| { limit: number; cursor?: string }
|
|
31
|
+
| { cursor: string; limit?: number };
|
|
32
|
+
|
|
33
|
+
export interface KVListPage<T = unknown> {
|
|
34
|
+
items: T[];
|
|
35
|
+
cursor?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
21
38
|
export class KV {
|
|
22
39
|
readonly MAX_KEY_SIZE: number;
|
|
23
40
|
readonly MAX_VALUE_SIZE: number;
|
|
@@ -28,12 +45,17 @@ export class KV {
|
|
|
28
45
|
incr (key: string, amount?: number | KVIncrementPath): Promise<number>;
|
|
29
46
|
decr (key: string, amount?: number | KVIncrementPath): Promise<number>;
|
|
30
47
|
add (key: string, value?: KVValue | KVAddPath): Promise<KVValue>;
|
|
48
|
+
remove (key: string, ...paths: string[]): Promise<KVValue>;
|
|
31
49
|
update (key: string, pathAndValueMap: KVUpdatePath, ttlSeconds?: number): Promise<KVValue>;
|
|
32
50
|
expire (key: string, ttlSeconds: number): Promise<boolean>;
|
|
33
51
|
expireAt (key: string, timestampSeconds: number): Promise<boolean>;
|
|
34
52
|
list (pattern?: string, returnValues?: false): Promise<string[]>;
|
|
35
53
|
list<T = unknown>(pattern: string, returnValues: true): Promise<KVPair<T>[]>;
|
|
36
54
|
list<T = unknown>(returnValues: true): Promise<KVPair<T>[]>;
|
|
55
|
+
list (options: KVListOptions & KVListPaginationOptions & { returnValues?: false }): Promise<KVListPage<string>>;
|
|
56
|
+
list<T = unknown>(options: KVListOptions & KVListPaginationOptions & { returnValues: true }): Promise<KVListPage<KVPair<T>>>;
|
|
57
|
+
list (options: KVListOptions & { returnValues?: false }): Promise<string[]>;
|
|
58
|
+
list<T = unknown>(options: KVListOptions & { returnValues: true }): Promise<KVPair<T>[]>;
|
|
37
59
|
flush (): Promise<boolean>;
|
|
38
60
|
clear (): Promise<boolean>;
|
|
39
61
|
}
|