@wxn0brp/db-core 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 (99) hide show
  1. package/.github/workflows/build-relase.yml +25 -0
  2. package/.github/workflows/build.yml +20 -0
  3. package/.github/workflows/types.yml +89 -0
  4. package/CHANGELOG.md +9 -0
  5. package/LICENSE +21 -0
  6. package/README.md +5 -0
  7. package/dist/base/actions.d.ts +17 -0
  8. package/dist/base/actions.js +56 -0
  9. package/dist/base/db.d.ts +21 -0
  10. package/dist/base/db.js +56 -0
  11. package/dist/customFileCpu.d.ts +19 -0
  12. package/dist/customFileCpu.js +70 -0
  13. package/dist/db/memory.d.ts +72 -0
  14. package/dist/db/memory.js +145 -0
  15. package/dist/db/valthera.d.ts +74 -0
  16. package/dist/db/valthera.js +107 -0
  17. package/dist/helpers/CollectionManager.d.ts +43 -0
  18. package/dist/helpers/CollectionManager.js +57 -0
  19. package/dist/helpers/eventEmiter.d.ts +31 -0
  20. package/dist/helpers/eventEmiter.js +65 -0
  21. package/dist/helpers/executor.d.ts +28 -0
  22. package/dist/helpers/executor.js +45 -0
  23. package/dist/helpers/gen.d.ts +8 -0
  24. package/dist/helpers/gen.js +134 -0
  25. package/dist/helpers/relation.d.ts +10 -0
  26. package/dist/helpers/relation.js +155 -0
  27. package/dist/helpers/updateOneOrAdd.d.ts +2 -0
  28. package/dist/helpers/updateOneOrAdd.js +18 -0
  29. package/dist/index.d.ts +11 -0
  30. package/dist/index.js +6 -0
  31. package/dist/types/Id.d.ts +3 -0
  32. package/dist/types/Id.js +1 -0
  33. package/dist/types/arg.d.ts +12 -0
  34. package/dist/types/arg.js +1 -0
  35. package/dist/types/data.d.ts +4 -0
  36. package/dist/types/data.js +1 -0
  37. package/dist/types/export.d.ts +13 -0
  38. package/dist/types/export.js +1 -0
  39. package/dist/types/fileCpu.d.ts +51 -0
  40. package/dist/types/fileCpu.js +1 -0
  41. package/dist/types/options.d.ts +19 -0
  42. package/dist/types/options.js +1 -0
  43. package/dist/types/query.d.ts +15 -0
  44. package/dist/types/query.js +1 -0
  45. package/dist/types/relation.d.ts +28 -0
  46. package/dist/types/relation.js +1 -0
  47. package/dist/types/searchOpts.d.ts +62 -0
  48. package/dist/types/searchOpts.js +7 -0
  49. package/dist/types/types.d.ts +3 -0
  50. package/dist/types/types.js +1 -0
  51. package/dist/types/updater.d.ts +26 -0
  52. package/dist/types/updater.js +4 -0
  53. package/dist/types/valthera.d.ts +36 -0
  54. package/dist/types/valthera.js +1 -0
  55. package/dist/utils/hasFields.d.ts +7 -0
  56. package/dist/utils/hasFields.js +18 -0
  57. package/dist/utils/hasFieldsAdvanced.d.ts +5 -0
  58. package/dist/utils/hasFieldsAdvanced.js +176 -0
  59. package/dist/utils/sort.d.ts +1 -0
  60. package/dist/utils/sort.js +23 -0
  61. package/dist/utils/updateFindObject.d.ts +11 -0
  62. package/dist/utils/updateFindObject.js +28 -0
  63. package/dist/utils/updateObject.d.ts +7 -0
  64. package/dist/utils/updateObject.js +145 -0
  65. package/dist/version.d.ts +1 -0
  66. package/dist/version.js +1 -0
  67. package/package.json +26 -0
  68. package/src/base/actions.ts +71 -0
  69. package/src/base/db.ts +75 -0
  70. package/src/customFileCpu.ts +100 -0
  71. package/src/db/memory.ts +177 -0
  72. package/src/db/valthera.ts +132 -0
  73. package/src/helpers/CollectionManager.ts +67 -0
  74. package/src/helpers/eventEmiter.ts +73 -0
  75. package/src/helpers/executor.ts +55 -0
  76. package/src/helpers/gen.ts +154 -0
  77. package/src/helpers/relation.ts +192 -0
  78. package/src/helpers/updateOneOrAdd.ts +24 -0
  79. package/src/index.ts +25 -0
  80. package/src/types/Id.ts +3 -0
  81. package/src/types/arg.ts +15 -0
  82. package/src/types/data.ts +4 -0
  83. package/src/types/export.ts +13 -0
  84. package/src/types/fileCpu.ts +78 -0
  85. package/src/types/options.ts +22 -0
  86. package/src/types/query.ts +16 -0
  87. package/src/types/relation.ts +34 -0
  88. package/src/types/searchOpts.ts +78 -0
  89. package/src/types/types.ts +3 -0
  90. package/src/types/updater.ts +33 -0
  91. package/src/types/valthera.ts +38 -0
  92. package/src/utils/hasFields.ts +18 -0
  93. package/src/utils/hasFieldsAdvanced.ts +181 -0
  94. package/src/utils/sort.ts +25 -0
  95. package/src/utils/updateFindObject.ts +35 -0
  96. package/src/utils/updateObject.ts +156 -0
  97. package/src/version.ts +1 -0
  98. package/suglite.json +10 -0
  99. package/tsconfig.json +22 -0
