@heyputer/puter.js 2.1.15 → 2.2.4

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.
Files changed (40) hide show
  1. package/dist/puter.cjs +2 -2
  2. package/index.d.ts +0 -2
  3. package/package.json +2 -3
  4. package/src/index.js +100 -82
  5. package/src/lib/utils.js +42 -55
  6. package/src/modules/AI.js +8 -204
  7. package/src/modules/Apps.js +42 -11
  8. package/src/modules/Auth.js +6 -5
  9. package/src/modules/Debug.js +4 -4
  10. package/src/modules/Drivers.js +12 -17
  11. package/src/modules/FileSystem/index.js +9 -24
  12. package/src/modules/Hosting.js +5 -4
  13. package/src/modules/KV.js +67 -11
  14. package/src/modules/OS.js +6 -5
  15. package/src/modules/Perms.js +4 -3
  16. package/src/modules/PuterDialog.js +2 -2
  17. package/src/modules/UI.js +44 -28
  18. package/src/modules/UsageLimitDialog.js +208 -0
  19. package/types/modules/ai.d.ts +0 -10
  20. package/types/modules/apps.d.ts +24 -15
  21. package/types/modules/auth.d.ts +3 -1
  22. package/types/modules/filesystem.d.ts +0 -2
  23. package/types/modules/kv.d.ts +10 -0
  24. package/types/modules/networking.d.ts +1 -1
  25. package/types/modules/os.d.ts +2 -7
  26. package/types/modules/ui.d.ts +10 -7
  27. package/types/modules/util.d.ts +0 -1
  28. package/types/modules/workers.d.ts +9 -10
  29. package/types/puter.d.ts +1 -7
  30. package/src/lib/filesystem/APIFS.js +0 -65
  31. package/src/lib/filesystem/CacheFS.js +0 -243
  32. package/src/lib/filesystem/PostMessageFS.js +0 -40
  33. package/src/lib/filesystem/definitions.js +0 -40
  34. package/src/modules/Threads.js +0 -72
  35. package/src/services/APIAccess.js +0 -46
  36. package/src/services/FSRelay.js +0 -20
  37. package/src/services/Filesystem.js +0 -137
  38. package/src/services/NoPuterYet.js +0 -20
  39. package/src/services/XDIncoming.js +0 -44
  40. package/types/modules/threads.d.ts +0 -27
package/src/modules/AI.js CHANGED
@@ -11,33 +11,6 @@ const normalizeTTSProvider = (value) => {
11
11
  return value;
12
12
  };
13
13
 
