@heyputer/puter.js 2.2.5 → 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 +123 -5
- package/src/modules/FileSystem/utils/getAbsolutePathForApp.js +14 -4
- package/src/modules/KV.js +45 -44
- package/types/modules/kv.d.ts +20 -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,97 @@ 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
|
+
|
|
5
96
|
/* eslint-disable */
|
|
6
97
|
const upload = async function (items, dirPath, options = {}) {
|
|
7
98
|
return new Promise(async (resolve, reject) => {
|
|
@@ -200,6 +291,19 @@ const upload = async function (items, dirPath, options = {}) {
|
|
|
200
291
|
return error({ code: 'EMPTY_UPLOAD', message: 'No files or directories to upload.' });
|
|
201
292
|
}
|
|
202
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
|
+
|
|
203
307
|
// Check storage capacity.
|
|
204
308
|
// We need to check the storage capacity before the upload starts because
|
|
205
309
|
// we want to avoid uploading files in case there is not enough storage space.
|
|
@@ -287,20 +391,28 @@ const upload = async function (items, dirPath, options = {}) {
|
|
|
287
391
|
// Append file metadata to upload request
|
|
288
392
|
if ( ! options.shortcutTo ) {
|
|
289
393
|
for ( let i = 0; i < files.length; i++ ) {
|
|
290
|
-
|
|
394
|
+
const thumbnail = thumbnails[i] ?? options.thumbnail ?? undefined;
|
|
395
|
+
const fileinfo_payload = {
|
|
291
396
|
name: files[i].name,
|
|
292
397
|
type: files[i].type,
|
|
293
398
|
size: files[i].size,
|
|
399
|
+
};
|
|
400
|
+
if ( thumbnail ) {
|
|
401
|
+
fileinfo_payload.thumbnail = thumbnail;
|
|
402
|
+
}
|
|
403
|
+
fd.append('fileinfo', JSON.stringify({
|
|
404
|
+
...fileinfo_payload,
|
|
294
405
|
}));
|
|
295
406
|
}
|
|
296
407
|
}
|
|
297
408
|
// Append write operations for each file
|
|
298
409
|
for ( let i = 0; i < files.length; i++ ) {
|
|
299
|
-
|
|
410
|
+
const thumbnail = thumbnails[i] ?? options.thumbnail ?? undefined;
|
|
411
|
+
const operation = {
|
|
300
412
|
op: options.shortcutTo ? 'shortcut' : 'write',
|
|
301
413
|
dedupe_name: options.dedupeName ?? true,
|
|
302
414
|
overwrite: options.overwrite ?? false,
|
|
303
|
-
thumbnail
|
|
415
|
+
thumbnail,
|
|
304
416
|
create_missing_ancestors: (options.createMissingAncestors || options.createMissingParents),
|
|
305
417
|
operation_id: operation_id,
|
|
306
418
|
path: (
|
|
@@ -315,7 +427,13 @@ const upload = async function (items, dirPath, options = {}) {
|
|
|
315
427
|
shortcut_to: options.shortcutTo,
|
|
316
428
|
shortcut_to_uid: options.shortcutTo,
|
|
317
429
|
app_uid: options.appUID,
|
|
318
|
-
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
if ( thumbnail === undefined ) {
|
|
433
|
+
delete operation.thumbnail;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
fd.append('operation', JSON.stringify(operation));
|
|
319
437
|
}
|
|
320
438
|
|
|
321
439
|
// Append files to upload
|
|
@@ -465,4 +583,4 @@ const upload = async function (items, dirPath, options = {}) {
|
|
|
465
583
|
});
|
|
466
584
|
};
|
|
467
585
|
|
|
468
|
-
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
|
@@ -350,43 +350,43 @@ class KV {
|
|
|
350
350
|
let pattern;
|
|
351
351
|
let returnValues = false;
|
|
352
352
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
+
}
|
|
361
378
|
}
|
|
362
379
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if ( (args && args.length === 1 && typeof args[0] === 'string') || (args && args.length === 2 && typeof args[0] === 'string' && args[1] === true) ) {
|
|
366
|
-
pattern = args[0];
|
|
380
|
+
if ( ! returnValues ) {
|
|
381
|
+
options.as = 'keys';
|
|
367
382
|
}
|
|
368
383
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
// consider both the key and the value
|
|
374
|
-
if ( ! returnValues ) {
|
|
375
|
-
let keys = res.filter((key) => {
|
|
376
|
-
return globMatch(pattern, key);
|
|
377
|
-
});
|
|
378
|
-
return keys;
|
|
379
|
-
} else {
|
|
380
|
-
let keys = res.filter((key_value_pair) => {
|
|
381
|
-
return globMatch(pattern, key_value_pair.key);
|
|
382
|
-
});
|
|
383
|
-
return keys;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
384
|
+
const normalizedPattern = normalizeListPattern(pattern);
|
|
385
|
+
if ( normalizedPattern ) {
|
|
386
|
+
options.pattern = normalizedPattern;
|
|
387
|
+
}
|
|
386
388
|
|
|
387
|
-
|
|
388
|
-
},
|
|
389
|
-
}).call(this, options);
|
|
389
|
+
return utils.make_driver_method([], 'puter-kvstore', undefined, 'list').call(this, options);
|
|
390
390
|
};
|
|
391
391
|
|
|
392
392
|
// resolve to 'true' on success, or rejects with an error on failure
|
|
@@ -397,18 +397,19 @@ class KV {
|
|
|
397
397
|
clear = this.flush;
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
function
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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;
|
|
412
413
|
}
|
|
413
414
|
|
|
414
415
|
export default KV;
|
package/types/modules/kv.d.ts
CHANGED
|
@@ -19,6 +19,22 @@ export interface KVAddPath {
|
|
|
19
19
|
[path: string]: KVValue | KVValue[];
|
|
20
20
|
}
|
|
21
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
|
+
|
|
22
38
|
export class KV {
|
|
23
39
|
readonly MAX_KEY_SIZE: number;
|
|
24
40
|
readonly MAX_VALUE_SIZE: number;
|
|
@@ -36,6 +52,10 @@ export class KV {
|
|
|
36
52
|
list (pattern?: string, returnValues?: false): Promise<string[]>;
|
|
37
53
|
list<T = unknown>(pattern: string, returnValues: true): Promise<KVPair<T>[]>;
|
|
38
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>[]>;
|
|
39
59
|
flush (): Promise<boolean>;
|
|
40
60
|
clear (): Promise<boolean>;
|
|
41
61
|
}
|