@neonwilderness/ecjsonstorage 1.0.1

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/.env.example ADDED
@@ -0,0 +1,3 @@
1
+ JSONAPI=apikey
2
+ JSONSEC=securitykey
3
+ JSONBIN=binkey
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [1.0.0] - 2026-09-29
8
+ - Initial release
9
+
10
+ ## [1.0.1] - 2026-09-30
11
+ - Fix package.json main,exports,types
12
+ - Enhance readme
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NeonWilderness
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # About ECJsonStorage
2
+ ECJsonStorage is a TS/JS wrapper to the [ExtendsClass JSON Storage API](https://extendsclass.com/json-storage.html) offering of Cyril Bois.
3
+
4
+ For API usage, you need a (free) API-Key for which you can apply [here](https://extendsclass.com/create-account-form).
5
+
6
+ ECJsonStorage enables you to create, update, patch and delete Bins, which store JSON data. There is a usage limit of
7
+ - 10.000 calls per month
8
+ - Bin size limit of 100kb
9
+ - total account limit of 10mb.
10
+
11
+ # Installation
12
+
13
+ - `npm i ecjsonstorage -S` to install the module, which is then both available as CommonJs and ESM version.
14
+
15
+ - Create an .env file in your project directory root and add the following keys:
16
+
17
+ `JSONAPI=your ExtendsClass Api-key`
18
+
19
+ `JSONSEC=your self defined Security-key (recommended)`
20
+
21
+ The Security-key is used to protect access, modification and deletion of a bin. If a Security-key is passed during create, it will also be required to update, patch and delete this bin.
22
+
23
+ The Security-key is also required to request private bins (i.e. bins that were created with `keepPrivate=true`), but not required for public bins.
24
+
25
+ # Usage
26
+
27
+ ## Constructor
28
+
29
+ ```
30
+ import { ECJsonStorage } from 'ecjsonstorage';
31
+ import { config } from 'dotenv-safe';
32
+ config();
33
+
34
+ const json = new ECJsonStorage(process.env.JSONAPI, process.env.JSONSEC);
35
+ ...
36
+ ```
37
+
38
+ ## Create a bin
39
+
40
+ `createBin(payload: object, keepPrivate: boolean)`
41
+
42
+ ```
43
+ const json = new ECJsonStorage(process.env.JSONAPI, process.env.JSONSEC);
44
+ const payload = { name: 'Neon', profession: 'IT-Consultant' };
45
+ const res = await json.createBin(payload, true);
46
+ console.log(res);
47
+ ```
48
+
49
+ Further Documentation/Usage details will follow swiftly; for now you may wanna consult the [TS types file](dist/index.d.cts).
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var a=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var y=(i,t)=>{for(var e in t)a(i,e,{get:t[e],enumerable:!0})},l=(i,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of u(t))!h.call(i,s)&&s!==e&&a(i,s,{get:()=>t[s],enumerable:!(r=p(t,s))||r.enumerable});return i};var d=i=>l(a({},"__esModule",{value:!0}),i);var g={};y(g,{ECJsonStorage:()=>o});module.exports=d(g);var o=class{url;apiKey;securityKey;monthlyLimit=1e4;constructor(t,e,r){if(this.url=r||"https://json.extendsclass.com/bin",this.apiKey=t||"",this.securityKey=e||"",!this.url||!this.apiKey)throw new Error("Invalid constructor params!");for(;this.url.endsWith("/");)this.url=this.url.slice(0,-1)}validateResponse=async t=>{if(!t.ok)throw new Error(`HTTP error! Status: ${t.status}`);if((t.headers.get("content-type")||"").startsWith("text/html"))throw new Error(`Expected JSON but server returned HTML: "${(await t.text()).slice(0,100)}..."`)};getBins=async()=>{try{let t=await fetch(`${this.url}s`,{method:"GET",headers:{"Api-key":this.apiKey,Accept:"application/json"}});await this.validateResponse(t);let e=Number(t.headers.get("x-counter")),r=await t.json();return{status:"ok",callsDone:e,callsRemaining:this.monthlyLimit-e,bins:r}}catch(t){return console.error("getBins error:",t),{status:"error",statusText:t}}};getStats=async()=>{try{let t=await this.getBins();if(t.status==="error")throw new Error(`HTTP error! Status: ${t.statusText}`);return delete t.bins,t}catch(t){return console.error("getStats error:",t),{status:"error",statusText:t}}};getBin=async t=>{try{if(!t)throw new Error("Bin ID missing!");let e=await fetch(`${this.url}/${t}`,{method:"GET",headers:{"Security-key":this.securityKey,Accept:"application/json"}});return await this.validateResponse(e),{status:"ok",data:await e.json()}}catch(e){return console.error("getBin error:",e),{status:"error",statusText:e}}};createBin=async(t,e)=>{let r=new Headers({"Api-key":this.apiKey,"Security-key":this.securityKey,"Content-Type":"application/json",Accept:"application/json"});r.append("Private",e.toString());try{let s=await fetch(this.url,{method:"POST",headers:r,body:JSON.stringify(t)});await this.validateResponse(s);let{uri:c,bin:n}=await s.json();return n||(n=c.split("/").pop()),{status:"ok",uri:c,bin:n}}catch(s){return console.error("createBin error:",s),{status:"error",statusText:s}}};updateBin=async(t,e)=>{try{let r=await fetch(`${this.url}/${e}`,{method:"PUT",headers:{"Security-key":this.securityKey,"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)});await this.validateResponse(r);let{data:s}=await r.json();return{status:"ok",data:s}}catch(r){return console.error("updateBin error:",r),{status:"error",statusText:r}}};patchBin=async(t,e)=>{try{let r=await fetch(`${this.url}/${e}`,{method:"PATCH",headers:{"Security-key":this.securityKey,"Content-Type":"application/merge-patch+json",Accept:"application/json"},body:JSON.stringify(t)});await this.validateResponse(r);let{data:s}=await r.json();return{status:"ok",data:s}}catch(r){return console.error("patchBin error:",r),{status:"error",statusText:r}}};deleteBin=async t=>{try{let e=await fetch(`${this.url}/${t}`,{method:"DELETE",headers:{"Security-key":this.securityKey,"Content-Type":"application/json",Accept:"application/json"}});await this.validateResponse(e);let{status:r}=await e.json();if(r!==0)throw new Error(`Bin delete failed with error: ${r.message}`);return{status:"ok"}}catch(e){return console.error("deleteBin error:",e),{status:"error",statusText:e}}}};0&&(module.exports={ECJsonStorage});
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\r\n * ESJsonStorage class is a wrapper to the ExtendsClass JSON-Storage API\r\n */\r\nexport type resGetBins = {\r\n status: 'ok';\r\n callsDone: number;\r\n callsRemaining: number;\r\n bins?: string[];\r\n};\r\nexport type resGetStats = Omit<resGetBins, 'bins'>;\r\n\r\nexport type resGetBin = {\r\n status: 'ok';\r\n data: object;\r\n};\r\n\r\nexport type resUpdateBin = {\r\n status: 'ok';\r\n data: string;\r\n};\r\nexport type resPatchBin = resUpdateBin;\r\n\r\nexport type resCreateBin = {\r\n status: 'ok';\r\n uri: string;\r\n bin: string;\r\n};\r\n\r\nexport type resDeleteBin = Omit<resGetBin, 'data'>;\r\n\r\nexport type resError = {\r\n status: 'error';\r\n statusText: string;\r\n};\r\n\r\nexport class ECJsonStorage {\r\n url: string;\r\n apiKey: string;\r\n securityKey: string;\r\n monthlyLimit: number = 10000;\r\n\r\n constructor(apiKey: string, securityKey?: string, url?: string) {\r\n this.url = url || 'https://json.extendsclass.com/bin';\r\n this.apiKey = apiKey || '';\r\n this.securityKey = securityKey || '';\r\n\r\n if (!this.url || !this.apiKey) throw new Error('Invalid constructor params!');\r\n while (this.url.endsWith('/')) {\r\n this.url = this.url.slice(0, -1);\r\n }\r\n }\r\n\r\n /**\r\n * Utility function to validate the response after the class function call\r\n * @param response\r\n */\r\n validateResponse = async (response: Response) => {\r\n if (!response.ok) {\r\n throw new Error(`HTTP error! Status: ${response.status}`);\r\n }\r\n\r\n const contentType = response.headers.get('content-type') || '';\r\n if (contentType.startsWith('text/html')) {\r\n throw new Error(`Expected JSON but server returned HTML: \"${(await response.text()).slice(0, 100)}...\"`);\r\n }\r\n };\r\n\r\n /**\r\n * Get all bin IDs for a certain Api-key\r\n * @returns {object} <resGetBins | resError>\r\n */\r\n getBins = async (): Promise<resGetBins | resError> => {\r\n try {\r\n const response = await fetch(`${this.url}s`, {\r\n method: 'GET',\r\n headers: {\r\n 'Api-key': this.apiKey,\r\n Accept: 'application/json'\r\n }\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n const counter = Number(response.headers.get('x-counter'));\r\n const data = await response.json();\r\n return {\r\n status: 'ok',\r\n callsDone: counter,\r\n callsRemaining: this.monthlyLimit - counter,\r\n bins: data\r\n };\r\n } catch (error) {\r\n console.error('getBins error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Syntactic sugar function for getBins to only return limit counter\r\n * @returns {object} <resGetStats | resError>\r\n */\r\n getStats = async (): Promise<resGetStats | resError> => {\r\n try {\r\n const res = await this.getBins();\r\n if (res.status === 'error') {\r\n throw new Error(`HTTP error! Status: ${(<resError>res).statusText}`);\r\n }\r\n\r\n delete (<resGetBins>res).bins;\r\n return <resGetStats>res;\r\n } catch (error) {\r\n console.error('getStats error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Reads the content of a certain bin ID\r\n * @param bin string Bin ID to read\r\n * @returns {object} <resGetBin | resError>\r\n */\r\n getBin = async (bin: string): Promise<resGetBin | resError> => {\r\n try {\r\n if (!bin) throw new Error('Bin ID missing!');\r\n const response = await fetch(`${this.url}/${bin}`, {\r\n method: 'GET',\r\n headers: {\r\n 'Security-key': this.securityKey,\r\n Accept: 'application/json'\r\n }\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n const data = await response.json();\r\n return {\r\n status: 'ok',\r\n data\r\n };\r\n } catch (error) {\r\n console.error('getBin error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Creates a new bin and saves the given JSON payload to it\r\n * @param payload {object} JSON object to create the bin with\r\n * @param keepPrivate {boolean} TRUE=bin shall be kept private\r\n * @returns {object} <resCreateBin | resError>\r\n */\r\n createBin = async (payload: object, keepPrivate: boolean): Promise<resCreateBin | resError> => {\r\n const headers = new Headers({\r\n 'Api-key': this.apiKey,\r\n 'Security-key': this.securityKey,\r\n 'Content-Type': 'application/json',\r\n Accept: 'application/json'\r\n });\r\n headers.append('Private', keepPrivate.toString());\r\n try {\r\n const response = await fetch(this.url, {\r\n method: 'POST',\r\n headers,\r\n body: JSON.stringify(payload)\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n let { uri, bin } = await response.json();\r\n if (!bin) bin = uri.split('/').pop();\r\n\r\n return {\r\n status: 'ok',\r\n uri,\r\n bin\r\n };\r\n } catch (error) {\r\n console.error('createBin error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Updates a certain bin with the given JSON object\r\n * @param payload {object} JSON object to update the bin with\r\n * @param bin string Bin ID to be updated\r\n * @returns {object} <resUpdateBin | resError>\r\n */\r\n updateBin = async (payload: object, bin: string): Promise<resUpdateBin | resError> => {\r\n try {\r\n const response = await fetch(`${this.url}/${bin}`, {\r\n method: 'PUT',\r\n headers: {\r\n 'Security-key': this.securityKey,\r\n 'Content-Type': 'application/json',\r\n Accept: 'application/json'\r\n },\r\n body: JSON.stringify(payload)\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n const { data } = await response.json();\r\n return {\r\n status: 'ok',\r\n data\r\n };\r\n } catch (error) {\r\n console.error('updateBin error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Patches/inserts/changes a certain bin with the given JSON payload\r\n * ref: \"Partially update JSON\" at https://extendsclass.com/json-storage.html#apiDocumentation\r\n * @param payload {object} JSON object to patch the bin with\r\n * @param bin string Bin ID to be patched\r\n * @returns {object} <resPatchBin | resError>\r\n */\r\n patchBin = async (payload: object, bin: string): Promise<resPatchBin | resError> => {\r\n try {\r\n const response = await fetch(`${this.url}/${bin}`, {\r\n method: 'PATCH',\r\n headers: {\r\n 'Security-key': this.securityKey,\r\n 'Content-Type': 'application/merge-patch+json',\r\n Accept: 'application/json'\r\n },\r\n body: JSON.stringify(payload)\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n const { data } = await response.json();\r\n return {\r\n status: 'ok',\r\n data\r\n };\r\n } catch (error) {\r\n console.error('patchBin error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Deletes a certain bin\r\n * @param bin string Bin ID to be deleted\r\n * @returns {object} <resDeleteBin | resError>\r\n */\r\n deleteBin = async (bin: string): Promise<resDeleteBin | resError> => {\r\n try {\r\n const response = await fetch(`${this.url}/${bin}`, {\r\n method: 'DELETE',\r\n headers: {\r\n 'Security-key': this.securityKey,\r\n 'Content-Type': 'application/json',\r\n Accept: 'application/json'\r\n }\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n const { status } = await response.json();\r\n if (status !== 0) throw new Error(`Bin delete failed with error: ${status.message}`);\r\n\r\n return {\r\n status: 'ok'\r\n };\r\n } catch (error) {\r\n console.error('deleteBin error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n}\r\n"],"mappings":"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","names":["index_exports","__export","ECJsonStorage","__toCommonJS","apiKey","securityKey","url","response","counter","data","error","res","bin","payload","keepPrivate","headers","uri","status"]}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * ESJsonStorage class is a wrapper to the ExtendsClass JSON-Storage API
3
+ */
4
+ type resGetBins = {
5
+ status: 'ok';
6
+ callsDone: number;
7
+ callsRemaining: number;
8
+ bins?: string[];
9
+ };
10
+ type resGetStats = Omit<resGetBins, 'bins'>;
11
+ type resGetBin = {
12
+ status: 'ok';
13
+ data: object;
14
+ };
15
+ type resUpdateBin = {
16
+ status: 'ok';
17
+ data: string;
18
+ };
19
+ type resPatchBin = resUpdateBin;
20
+ type resCreateBin = {
21
+ status: 'ok';
22
+ uri: string;
23
+ bin: string;
24
+ };
25
+ type resDeleteBin = Omit<resGetBin, 'data'>;
26
+ type resError = {
27
+ status: 'error';
28
+ statusText: string;
29
+ };
30
+ declare class ECJsonStorage {
31
+ url: string;
32
+ apiKey: string;
33
+ securityKey: string;
34
+ monthlyLimit: number;
35
+ constructor(apiKey: string, securityKey?: string, url?: string);
36
+ /**
37
+ * Utility function to validate the response after the class function call
38
+ * @param response
39
+ */
40
+ validateResponse: (response: Response) => Promise<void>;
41
+ /**
42
+ * Get all bin IDs for a certain Api-key
43
+ * @returns {object} <resGetBins | resError>
44
+ */
45
+ getBins: () => Promise<resGetBins | resError>;
46
+ /**
47
+ * Syntactic sugar function for getBins to only return limit counter
48
+ * @returns {object} <resGetStats | resError>
49
+ */
50
+ getStats: () => Promise<resGetStats | resError>;
51
+ /**
52
+ * Reads the content of a certain bin ID
53
+ * @param bin string Bin ID to read
54
+ * @returns {object} <resGetBin | resError>
55
+ */
56
+ getBin: (bin: string) => Promise<resGetBin | resError>;
57
+ /**
58
+ * Creates a new bin and saves the given JSON payload to it
59
+ * @param payload {object} JSON object to create the bin with
60
+ * @param keepPrivate {boolean} TRUE=bin shall be kept private
61
+ * @returns {object} <resCreateBin | resError>
62
+ */
63
+ createBin: (payload: object, keepPrivate: boolean) => Promise<resCreateBin | resError>;
64
+ /**
65
+ * Updates a certain bin with the given JSON object
66
+ * @param payload {object} JSON object to update the bin with
67
+ * @param bin string Bin ID to be updated
68
+ * @returns {object} <resUpdateBin | resError>
69
+ */
70
+ updateBin: (payload: object, bin: string) => Promise<resUpdateBin | resError>;
71
+ /**
72
+ * Patches/inserts/changes a certain bin with the given JSON payload
73
+ * ref: "Partially update JSON" at https://extendsclass.com/json-storage.html#apiDocumentation
74
+ * @param payload {object} JSON object to patch the bin with
75
+ * @param bin string Bin ID to be patched
76
+ * @returns {object} <resPatchBin | resError>
77
+ */
78
+ patchBin: (payload: object, bin: string) => Promise<resPatchBin | resError>;
79
+ /**
80
+ * Deletes a certain bin
81
+ * @param bin string Bin ID to be deleted
82
+ * @returns {object} <resDeleteBin | resError>
83
+ */
84
+ deleteBin: (bin: string) => Promise<resDeleteBin | resError>;
85
+ }
86
+
87
+ export { ECJsonStorage, type resCreateBin, type resDeleteBin, type resError, type resGetBin, type resGetBins, type resGetStats, type resPatchBin, type resUpdateBin };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var a=class{url;apiKey;securityKey;monthlyLimit=1e4;constructor(t,e,r){if(this.url=r||"https://json.extendsclass.com/bin",this.apiKey=t||"",this.securityKey=e||"",!this.url||!this.apiKey)throw new Error("Invalid constructor params!");for(;this.url.endsWith("/");)this.url=this.url.slice(0,-1)}validateResponse=async t=>{if(!t.ok)throw new Error(`HTTP error! Status: ${t.status}`);if((t.headers.get("content-type")||"").startsWith("text/html"))throw new Error(`Expected JSON but server returned HTML: "${(await t.text()).slice(0,100)}..."`)};getBins=async()=>{try{let t=await fetch(`${this.url}s`,{method:"GET",headers:{"Api-key":this.apiKey,Accept:"application/json"}});await this.validateResponse(t);let e=Number(t.headers.get("x-counter")),r=await t.json();return{status:"ok",callsDone:e,callsRemaining:this.monthlyLimit-e,bins:r}}catch(t){return console.error("getBins error:",t),{status:"error",statusText:t}}};getStats=async()=>{try{let t=await this.getBins();if(t.status==="error")throw new Error(`HTTP error! Status: ${t.statusText}`);return delete t.bins,t}catch(t){return console.error("getStats error:",t),{status:"error",statusText:t}}};getBin=async t=>{try{if(!t)throw new Error("Bin ID missing!");let e=await fetch(`${this.url}/${t}`,{method:"GET",headers:{"Security-key":this.securityKey,Accept:"application/json"}});return await this.validateResponse(e),{status:"ok",data:await e.json()}}catch(e){return console.error("getBin error:",e),{status:"error",statusText:e}}};createBin=async(t,e)=>{let r=new Headers({"Api-key":this.apiKey,"Security-key":this.securityKey,"Content-Type":"application/json",Accept:"application/json"});r.append("Private",e.toString());try{let s=await fetch(this.url,{method:"POST",headers:r,body:JSON.stringify(t)});await this.validateResponse(s);let{uri:n,bin:i}=await s.json();return i||(i=n.split("/").pop()),{status:"ok",uri:n,bin:i}}catch(s){return console.error("createBin error:",s),{status:"error",statusText:s}}};updateBin=async(t,e)=>{try{let r=await fetch(`${this.url}/${e}`,{method:"PUT",headers:{"Security-key":this.securityKey,"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)});await this.validateResponse(r);let{data:s}=await r.json();return{status:"ok",data:s}}catch(r){return console.error("updateBin error:",r),{status:"error",statusText:r}}};patchBin=async(t,e)=>{try{let r=await fetch(`${this.url}/${e}`,{method:"PATCH",headers:{"Security-key":this.securityKey,"Content-Type":"application/merge-patch+json",Accept:"application/json"},body:JSON.stringify(t)});await this.validateResponse(r);let{data:s}=await r.json();return{status:"ok",data:s}}catch(r){return console.error("patchBin error:",r),{status:"error",statusText:r}}};deleteBin=async t=>{try{let e=await fetch(`${this.url}/${t}`,{method:"DELETE",headers:{"Security-key":this.securityKey,"Content-Type":"application/json",Accept:"application/json"}});await this.validateResponse(e);let{status:r}=await e.json();if(r!==0)throw new Error(`Bin delete failed with error: ${r.message}`);return{status:"ok"}}catch(e){return console.error("deleteBin error:",e),{status:"error",statusText:e}}}};export{a as ECJsonStorage};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\r\n * ESJsonStorage class is a wrapper to the ExtendsClass JSON-Storage API\r\n */\r\nexport type resGetBins = {\r\n status: 'ok';\r\n callsDone: number;\r\n callsRemaining: number;\r\n bins?: string[];\r\n};\r\nexport type resGetStats = Omit<resGetBins, 'bins'>;\r\n\r\nexport type resGetBin = {\r\n status: 'ok';\r\n data: object;\r\n};\r\n\r\nexport type resUpdateBin = {\r\n status: 'ok';\r\n data: string;\r\n};\r\nexport type resPatchBin = resUpdateBin;\r\n\r\nexport type resCreateBin = {\r\n status: 'ok';\r\n uri: string;\r\n bin: string;\r\n};\r\n\r\nexport type resDeleteBin = Omit<resGetBin, 'data'>;\r\n\r\nexport type resError = {\r\n status: 'error';\r\n statusText: string;\r\n};\r\n\r\nexport class ECJsonStorage {\r\n url: string;\r\n apiKey: string;\r\n securityKey: string;\r\n monthlyLimit: number = 10000;\r\n\r\n constructor(apiKey: string, securityKey?: string, url?: string) {\r\n this.url = url || 'https://json.extendsclass.com/bin';\r\n this.apiKey = apiKey || '';\r\n this.securityKey = securityKey || '';\r\n\r\n if (!this.url || !this.apiKey) throw new Error('Invalid constructor params!');\r\n while (this.url.endsWith('/')) {\r\n this.url = this.url.slice(0, -1);\r\n }\r\n }\r\n\r\n /**\r\n * Utility function to validate the response after the class function call\r\n * @param response\r\n */\r\n validateResponse = async (response: Response) => {\r\n if (!response.ok) {\r\n throw new Error(`HTTP error! Status: ${response.status}`);\r\n }\r\n\r\n const contentType = response.headers.get('content-type') || '';\r\n if (contentType.startsWith('text/html')) {\r\n throw new Error(`Expected JSON but server returned HTML: \"${(await response.text()).slice(0, 100)}...\"`);\r\n }\r\n };\r\n\r\n /**\r\n * Get all bin IDs for a certain Api-key\r\n * @returns {object} <resGetBins | resError>\r\n */\r\n getBins = async (): Promise<resGetBins | resError> => {\r\n try {\r\n const response = await fetch(`${this.url}s`, {\r\n method: 'GET',\r\n headers: {\r\n 'Api-key': this.apiKey,\r\n Accept: 'application/json'\r\n }\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n const counter = Number(response.headers.get('x-counter'));\r\n const data = await response.json();\r\n return {\r\n status: 'ok',\r\n callsDone: counter,\r\n callsRemaining: this.monthlyLimit - counter,\r\n bins: data\r\n };\r\n } catch (error) {\r\n console.error('getBins error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Syntactic sugar function for getBins to only return limit counter\r\n * @returns {object} <resGetStats | resError>\r\n */\r\n getStats = async (): Promise<resGetStats | resError> => {\r\n try {\r\n const res = await this.getBins();\r\n if (res.status === 'error') {\r\n throw new Error(`HTTP error! Status: ${(<resError>res).statusText}`);\r\n }\r\n\r\n delete (<resGetBins>res).bins;\r\n return <resGetStats>res;\r\n } catch (error) {\r\n console.error('getStats error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Reads the content of a certain bin ID\r\n * @param bin string Bin ID to read\r\n * @returns {object} <resGetBin | resError>\r\n */\r\n getBin = async (bin: string): Promise<resGetBin | resError> => {\r\n try {\r\n if (!bin) throw new Error('Bin ID missing!');\r\n const response = await fetch(`${this.url}/${bin}`, {\r\n method: 'GET',\r\n headers: {\r\n 'Security-key': this.securityKey,\r\n Accept: 'application/json'\r\n }\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n const data = await response.json();\r\n return {\r\n status: 'ok',\r\n data\r\n };\r\n } catch (error) {\r\n console.error('getBin error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Creates a new bin and saves the given JSON payload to it\r\n * @param payload {object} JSON object to create the bin with\r\n * @param keepPrivate {boolean} TRUE=bin shall be kept private\r\n * @returns {object} <resCreateBin | resError>\r\n */\r\n createBin = async (payload: object, keepPrivate: boolean): Promise<resCreateBin | resError> => {\r\n const headers = new Headers({\r\n 'Api-key': this.apiKey,\r\n 'Security-key': this.securityKey,\r\n 'Content-Type': 'application/json',\r\n Accept: 'application/json'\r\n });\r\n headers.append('Private', keepPrivate.toString());\r\n try {\r\n const response = await fetch(this.url, {\r\n method: 'POST',\r\n headers,\r\n body: JSON.stringify(payload)\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n let { uri, bin } = await response.json();\r\n if (!bin) bin = uri.split('/').pop();\r\n\r\n return {\r\n status: 'ok',\r\n uri,\r\n bin\r\n };\r\n } catch (error) {\r\n console.error('createBin error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Updates a certain bin with the given JSON object\r\n * @param payload {object} JSON object to update the bin with\r\n * @param bin string Bin ID to be updated\r\n * @returns {object} <resUpdateBin | resError>\r\n */\r\n updateBin = async (payload: object, bin: string): Promise<resUpdateBin | resError> => {\r\n try {\r\n const response = await fetch(`${this.url}/${bin}`, {\r\n method: 'PUT',\r\n headers: {\r\n 'Security-key': this.securityKey,\r\n 'Content-Type': 'application/json',\r\n Accept: 'application/json'\r\n },\r\n body: JSON.stringify(payload)\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n const { data } = await response.json();\r\n return {\r\n status: 'ok',\r\n data\r\n };\r\n } catch (error) {\r\n console.error('updateBin error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Patches/inserts/changes a certain bin with the given JSON payload\r\n * ref: \"Partially update JSON\" at https://extendsclass.com/json-storage.html#apiDocumentation\r\n * @param payload {object} JSON object to patch the bin with\r\n * @param bin string Bin ID to be patched\r\n * @returns {object} <resPatchBin | resError>\r\n */\r\n patchBin = async (payload: object, bin: string): Promise<resPatchBin | resError> => {\r\n try {\r\n const response = await fetch(`${this.url}/${bin}`, {\r\n method: 'PATCH',\r\n headers: {\r\n 'Security-key': this.securityKey,\r\n 'Content-Type': 'application/merge-patch+json',\r\n Accept: 'application/json'\r\n },\r\n body: JSON.stringify(payload)\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n const { data } = await response.json();\r\n return {\r\n status: 'ok',\r\n data\r\n };\r\n } catch (error) {\r\n console.error('patchBin error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n\r\n /**\r\n * Deletes a certain bin\r\n * @param bin string Bin ID to be deleted\r\n * @returns {object} <resDeleteBin | resError>\r\n */\r\n deleteBin = async (bin: string): Promise<resDeleteBin | resError> => {\r\n try {\r\n const response = await fetch(`${this.url}/${bin}`, {\r\n method: 'DELETE',\r\n headers: {\r\n 'Security-key': this.securityKey,\r\n 'Content-Type': 'application/json',\r\n Accept: 'application/json'\r\n }\r\n });\r\n\r\n await this.validateResponse(response);\r\n\r\n const { status } = await response.json();\r\n if (status !== 0) throw new Error(`Bin delete failed with error: ${status.message}`);\r\n\r\n return {\r\n status: 'ok'\r\n };\r\n } catch (error) {\r\n console.error('deleteBin error:', error);\r\n return {\r\n status: 'error',\r\n statusText: <string>error\r\n };\r\n }\r\n };\r\n}\r\n"],"mappings":"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","names":["ECJsonStorage","apiKey","securityKey","url","response","counter","data","error","res","bin","payload","keepPrivate","headers","uri","status"]}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@neonwilderness/ecjsonstorage",
3
+ "version": "1.0.1",
4
+ "description": "",
5
+ "main": "./dist/index.cjs",
6
+ "exports": "./dist/index.js",
7
+ "types": "./dist/index.d.cts",
8
+ "scripts": {
9
+ "update": "ncu -u -x typescript && npm i",
10
+ "build": "npm run build:js && npm run build:dts",
11
+ "build:js": "tsup src/*.ts --format esm,cjs --minify",
12
+ "build:dts": "tsup src/index.ts --dts-only",
13
+ "deleteAllBins": "node dist/deleteAllBins.cjs",
14
+ "test": "vitest"
15
+ },
16
+ "keywords": [
17
+ "ExtendsClass JSON-Storage API wrapper"
18
+ ],
19
+ "files": [
20
+ "dist/index.*",
21
+ "src/index.ts",
22
+ "test/*",
23
+ ".env.example",
24
+ "CHANGELOG.md",
25
+ "LICENSE",
26
+ "pack*.json",
27
+ "README.md"
28
+ ],
29
+ "type": "module",
30
+ "devDependencies": {
31
+ "@types/dotenv-safe": "^9.1.0",
32
+ "@types/node": "^26.6.3",
33
+ "dotenv": "^18.0.4",
34
+ "dotenv-safe": "^9.1.0",
35
+ "tsup": "^8.5.1",
36
+ "typescript": "^6.0.3",
37
+ "vitest": "^5.0.3"
38
+ },
39
+ "allowScripts": {
40
+ "esbuild@0.28.2": true
41
+ }
42
+ }
package/src/index.ts ADDED
@@ -0,0 +1,296 @@
1
+ /**
2
+ * ESJsonStorage class is a wrapper to the ExtendsClass JSON-Storage API
3
+ */
4
+ export type resGetBins = {
5
+ status: 'ok';
6
+ callsDone: number;
7
+ callsRemaining: number;
8
+ bins?: string[];
9
+ };
10
+ export type resGetStats = Omit<resGetBins, 'bins'>;
11
+
12
+ export type resGetBin = {
13
+ status: 'ok';
14
+ data: object;
15
+ };
16
+
17
+ export type resUpdateBin = {
18
+ status: 'ok';
19
+ data: string;
20
+ };
21
+ export type resPatchBin = resUpdateBin;
22
+
23
+ export type resCreateBin = {
24
+ status: 'ok';
25
+ uri: string;
26
+ bin: string;
27
+ };
28
+
29
+ export type resDeleteBin = Omit<resGetBin, 'data'>;
30
+
31
+ export type resError = {
32
+ status: 'error';
33
+ statusText: string;
34
+ };
35
+
36
+ export class ECJsonStorage {
37
+ url: string;
38
+ apiKey: string;
39
+ securityKey: string;
40
+ monthlyLimit: number = 10000;
41
+
42
+ constructor(apiKey: string, securityKey?: string, url?: string) {
43
+ this.url = url || 'https://json.extendsclass.com/bin';
44
+ this.apiKey = apiKey || '';
45
+ this.securityKey = securityKey || '';
46
+
47
+ if (!this.url || !this.apiKey) throw new Error('Invalid constructor params!');
48
+ while (this.url.endsWith('/')) {
49
+ this.url = this.url.slice(0, -1);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Utility function to validate the response after the class function call
55
+ * @param response
56
+ */
57
+ validateResponse = async (response: Response) => {
58
+ if (!response.ok) {
59
+ throw new Error(`HTTP error! Status: ${response.status}`);
60
+ }
61
+
62
+ const contentType = response.headers.get('content-type') || '';
63
+ if (contentType.startsWith('text/html')) {
64
+ throw new Error(`Expected JSON but server returned HTML: "${(await response.text()).slice(0, 100)}..."`);
65
+ }
66
+ };
67
+
68
+ /**
69
+ * Get all bin IDs for a certain Api-key
70
+ * @returns {object} <resGetBins | resError>
71
+ */
72
+ getBins = async (): Promise<resGetBins | resError> => {
73
+ try {
74
+ const response = await fetch(`${this.url}s`, {
75
+ method: 'GET',
76
+ headers: {
77
+ 'Api-key': this.apiKey,
78
+ Accept: 'application/json'
79
+ }
80
+ });
81
+
82
+ await this.validateResponse(response);
83
+
84
+ const counter = Number(response.headers.get('x-counter'));
85
+ const data = await response.json();
86
+ return {
87
+ status: 'ok',
88
+ callsDone: counter,
89
+ callsRemaining: this.monthlyLimit - counter,
90
+ bins: data
91
+ };
92
+ } catch (error) {
93
+ console.error('getBins error:', error);
94
+ return {
95
+ status: 'error',
96
+ statusText: <string>error
97
+ };
98
+ }
99
+ };
100
+
101
+ /**
102
+ * Syntactic sugar function for getBins to only return limit counter
103
+ * @returns {object} <resGetStats | resError>
104
+ */
105
+ getStats = async (): Promise<resGetStats | resError> => {
106
+ try {
107
+ const res = await this.getBins();
108
+ if (res.status === 'error') {
109
+ throw new Error(`HTTP error! Status: ${(<resError>res).statusText}`);
110
+ }
111
+
112
+ delete (<resGetBins>res).bins;
113
+ return <resGetStats>res;
114
+ } catch (error) {
115
+ console.error('getStats error:', error);
116
+ return {
117
+ status: 'error',
118
+ statusText: <string>error
119
+ };
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Reads the content of a certain bin ID
125
+ * @param bin string Bin ID to read
126
+ * @returns {object} <resGetBin | resError>
127
+ */
128
+ getBin = async (bin: string): Promise<resGetBin | resError> => {
129
+ try {
130
+ if (!bin) throw new Error('Bin ID missing!');
131
+ const response = await fetch(`${this.url}/${bin}`, {
132
+ method: 'GET',
133
+ headers: {
134
+ 'Security-key': this.securityKey,
135
+ Accept: 'application/json'
136
+ }
137
+ });
138
+
139
+ await this.validateResponse(response);
140
+
141
+ const data = await response.json();
142
+ return {
143
+ status: 'ok',
144
+ data
145
+ };
146
+ } catch (error) {
147
+ console.error('getBin error:', error);
148
+ return {
149
+ status: 'error',
150
+ statusText: <string>error
151
+ };
152
+ }
153
+ };
154
+
155
+ /**
156
+ * Creates a new bin and saves the given JSON payload to it
157
+ * @param payload {object} JSON object to create the bin with
158
+ * @param keepPrivate {boolean} TRUE=bin shall be kept private
159
+ * @returns {object} <resCreateBin | resError>
160
+ */
161
+ createBin = async (payload: object, keepPrivate: boolean): Promise<resCreateBin | resError> => {
162
+ const headers = new Headers({
163
+ 'Api-key': this.apiKey,
164
+ 'Security-key': this.securityKey,
165
+ 'Content-Type': 'application/json',
166
+ Accept: 'application/json'
167
+ });
168
+ headers.append('Private', keepPrivate.toString());
169
+ try {
170
+ const response = await fetch(this.url, {
171
+ method: 'POST',
172
+ headers,
173
+ body: JSON.stringify(payload)
174
+ });
175
+
176
+ await this.validateResponse(response);
177
+
178
+ let { uri, bin } = await response.json();
179
+ if (!bin) bin = uri.split('/').pop();
180
+
181
+ return {
182
+ status: 'ok',
183
+ uri,
184
+ bin
185
+ };
186
+ } catch (error) {
187
+ console.error('createBin error:', error);
188
+ return {
189
+ status: 'error',
190
+ statusText: <string>error
191
+ };
192
+ }
193
+ };
194
+
195
+ /**
196
+ * Updates a certain bin with the given JSON object
197
+ * @param payload {object} JSON object to update the bin with
198
+ * @param bin string Bin ID to be updated
199
+ * @returns {object} <resUpdateBin | resError>
200
+ */
201
+ updateBin = async (payload: object, bin: string): Promise<resUpdateBin | resError> => {
202
+ try {
203
+ const response = await fetch(`${this.url}/${bin}`, {
204
+ method: 'PUT',
205
+ headers: {
206
+ 'Security-key': this.securityKey,
207
+ 'Content-Type': 'application/json',
208
+ Accept: 'application/json'
209
+ },
210
+ body: JSON.stringify(payload)
211
+ });
212
+
213
+ await this.validateResponse(response);
214
+
215
+ const { data } = await response.json();
216
+ return {
217
+ status: 'ok',
218
+ data
219
+ };
220
+ } catch (error) {
221
+ console.error('updateBin error:', error);
222
+ return {
223
+ status: 'error',
224
+ statusText: <string>error
225
+ };
226
+ }
227
+ };
228
+
229
+ /**
230
+ * Patches/inserts/changes a certain bin with the given JSON payload
231
+ * ref: "Partially update JSON" at https://extendsclass.com/json-storage.html#apiDocumentation
232
+ * @param payload {object} JSON object to patch the bin with
233
+ * @param bin string Bin ID to be patched
234
+ * @returns {object} <resPatchBin | resError>
235
+ */
236
+ patchBin = async (payload: object, bin: string): Promise<resPatchBin | resError> => {
237
+ try {
238
+ const response = await fetch(`${this.url}/${bin}`, {
239
+ method: 'PATCH',
240
+ headers: {
241
+ 'Security-key': this.securityKey,
242
+ 'Content-Type': 'application/merge-patch+json',
243
+ Accept: 'application/json'
244
+ },
245
+ body: JSON.stringify(payload)
246
+ });
247
+
248
+ await this.validateResponse(response);
249
+
250
+ const { data } = await response.json();
251
+ return {
252
+ status: 'ok',
253
+ data
254
+ };
255
+ } catch (error) {
256
+ console.error('patchBin error:', error);
257
+ return {
258
+ status: 'error',
259
+ statusText: <string>error
260
+ };
261
+ }
262
+ };
263
+
264
+ /**
265
+ * Deletes a certain bin
266
+ * @param bin string Bin ID to be deleted
267
+ * @returns {object} <resDeleteBin | resError>
268
+ */
269
+ deleteBin = async (bin: string): Promise<resDeleteBin | resError> => {
270
+ try {
271
+ const response = await fetch(`${this.url}/${bin}`, {
272
+ method: 'DELETE',
273
+ headers: {
274
+ 'Security-key': this.securityKey,
275
+ 'Content-Type': 'application/json',
276
+ Accept: 'application/json'
277
+ }
278
+ });
279
+
280
+ await this.validateResponse(response);
281
+
282
+ const { status } = await response.json();
283
+ if (status !== 0) throw new Error(`Bin delete failed with error: ${status.message}`);
284
+
285
+ return {
286
+ status: 'ok'
287
+ };
288
+ } catch (error) {
289
+ console.error('deleteBin error:', error);
290
+ return {
291
+ status: 'error',
292
+ statusText: <string>error
293
+ };
294
+ }
295
+ };
296
+ }
@@ -0,0 +1,136 @@
1
+ import { expect, test } from 'vitest';
2
+ import {
3
+ ECJsonStorage,
4
+ type resGetBins,
5
+ type resGetBin,
6
+ type resCreateBin,
7
+ type resUpdateBin,
8
+ type resPatchBin
9
+ } from '../src/index';
10
+ import { config } from 'dotenv-safe';
11
+ config();
12
+
13
+ test('Rejects a constructor call with a missing Api-key', () => {
14
+ expect(() => {
15
+ const json = new ECJsonStorage('');
16
+ }).toThrow('Invalid constructor params!');
17
+ });
18
+
19
+ test('Fixes an alternative Api URL if it has ending slash/es', () => {
20
+ const json = new ECJsonStorage(process.env.JSONAPI!, process.env.JSONSEC!, 'https://other.com/api////');
21
+ expect(json.url).toBe('https://other.com/api');
22
+ });
23
+
24
+ test.only('Can construct a valid class', () => {
25
+ const json = new ECJsonStorage(process.env.JSONAPI!, process.env.JSONSEC!);
26
+ expect(json.url).toBe('https://json.extendsclass.com/bin');
27
+ expect(json.apiKey).toBe(process.env.JSONAPI);
28
+ expect(json.securityKey).toBe(process.env.JSONSEC);
29
+ expect(json.monthlyLimit).toBe(10000);
30
+ });
31
+
32
+ test('Can overwrite the API url if needed', () => {
33
+ const json = new ECJsonStorage(process.env.JSONAPI!, process.env.JSONSEC!, 'https://someotherapiurl.de');
34
+ expect(json.url).toBe('https://someotherapiurl.de');
35
+ expect(json.apiKey).toBe(process.env.JSONAPI);
36
+ expect(json.securityKey).toBe(process.env.JSONSEC);
37
+ expect(json.monthlyLimit).toBe(10000);
38
+ });
39
+
40
+ test('Can get all bins of an API Key', async () => {
41
+ const json = new ECJsonStorage(process.env.JSONAPI!, process.env.JSONSEC!);
42
+ const res = await json.getBins();
43
+ console.log(res);
44
+ expect(res.status).toBe('ok');
45
+ expect((res as resGetBins).callsDone).not.toBeNaN();
46
+ expect((res as resGetBins).callsDone).toBeGreaterThan(0);
47
+ expect((res as resGetBins).callsRemaining).not.toBeNaN();
48
+ expect((res as resGetBins).callsRemaining).toBeGreaterThan(0);
49
+ expect(Array.isArray((res as resGetBins).bins)).toBe(true);
50
+ expect((res as resGetBins).bins!.length).toBeGreaterThanOrEqual(0);
51
+ });
52
+
53
+ test('Can get the current limit state', async () => {
54
+ const json = new ECJsonStorage(process.env.JSONAPI!, process.env.JSONSEC!);
55
+ const res = await json.getStats();
56
+ console.log(res);
57
+ expect(res.status).toBe('ok');
58
+ expect((res as resGetBins).callsDone).not.toBeNaN();
59
+ expect((res as resGetBins).callsDone).toBeGreaterThan(0);
60
+ expect((res as resGetBins).callsRemaining).not.toBeNaN();
61
+ expect((res as resGetBins).callsRemaining).toBeGreaterThan(0);
62
+ });
63
+
64
+ test('Can get the content of a bin', async () => {
65
+ const json = new ECJsonStorage(process.env.JSONAPI!, process.env.JSONSEC!);
66
+ const res = await json.getBin(process.env.JSONBIN!);
67
+ console.log(res);
68
+ expect(res.status).toBe('ok');
69
+ expect(typeof (res as resGetBin).data).toBe('object');
70
+ expect(Object.keys((res as resGetBin).data).length).toBe(2);
71
+ expect(Object.keys((res as resGetBin).data)).toContain('name');
72
+ expect(Object.keys((res as resGetBin).data)).toContain('profession');
73
+ });
74
+
75
+ test('Can create a new bin', async () => {
76
+ const json = new ECJsonStorage(process.env.JSONAPI!, process.env.JSONSEC!);
77
+ const payload = { name: 'Neon', profession: 'IT-Consultant' };
78
+ const res = await json.createBin(payload, true);
79
+ console.log(res);
80
+ expect(res.status).toBe('ok');
81
+ expect(typeof (res as resCreateBin).uri).toBeTruthy();
82
+ expect(typeof (res as resCreateBin).bin).toBeTruthy();
83
+ });
84
+
85
+ interface payloadData {
86
+ name: string;
87
+ profession: string;
88
+ }
89
+ test('Can update/change the content of a bin', async () => {
90
+ const json = new ECJsonStorage(process.env.JSONAPI!, process.env.JSONSEC!);
91
+ const payload: payloadData = { name: 'NeonWilderness', profession: 'Web Ninja' };
92
+ const res = await json.updateBin(payload, process.env.JSONBIN!);
93
+ console.log(res);
94
+ expect(res.status).toBe('ok');
95
+ const dataParsed = JSON.parse((res as resUpdateBin).data);
96
+ expect(Object.keys(dataParsed).length).toBe(2);
97
+ expect(Object.keys(dataParsed)).toContain('name');
98
+ expect(Object.keys(dataParsed)).toContain('profession');
99
+ const { name, profession } = dataParsed as payloadData;
100
+ expect(name).toBe(payload.name);
101
+ expect(profession).toBe(payload.profession);
102
+ });
103
+
104
+ interface payloadPatched extends payloadData {
105
+ added: string;
106
+ times: number;
107
+ }
108
+ test('Can patch the content of a bin', async () => {
109
+ const json = new ECJsonStorage(process.env.JSONAPI!, process.env.JSONSEC!);
110
+ const payloadAdd = { added: 'IT-Consulting', times: 999 };
111
+ const res = await json.patchBin(payloadAdd, process.env.JSONBIN!);
112
+ console.log(res);
113
+ expect(res.status).toBe('ok');
114
+ const data = JSON.parse((res as resPatchBin).data);
115
+ expect(Object.keys(data).length).toBe(4);
116
+ expect(Object.keys(data)).toContain('name');
117
+ expect(Object.keys(data)).toContain('profession');
118
+ expect(Object.keys(data)).toContain('added');
119
+ expect(Object.keys(data)).toContain('times');
120
+ const { name, profession, added, times } = data as payloadPatched;
121
+ expect(name).toBe('NeonWilderness');
122
+ expect(profession).toBe('Web Ninja');
123
+ expect(added).toBe('IT-Consulting');
124
+ expect(times).toBe(999);
125
+ });
126
+
127
+ test('Can delete a bin', async () => {
128
+ const json = new ECJsonStorage(process.env.JSONAPI!, process.env.JSONSEC!);
129
+ const res1 = await json.createBin({ type: 'dummy' }, true);
130
+ expect(res1.status).toBe('ok');
131
+ const bin = (res1 as resCreateBin).bin;
132
+ expect(bin).toBeTruthy();
133
+ console.log(bin);
134
+ const res2 = await json.deleteBin(bin);
135
+ expect(res2.status).toBe('ok');
136
+ });