@@ -0,0 +1,177 @@
1
+
2
+ import ValtheraClass from "./valthera";
3
+ import CustomFileCpu from "../customFileCpu";
4
+ import genId from "../helpers/gen";
5
+ import dbActionBase from "../base/actions";
6
+ import Data from "../types/data";
7
+ import FileCpu from "../types/fileCpu";
8
+ import { DbOpts } from "../types/options";
9
+ import { VQuery } from "../types/query";
10
+ import { compareSafe } from "../utils/sort";
11
+
12
+ export class MemoryAction extends dbActionBase {
13
+ folder: string;
14
+ options: DbOpts;
15
+ fileCpu: FileCpu;
16
+ memory: Map<string, any[]>;
17
+
18
+ /**
19
+ * Creates a new instance of dbActionC.
20
+ * @constructor
21
+ * @param folder - The folder where database files are stored.
22
+ * @param options - The options object.
23
+ */
24
+ constructor() {
25
+ super();
26
+ this.memory = new Map();
27
+ this.fileCpu = new CustomFileCpu(this._readMemory.bind(this), this._writeMemory.bind(this));
28
+ }
29
+
30
+ _readMemory(key: string) {
31
+ if (!this.memory.has(key)) return [];
32
+ return this.memory.get(key);
33
+ }
34
+
35
+ _writeMemory(key: string, data: any[]) {
36
+ this.memory.set(key, data);
37
+ }
38
+
39
+ _getCollectionPath(collection: string) {
40
+ return this.folder + "/" + collection + "/";
41
+ }
42
+
43
+ /**
44
+ * Get a list of available databases in the specified folder.
45
+ */
46
+ async getCollections() {
47
+ const collections = Array.from(this.memory.keys());
48
+ return collections;
49
+ }
50
+
51
+ /**
52
+ * Check and create the specified collection if it doesn't exist.
53
+ */
54
+ async checkCollection({ collection }: VQuery) {
55
+ if (this.issetCollection(collection)) return;
56
+ this.memory.set(collection, []);
57
+ return true;
58
+ }
59
+ /**
60
+ * Check if a collection exists.
61
+ */
62
+ async issetCollection({ collection }: VQuery) {
63
+ return this.memory.has(collection);
64
+ }
65
+
66
+ /**
67
+ * Add a new entry to the specified database.
68
+ */
69
+ async add({ collection, data, id_gen = true }: VQuery) {
70
+ await this.checkCollection(arguments[0]);
71
+
72
+ if (id_gen) data._id = data._id || genId();
73
+ await this.fileCpu.add(collection, data);
74
+ return data;
75
+ }
76
+
77
+ /**
78
+ * Find entries in the specified database based on search criteria.
79
+ */
80
+ async find({ collection, search, context = {}, dbFindOpts = {}, findOpts = {} }: VQuery) {
81
+ const {
82
+ reverse = false,
83
+ max = -1,
84
+ offset = 0,
85
+ sortBy,
86
+ sortAsc = true
87
+ } = dbFindOpts;
88
+
89
+ await this.checkCollection(arguments[0]);
90
+
91
+ let data = await this.fileCpu.find(collection, search, context, findOpts) as Data[];
92
+
93
+ if (reverse) data.reverse();
94
+
95
+ if (sortBy) {
96
+ const dir = sortAsc ? 1 : -1;
97
+ data.sort((a, b) => compareSafe(a[sortBy], b[sortBy]) * dir);
98
+ }
99
+
100
+ if (offset > 0) {
101
+ if (data.length <= offset) return [];
102
+ data = data.slice(offset);
103
+ }
104
+
105
+ if (max !== -1 && data.length > max) {
106
+ data = data.slice(0, max);
107
+ }
108
+
109
+ return data;
110
+ }
111
+
112
+ /**
113
+ * Find the first matching entry in the specified database based on search criteria.
114
+ */
115
+ async findOne({ collection, search, context = {}, findOpts = {} }: VQuery) {
116
+ await this.checkCollection(arguments[0]);
117
+ let data = await this.fileCpu.findOne(collection, search, context, findOpts) as Data;
118
+ return data || null;
119
+ }
120
+
121
+ /**
122
+ * Update entries in the specified database based on search criteria and an updater function or object.
123
+ */
124
+ async update({ collection, search, updater, context = {} }: VQuery) {
125
+ await this.checkCollection(arguments[0]);
126
+ return await this.fileCpu.update(collection, false, search, updater, context);
127
+ }
128
+
129
+ /**
130
+ * Update the first matching entry in the specified database based on search criteria and an updater function or object.
131
+ */
132
+ async updateOne({ collection, search, updater, context = {} }: VQuery) {
133
+ await this.checkCollection(arguments[0]);
134
+ return await this.fileCpu.update(collection, true, search, updater, context);
135
+ }
136
+
137
+ /**
138
+ * Remove entries from the specified database based on search criteria.
139
+ */
140
+ async remove({ collection, search, context = {} }: VQuery) {
141
+ await this.checkCollection(arguments[0]);
142
+ return await this.fileCpu.remove(collection, false, search, context);
143
+ }
144
+
145
+ /**
146
+ * Remove the first matching entry from the specified database based on search criteria.
147
+ */
148
+ async removeOne({ collection, search, context = {} }: VQuery) {
149
+ await this.checkCollection(arguments[0]);
150
+ return await this.fileCpu.remove(collection, true, search, context);
151
+ }
152
+
153
+ /**
154
+ * Removes a database collection from the file system.
155
+ */
156
+ async removeCollection({ collection }: VQuery) {
157
+ this.memory.delete(collection);
158
+ return true;
159
+ }
160
+ }
161
+
162
+ export default class ValtheraMemory extends ValtheraClass {
163
+ constructor(...args: any[]) {
164
+ super({ dbAction: new MemoryAction() });
165
+ }
166
+ }
167
+
168
+ export function createMemoryValthera<T = { [key: string]: Data[] }>(data?: T) {
169
+ const db = new ValtheraMemory();
170
+ if (!data) return db;
171
+
172
+ for (const collection of Object.keys(data)) {
173
+ (db.dbAction as MemoryAction).memory.set(collection, data[collection]);
174
+ }
175
+
176
+ return db;
177
+ }
@@ -0,0 +1,132 @@
1
+
2
+ import { version } from "../version";
3
+ import { UniversalEventEmitter } from "../helpers/eventEmiter";
4
+ import dbActionBase from "../base/actions";
5
+ import CollectionManager from "../helpers/CollectionManager";
6
+ import executorC from "../helpers/executor";
7
+ import { Arg, Search, Updater } from "../types/arg";
8
+ import Data from "../types/data";
9
+ import { DbOpts, DbFindOpts, FindOpts } from "../types/options";
10
+ import { VContext } from "../types/types";
11
+ import { ValtheraCompatible } from "../types/valthera";
12
+
13
+ type DbActionsFns = keyof {
14
+ [K in keyof dbActionBase as dbActionBase[K] extends (...args: any[]) => any ? K : never]: any;
15
+ }
16
+
17
+ /**
18
+ * Represents a database management class for performing CRUD operations.
19
+ * @class
20
+ */
21
+ class ValtheraClass implements ValtheraCompatible {
22
+ dbAction: dbActionBase;
23
+ executor: executorC;
24
+ emiter: UniversalEventEmitter;
25
+ version = version;
26
+
27
+ constructor(options: DbOpts = {}) {
28
+ this.dbAction = options.dbAction || new dbActionBase();
29
+ this.executor = options.executor || new executorC();
30
+ this.emiter = new UniversalEventEmitter();
31
+ }
32
+
33
+ private async execute<T>(name: DbActionsFns, ...args: any[]) {
34
+ const result = await this.executor.addOp(this.dbAction[name].bind(this.dbAction), ...args) as T;
35
+ if (this.emiter.listenerCount(name) !== 0) this.emiter.emit(name, args, result);
36
+ if (this.emiter.listenerCount("*") !== 0) this.emiter.emit("*", name, args, result);
37
+ return result;
38
+ }
39
+
40
+ /**
41
+ * Create a new instance of a CollectionManager class.
42
+ */
43
+ c(collection: string) {
44
+ return new CollectionManager(this, collection);
45
+ }
46
+
47
+ /**
48
+ * Get the names of all available databases.
49
+ */
50
+ async getCollections() {
51
+ return await this.execute<string[]>("getCollections");
52
+ }
53
+
54
+ /**
55
+ * Check and create the specified collection if it doesn't exist.
56
+ */
57
+ async checkCollection(collection: string) {
58
+ return await this.execute<boolean>("checkCollection", { collection });
59
+ }
60
+
61
+ /**
62
+ * Check if a collection exists.
63
+ */
64
+ async issetCollection(collection: string) {
65
+ return await this.execute<boolean>("issetCollection", { collection });
66
+ }
67
+
68
+ /**
69
+ * Add data to a database.
70
+ */
71
+ async add<T = Data>(collection: string, data: Arg, id_gen: boolean = true) {
72
+ return await this.execute<T>("add", { collection, data, id_gen });
73
+ }
74
+
75
+ /**
76
+ * Find data in a database.
77
+ */
78
+ async find<T = Data>(collection: string, search: Search, context: VContext = {}, dbFindOpts: DbFindOpts = {}, findOpts: FindOpts = {}) {
79
+ return await this.execute<T[]>("find", { collection, search, context, dbFindOpts, findOpts });
80
+ }
81
+
82
+ /**
83
+ * Find one data entry in a database.
84
+ */
85
+ async findOne<T = Data>(collection: string, search: Search, context: VContext = {}, findOpts: FindOpts = {}) {
86
+ return await this.execute<T | null>("findOne", { collection, search, context, findOpts });
87
+ }
88
+
89
+ /**
90
+ * Update data in a database.
91
+ */
92
+ async update(collection: string, search: Search, updater: Updater, context = {}) {
93
+ return await this.execute<boolean>("update", { collection, search, updater, context });
94
+ }
95
+
96
+ /**
97
+ * Update one data entry in a database.
98
+ */
99
+ async updateOne(collection: string, search: Search, updater: Updater, context: VContext = {}) {
100
+ return await this.execute<boolean>("updateOne", { collection, search, updater, context });
101
+ }
102
+
103
+ /**
104
+ * Remove data from a database.
105
+ */
106
+ async remove(collection: string, search: Search, context: VContext = {}) {
107
+ return await this.execute<boolean>("remove", { collection, search, context });
108
+ }
109
+
110
+ /**
111
+ * Remove one data entry from a database.
112
+ */
113
+ async removeOne(collection: string, search: Search, context: VContext = {}) {
114
+ return await this.execute<boolean>("removeOne", { collection, search, context });
115
+ }
116
+
117
+ /**
118
+ * Asynchronously updates one entry in a database or adds a new one if it doesn't exist.
119
+ */
120
+ async updateOneOrAdd(collection: string, search: Search, updater: Updater, add_arg: Arg = {}, context: VContext = {}, id_gen: boolean = true) {
121
+ return await this.execute<boolean>("updateOneOrAdd", { collection, search, updater, add_arg, context, id_gen });
122
+ }
123
+
124
+ /**
125
+ * Removes a database collection from the file system.
126
+ */
127
+ async removeCollection(collection: string) {
128
+ return await this.execute<boolean>("removeCollection", { collection });
129
+ }
130
+ }
131
+
132
+ export default ValtheraClass;
@@ -0,0 +1,67 @@
1
+ import { Arg, Search, Updater } from "../types/arg";
2
+ import { DbFindOpts, FindOpts } from "../types/options";
3
+ import { VContext } from "../types/types";
4
+ import Data from "../types/data";
5
+ import { ValtheraCompatible } from "../types/valthera";
6
+
7
+ class CollectionManager {
8
+ constructor(private db: ValtheraCompatible, private collection: string) {}
9
+
10
+ /**
11
+ * Add data to a database.
12
+ */
13
+ async add<T = Data>(data: Arg, id_gen: boolean = true) {
14
+ return await this.db.add(this.collection, data, id_gen) as T;
15
+ }
16
+
17
+ /**
18
+ * Find data in a database.
19
+ */
20
+ async find<T = Data>(search: Search, context: VContext = {}, options: DbFindOpts = {}, findOpts: FindOpts = {}) {
21
+ return await this.db.find(this.collection, search, context, options, findOpts) as T[];
22
+ }
23
+
24
+ /**
25
+ * Find one data entry in a database.
26
+ */
27
+ async findOne<T = Data>(search: Search, context: VContext = {}, findOpts: FindOpts = {}) {
28
+ return await this.db.findOne(this.collection, search, context, findOpts) as (T | null);
29
+ }
30
+
31
+ /**
32
+ * Update data in a database.
33
+ */
34
+ async update(search: Search, updater: Updater, context: VContext = {}) {
35
+ return await this.db.update(this.collection, search, updater, context) as boolean;
36
+ }
37
+
38
+ /**
39
+ * Update one data entry in a database.
40
+ */
41
+ async updateOne(search: Search, updater: Updater, context: VContext = {}) {
42
+ return await this.db.updateOne(this.collection, search, updater, context) as boolean;
43
+ }
44
+
45
+ /**
46
+ * Remove data from a database.
47
+ */
48
+ async remove(search: Search, context: VContext = {}) {
49
+ return await this.db.remove(this.collection, search, context) as boolean;
50
+ }
51
+
52
+ /**
53
+ * Remove one data entry from a database.
54
+ */
55
+ async removeOne(search: Search, context: VContext = {}) {
56
+ return await this.db.removeOne(this.collection, search, context) as boolean;
57
+ }
58
+
59
+ /**
60
+ * Asynchronously updates one entry in a database or adds a new one if it doesn't exist.
61
+ */
62
+ async updateOneOrAdd(search: Search, updater: Updater, add_arg: Arg = {}, context: VContext = {}, id_gen: boolean = true) {
63
+ return await this.db.updateOneOrAdd(this.collection, search, updater, add_arg, context, id_gen) as boolean;
64
+ }
65
+ }
66
+
67
+ export default CollectionManager;
@@ -0,0 +1,73 @@
1
+ export class UniversalEventEmitter {
2
+ private _events: { [event: string]: Function[] } = {};
3
+
4
+ /**
5
+ * Registers an event listener
6
+ * @param {string} event - event name
7
+ * @param {Function} listener - function to be called when event occurs
8
+ */
9
+ public on(event: string, listener: Function): void {
10
+ if (!this._events[event])
11
+ this._events[event] = [];
12
+ this._events[event].push(listener);
13
+ }
14
+
15
+ /**
16
+ * Registers a one-time event listener
17
+ * @param {string} event - event name
18
+ * @param {Function} listener - function to be called once
19
+ */
20
+ public once(event: string, listener: Function): void {
21
+ const onceListener = (...args: any[]) => {
22
+ this.off(event, onceListener);
23
+ listener(...args);
24
+ };
25
+ this.on(event, onceListener);
26
+ }
27
+
28
+ /**
29
+ * Removes an event listener
30
+ * @param {string} event - event name
31
+ * @param {Function} [listener] - optional: specific listener to remove
32
+ */
33
+ public off(event?: string, listener?: Function): void {
34
+ if (!event) {
35
+ // Remove all events
36
+ this._events = {};
37
+ return;
38
+ }
39
+
40
+ if (!listener) {
41
+ // Remove all listeners for the given event
42
+ delete this._events[event];
43
+ return;
44
+ }
45
+
46
+ // Remove specific listener
47
+ if (this._events[event]) {
48
+ this._events[event] = this._events[event].filter(l => l !== listener);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Emits an event
54
+ * @param {string} event - event name
55
+ * @param {...any} args - arguments to be passed to listeners
56
+ */
57
+ public emit(event: string, ...args: any[]): void {
58
+ const listeners = this._events[event];
59
+ if (listeners && listeners.length > 0) {
60
+ listeners.forEach(listener => {
61
+ listener(...args);
62
+ });
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Returns the number of listeners for the given event
68
+ */
69
+ public listenerCount(event: string): number {
70
+ return this._events[event]?.length || 0;
71
+ }
72
+ }
73
+
@@ -0,0 +1,55 @@
1
+ interface Task {
2
+ func: Function;
3
+ param: any[];
4
+ resolve: Function;
5
+ reject: Function;
6
+ }
7
+
8
+ /**
9
+ * A simple executor for queuing and executing asynchronous operations sequentially.
10
+ * @class
11
+ */
12
+ class executorC {
13
+ quote: Task[];
14
+ isExecuting: boolean;
15
+
16
+ /**
17
+ * Create a new executor instance.
18
+ * @constructor
19
+ */
20
+ constructor() {
21
+ this.quote = [];
22
+ this.isExecuting = false;
23
+ }
24
+
25
+ /**
26
+ * Add an asynchronous operation to the execution queue.
27
+ */
28
+ async addOp(func: Function, ...param) {
29
+ return await new Promise((resolve, reject) => {
30
+ this.quote.push({
31
+ func,
32
+ param,
33
+ resolve,
34
+ reject
35
+ });
36
+ this.execute();
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Execute the queued asynchronous operations sequentially.
42
+ */
43
+ async execute() {
44
+ if (this.isExecuting) return;
45
+ this.isExecuting = true;
46
+ while (this.quote.length > 0) {
47
+ let q = this.quote.shift();
48
+ let res = await q.func(...q.param);
49
+ q.resolve(res)
50
+ }
51
+ this.isExecuting = false;
52
+ }
53
+ }
54
+
55
+ export default executorC;
@@ -0,0 +1,154 @@
1
+ import Id from "../types/Id";
2
+
3
+ const usedIdsMap = new Map();
4
+ let lastId: Id;
5
+ const recentIdsTimestamps: number[] = [];
6
+ let startIndex = 0;
7
+ let lastGeneratedMs = 0;
8
+ let lastRandomValue = 0;
9
+
10
+ /**
11
+ * Generates a unique random identifier based on time and parts.
12
+ *
13
+ * @param {number[]} [parts] - an array of lengths of parts of the identifier
14
+ * @returns {Id} - a new unique identifier
15
+ */
16
+ export default function genId(parts: number[] = null): Id {
17
+ if (parts === null) parts = [1, 1];
18
+
19
+ const time = getTime();
20
+ const id = getUniqueRandom({
21
+ time,
22
+ partsSchema: parts,
23
+ s: 0,
24
+ });
25
+ return id;
26
+ }
27
+
28
+ interface GetUniqueRandomOpts {
29
+ time: string,
30
+ partsSchema: number[],
31
+ s: number
32
+ }
33
+
34
+ /**
35
+ * Generates a unique random identifier based on time and parts.
36
+ */
37
+ function getUniqueRandom(opts: GetUniqueRandomOpts): string {
38
+ while (true) {
39
+ let minValue = 0;
40
+ if (lastId) {
41
+ const parts = lastId.split("-");
42
+ const lastTime = parts.shift();
43
+ if (lastTime === opts.time) {
44
+ const int36 = parts.join("");
45
+ minValue = parseInt(int36, 36) + 1;
46
+ }
47
+ }
48
+
49
+ const partsLengthSum = opts.partsSchema.reduce((acc, num) => acc + num, 0);
50
+ const partsData = generateBase36InRange(minValue, partsLengthSum);
51
+ const parts = splitStringByArray(opts.partsSchema, partsData);
52
+
53
+ const id = [opts.time, ...parts].join("-");
54
+
55
+ if (!usedIdsMap.has(id)) {
56
+ usedIdsMap.set(id, true);
57
+ setTimeout(() => usedIdsMap.delete(id), 1000);
58
+ lastId = id;
59
+ return id;
60
+ }
61
+ opts.s++;
62
+ if (opts.s >= partsLengthSum) {
63
+ opts.time = getTime();
64
+ opts.s = 0;
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Tracks the current load of the application by counting the number of ids that have been generated in the last second.
71
+ * @returns The number of ids that have been generated in the last second.
72
+ */
73
+ function trackLoad() {
74
+ const now = Date.now();
75
+ recentIdsTimestamps.push(now);
76
+
77
+ while (startIndex < recentIdsTimestamps.length && recentIdsTimestamps[startIndex] < now - 1) {
78
+ startIndex++;
79
+ }
80
+
81
+ return recentIdsTimestamps.length - startIndex;
82
+ }
83
+
84
+ /**
85
+ * Generates a base-36 encoded string with a specified length based on a random value within a range.
86
+ *
87
+ * The function calculates a random value within the base-36 range determined by the provided
88
+ * `minValue` and `length`. It uses a bias mechanism to adjust the randomness based on the
89
+ * current load, ensuring unique generation over short periods. If the system load is low or
90
+ * the current millisecond has changed, a new random value is generated. Otherwise, the random
91
+ * value is incremented by a small amount with certain probabilities.
92
+ *
93
+ * @param minValue - The minimum value for the base-36 encoded number.
94
+ * @param length - The length of the resulting base-36 string.
95
+ * @returns A base-36 encoded string representation of the random value, padded to the specified length.
96
+ */
97
+
98
+ function generateBase36InRange(minValue: number, length: number): string {
99
+ const maxValue = Math.pow(36, length) - 1;
100
+ const load = trackLoad();
101
+ const currentMs = Date.now();
102
+
103
+ if (load < 3 || currentMs !== lastGeneratedMs) {
104
+ lastRandomValue = biasedRandomInRange(minValue, maxValue);
105
+ lastGeneratedMs = currentMs;
106
+ } else {
107
+ const rand = Math.random();
108
+ if (rand < 0.6) {
109
+ lastRandomValue += 1;
110
+ } else if (rand < 0.9) {
111
+ lastRandomValue += 2;
112
+ } else {
113
+ lastRandomValue = biasedRandomInRange(lastRandomValue, maxValue);
114
+ }
115
+ }
116
+
117
+ return lastRandomValue.toString(36).padStart(length, "0");
118
+ }
119
+
120
+ /**
121
+ * Generates a random number between the specified range, but with a bias towards the lower
122
+ * end of the range. The bias is calculated as the cube root of the random value, which
123
+ * will make the random value more likely to be closer to the minimum value.
124
+ * @param min - The minimum value of the range.
125
+ * @param max - The maximum value of the range.
126
+ * @returns A random number between the specified range, with a bias towards the lower end.
127
+ */
128
+ function biasedRandomInRange(min: number, max: number): number {
129
+ let randomValue = min;
130
+ const bias = Math.pow(Math.random(), 3);
131
+ randomValue = Math.floor(min + (max - min) * bias);
132
+ return Math.min(randomValue, max);
133
+ }
134
+
135
+ /**
136
+ * Splits a string into an array of substrings, where each substring is determined by
137
+ * the values in the provided array.
138
+ *
139
+ * @param arr - An array of numbers, where each number represents the length of a substring.
140
+ * @param str - The string to split.
141
+ * @returns An array of substrings, where each element is a substring of the original string,
142
+ * determined by the corresponding element in the provided array.
143
+ */
144
+ function splitStringByArray(arr: number[], str: string) {
145
+ let start = 0;
146
+ return arr.map(length => str.slice(start, start += length));
147
+ }
148
+
149
+ /**
150
+ * Gets the current time in a base36 string format.
151
+ */
152
+ function getTime() {
153
+ return new Date().getTime().toString(36);
154
+ }