@wxn0brp/db 0.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.
Files changed (53) hide show
  1. package/CollectionManager.js +119 -0
  2. package/LICENSE +21 -0
  3. package/README.md +194 -0
  4. package/action.js +250 -0
  5. package/cacheManager.js +83 -0
  6. package/database.js +208 -0
  7. package/executor.js +54 -0
  8. package/file/find.js +88 -0
  9. package/file/index.js +3 -0
  10. package/file/remove.js +75 -0
  11. package/file/update.js +83 -0
  12. package/file/utils.js +27 -0
  13. package/format.js +29 -0
  14. package/gen.js +97 -0
  15. package/graph.js +130 -0
  16. package/more.js +103 -0
  17. package/package.json +32 -0
  18. package/remote/client/database.js +229 -0
  19. package/remote/client/graph.js +139 -0
  20. package/remote/client/index.js +10 -0
  21. package/remote/server/auth.js +100 -0
  22. package/remote/server/db.js +197 -0
  23. package/remote/server/function.js +43 -0
  24. package/remote/server/graph.js +121 -0
  25. package/remote/server/gui/css/main.css +130 -0
  26. package/remote/server/gui/css/scrool.css +81 -0
  27. package/remote/server/gui/css/style.css +61 -0
  28. package/remote/server/gui/favicon.svg +12 -0
  29. package/remote/server/gui/html/data.html +15 -0
  30. package/remote/server/gui/html/main.html +46 -0
  31. package/remote/server/gui/html/nav.html +25 -0
  32. package/remote/server/gui/html/popup.html +51 -0
  33. package/remote/server/gui/index.html +49 -0
  34. package/remote/server/gui/js/api.js +166 -0
  35. package/remote/server/gui/js/index.js +17 -0
  36. package/remote/server/gui/js/loadHTML.js +16 -0
  37. package/remote/server/gui/js/popUp.js +72 -0
  38. package/remote/server/gui/js/queryApi.js +51 -0
  39. package/remote/server/gui/js/queryDb.js +79 -0
  40. package/remote/server/gui/js/queryGraph.js +144 -0
  41. package/remote/server/gui/js/render.js +64 -0
  42. package/remote/server/gui/js/templates.js +31 -0
  43. package/remote/server/gui/js/utils.js +36 -0
  44. package/remote/server/gui/js/vars.js +9 -0
  45. package/remote/server/gui/libs/core.js +176 -0
  46. package/remote/server/gui/libs/d3.v7.min.js +2 -0
  47. package/remote/server/gui/libs/handlebars.min.js +29 -0
  48. package/remote/server/gui/libs/json5.min.js +1 -0
  49. package/remote/server/index.js +63 -0
  50. package/remote/server/initDataBases.js +20 -0
  51. package/remote/server/pathUtils.js +7 -0
  52. package/remote/server/secret.js +23 -0
  53. package/remote/serverMgmt/index.js +86 -0