14
- const TOGETHER_IMAGE_MODEL_PREFIXES = [
15
- 'black-forest-labs/',
16
- 'stabilityai/',
17
- 'togethercomputer/',
18
- 'playgroundai/',
19
- 'runwayml/',
20
- 'lightricks/',
21
- 'sg161222/',
22
- 'wavymulder/',
23
- 'prompthero/',
24
- 'bytedance-seed/',
25
- 'hidream-ai/',
26
- 'lykon/',
27
- 'qwen/',
28
- 'rundiffusion/',
29
- 'google/',
30
- 'ideogram/',
31
- ];
32
-
33
- const TOGETHER_IMAGE_MODEL_KEYWORDS = [
34
- 'flux',
35
- 'kling',
36
- 'sd3',
37
- 'stable-diffusion',
38
- 'kolors',
39
- ];
40
-
41
14
  const TOGETHER_VIDEO_MODEL_PREFIXES = [
42
15
  'minimax/',
43
16
  'google/',
@@ -57,10 +30,11 @@ class AI {
57
30
  * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
58
31
  * @param {string} appID - ID of the app to use.
59
32
  */
60
- constructor (context) {
61
- this.authToken = context.authToken;
62
- this.APIOrigin = context.APIOrigin;
63
- this.appID = context.appID;
33
+ constructor (puter) {
34
+ this.puter = puter;
35
+ this.authToken = puter.authToken;
36
+ this.APIOrigin = puter.APIOrigin;
37
+ this.appID = puter.appID;
64
38
  }
65
39
 
66
40
  /**
@@ -666,7 +640,7 @@ class AI {
666
640
  let testMode = false;
667
641
 
668
642
  // default driver is openai-completion
669
- let driver = 'openai-completion';
643
+ let driver = 'ai-chat';
670
644
 
671
645
  // Check that the argument is not undefined or null
672
646
  if ( ! args ) {
@@ -765,160 +739,6 @@ class AI {
765
739
  // convert undefined to empty string so that .startsWith works
766
740
  requestParams.model = requestParams.model ?? '';
767
741
 
768
- // If model starts with "anthropic/", remove it
769
- // later on we should standardize the model names to [vendor]/[model]
770
- // for example: "claude-3-5-sonnet" should become "anthropic/claude-3-5-sonnet"
771
- // but for now, we want to keep the old behavior
772
- // so we remove the "anthropic/" prefix if it exists
773
- if ( requestParams.model && requestParams.model.startsWith('anthropic/') ) {
774
- requestParams.model = requestParams.model.replace('anthropic/', '');
775
- }
776
-
777
- // convert to the correct model name if necessary
778
- if ( requestParams.model === 'claude-3-5-sonnet' ) {
779
- requestParams.model = 'claude-3-5-sonnet-latest';
780
- }
781
- if ( requestParams.model === 'claude-3-7-sonnet' || requestParams.model === 'claude' ) {
782
- requestParams.model = 'claude-3-7-sonnet-latest';
783
- }
784
- if ( requestParams.model === 'claude-sonnet-4' || requestParams.model === 'claude-sonnet-4-latest' ) {
785
- requestParams.model = 'claude-sonnet-4-20250514';
786
- }
787
- if ( requestParams.model === 'claude-opus-4' || requestParams.model === 'claude-opus-4-latest' ) {
788
- requestParams.model = 'claude-opus-4-20250514';
789
- }
790
- if ( requestParams.model === 'mistral' ) {
791
- requestParams.model = 'mistral-large-latest';
792
- }
793
- if ( requestParams.model === 'groq' ) {
794
- requestParams.model = 'llama3-8b-8192';
795
- }
796
- if ( requestParams.model === 'deepseek' ) {
797
- requestParams.model = 'deepseek-chat';
798
- }
799
-
800
- // o1-mini to openrouter:openai/o1-mini
801
- if ( requestParams.model === 'o1-mini' ) {
802
- requestParams.model = 'openrouter:openai/o1-mini';
803
- }
804
-
805
- // if a model is prepended with "openai/", remove it
806
- if ( requestParams.model && requestParams.model.startsWith('openai/') ) {
807
- requestParams.model = requestParams.model.replace('openai/', '');
808
- driver = 'openai-completion';
809
- }
810
- // For the following providers, we need to prepend "openrouter:" to the model name so that the backend driver can handle it
811
- if (
812
- requestParams.model.startsWith('agentica-org/') ||
813
- requestParams.model.startsWith('ai21/') ||
814
- requestParams.model.startsWith('aion-labs/') ||
815
- requestParams.model.startsWith('alfredpros/') ||
816
- requestParams.model.startsWith('allenai/') ||
817
- requestParams.model.startsWith('alpindale/') ||
818
- requestParams.model.startsWith('amazon/') ||
819
- requestParams.model.startsWith('anthracite-org/') ||
820
- requestParams.model.startsWith('arcee-ai/') ||
821
- requestParams.model.startsWith('arliai/') ||
822
- requestParams.model.startsWith('baidu/') ||
823
- requestParams.model.startsWith('bytedance/') ||
824
- requestParams.model.startsWith('cognitivecomputations/') ||
825
- requestParams.model.startsWith('cohere/') ||
826
- requestParams.model.startsWith('deepseek/') ||
827
- requestParams.model.startsWith('eleutherai/') ||
828
- requestParams.model.startsWith('google/') ||
829
- requestParams.model.startsWith('gryphe/') ||
830
- requestParams.model.startsWith('inception/') ||
831
- requestParams.model.startsWith('infermatic/') ||
832
- requestParams.model.startsWith('liquid/') ||
833
- requestParams.model.startsWith('mancer/') ||
834
- requestParams.model.startsWith('meta-llama/') ||
835
- requestParams.model.startsWith('microsoft/') ||
836
- requestParams.model.startsWith('minimax/') ||
837
- requestParams.model.startsWith('mistralai/') ||
838
- requestParams.model.startsWith('moonshotai/') ||
839
- requestParams.model.startsWith('morph/') ||
840
- requestParams.model.startsWith('neversleep/') ||
841
- requestParams.model.startsWith('nousresearch/') ||
842
- requestParams.model.startsWith('nvidia/') ||
843
- requestParams.model.startsWith('openrouter/') ||
844
- requestParams.model.startsWith('perplexity/') ||
845
- requestParams.model.startsWith('pygmalionai/') ||
846
- requestParams.model.startsWith('qwen/') ||
847
- requestParams.model.startsWith('raifle/') ||
848
- requestParams.model.startsWith('rekaai/') ||
849
- requestParams.model.startsWith('sao10k/') ||
850
- requestParams.model.startsWith('sarvamai/') ||
851
- requestParams.model.startsWith('scb10x/') ||
852
- requestParams.model.startsWith('shisa-ai/') ||
853
- requestParams.model.startsWith('sophosympatheia/') ||
854
- requestParams.model.startsWith('switchpoint/') ||
855
- requestParams.model.startsWith('tencent/') ||
856
- requestParams.model.startsWith('thedrummer/') ||
857
- requestParams.model.startsWith('thudm/') ||
858
- requestParams.model.startsWith('tngtech/') ||
859
- requestParams.model.startsWith('undi95/') ||
860
- requestParams.model.startsWith('x-ai/') ||
861
- requestParams.model.startsWith('z-ai/')
862
- ) {
863
- requestParams.model = `openrouter:${ requestParams.model}`;
864
- }
865
-
866
- // map model to the appropriate driver
867
- if ( !requestParams.model || requestParams.model.startsWith('gpt-') ) {
868
- driver = 'openai-completion';
869
- } else if (
870
- requestParams.model.startsWith('claude-')
871
- ) {
872
- driver = 'claude';
873
- } else if ( requestParams.model === 'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo' || requestParams.model === 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo' || requestParams.model === 'meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo' || requestParams.model === 'google/gemma-2-27b-it' ) {
874
- driver = 'together-ai';
875
- } else if ( requestParams.model.startsWith('mistral-') || requestParams.model.startsWith('codestral-') || requestParams.model.startsWith('pixtral-') || requestParams.model.startsWith('magistral-') || requestParams.model.startsWith('devstral-') || requestParams.model.startsWith('mistral-ocr-') || requestParams.model.startsWith('open-mistral-') ) {
876
- driver = 'mistral';
877
- } else if ( [
878
- 'distil-whisper-large-v3-en',
879
- 'gemma2-9b-it',
880
- 'gemma-7b-it',
881
- 'llama-3.1-70b-versatile',
882
- 'llama-3.1-8b-instant',
883
- 'llama3-70b-8192',
884
- 'llama3-8b-8192',
885
- 'llama3-groq-70b-8192-tool-use-preview',
886
- 'llama3-groq-8b-8192-tool-use-preview',
887
- 'llama-guard-3-8b',
888
- 'mixtral-8x7b-32768',
889
- 'whisper-large-v3',
890
- ].includes(requestParams.model) ) {
891
- driver = 'groq';
892
- } else if ( requestParams.model === 'grok-beta' ) {
893
- driver = 'xai';
894
- }
895
- else if ( requestParams.model.startsWith('grok-') ) {
896
- driver = 'openrouter';
897
- }
898
- else if (
899
- requestParams.model === 'deepseek-chat' ||
900
- requestParams.model === 'deepseek-reasoner'
901
- ) {
902
- driver = 'deepseek';
903
- }
904
- else if (
905
- requestParams.model === 'gemini-1.5-flash' ||
906
- requestParams.model === 'gemini-2.0-flash' ||
907
- requestParams.model === 'gemini-2.5-flash' ||
908
- requestParams.model === 'gemini-2.5-flash-lite' ||
909
- requestParams.model === 'gemini-2.0-flash-lite' ||
910
- requestParams.model === 'gemini-3-pro-preview' ||
911
- requestParams.model === 'gemini-2.5-pro'
912
- ) {
913
- driver = 'gemini';
914
- }
915
- else if ( requestParams.model.startsWith('openrouter:') ) {
916
- driver = 'openrouter';
917
- }
918
- else if ( requestParams.model.startsWith('ollama:') ) {
919
- driver = 'ollama';
920
- }
921
-
922
742
  // stream flag from userParams
923
743
  if ( userParams.stream !== undefined && typeof userParams.stream === 'boolean' ) {
924
744
  requestParams.stream = userParams.stream;
@@ -1020,27 +840,11 @@ class AI {
1020
840
  }
1021
841
 
1022
842
  const driverHint = typeof options.driver === 'string' ? options.driver : undefined;
1023
- const providerRaw = typeof options.provider === 'string'
1024
- ? options.provider
1025
- : (typeof options.service === 'string' ? options.service : undefined);
1026
- const providerHint = typeof providerRaw === 'string' ? providerRaw.toLowerCase() : undefined;
1027
- const modelLower = typeof options.model === 'string' ? options.model.toLowerCase() : '';
1028
-
1029
- const looksLikeTogetherModel =
1030
- typeof options.model === 'string' &&
1031
- (TOGETHER_IMAGE_MODEL_PREFIXES.some(prefix => modelLower.startsWith(prefix)) ||
1032
- TOGETHER_IMAGE_MODEL_KEYWORDS.some(keyword => modelLower.includes(keyword)));
1033
843
 
1034
844
  if ( driverHint ) {
1035
845
  AIService = driverHint;
1036
- } else if ( providerHint === 'gemini' ) {
1037
- AIService = 'gemini-image-generation';
1038
- } else if ( providerHint === 'together' || providerHint === 'together-ai' ) {
1039
- AIService = 'together-image-generation';
1040
- } else if (options.model === 'gemini-2.5-flash-image-preview' || options.model === "gemini-3-pro-image-preview" ) {
1041
- AIService = 'gemini-image-generation';
1042
- } else if ( looksLikeTogetherModel ) {
1043
- AIService = 'together-image-generation';
846
+ } else {
847
+ AIService = 'ai-image';
1044
848
  }
1045
849
  // Call the original chat.complete method
1046
850
  return await utils.make_driver_method(['prompt'], 'puter-image-generation', AIService, 'generate', {
@@ -9,10 +9,42 @@ class Apps {
9
9
  * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
10
10
  * @param {string} appID - ID of the app to use.
11
11
  */
12
- constructor (context) {
13
- this.authToken = context.authToken;
14
- this.APIOrigin = context.APIOrigin;
15
- this.appID = context.appID;
12
+ constructor (puter) {
13
+ this.puter = puter;
14
+ this.authToken = puter.authToken;
15
+ this.APIOrigin = puter.APIOrigin;
16
+ this.appID = puter.appID;
17
+ }
18
+
19
+ #addUserIterationToApp (app) {
20
+ app.getUsers = async (params) => {
21
+ params = params ?? {};
22
+ return (await puter.drivers.call('app-telemetry', 'app-telemetry', 'get_users', { app_uuid: app.uid, limit: params.limit, offset: params.offset })).result;
23
+ };
24
+ app.users = async function* (pageSize = 100) {
25
+ let offset = 0;
26
+
27
+ while ( true ) {
28
+ const users = await app.getUsers({ limit: pageSize, offset });
29
+
30
+ if ( !users || users.length === 0 ) return;
31
+
32
+ for ( const user of users ) {
33
+ yield user;
34
+ }
35
+
36
+ offset += users.length;
37
+ if ( users.length < pageSize ) return;
38
+ }
39
+ };
40
+ return app;
41
+ }
42
+
43
+ #addUserIterationToApps (apps) {
44
+ apps.forEach(app => {
45
+ this.#addUserIterationToApp(app);
46
+ });
47
+ return apps;
16
48
  }
17
49
 
18
50
  /**
@@ -47,7 +79,7 @@ class Apps {
47
79
 
48
80
  options.predicate = ['user-can-edit'];
49
81
 
50
- return utils.make_driver_method(['uid'], 'puter-apps', undefined, 'select').call(this, options);
82
+ return this.#addUserIterationToApps(await utils.make_driver_method(['uid'], 'puter-apps', 'es:app', 'select').call(this, options));
51
83
  };
52
84
 
53
85
  create = async (...args) => {
@@ -111,7 +143,7 @@ class Apps {
111
143
  }
112
144
 
113
145
  // Call the original chat.complete method
114
- return await utils.make_driver_method(['object'], 'puter-apps', undefined, 'create').call(this, options);
146
+ return this.#addUserIterationToApp(await utils.make_driver_method(['object'], 'puter-apps', 'es:app', 'create').call(this, options));
115
147
  };
116
148
 
117
149
  update = async (...args) => {
@@ -137,7 +169,7 @@ class Apps {
137
169
  }
138
170
 
139
171
  // Call the original chat.complete method
140
- return await utils.make_driver_method(['object'], 'puter-apps', undefined, 'update').call(this, options);
172
+ return this.#addUserIterationToApp(await utils.make_driver_method(['object'], 'puter-apps', 'es:app', 'update').call(this, options));
141
173
  };
142
174
 
143
175
  get = async (...args) => {
@@ -158,8 +190,7 @@ class Apps {
158
190
  if ( typeof args[0] === 'object' && args[0] !== null ) {
159
191
  options.params = args[0];
160
192
  }
161
-
162
- return utils.make_driver_method(['uid'], 'puter-apps', undefined, 'read').call(this, options);
193
+ return this.#addUserIterationToApp(await utils.make_driver_method(['uid'], 'puter-apps', 'es:app', 'read').call(this, options));
163
194
  };
164
195
 
165
196
  delete = async (...args) => {
@@ -169,7 +200,7 @@ class Apps {
169
200
  if ( Array.isArray(args) && typeof args[0] === 'string' ) {
170
201
  options = { id: { name: args[0] } };
171
202
  }
172
- return utils.make_driver_method(['uid'], 'puter-apps', undefined, 'delete').call(this, options);
203
+ return utils.make_driver_method(['uid'], 'puter-apps', 'es:app', 'delete').call(this, options);
173
204
  };
174
205
 
175
206
  getDeveloperProfile = function (...args) {
@@ -212,4 +243,4 @@ class Apps {
212
243
  };
213
244
  }
214
245
 
215
- export default Apps;
246
+ export default Apps;
@@ -13,10 +13,11 @@ class Auth {
13
13
  * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
14
14
  * @param {string} appID - ID of the app to use.
15
15
  */
16
- constructor (context) {
17
- this.authToken = context.authToken;
18
- this.APIOrigin = context.APIOrigin;
19
- this.appID = context.appID;
16
+ constructor (puter) {
17
+ this.puter = puter;
18
+ this.authToken = puter.authToken;
19
+ this.APIOrigin = puter.APIOrigin;
20
+ this.appID = puter.appID;
20
21
  }
21
22
 
22
23
  /**
@@ -291,4 +292,4 @@ class Auth {
291
292
  }
292
293
  }
293
294
 
294
- export default Auth;
295
+ export default Auth;
@@ -1,6 +1,6 @@
1
1
  export class Debug {
2
- constructor (context, parameters) {
3
- this.context = context;
2
+ constructor (puter, parameters) {
3
+ this.puter = puter;
4
4
  this.parameters = parameters;
5
5
 
6
6
  this._init();
@@ -14,7 +14,7 @@ export class Debug {
14
14
  enabled_logs = enabled_logs.split(';');
15
15
  for ( const category of enabled_logs ) {
16
16
  if ( category === '' ) continue;
17
- this.context.puter.logger.on(category);
17
+ this.puter.logger.on(category);
18
18
  }
19
19
 
20
20
  globalThis.addEventListener('message', async e => {
@@ -32,7 +32,7 @@ export class Debug {
32
32
 
33
33
  if ( e.data.cmd === 'log.on' ) {
34
34
  console.log('Got instruction to turn logs on!');
35
- this.context.puter.logger.on(e.data.category);
35
+ this.puter.logger.on(e.data.category);
36
36
  }
37
37
  });
38
38
  }
@@ -1,6 +1,7 @@
1
1
  class FetchDriverCallBackend {
2
- constructor ({ context }) {
3
- this.context = context;
2
+ constructor ({ getAPIOrigin, getAuthToken }) {
3
+ this.getAPIOrigin = getAPIOrigin;
4
+ this.getAuthToken = getAuthToken;
4
5
  this.response_handlers = this.constructor.response_handlers;
5
6
  }
6
7
 
@@ -32,7 +33,7 @@ class FetchDriverCallBackend {
32
33
 
33
34
  async call ({ driver, method_name, parameters }) {
34
35
  try {
35
- const resp = await fetch(`${this.context.APIOrigin}/drivers/call`, {
36
+ const resp = await fetch(`${this.getAPIOrigin()}/drivers/call`, {
36
37
  headers: {
37
38
  'Content-Type': 'text/plain;actually=json',
38
39
  },
@@ -44,7 +45,7 @@ class FetchDriverCallBackend {
44
45
  : {}),
45
46
  method: method_name,
46
47
  args: parameters,
47
- auth_token: this.context.authToken,
48
+ auth_token: this.getAuthToken(),
48
49
  }),
49
50
  });
50
51
 
@@ -131,22 +132,15 @@ class Drivers {
131
132
  * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
132
133
  * @param {string} appID - ID of the app to use.
133
134
  */
134
- constructor (context) {
135
- this.authToken = context.authToken;
136
- this.APIOrigin = context.APIOrigin;
137
- this.appID = context.appID;
135
+ constructor (puter) {
136
+ this.puter = puter;
137
+ this.authToken = puter.authToken;
138
+ this.APIOrigin = puter.APIOrigin;
139
+ this.appID = puter.appID;
138
140
 
139
141
  // Driver-specific
140
142
  this.drivers_ = {};
141
143
 
142
- // TODO: replace with `context` from constructor and test site login
143
- this.context = {};
144
- Object.defineProperty(this.context, 'authToken', {
145
- get: () => this.authToken,
146
- });
147
- Object.defineProperty(this.context, 'APIOrigin', {
148
- get: () => this.APIOrigin,
149
- });
150
144
  }
151
145
 
152
146
  _init ({ puter }) {
@@ -226,7 +220,8 @@ class Drivers {
226
220
 
227
221
  return this.drivers_[key] = new Driver ({
228
222
  call_backend: new FetchDriverCallBackend({
229
- context: this.context,
223
+ getAPIOrigin: () => this.APIOrigin,
224
+ getAuthToken: () => this.authToken,
230
225
  }),
231
226
  // iface: interfaces[iface_name],
232
227
  iface_name,
@@ -20,14 +20,11 @@ import stat from './operations/stat.js';
20
20
  import symlink from './operations/symlink.js';
21
21
  import upload from './operations/upload.js';
22
22
  import write from './operations/write.js';
23
- // Why is this called deleteFSEntry instead of just delete? because delete is
24
- // a reserved keyword in javascript
25
- import { AdvancedBase } from '../../../../putility/index.js';
26
23
  import FSItem from '../FSItem.js';
27
24
  import deleteFSEntry from './operations/deleteFSEntry.js';
28
25
  import getReadURL from './operations/getReadUrl.js';
29
26
 
30
- export class PuterJSFileSystemModule extends AdvancedBase {
27
+ export class PuterJSFileSystemModule {
31
28
 
32
29
  space = space;
33
30
  mkdir = mkdir;
@@ -48,17 +45,6 @@ export class PuterJSFileSystemModule extends AdvancedBase {
48
45
 
49
46
  FSItem = FSItem;
50
47
 
51
- static NARI_METHODS = {
52
- // stat: {
53
- // positional: ['path'],
54
- // firstarg_options: true,
55
- // async fn (parameters) {
56
- // const svc_fs = await this.context.services.aget('filesystem');
57
- // return svc_fs.filesystem.stat(parameters);
58
- // }
59
- // },
60
- };
61
-
62
48
  /**
63
49
  * Creates a new instance with the given authentication token, API origin, and app ID,
64
50
  * and connects to the socket.
@@ -68,12 +54,11 @@ export class PuterJSFileSystemModule extends AdvancedBase {
68
54
  * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
69
55
  * @param {string} appID - ID of the app to use.
70
56
  */
71
- constructor (context) {
72
- super();
73
- this.authToken = context.authToken;
74
- this.APIOrigin = context.APIOrigin;
75
- this.appID = context.appID;
76
- this.context = context;
57
+ constructor (puter) {
58
+ this.puter = puter;
59
+ this.authToken = puter.authToken;
60
+ this.APIOrigin = puter.APIOrigin;
61
+ this.appID = puter.appID;
77
62
  this.cacheUpdateTimer = null;
78
63
  // Connect socket.
79
64
  this.initializeSocket();
@@ -107,7 +92,7 @@ export class PuterJSFileSystemModule extends AdvancedBase {
107
92
  auth: {
108
93
  auth_token: this.authToken,
109
94
  },
110
- autoUnref: this.context.env === 'nodejs',
95
+ autoUnref: this.puter.env === 'nodejs',
111
96
  });
112
97
 
113
98
  this.bindSocketEvents();
@@ -212,7 +197,7 @@ export class PuterJSFileSystemModule extends AdvancedBase {
212
197
  this.authToken = authToken;
213
198
 
214
199
  // Check cache timestamp and purge if needed (only in GUI environment)
215
- if ( this.context.env === 'gui' ) {
200
+ if ( this.puter.env === 'gui' ) {
216
201
  this.checkCacheAndPurge();
217
202
  // Start background task to update LAST_VALID_TS every 1 second
218
203
  this.startCacheUpdateTimer();
@@ -305,7 +290,7 @@ export class PuterJSFileSystemModule extends AdvancedBase {
305
290
  * @returns {void}
306
291
  */
307
292
  startCacheUpdateTimer () {
308
- if ( this.context.env !== 'gui' ) {
293
+ if ( this.puter.env !== 'gui' ) {
309
294
  return;
310
295
  }
311
296
 
@@ -10,10 +10,11 @@ class Hosting {
10
10
  * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
11
11
  * @param {string} appID - ID of the app to use.
12
12
  */
13
- constructor (context) {
14
- this.authToken = context.authToken;
15
- this.APIOrigin = context.APIOrigin;
16
- this.appID = context.appID;
13
+ constructor (puter) {
14
+ this.puter = puter;
15
+ this.authToken = puter.authToken;
16
+ this.APIOrigin = puter.APIOrigin;
17
+ this.appID = puter.appID;
17
18
  }
18
19
 
19
20
  /**
package/src/modules/KV.js CHANGED
@@ -1,6 +1,15 @@
1
- import { TeePromise } from '@heyputer/putility/src/libs/promise.js';
2
1
  import * as utils from '../lib/utils.js';
3
2
 
3
+ const createDeferred = () => {
4
+ let resolve;
5
+ let reject;
6
+ const promise = new Promise((res, rej) => {
7
+ resolve = res;
8
+ reject = rej;
9
+ });
10
+ return { promise, resolve, reject };
11
+ };
12
+
4
13
  const gui_cache_keys = [
5
14
  'has_set_default_app_user_permissions',
6
15
  'window_sidebar_width',
@@ -29,15 +38,16 @@ class KV {
29
38
  * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
30
39
  * @param {string} appID - ID of the app to use.
31
40
  */
32
- constructor (context) {
33
- this.authToken = context.authToken;
34
- this.APIOrigin = context.APIOrigin;
35
- this.appID = context.appID;
36
-
37
- this.gui_cached = new TeePromise();
38
- this.gui_cache_init = new TeePromise();
41
+ constructor (puter) {
42
+ this.puter = puter;
43
+ this.authToken = puter.authToken;
44
+ this.APIOrigin = puter.APIOrigin;
45
+ this.appID = puter.appID;
46
+
47
+ this.gui_cached = createDeferred();
48
+ this.gui_cache_init = createDeferred();
39
49
  (async () => {
40
- await this.gui_cache_init;
50
+ await this.gui_cache_init.promise;
41
51
  this.gui_cache_init = null;
42
52
  const resp = await fetch(`${this.APIOrigin}/drivers/call`, {
43
53
  method: 'POST',
@@ -142,7 +152,7 @@ class KV {
142
152
  this.gui_cached !== null
143
153
  ) {
144
154
  this.gui_cache_init && this.gui_cache_init.resolve();
145
- const cache = await this.gui_cached;
155
+ const cache = await this.gui_cached.promise;
146
156
  return cache[args[0]];
147
157
  }
148
158
 
@@ -202,6 +212,52 @@ class KV {
202
212
  return utils.make_driver_method(['key'], 'puter-kvstore', undefined, 'decr').call(this, options);
203
213
  };
204
214
 
215
+ add = async (...args) => {
216
+ let options = {};
217
+
218
+ // arguments are required
219
+ if ( !args || args.length === 0 ) {
220
+ throw ({ message: 'Arguments are required', code: 'arguments_required' });
221
+ }
222
+
223
+ options.key = args[0];
224
+ const provided = args[1];
225
+ const isPathMap = provided && typeof provided === 'object' && !Array.isArray(provided);
226
+ options.pathAndValueMap = provided === undefined ? { '': 1 } : isPathMap ? provided : { '': provided };
227
+
228
+ // key size cannot be larger than MAX_KEY_SIZE
229
+ if ( options.key.length > this.MAX_KEY_SIZE ) {
230
+ throw ({ message: `Key size cannot be larger than ${this.MAX_KEY_SIZE}`, code: 'key_too_large' });
231
+ }
232
+
233
+ return utils.make_driver_method(['key'], 'puter-kvstore', undefined, 'add').call(this, options);
234
+ };
235
+
236
+ update = utils.make_driver_method(['key', 'pathAndValueMap', 'ttl'], 'puter-kvstore', undefined, 'update', {
237
+ preprocess: (args) => {
238
+ if ( args.key === undefined || args.key === null ) {
239
+ throw { message: 'Key cannot be undefined', code: 'key_undefined' };
240
+ }
241
+ if ( args.key.length > this.MAX_KEY_SIZE ) {
242
+ throw { message: `Key size cannot be larger than ${this.MAX_KEY_SIZE}`, code: 'key_too_large' };
243
+ }
244
+ if ( args.pathAndValueMap === undefined || args.pathAndValueMap === null || Array.isArray(args.pathAndValueMap) || typeof args.pathAndValueMap !== 'object' ) {
245
+ throw { message: 'pathAndValueMap must be an object', code: 'path_map_invalid' };
246
+ }
247
+ if ( Object.keys(args.pathAndValueMap).length === 0 ) {
248
+ throw { message: 'pathAndValueMap cannot be empty', code: 'path_map_invalid' };
249
+ }
250
+ if ( args.ttl !== undefined && args.ttl !== null ) {
251
+ const ttl = Number(args.ttl);
252
+ if ( Number.isNaN(ttl) ) {
253
+ throw { message: 'ttl must be a number', code: 'ttl_invalid' };
254
+ }
255
+ args.ttl = ttl;
256
+ }
257
+ return args;
258
+ },
259
+ });
260
+
205
261
  /**
206
262
  * Set a time to live (in seconds) on a key. After the time to live has expired, the key will be deleted.
207
263
  * Prefer this over expireAt if you want timestamp to be set by the server, to avoid issues with clock drift.
@@ -323,4 +379,4 @@ function globMatch (pattern, str) {
323
379
  return re.test(str);
324
380
  }
325
381
 
326
- export default KV;
382
+ export default KV;