@filen/utils 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.
package/dist/cn.js ADDED
@@ -0,0 +1,5 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from "./cn";
2
+ export * from "./run";
3
+ export * from "./semaphore";
4
+ export * from "./serialize";
5
+ export * from "./types";
6
+ export * from "./misc";
7
+ export * from "./time";
package/dist/misc.js ADDED
@@ -0,0 +1,311 @@
1
+ import { validate as validateUUID } from "uuid";
2
+ export function parseNumbersFromString(string) {
3
+ if (!string) {
4
+ return 0;
5
+ }
6
+ const len = string.length;
7
+ if (len < 10) {
8
+ let result = 0;
9
+ let hasDigit = false;
10
+ for (let i = 0; i < len; i++) {
11
+ const code = string.charCodeAt(i);
12
+ if (code >= 48 && code <= 57) {
13
+ result = result * 10 + (code - 48);
14
+ hasDigit = true;
15
+ }
16
+ }
17
+ return hasDigit ? result : 0;
18
+ }
19
+ let result = 0;
20
+ let digitCount = 0;
21
+ const maxDigits = 16;
22
+ for (let i = 0; i < len && digitCount < maxDigits; i++) {
23
+ const code = string.charCodeAt(i);
24
+ if (code >= 48 && code <= 57) {
25
+ result = result * 10 + (code - 48);
26
+ digitCount++;
27
+ }
28
+ }
29
+ return result;
30
+ }
31
+ export function convertTimestampToMs(timestamp) {
32
+ if (timestamp < 10000000000) {
33
+ return timestamp * 1000;
34
+ }
35
+ return timestamp;
36
+ }
37
+ export function isValidHexColor(value, length = 6) {
38
+ if (value.length !== (length >= 6 ? 7 : 4) && value.length !== 7) {
39
+ return false;
40
+ }
41
+ if (value.charCodeAt(0) !== 35) {
42
+ return false;
43
+ }
44
+ const len = value.length;
45
+ for (let i = 1; i < len; i++) {
46
+ const code = value.charCodeAt(i);
47
+ if (!((code >= 48 && code <= 57) || (code >= 65 && code <= 70) || (code >= 97 && code <= 102))) {
48
+ return false;
49
+ }
50
+ }
51
+ return true;
52
+ }
53
+ export function chunkArray(array, chunkSize) {
54
+ const chunks = [];
55
+ for (let i = 0; i < array.length; i += chunkSize) {
56
+ chunks.push(array.slice(i, i + chunkSize));
57
+ }
58
+ return chunks;
59
+ }
60
+ export function sanitizeFileName(filename, replacement = "_") {
61
+ let sanitizedFilename = filename.normalize("NFC");
62
+ sanitizedFilename = sanitizedFilename.replace(/[\u200B-\u200D\uFEFF\u00AD\u0000-\u001F\u007F-\u009F]/g, "");
63
+ sanitizedFilename = sanitizedFilename.replace(/[^\x00-\x7F]/g, replacement);
64
+ const illegalCharsWindows = /[<>:"/\\|?*]/g;
65
+ const illegalCharsUnix = /\//g;
66
+ const reservedNamesWindows = new Set([
67
+ "CON",
68
+ "PRN",
69
+ "AUX",
70
+ "NUL",
71
+ "COM1",
72
+ "COM2",
73
+ "COM3",
74
+ "COM4",
75
+ "COM5",
76
+ "COM6",
77
+ "COM7",
78
+ "COM8",
79
+ "COM9",
80
+ "LPT1",
81
+ "LPT2",
82
+ "LPT3",
83
+ "LPT4",
84
+ "LPT5",
85
+ "LPT6",
86
+ "LPT7",
87
+ "LPT8",
88
+ "LPT9"
89
+ ]);
90
+ sanitizedFilename = sanitizedFilename.replace(illegalCharsWindows, replacement);
91
+ sanitizedFilename = sanitizedFilename.replace(illegalCharsUnix, replacement);
92
+ sanitizedFilename = sanitizedFilename.replace(/[. ]+$/, "");
93
+ sanitizedFilename = sanitizedFilename.replace(/\s+/g, replacement);
94
+ if (reservedNamesWindows.has(sanitizedFilename.toUpperCase())) {
95
+ sanitizedFilename += replacement;
96
+ }
97
+ const maxByteLength = 255;
98
+ let byteLength = new TextEncoder().encode(sanitizedFilename).length;
99
+ while (byteLength > maxByteLength && sanitizedFilename.length > 0) {
100
+ sanitizedFilename = sanitizedFilename.slice(0, -1);
101
+ byteLength = new TextEncoder().encode(sanitizedFilename).length;
102
+ }
103
+ if (!sanitizedFilename) {
104
+ return "file";
105
+ }
106
+ return sanitizedFilename;
107
+ }
108
+ export function findClosestIndexString(sourceString, targetString, givenIndex) {
109
+ const extractedSubstring = sourceString.slice(0, givenIndex + 1);
110
+ const lastIndexWithinExtracted = extractedSubstring.lastIndexOf(targetString);
111
+ if (lastIndexWithinExtracted !== -1) {
112
+ return lastIndexWithinExtracted;
113
+ }
114
+ for (let offset = 1; offset <= givenIndex; offset++) {
115
+ const substringBefore = sourceString.slice(givenIndex - offset, givenIndex + 1);
116
+ const lastIndexBefore = substringBefore.lastIndexOf(targetString);
117
+ if (lastIndexBefore !== -1) {
118
+ return givenIndex - offset + lastIndexBefore;
119
+ }
120
+ }
121
+ return -1;
122
+ }
123
+ export const URL_REGEX = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,64}\b(?:[-a-zA-Z0-9()@:%_+.~#?&//=]*)/gi;
124
+ export function extractLinksFromString(text) {
125
+ if (!text) {
126
+ return [];
127
+ }
128
+ const matches = text.matchAll(URL_REGEX);
129
+ const results = [];
130
+ for (const match of matches) {
131
+ if (match[0]) {
132
+ results.push(match[0]);
133
+ }
134
+ }
135
+ return results;
136
+ }
137
+ export function parseYouTubeVideoId(url) {
138
+ const regExp = /(?:\?v=|\/embed\/|\/watch\?v=|\/\w+\/\w+\/|youtu.be\/)([\w-]{11})/;
139
+ const match = url.match(regExp);
140
+ if (match && match.length === 2 && match[1]) {
141
+ return match[1];
142
+ }
143
+ return null;
144
+ }
145
+ export function parseFilenPublicLink(url) {
146
+ if (!url || url.length === 0) {
147
+ return null;
148
+ }
149
+ const filenRegex = /https?:\/\/(?:app|drive)\.filen\.io\/#\/([df])\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:%23|#)([A-Za-z0-9]{32,})/;
150
+ const match = filenRegex.exec(url);
151
+ if (!match || match.length < 4 || !match[1] || !match[2] || !match[3]) {
152
+ return null;
153
+ }
154
+ const pathType = match[1];
155
+ const uuid = match[2];
156
+ let key = match[3];
157
+ if (/^[0-9A-Fa-f]{64}$/.test(key)) {
158
+ try {
159
+ key = Buffer.from(key, "hex").toString("utf8");
160
+ }
161
+ catch (_a) {
162
+ return null;
163
+ }
164
+ }
165
+ if (Buffer.from(key).length !== 32 || !validateUUID(uuid) || (pathType !== "d" && pathType !== "f")) {
166
+ return null;
167
+ }
168
+ return {
169
+ uuid,
170
+ key,
171
+ type: pathType === "d" ? "file" : "directory"
172
+ };
173
+ }
174
+ export function parseXStatusId(url) {
175
+ const ex = url.split("/");
176
+ const part = ex[ex.length - 1];
177
+ if (!part) {
178
+ return "";
179
+ }
180
+ return part.trim();
181
+ }
182
+ export function ratePasswordStrength(password) {
183
+ const hasUppercase = /[A-Z]/.test(password);
184
+ const hasLowercase = /[a-z]/.test(password);
185
+ const hasSpecialChars = /[!@#$%^&*(),.?":{}|<>]/.test(password);
186
+ const length = password.length;
187
+ let strength = "weak";
188
+ if (length >= 10 && hasUppercase && hasLowercase && hasSpecialChars) {
189
+ if (length >= 16) {
190
+ strength = "best";
191
+ }
192
+ else {
193
+ strength = "strong";
194
+ }
195
+ }
196
+ else if (length >= 10 && ((hasUppercase && hasLowercase) || (hasUppercase && hasSpecialChars) || (hasLowercase && hasSpecialChars))) {
197
+ strength = "normal";
198
+ }
199
+ return {
200
+ strength,
201
+ uppercase: hasUppercase,
202
+ lowercase: hasLowercase,
203
+ specialChars: hasSpecialChars,
204
+ length: length >= 10
205
+ };
206
+ }
207
+ export function sortParams(params) {
208
+ const keys = Object.keys(params).sort();
209
+ const len = keys.length;
210
+ const result = {};
211
+ for (let i = 0; i < len; i++) {
212
+ const key = keys[i];
213
+ result[key] = params[key];
214
+ }
215
+ return result;
216
+ }
217
+ export function jsonBigIntReplacer(_, value) {
218
+ if (typeof value === "bigint") {
219
+ return `$bigint:${value.toString()}n`;
220
+ }
221
+ return value;
222
+ }
223
+ export function jsonBigIntReviver(_, value) {
224
+ if (typeof value === "string" && value.startsWith("$bigint:") && value.endsWith("n")) {
225
+ return BigInt(value.substring(8, -1));
226
+ }
227
+ return value;
228
+ }
229
+ export function createExecutableTimeout(callback, delay) {
230
+ let timeoutId = setTimeout(callback, delay);
231
+ return {
232
+ id: timeoutId,
233
+ execute: () => {
234
+ if (timeoutId !== null) {
235
+ clearTimeout(timeoutId);
236
+ timeoutId = null;
237
+ }
238
+ callback();
239
+ },
240
+ cancel: () => {
241
+ if (timeoutId !== null) {
242
+ clearTimeout(timeoutId);
243
+ timeoutId = null;
244
+ }
245
+ }
246
+ };
247
+ }
248
+ export function fastLocaleCompare(a, b) {
249
+ if (a === b) {
250
+ return 0;
251
+ }
252
+ const lenA = a.length;
253
+ const lenB = b.length;
254
+ let idxA = 0;
255
+ let idxB = 0;
256
+ let caseDiff = 0;
257
+ while (idxA < lenA && idxB < lenB) {
258
+ const charA = a.charCodeAt(idxA);
259
+ const charB = b.charCodeAt(idxB);
260
+ const isDigitA = charA >= 48 && charA <= 57;
261
+ const isDigitB = charB >= 48 && charB <= 57;
262
+ if (isDigitA && isDigitB) {
263
+ let numA = 0;
264
+ let numB = 0;
265
+ while (idxA < lenA) {
266
+ const c = a.charCodeAt(idxA);
267
+ if (c < 48 || c > 57) {
268
+ break;
269
+ }
270
+ numA = numA * 10 + (c - 48);
271
+ idxA++;
272
+ }
273
+ while (idxB < lenB) {
274
+ const c = b.charCodeAt(idxB);
275
+ if (c < 48 || c > 57) {
276
+ break;
277
+ }
278
+ numB = numB * 10 + (c - 48);
279
+ idxB++;
280
+ }
281
+ if (numA !== numB) {
282
+ return numA < numB ? -1 : 1;
283
+ }
284
+ }
285
+ else if (isDigitA) {
286
+ return -1;
287
+ }
288
+ else if (isDigitB) {
289
+ return 1;
290
+ }
291
+ else {
292
+ const lowerA = charA >= 65 && charA <= 90 ? charA + 32 : charA;
293
+ const lowerB = charB >= 65 && charB <= 90 ? charB + 32 : charB;
294
+ if (lowerA !== lowerB) {
295
+ return lowerA < lowerB ? -1 : 1;
296
+ }
297
+ if (caseDiff === 0 && charA !== charB) {
298
+ caseDiff = charA > charB ? -1 : 1;
299
+ }
300
+ idxA++;
301
+ idxB++;
302
+ }
303
+ }
304
+ if (idxA < lenA) {
305
+ return 1;
306
+ }
307
+ if (idxB < lenB) {
308
+ return -1;
309
+ }
310
+ return caseDiff;
311
+ }
package/dist/run.js ADDED
@@ -0,0 +1,294 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ export function run(fn, options) {
11
+ return __awaiter(this, void 0, void 0, function* () {
12
+ var _a, _b, _c;
13
+ const deferredFunctions = [];
14
+ const defer = deferFn => {
15
+ deferredFunctions.push(deferFn);
16
+ };
17
+ try {
18
+ const result = yield fn(defer);
19
+ return {
20
+ success: true,
21
+ data: result,
22
+ error: null
23
+ };
24
+ }
25
+ catch (e) {
26
+ const error = e instanceof Error ? e : new Error("Unknown error");
27
+ (_a = options === null || options === void 0 ? void 0 : options.onError) === null || _a === void 0 ? void 0 : _a.call(options, error);
28
+ if (options === null || options === void 0 ? void 0 : options.throw) {
29
+ throw error;
30
+ }
31
+ return {
32
+ success: false,
33
+ data: null,
34
+ error: error
35
+ };
36
+ }
37
+ finally {
38
+ for (let i = deferredFunctions.length - 1; i >= 0; i--) {
39
+ try {
40
+ yield ((_b = deferredFunctions[i]) === null || _b === void 0 ? void 0 : _b.call(deferredFunctions));
41
+ }
42
+ catch (e) {
43
+ (_c = options === null || options === void 0 ? void 0 : options.onError) === null || _c === void 0 ? void 0 : _c.call(options, e instanceof Error ? e : new Error("Unknown error"));
44
+ }
45
+ }
46
+ }
47
+ });
48
+ }
49
+ export class AbortError extends Error {
50
+ constructor(message = "Operation aborted") {
51
+ super(message);
52
+ this.name = "AbortError";
53
+ }
54
+ }
55
+ export function abortSignalReason(signal) {
56
+ try {
57
+ if (signal.reason instanceof Error) {
58
+ return signal.reason.message;
59
+ }
60
+ else if (typeof signal.reason === "string") {
61
+ return signal.reason;
62
+ }
63
+ else if (signal.reason !== undefined) {
64
+ return String(signal.reason);
65
+ }
66
+ return undefined;
67
+ }
68
+ catch (_a) {
69
+ return undefined;
70
+ }
71
+ }
72
+ export function runAbortable(fn, options) {
73
+ return __awaiter(this, void 0, void 0, function* () {
74
+ var _a, _b, _c, _d, _e, _f, _g;
75
+ const deferredFunctions = [];
76
+ const controller = (_a = options === null || options === void 0 ? void 0 : options.controller) !== null && _a !== void 0 ? _a : new AbortController();
77
+ const signal = (_d = (_b = options === null || options === void 0 ? void 0 : options.signal) !== null && _b !== void 0 ? _b : (_c = options === null || options === void 0 ? void 0 : options.controller) === null || _c === void 0 ? void 0 : _c.signal) !== null && _d !== void 0 ? _d : controller.signal;
78
+ const defer = deferFn => {
79
+ deferredFunctions.push(deferFn);
80
+ };
81
+ const abortable = (abortableFn, opts) => __awaiter(this, void 0, void 0, function* () {
82
+ if (signal.aborted) {
83
+ throw new AbortError(abortSignalReason(signal));
84
+ }
85
+ return yield new Promise((resolve, reject) => {
86
+ ;
87
+ (() => __awaiter(this, void 0, void 0, function* () {
88
+ var _a;
89
+ const signal = (_a = opts === null || opts === void 0 ? void 0 : opts.signal) !== null && _a !== void 0 ? _a : controller.signal;
90
+ const abortHandler = () => {
91
+ reject(new AbortError(abortSignalReason(signal)));
92
+ };
93
+ signal.addEventListener("abort", abortHandler);
94
+ try {
95
+ if (signal.aborted) {
96
+ reject(new AbortError(abortSignalReason(signal)));
97
+ return;
98
+ }
99
+ const result = yield abortableFn();
100
+ if (signal.aborted) {
101
+ reject(new AbortError(abortSignalReason(signal)));
102
+ return;
103
+ }
104
+ resolve(result);
105
+ }
106
+ catch (error) {
107
+ reject(error);
108
+ }
109
+ finally {
110
+ signal.removeEventListener("abort", abortHandler);
111
+ }
112
+ }))();
113
+ });
114
+ });
115
+ try {
116
+ if (signal.aborted) {
117
+ throw new AbortError(abortSignalReason(signal));
118
+ }
119
+ const result = yield fn({
120
+ abortable,
121
+ defer,
122
+ signal,
123
+ controller
124
+ });
125
+ if (signal.aborted) {
126
+ throw new AbortError(abortSignalReason(signal));
127
+ }
128
+ return {
129
+ success: true,
130
+ data: result,
131
+ error: null
132
+ };
133
+ }
134
+ catch (e) {
135
+ const error = e instanceof Error ? e : new Error("Unknown error");
136
+ (_e = options === null || options === void 0 ? void 0 : options.onError) === null || _e === void 0 ? void 0 : _e.call(options, error);
137
+ if (options === null || options === void 0 ? void 0 : options.throw) {
138
+ throw error;
139
+ }
140
+ return {
141
+ success: false,
142
+ data: null,
143
+ error: error
144
+ };
145
+ }
146
+ finally {
147
+ for (let i = deferredFunctions.length - 1; i >= 0; i--) {
148
+ try {
149
+ yield ((_f = deferredFunctions[i]) === null || _f === void 0 ? void 0 : _f.call(deferredFunctions));
150
+ }
151
+ catch (e) {
152
+ (_g = options === null || options === void 0 ? void 0 : options.onError) === null || _g === void 0 ? void 0 : _g.call(options, e instanceof Error ? e : new Error("Unknown error"));
153
+ }
154
+ }
155
+ }
156
+ });
157
+ }
158
+ export function runEffect(fn, options) {
159
+ var _a;
160
+ const deferredFunctions = [];
161
+ const defer = deferFn => {
162
+ deferredFunctions.push(deferFn);
163
+ };
164
+ const cleanup = () => {
165
+ var _a, _b;
166
+ for (let i = deferredFunctions.length - 1; i >= 0; i--) {
167
+ try {
168
+ (_a = deferredFunctions[i]) === null || _a === void 0 ? void 0 : _a.call(deferredFunctions);
169
+ }
170
+ catch (e) {
171
+ (_b = options === null || options === void 0 ? void 0 : options.onError) === null || _b === void 0 ? void 0 : _b.call(options, e instanceof Error ? e : new Error("Unknown error"));
172
+ }
173
+ }
174
+ };
175
+ try {
176
+ const result = fn(defer);
177
+ return {
178
+ success: true,
179
+ data: result,
180
+ error: null,
181
+ cleanup
182
+ };
183
+ }
184
+ catch (e) {
185
+ const error = e instanceof Error ? e : new Error("Unknown error");
186
+ (_a = options === null || options === void 0 ? void 0 : options.onError) === null || _a === void 0 ? void 0 : _a.call(options, error);
187
+ if (options === null || options === void 0 ? void 0 : options.throw) {
188
+ throw error;
189
+ }
190
+ return {
191
+ success: false,
192
+ data: null,
193
+ error: error,
194
+ cleanup
195
+ };
196
+ }
197
+ finally {
198
+ if (options === null || options === void 0 ? void 0 : options.automaticCleanup) {
199
+ cleanup();
200
+ }
201
+ }
202
+ }
203
+ export function runRetry(fn, options) {
204
+ return __awaiter(this, void 0, void 0, function* () {
205
+ var _a, _b, _c, _d;
206
+ const maxAttempts = (_a = options === null || options === void 0 ? void 0 : options.maxAttempts) !== null && _a !== void 0 ? _a : 3;
207
+ const delayMs = (_b = options === null || options === void 0 ? void 0 : options.delayMs) !== null && _b !== void 0 ? _b : 1000;
208
+ const backoff = (_c = options === null || options === void 0 ? void 0 : options.backoff) !== null && _c !== void 0 ? _c : "exponential";
209
+ let lastError = null;
210
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
211
+ const result = yield run(defer => fn(defer, attempt), Object.assign(Object.assign({}, options), { throw: false }));
212
+ if (result.success) {
213
+ return result;
214
+ }
215
+ lastError = result.error;
216
+ if (attempt < maxAttempts) {
217
+ const shouldRetry = typeof (options === null || options === void 0 ? void 0 : options.shouldRetry) === "boolean"
218
+ ? options.shouldRetry
219
+ : options && typeof options.shouldRetry === "function"
220
+ ? options.shouldRetry(result.error, attempt)
221
+ : true;
222
+ if (!shouldRetry) {
223
+ break;
224
+ }
225
+ (_d = options === null || options === void 0 ? void 0 : options.onRetry) === null || _d === void 0 ? void 0 : _d.call(options, result.error, attempt);
226
+ const delay = backoff === "exponential" ? delayMs * Math.pow(2, attempt - 1) : delayMs * attempt;
227
+ yield new Promise(resolve => setTimeout(resolve, delay));
228
+ }
229
+ }
230
+ return {
231
+ success: false,
232
+ data: null,
233
+ error: lastError
234
+ };
235
+ });
236
+ }
237
+ export class TimeoutError extends Error {
238
+ constructor(message = "Operation timed out") {
239
+ super(message);
240
+ this.name = "TimeoutError";
241
+ }
242
+ }
243
+ export function runTimeout(fn, timeoutMs, options) {
244
+ return __awaiter(this, void 0, void 0, function* () {
245
+ var _a;
246
+ const controller = new AbortController();
247
+ try {
248
+ const result = yield Promise.race([
249
+ run(fn, options),
250
+ new Promise((_, reject) => {
251
+ const timeoutId = setTimeout(() => {
252
+ controller.abort();
253
+ reject(new TimeoutError(`Operation timed out after ${timeoutMs}ms`));
254
+ }, timeoutMs);
255
+ controller.signal.addEventListener("abort", () => clearTimeout(timeoutId));
256
+ })
257
+ ]);
258
+ return result;
259
+ }
260
+ catch (e) {
261
+ const error = e instanceof Error ? e : new Error("Unknown error");
262
+ (_a = options === null || options === void 0 ? void 0 : options.onError) === null || _a === void 0 ? void 0 : _a.call(options, error);
263
+ if (options === null || options === void 0 ? void 0 : options.throw) {
264
+ throw error;
265
+ }
266
+ return {
267
+ success: false,
268
+ data: null,
269
+ error: error
270
+ };
271
+ }
272
+ });
273
+ }
274
+ export function runDebounced(fn, delayMs, options) {
275
+ let timeoutId = null;
276
+ let pendingPromise = null;
277
+ return (...args) => {
278
+ if (timeoutId) {
279
+ clearTimeout(timeoutId);
280
+ }
281
+ if (!pendingPromise) {
282
+ pendingPromise = new Promise(resolve => {
283
+ timeoutId = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
284
+ const result = yield run(defer => fn(defer, ...args), options);
285
+ resolve(result);
286
+ pendingPromise = null;
287
+ timeoutId = null;
288
+ }), delayMs);
289
+ });
290
+ }
291
+ return pendingPromise;
292
+ };
293
+ }
294
+ export default run;
@@ -0,0 +1,52 @@
1
+ export class Semaphore {
2
+ constructor(max = 1) {
3
+ this.counter = 0;
4
+ this.waiting = [];
5
+ this.maxCount = max;
6
+ }
7
+ acquire() {
8
+ if (this.counter < this.maxCount) {
9
+ this.counter++;
10
+ return Promise.resolve();
11
+ }
12
+ return new Promise((resolve, reject) => {
13
+ this.waiting.push({
14
+ resolve,
15
+ reject
16
+ });
17
+ });
18
+ }
19
+ release() {
20
+ if (this.counter <= 0) {
21
+ return;
22
+ }
23
+ this.counter--;
24
+ this.processQueue();
25
+ }
26
+ count() {
27
+ return this.counter;
28
+ }
29
+ setMax(newMax) {
30
+ this.maxCount = newMax;
31
+ this.processQueue();
32
+ }
33
+ purge() {
34
+ const unresolved = this.waiting.length;
35
+ for (const waiter of this.waiting) {
36
+ waiter.reject("Task has been purged");
37
+ }
38
+ this.counter = 0;
39
+ this.waiting = [];
40
+ return unresolved;
41
+ }
42
+ processQueue() {
43
+ if (this.waiting.length > 0 && this.counter < this.maxCount) {
44
+ this.counter++;
45
+ const waiter = this.waiting.shift();
46
+ if (waiter) {
47
+ waiter.resolve();
48
+ }
49
+ }
50
+ }
51
+ }
52
+ export default Semaphore;
@@ -0,0 +1,15 @@
1
+ export function serializeError(error) {
2
+ return {
3
+ name: error.name,
4
+ message: error.message,
5
+ stack: error.stack,
6
+ stringified: JSON.stringify(error)
7
+ };
8
+ }
9
+ export function deserializeError(serializedError) {
10
+ const error = new Error(serializedError.message);
11
+ error.name = serializedError.name;
12
+ error.stack = serializedError.stack;
13
+ error.message = serializedError.message;
14
+ return error;
15
+ }
package/dist/time.js ADDED
@@ -0,0 +1,68 @@
1
+ export function isTimestampSameDay(timestamp1, timestamp2) {
2
+ const diff = timestamp1 - timestamp2;
3
+ if (diff >= -86400000 && diff <= 86400000) {
4
+ const day1 = Math.floor(timestamp1 / 86400000);
5
+ const day2 = Math.floor(timestamp2 / 86400000);
6
+ if (day1 === day2) {
7
+ return true;
8
+ }
9
+ }
10
+ const date1 = new Date(timestamp1);
11
+ const date2 = new Date(timestamp2);
12
+ return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
13
+ }
14
+ export function isTimestampSameMinute(timestamp1, timestamp2) {
15
+ const diff = Math.abs(timestamp1 - timestamp2);
16
+ if (diff > 120000) {
17
+ return false;
18
+ }
19
+ const date1 = new Date(timestamp1);
20
+ const date2 = new Date(timestamp2);
21
+ if (date1.getFullYear() !== date2.getFullYear() ||
22
+ date1.getMonth() !== date2.getMonth() ||
23
+ date1.getDate() !== date2.getDate() ||
24
+ date1.getHours() !== date2.getHours()) {
25
+ return false;
26
+ }
27
+ const minuteDiff = Math.abs(date1.getMinutes() - date2.getMinutes());
28
+ return minuteDiff <= 2;
29
+ }
30
+ export function formatSecondsToHHMM(seconds) {
31
+ if (seconds < 0 || seconds !== seconds) {
32
+ return "00:00";
33
+ }
34
+ const hours = (seconds / 3600) | 0;
35
+ const minutes = ((seconds % 3600) / 60) | 0;
36
+ const h1 = (hours / 10) | 0;
37
+ const h2 = hours % 10;
38
+ const m1 = (minutes / 10) | 0;
39
+ const m2 = minutes % 10;
40
+ return String(h1) + h2 + ":" + m1 + m2;
41
+ }
42
+ export function formatSecondsToMMSS(seconds) {
43
+ if (seconds < 0 || seconds !== seconds) {
44
+ return "00:00";
45
+ }
46
+ const minutes = (seconds / 60) | 0;
47
+ const remainingSeconds = seconds % 60 | 0;
48
+ const m1 = (minutes / 10) | 0;
49
+ const m2 = minutes % 10;
50
+ const s1 = (remainingSeconds / 10) | 0;
51
+ const s2 = remainingSeconds % 10;
52
+ return String(m1) + m2 + ":" + s1 + s2;
53
+ }
54
+ export function getTimeRemaining(endTimestamp) {
55
+ const total = endTimestamp - Date.now();
56
+ const totalSeconds = (total / 1000) | 0;
57
+ const days = (totalSeconds / 86400) | 0;
58
+ const hours = ((totalSeconds % 86400) / 3600) | 0;
59
+ const minutes = ((totalSeconds % 3600) / 60) | 0;
60
+ const seconds = totalSeconds % 60;
61
+ return {
62
+ total,
63
+ days,
64
+ hours,
65
+ minutes,
66
+ seconds
67
+ };
68
+ }
@@ -0,0 +1,2 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;
@@ -0,0 +1,7 @@
1
+ export * from "./cn";
2
+ export * from "./run";
3
+ export * from "./semaphore";
4
+ export * from "./serialize";
5
+ export * from "./types";
6
+ export * from "./misc";
7
+ export * from "./time";
@@ -0,0 +1,31 @@
1
+ export declare function parseNumbersFromString(string: string): number;
2
+ export declare function convertTimestampToMs(timestamp: number): number;
3
+ export declare function isValidHexColor(value: string, length?: number): boolean;
4
+ export declare function chunkArray<T>(array: T[], chunkSize: number): T[][];
5
+ export declare function sanitizeFileName(filename: string, replacement?: string): string;
6
+ export declare function findClosestIndexString(sourceString: string, targetString: string, givenIndex: number): number;
7
+ export declare const URL_REGEX: RegExp;
8
+ export declare function extractLinksFromString(text: string): string[];
9
+ export declare function parseYouTubeVideoId(url: string): string | null;
10
+ export declare function parseFilenPublicLink(url: string): {
11
+ uuid: string;
12
+ key: string;
13
+ type: "file" | "directory";
14
+ } | null;
15
+ export declare function parseXStatusId(url: string): string;
16
+ export declare function ratePasswordStrength(password: string): {
17
+ strength: "weak" | "normal" | "strong" | "best";
18
+ uppercase: boolean;
19
+ lowercase: boolean;
20
+ specialChars: boolean;
21
+ length: boolean;
22
+ };
23
+ export declare function sortParams<T extends Record<string, unknown>>(params: T): T;
24
+ export declare function jsonBigIntReplacer(_: string, value: unknown): unknown;
25
+ export declare function jsonBigIntReviver(_: string, value: unknown): unknown;
26
+ export declare function createExecutableTimeout(callback: () => void, delay?: number): {
27
+ id: NodeJS.Timeout;
28
+ execute: () => void;
29
+ cancel: () => void;
30
+ };
31
+ export declare function fastLocaleCompare(a: string, b: string): number;
@@ -0,0 +1,54 @@
1
+ export type Success<T> = {
2
+ success: true;
3
+ data: T;
4
+ error: null;
5
+ };
6
+ export type Failure<E = Error> = {
7
+ success: false;
8
+ data: null;
9
+ error: E;
10
+ };
11
+ export type Result<T, E = Error> = Success<T> | Failure<E>;
12
+ export type GenericFnResult = number | boolean | string | object | null | undefined | symbol | bigint | void | Promise<number | boolean | string | object | null | undefined | symbol | bigint | void> | Array<number | boolean | string | object | null | undefined | symbol | bigint | void>;
13
+ export type DeferFn = (fn: () => GenericFnResult) => void;
14
+ export type DeferredFunction = () => GenericFnResult;
15
+ export type DeferredFunctions = Array<DeferredFunction>;
16
+ export type Options = {
17
+ throw?: boolean;
18
+ onError?: (err: Error) => void;
19
+ };
20
+ export declare function run<TResult, E = Error>(fn: (deferFn: DeferFn) => Promise<TResult> | TResult, options?: Options): Promise<Result<TResult, E>>;
21
+ export type AbortableFn = (abortableFn: () => GenericFnResult, opts?: {
22
+ signal?: AbortSignal;
23
+ }) => GenericFnResult;
24
+ export declare class AbortError extends Error {
25
+ constructor(message?: string);
26
+ }
27
+ export declare function abortSignalReason(signal: AbortSignal): string | undefined;
28
+ export declare function runAbortable<TResult, E = Error>(fn: ({ abortable, defer, signal, controller }: {
29
+ abortable: AbortableFn;
30
+ defer: DeferFn;
31
+ signal: AbortSignal;
32
+ controller: AbortController;
33
+ }) => Promise<TResult> | TResult, options?: Options & {
34
+ controller?: AbortController;
35
+ signal?: AbortSignal;
36
+ }): Promise<Result<TResult, E>>;
37
+ export declare function runEffect<TResult, E = Error>(fn: (deferFn: DeferFn) => TResult, options?: Options & {
38
+ automaticCleanup?: boolean;
39
+ }): Result<TResult, E> & {
40
+ cleanup: () => void;
41
+ };
42
+ export declare function runRetry<TResult, E = Error>(fn: (deferFn: DeferFn, attempt: number) => Promise<TResult> | TResult, options?: Options & {
43
+ maxAttempts?: number;
44
+ delayMs?: number;
45
+ backoff?: "linear" | "exponential";
46
+ shouldRetry?: ((err: E, attempt: number) => boolean) | boolean;
47
+ onRetry?: (err: E, attempt: number) => void;
48
+ }): Promise<Result<TResult, E>>;
49
+ export declare class TimeoutError extends Error {
50
+ constructor(message?: string);
51
+ }
52
+ export declare function runTimeout<TResult, E = Error>(fn: (deferFn: DeferFn) => Promise<TResult> | TResult, timeoutMs: number, options?: Options): Promise<Result<TResult, E>>;
53
+ export declare function runDebounced<TResult, TArgs extends unknown[]>(fn: (defer: DeferFn, ...args: TArgs) => Promise<TResult> | TResult, delayMs: number, options?: Options): (...args: TArgs) => Promise<Result<TResult, Error>>;
54
+ export default run;
@@ -0,0 +1,13 @@
1
+ export declare class Semaphore {
2
+ private counter;
3
+ private waiting;
4
+ private maxCount;
5
+ constructor(max?: number);
6
+ acquire(): Promise<void>;
7
+ release(): void;
8
+ count(): number;
9
+ setMax(newMax: number): void;
10
+ purge(): number;
11
+ private processQueue;
12
+ }
13
+ export default Semaphore;
@@ -0,0 +1,8 @@
1
+ export type SerializedError = {
2
+ name: string;
3
+ message: string;
4
+ stack?: string;
5
+ stringified: string;
6
+ };
7
+ export declare function serializeError(error: Error): SerializedError;
8
+ export declare function deserializeError(serializedError: SerializedError): Error;
@@ -0,0 +1,11 @@
1
+ export declare function isTimestampSameDay(timestamp1: number, timestamp2: number): boolean;
2
+ export declare function isTimestampSameMinute(timestamp1: number, timestamp2: number): boolean;
3
+ export declare function formatSecondsToHHMM(seconds: number): string;
4
+ export declare function formatSecondsToMMSS(seconds: number): string;
5
+ export declare function getTimeRemaining(endTimestamp: number): {
6
+ total: number;
7
+ days: number;
8
+ hours: number;
9
+ minutes: number;
10
+ seconds: number;
11
+ };
@@ -0,0 +1,4 @@
1
+ export type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
2
+ export type Prettify<T> = {
3
+ [K in keyof T]: T[K];
4
+ } & {};
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@filen/utils",
3
+ "version": "0.0.1",
4
+ "description": "A collection of utils for Filen",
5
+ "private": false,
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/FilenCloudDienste/filen-apps/packages/filen-utils"
9
+ },
10
+ "author": "Filen Cloud Dienste UG",
11
+ "main": "dist/index.js",
12
+ "types": "dist/types/index.d.ts",
13
+ "scripts": {
14
+ "build": "npm run build:clean && tsc --extendedDiagnostics --project tsconfig.json",
15
+ "build:clean": "rimraf ./dist",
16
+ "typecheck": "tsc --noEmit",
17
+ "dev": "tsx src/dev.ts",
18
+ "test": "vitest",
19
+ "test:ui": "vitest --ui",
20
+ "test:coverage": "vitest --coverage"
21
+ },
22
+ "keywords": [
23
+ "filen"
24
+ ],
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "license": "AGPLv3",
29
+ "devDependencies": {
30
+ "rimraf": "^6.1.0",
31
+ "tsx": "^4.20.6",
32
+ "typescript": "^5.9.3",
33
+ "vitest": "^3.2.4"
34
+ },
35
+ "dependencies": {
36
+ "clsx": "^2.1.1",
37
+ "tailwind-merge": "^3.3.1",
38
+ "uuid": "^13.0.0"
39
+ }
40
+ }