package/database.js ADDED
@@ -0,0 +1,208 @@
1
+ import dbActionC from "./action.js";
2
+ import executorC from "./executor.js";
3
+ import CollectionManager from "./CollectionManager.js";
4
+
5
+ /**
6
+ * Represents a database management class for performing CRUD operations.
7
+ * @class
8
+ */
9
+ class DataBase{
10
+ /**
11
+ * Create a new database instance.
12
+ * @constructor
13
+ * @param {string} folder - The folder path where the database files are stored.
14
+ * @param {object} [options] - The options object.
15
+ * @param {number} [options.cacheThreshold=3] - The cache threshold for database entries (default: 3).
16
+ * @param {number} [options.cacheTTL=300000] - The time-to-live (TTL) for cached entries in milliseconds (default: 300,000 milliseconds or 5 minutes).
17
+ */
18
+ constructor(folder, options={}){
19
+ options = {
20
+ cacheThreshold: 3,
21
+ cacheTTL: 300_000,
22
+ ...options
23
+ }
24
+ this.dbAction = new dbActionC(folder, options);
25
+ this.executor = new executorC();
26
+ }
27
+
28
+ /**
29
+ * Create a new instance of a CollectionManager class.
30
+ * @function
31
+ * @param {string} collection - The name of the collection.
32
+ * @returns {CollectionManager} A new instance of CollectionManager.
33
+ */
34
+ c(collection){
35
+ return new CollectionManager(this, collection);
36
+ }
37
+
38
+ /**
39
+ * Get the names of all available databases.
40
+ *
41
+ * @function
42
+ * @returns {string[]} An array of database names.
43
+ */
44
+ async getCollections(){
45
+ return await this.dbAction.getCollections();
46
+ }
47
+
48
+ /**
49
+ * Check and create the specified collection if it doesn't exist.
50
+ *
51
+ * @function
52
+ * @param {string} collection - The collection to check.
53
+ */
54
+ async checkCollection(collection){
55
+ await this.dbAction.checkCollection(collection);
56
+ }
57
+
58
+ /**
59
+ * Check if a collection exists.
60
+ *
61
+ * @function
62
+ * @param {string} collection - The name of the collection.
63
+ * @returns {boolean} True if the collection exists, false otherwise.
64
+ */
65
+ async issetCollection(collection){
66
+ return await this.dbAction.issetCollection(collection);
67
+ }
68
+
69
+ /**
70
+ * Add data to a database.
71
+ *
72
+ * @async
73
+ * @function
74
+ * @param {string} collection - The name of the collection.
75
+ * @param {Object} data - The data to add.
76
+ * @param {boolean} id_gen - Whether to generate an ID for the entry. Default is true.
77
+ * @returns {Promise<Object>} A Promise that resolves with the added data.
78
+ */
79
+ async add(collection, data, id_gen=true){
80
+ return await this.executor.addOp(this.dbAction.add.bind(this.dbAction), collection, data, id_gen);
81
+ }
82
+
83
+ /**
84
+ * Find data in a database.
85
+ *
86
+ * @async
87
+ * @function
88
+ * @param {string} collection - Name of the database collection.
89
+ * @param {function|Object} search - The query. It can be an object or a function.
90
+ * @param {Object} context - The context object (for functions).
91
+ * @param {Object} options - The options for the search.
92
+ * @param {number} options.max - The maximum number of entries to return. Default is -1, meaning no limit.
93
+ * @param {boolean} options.reverse - Whether to reverse the order of returned entries. Default is false.
94
+ * @param {Object} findOpts - Update result object with findOpts options.
95
+ * @returns {Promise<Array<Object>>} A Promise that resolves with the matching data.
96
+ */
97
+ async find(collection, search, context={}, options={}, findOpts={}){
98
+ return await this.executor.addOp(this.dbAction.find.bind(this.dbAction), collection, search, context, options, findOpts);
99
+ }
100
+
101
+ /**
102
+ * Find one data entry in a database.
103
+ *
104
+ * @async
105
+ * @function
106
+ * @param {string} collection - Name of the database collection.
107
+ * @param {function|Object} search - The query. It can be an object or a function.
108
+ * @param {Object} context - The context object (for functions).
109
+ * @param {Object} findOpts - Update result object with findOpts options.
110
+ * @returns {Promise<Object|null>} A Promise that resolves with the first matching data entry.
111
+ */
112
+ async findOne(collection, search, context={}, findOpts={}){
113
+ return await this.executor.addOp(this.dbAction.findOne.bind(this.dbAction), collection, search, context, findOpts);
114
+ }
115
+
116
+ /**
117
+ * Update data in a database.
118
+ *
119
+ * @async
120
+ * @function
121
+ * @param {string} collection - Name of the database collection.
122
+ * @param {function|Object} search - The query. It can be an object or a function.
123
+ * @param {function|Object} arg - Update arguments.
124
+ * @param {Object} context - The context object (for functions).
125
+ * @returns {Promise<boolean>} A Promise that resolves when the data is updated.
126
+ */
127
+ async update(collection, search, arg, context={}){
128
+ return await this.executor.addOp(this.dbAction.update.bind(this.dbAction), collection, search, arg, context);
129
+ }
130
+
131
+ /**
132
+ * Update one data entry in a database.
133
+ *
134
+ * @async
135
+ * @function
136
+ * @param {string} collection - Name of the database collection.
137
+ * @param {function|Object} search - The query. It can be an object or a function.
138
+ * @param {function|Object} arg - The query.
139
+ * @param {Object} context - The context object (for functions).
140
+ * @returns {Promise<boolean>} A Promise that resolves when the data entry is updated.
141
+ */
142
+ async updateOne(collection, search, arg, context={}){
143
+ return await this.executor.addOp(this.dbAction.updateOne.bind(this.dbAction), collection, search, arg, context);
144
+ }
145
+
146
+ /**
147
+ * Remove data from a database.
148
+ *
149
+ * @async
150
+ * @function
151
+ * @param {string} collection - Name of the database collection.
152
+ * @param {function|Object} search - The query. It can be an object or a function.
153
+ * @param {Object} context - The context object (for functions).
154
+ * @returns {Promise<boolean>} A Promise that resolves when the data is removed.
155
+ */
156
+ async remove(collection, search, context={}){
157
+ return await this.executor.addOp(this.dbAction.remove.bind(this.dbAction), collection, search, context);
158
+ }
159
+
160
+ /**
161
+ * Remove one data entry from a database.
162
+ *
163
+ * @async
164
+ * @function
165
+ * @param {string} collection - Name of the database collection.
166
+ * @param {function|Object} search - The query. It can be an object or a function.
167
+ * @param {Object} context - The context object (for functions).
168
+ * @returns {Promise<boolean>} A Promise that resolves when the data entry is removed.
169
+ */
170
+ async removeOne(collection, search, context={}){
171
+ return await this.executor.addOp(this.dbAction.removeOne.bind(this.dbAction), collection, search, context);
172
+ }
173
+
174
+ /**
175
+ * Asynchronously updates one entry in a database or adds a new one if it doesn't exist.
176
+ *
177
+ * @param {string} collection - Name of the database collection.
178
+ * @param {function|Object} search - The query. It can be an object or a function.
179
+ * @param {function|Object} arg - The search criteria for the update.
180
+ * @param {function|Object} add_arg - The arguments to be added to the new entry.
181
+ * @param {Object} context - The context object (for functions).
182
+ * @param {boolean} id_gen - Whether to generate an ID for the entry. Default is true.
183
+ * @return {Promise<boolean>} A Promise that resolves to `true` if the entry was updated, or `false` if it was added.
184
+ */
185
+ async updateOneOrAdd(collection, search, arg, add_arg={}, context={}, id_gen=true){
186
+ const res = await this.updateOne(collection, search, arg, context);
187
+ if(!res){
188
+ const assignData = [];
189
+ if(typeof search === "object" && !Array.isArray(search)) assignData.push(search);
190
+ if(typeof arg === "object" && !Array.isArray(arg)) assignData.push(arg);
191
+ if(typeof add_arg === "object" && !Array.isArray(add_arg)) assignData.push(add_arg);
192
+ await this.add(collection, Object.assign({}, ...assignData), id_gen);
193
+ }
194
+ return res;
195
+ }
196
+
197
+ /**
198
+ * Removes a database collection from the file system.
199
+ *
200
+ * @param {string} collection - The name of the collection to remove.
201
+ * @return {void}
202
+ */
203
+ removeDb(collection){
204
+ this.dbAction.removeDb(collection);
205
+ }
206
+ }
207
+
208
+ export default DataBase;
package/executor.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * A simple executor for queuing and executing asynchronous operations sequentially.
3
+ * @class
4
+ */
5
+ class executorC{
6
+ /**
7
+ * Create a new executor instance.
8
+ * @constructor
9
+ */
10
+ constructor(){
11
+ this.quote = [];
12
+ this.isExecuting = false;
13
+ }
14
+
15
+ /**
16
+ * Add an asynchronous operation to the execution queue.
17
+ *
18
+ * @async
19
+ * @function
20
+ * @param {Function} func - The asynchronous function to execute.
21
+ * @param {...*} param - Parameters to pass to the function.
22
+ * @returns {Promise} A Promise that resolves when the operation is executed.
23
+ */
24
+ async addOp(func, ...param){
25
+ return await new Promise((resolve, reject) => {
26
+ this.quote.push({
27
+ func,
28
+ param,
29
+ resolve,
30
+ reject
31
+ });
32
+ this.execute();
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Execute the queued asynchronous operations sequentially.
38
+ *
39
+ * @async
40
+ * @function
41
+ */
42
+ async execute(){
43
+ if(this.isExecuting) return;
44
+ this.isExecuting = true;
45
+ while(this.quote.length > 0){
46
+ let q = this.quote.shift();
47
+ let res = await q.func(...q.param);
48
+ q.resolve(res)
49
+ }
50
+ this.isExecuting = false;
51
+ }
52
+ }
53
+
54
+ export default executorC;
package/file/find.js ADDED
@@ -0,0 +1,88 @@
1
+ import { existsSync, promises } from "fs";
2
+ import { pathRepair, createRL } from "./utils.js";
3
+ import { parse } from "../format.js";
4
+ import { hasFieldsAdvanced, updateFindObject } from "../more.js";
5
+
6
+ /**
7
+ * Processes a line of text from a file and checks if it matches the search criteria.
8
+ * @private
9
+ * @param {function|Object} arg - The search criteria. It can be a function or an object.
10
+ * @param {string} line - The line of text from the file.
11
+ * @param {Object} context - The context object (for functions).
12
+ * @param {Object} findOpts - Update result object with findOpts options.
13
+ * @returns {Promise<Object|null>} A Promise that resolves to the matching object or null.
14
+ */
15
+ async function findProcesLine(arg, line, context={}, findOpts={}){
16
+ const ob = parse(line);
17
+ let res = false;
18
+
19
+ if(typeof arg === "function"){
20
+ if(arg(ob, context)) res = true;
21
+ }else if(typeof arg === "object" && !Array.isArray(arg)){
22
+ if(hasFieldsAdvanced(ob, arg)) res = true;
23
+ }
24
+
25
+ if(res) return updateFindObject(ob, findOpts);
26
+ return null;
27
+ }
28
+
29
+ /**
30
+ * Asynchronously finds entries in a file based on search criteria.
31
+ * @function
32
+ * @param {string} file - The file path to search in.
33
+ * @param {function|Object} arg - The search criteria. It can be a function or an object.
34
+ * @param {Object} context - The context object (for functions).
35
+ * @param {Object} findOpts - Update result object with findOpts options.
36
+ * @returns {Promise<Object[]>} A Promise that resolves to an array of matching objects.
37
+ */
38
+ export async function find(file, arg, context={}, findOpts={}){
39
+ file = pathRepair(file);
40
+ return await new Promise(async (resolve) => {
41
+ if(!existsSync(file)){
42
+ await promises.writeFile(file, "");
43
+ resolve(false);
44
+ return;
45
+ }
46
+ const rl = createRL(file);
47
+ const resF = [];
48
+ for await(const line of rl){
49
+ if(line == "" || !line) continue;
50
+
51
+ const res = await findProcesLine(arg, line, context, findOpts);
52
+ if(res) resF.push(res);
53
+ };
54
+ resolve(resF);
55
+ rl.close();
56
+ })
57
+ }
58
+
59
+ /**
60
+ * Asynchronously finds one entry in a file based on search criteria.
61
+ * @function
62
+ * @param {string} file - The file path to search in.
63
+ * @param {function|Object} arg - The search criteria. It can be a function or an object.
64
+ * @param {Object} context - The context object (for functions).
65
+ * @param {Object} findOpts - Update result object with findOpts options.
66
+ * @returns {Promise<Object>} A Promise that resolves to the first matching object found or an empty array.
67
+ */
68
+ export async function findOne(file, arg, context={}, findOpts={}){
69
+ file = pathRepair(file);
70
+ return await new Promise(async (resolve) => {
71
+ if(!existsSync(file)){
72
+ await promises.writeFile(file, "");
73
+ resolve(false);
74
+ return;
75
+ }
76
+ const rl = createRL(file);
77
+ for await(const line of rl){
78
+ if(line == "" || !line) continue;
79
+
80
+ const res = await findProcesLine(arg, line, context, findOpts);
81
+ if(res){
82
+ resolve(res);
83
+ rl.close();
84
+ }
85
+ };
86
+ resolve(false);
87
+ });
88
+ }
package/file/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default as update } from "./update.js";
2
+ export { default as remove } from "./remove.js";
3
+ export * from "./find.js";
package/file/remove.js ADDED
@@ -0,0 +1,75 @@
1
+ import { existsSync, promises, appendFileSync, readdirSync } from "fs";
2
+ import { pathRepair, createRL } from "./utils.js";
3
+ import { parse } from "../format.js";
4
+ import { hasFieldsAdvanced } from "../more.js";
5
+
6
+ /**
7
+ * Removes entries from a file based on search criteria.
8
+ * @private
9
+ * @param {string} file - The file path to remove entries from.
10
+ * @param {function|Object} search - The search criteria. It can be a function or an object.
11
+ * @param {Object} context - The context object (for functions).
12
+ * @param {boolean} [one=false] - Indicates whether to remove only one matching entry (default: false).
13
+ * @returns {Promise<boolean>} A Promise that resolves to `true` if entries were removed, or `false` otherwise.
14
+ */
15
+ async function removeWorker(file, search, context={}, one=false){
16
+ file = pathRepair(file);
17
+ if(!existsSync(file)){
18
+ await promises.writeFile(file, "");
19
+ return false;
20
+ }
21
+ await promises.copyFile(file, file+".tmp");
22
+ await promises.writeFile(file, "");
23
+
24
+ const rl = createRL(file+".tmp");
25
+
26
+ let removed = false;
27
+ for await(let line of rl){
28
+ if(one && removed){
29
+ appendFileSync(file, line+"\n");
30
+ continue;
31
+ }
32
+
33
+ const data = parse(line);
34
+
35
+ if(typeof search === "function"){
36
+ if(search(data, context)){
37
+ removed = true;
38
+ continue;
39
+ }
40
+ }else if(typeof search === "object" && !Array.isArray(search)){
41
+ if(hasFieldsAdvanced(data, search)){
42
+ removed = true;
43
+ continue;
44
+ }
45
+ }
46
+
47
+ appendFileSync(file, line+"\n");
48
+ }
49
+ await promises.writeFile(file+".tmp", "");
50
+ return removed;
51
+ }
52
+
53
+ /**
54
+ * Asynchronously removes entries from a file based on search criteria.
55
+ * @function
56
+ * @param {string} folder - The folder containing the file.
57
+ * @param {string} name - The name of the file to remove entries from.
58
+ * @param {function|Object} arg - The search criteria. It can be a function or an object.
59
+ * @param {Object} context - The context object (for functions).
60
+ * @param {boolean} one - Indicates whether to remove only one matching entry (default: false).
61
+ * @returns {Promise<boolean>} A Promise that resolves to `true` if entries were removed, or `false` otherwise.
62
+ */
63
+ async function remove(folder, name, arg, context={}, one){
64
+ let files = readdirSync(folder + "/" + name).filter(file => !/\.tmp$/.test(file));
65
+ files.reverse();
66
+ let remove = false;
67
+ for(const file of files){
68
+ const removed = await removeWorker(folder + "/" + name + "/" + file, arg, context, one);
69
+ if(one && removed) break;
70
+ remove = remove || removed;
71
+ }
72
+ return remove;
73
+ }
74
+
75
+ export default remove;
package/file/update.js ADDED
@@ -0,0 +1,83 @@
1
+ import { existsSync, promises, appendFileSync, readdirSync } from "fs";
2
+ import { pathRepair, createRL } from "./utils.js";
3
+ import { parse, stringify } from "../format.js";
4
+ import { hasFieldsAdvanced, updateObject } from "../more.js";
5
+
6
+ /**
7
+ * Updates a file based on search criteria and an updater function or object.
8
+ * @private
9
+ * @param {string} file - The file path to update.
10
+ * @param {function|Object} search - The search criteria. It can be a function or an object.
11
+ * @param {function|Object} updater - The updater function or object.
12
+ * @param {Object} context - The context object (for functions).
13
+ * @param {boolean} [one=false] - Indicates whether to update only one matching entry (default: false).
14
+ * @returns {Promise<boolean>} A Promise that resolves to `true` if the file was updated, or `false` otherwise.
15
+ */
16
+ async function updateWorker(file, search, updater, context={}, one=false){
17
+ file = pathRepair(file);
18
+ if(!existsSync(file)){
19
+ await promises.writeFile(file, "");
20
+ return false;
21
+ }
22
+ await promises.copyFile(file, file+".tmp");
23
+ await promises.writeFile(file, "");
24
+
25
+ const rl = createRL(file+".tmp");
26
+
27
+ let updated = false;
28
+ for await(let line of rl){
29
+ if(one && updated){
30
+ appendFileSync(file, line+"\n");
31
+ continue;
32
+ }
33
+
34
+ const data = parse(line);
35
+ let ob = false;
36
+
37
+ if(typeof search === "function"){
38
+ ob = search(data, context) || false;
39
+ }else if(typeof search === "object" && !Array.isArray(search)){
40
+ ob = hasFieldsAdvanced(data, search);
41
+ }
42
+
43
+ if(ob){
44
+ let updateObj;
45
+ if(typeof updater === "function"){
46
+ updateObj = updater(data, context);
47
+ }else if(typeof updater === "object" && !Array.isArray(updater)){
48
+ updateObj = updateObject(data, updater);
49
+ }
50
+ line = await stringify(updateObj);
51
+ updated = true;
52
+ }
53
+
54
+ appendFileSync(file, line+"\n");
55
+ }
56
+ await promises.writeFile(file+".tmp", "");
57
+ return updated;
58
+ }
59
+
60
+ /**
61
+ * Asynchronously updates entries in a file based on search criteria and an updater function or object.
62
+ * @function
63
+ * @param {string} folder - The folder containing the file.
64
+ * @param {string} name - The name of the file to update.
65
+ * @param {function|Object} arg - The search criteria. It can be a function or an object.
66
+ * @param {function|Object} obj - The updater function or object.
67
+ * @param {Object} context - The context object (for functions).
68
+ * @param {boolean} one - Indicates whether to update only one matching entry (default: false).
69
+ * @returns {Promise<boolean>} A Promise that resolves to `true` if entries were updated, or `false` otherwise.
70
+ */
71
+ async function update(folder, name, arg, obj, context={}, one){
72
+ let files = readdirSync(folder + "/" + name).filter(file => !/\.tmp$/.test(file));
73
+ files.reverse();
74
+ let update = false;
75
+ for(const file of files){
76
+ const updated = await updateWorker(folder + "/" + name + "/" + file, arg, obj, context, one);
77
+ if(one && updated) return true;
78
+ update = update || updated;
79
+ }
80
+ return update;
81
+ }
82
+
83
+ export default update;
package/file/utils.js ADDED
@@ -0,0 +1,27 @@
1
+ import { createReadStream } from "fs";
2
+ import { createInterface } from "readline";
3
+
4
+ /**
5
+ * Repairs a file path by replacing double slashes
6
+ * @private
7
+ * @param {string} path - The file path to repair.
8
+ * @returns {string} The repaired file path.
9
+ */
10
+ export function pathRepair(path){
11
+ return path.replaceAll("//", "/");
12
+ }
13
+
14
+ /**
15
+ * Creates a Readline interface for reading large files with a specified high water mark.
16
+ * @private
17
+ * @param {string} file - The file path to create a Readline interface for.
18
+ * @returns {readline.Interface} The Readline interface.
19
+ */
20
+ export function createRL(file){
21
+ const read_stream = createReadStream(file, { highWaterMark: 10 * 1024 * 1024 }); //10MB
22
+ const rl = createInterface({
23
+ input: read_stream,
24
+ crlfDelay: Infinity
25
+ });
26
+ return rl;
27
+ }
package/format.js ADDED
@@ -0,0 +1,29 @@
1
+ import json5 from "json5";
2
+
3
+ /**
4
+ * Parses given string into a JSON object. If the string does not start with
5
+ * a {, it is wrapped in one. This allows for a shorthand when
6
+ * storing/reading data from a file.
7
+ *
8
+ * @param {string} data
9
+ * @returns {Object}
10
+ */
11
+ export function parse(data){
12
+ if(!data.startsWith("{")) data = "{" + data + "}";
13
+ return json5.parse(data);
14
+ }
15
+ /**
16
+ * Converts given object to a string. If the string is a valid json5, it is
17
+ * returned as is. If it is a valid json5 wrapped in {}, the curly brackets
18
+ * are removed. Otherwise the string is wrapped in {}.
19
+ *
20
+ * @param {Object} data
21
+ * @return {String}
22
+ */
23
+ export function stringify(data){
24
+ data = json5.stringify(data);
25
+ if(data.startsWith("{")){
26
+ data = data.slice(1, -1);
27
+ }
28
+ return data;
29
+ }
package/gen.js ADDED
@@ -0,0 +1,97 @@
1
+ const usedIdsMap = new Map();
2
+
3
+ /**
4
+ * Generates a unique identifier based on specified parts.
5
+ * @function
6
+ * @param {number|number[]} parts - The number of parts or an array of parts.
7
+ * @param {number} [fill=1] - The fill value for each part (default: 1).
8
+ * @returns {string} The generated unique identifier.
9
+ */
10
+ export default function genId(parts, fill=1){
11
+ parts = changeInputToPartsArray(parts, fill);
12
+ const time = getTime();
13
+ const id = getUniqueRandom(time, parts);
14
+ return id;
15
+ }
16
+
17
+ /**
18
+ * Generates a unique random identifier based on time and parts.
19
+ * @private
20
+ * @param {string} time - The current time in a base36 string format.
21
+ * @param {number[]} parts - An array of parts to be used for generating the identifier.
22
+ * @param {number} [s=0] - Recursion counter for handling collision (default: 0).
23
+ * @returns {string} The unique random identifier.
24
+ */
25
+ function getUniqueRandom(time, partsA, s=0){
26
+ const parts = partsA.map(l => getRandom(l));
27
+ const id = [time, ...parts].join("-");
28
+ if(usedIdsMap.has(id)){
29
+ s++;
30
+ if(s < 25) return getUniqueRandom(time, partsA, s);
31
+ partsA = addOneToPods(partsA);
32
+ time = getTime();
33
+ return getUniqueRandom(time, partsA);
34
+ }
35
+ usedIdsMap.set(id, Date.now() + 2000);
36
+
37
+ usedIdsMap.forEach((value, key) => {
38
+ if(value < Date.now()) usedIdsMap.delete(key);
39
+ });
40
+
41
+ return id;
42
+ }
43
+
44
+ /**
45
+ * Generates a random string of base36 characters.
46
+ * @private
47
+ * @param {string} unix - The Unix timestamp used for generating the random string.
48
+ * @returns {string} The random string.
49
+ */
50
+ function getRandom(unix){
51
+ return (Math.floor(Math.random() * Math.pow(36, unix))).toString(36);
52
+ }
53
+
54
+ /**
55
+ * Gets the current time in a base36 string format.
56
+ * @private
57
+ * @returns {string} The current time in base36.
58
+ */
59
+ function getTime(){
60
+ return Math.floor(new Date().getTime() / 1000).toString(36);
61
+ }
62
+
63
+ /**
64
+ * Adds one to each part of the input array.
65
+ * @private
66
+ * @param {number[]} array - The input array.
67
+ * @returns {number[]} An array with one added to each element.
68
+ */
69
+ function addOneToPods(array){
70
+ const sum = array.reduce((acc, current) => acc + current, 0);
71
+ const num = sum + 1;
72
+ const len = array.length;
73
+
74
+ const result = [];
75
+ const quotient = Math.floor(num / len);
76
+ const remainder = num % len;
77
+
78
+ for(let i=0; i<len; i++){
79
+ if(i < remainder) result.push(quotient + 1);
80
+ else result.push(quotient);
81
+ }
82
+
83
+ return result;
84
+ }
85
+
86
+ /**
87
+ * Converts input to an array of parts.
88
+ * @private
89
+ * @param {number|number[]} parts - The number of parts or an array of parts.
90
+ * @param {number} [fill=1] - The fill value for each part (default: 1).
91
+ * @returns {number[]} An array of parts.
92
+ */
93
+ function changeInputToPartsArray(parts, fill=1){
94
+ if(Array.isArray(parts)) return parts;
95
+ if(typeof parts == "number") return Array(parts).fill(fill);
96
+ return [1, 1];
97
+ }