@regardio/js 0.4.2 → 0.5.0

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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > **TypeScript utilities for Regardio applications**
4
4
 
5
5
  A collection of lightweight, tree-shakeable utility functions for common tasks
6
- like HTTP handling, internationalization, time formatting, and validation.
6
+ like text manipulation, HTTP handling, internationalization, time formatting, and validation.
7
7
 
8
8
  ## ⚠️ Pre-release Notice
9
9
 
@@ -18,7 +18,7 @@ like HTTP handling, internationalization, time formatting, and validation.
18
18
  We created `@regardio/js` to:
19
19
 
20
20
  - **Share battle-tested utilities** — These functions power real Regardio projects and have been refined through actual use
21
- - **Reduce boilerplate** — Common patterns like cookie handling, language detection, and time formatting in one place
21
+ - **Reduce boilerplate** — Common patterns like text formatting, cookie handling, language detection, and time formatting in one place
22
22
  - **Stay framework-agnostic** — Works with any JavaScript/TypeScript project (React, Node, Deno, etc.)
23
23
  - **Enable tree-shaking** — Import only what you need; unused utilities won't bloat your bundle
24
24
 
@@ -28,92 +28,71 @@ We created `@regardio/js` to:
28
28
  pnpm add @regardio/js
29
29
  ```
30
30
 
31
- ## Documentation
32
-
33
- See the [docs](./docs) folder for detailed documentation on each module.
34
-
35
31
  ## Modules
36
32
 
37
- ### async/delay
33
+ ### @regardio/js/text
38
34
 
39
- Promise-based delay utility.
35
+ String manipulation and formatting utilities.
40
36
 
41
37
  ```ts
42
- import { delay } from '@regardio/js/async/delay';
43
-
44
- await delay(1000); // Wait 1 second
45
- ```
46
-
47
- ### browser/base64
48
-
49
- URL-safe base64 to Uint8Array conversion (useful for Web Push).
50
-
51
- ```ts
52
- import { urlBase64ToUint8Array } from '@regardio/js/browser/base64';
53
-
54
- const bytes = urlBase64ToUint8Array(vapidPublicKey);
55
- ```
56
-
57
- ### format/bytes
58
-
59
- Human-readable byte formatting.
60
-
61
- ```ts
62
- import { formatBytes } from '@regardio/js/format/bytes';
63
-
38
+ import {
39
+ typographicQuotes,
40
+ truncateText,
41
+ splitIntoSentences,
42
+ formatBytes,
43
+ parseAuthorString,
44
+ } from '@regardio/js/text';
45
+
46
+ typographicQuotes('"Hello"', 'de'); // „Hello"
47
+ truncateText('Hello world', 8); // "Hello..."
64
48
  formatBytes(1500000); // "1.5 MB"
49
+ parseAuthorString('John <john@example.com>'); // { name: 'John', email: 'john@example.com' }
65
50
  ```
66
51
 
67
- ### format/measure
52
+ ### @regardio/js/time
68
53
 
69
- Performance measurement with logging.
54
+ Date, time, and async utilities.
70
55
 
71
56
  ```ts
72
- import { measure } from '@regardio/js/format/measure';
57
+ import {
58
+ timeAgo,
59
+ friendlyDuration,
60
+ oneWeekFromNow,
61
+ delay,
62
+ measure,
63
+ } from '@regardio/js/time';
73
64
 
74
- const result = await measure('fetchData', async () => {
75
- return await fetch('/api/data');
76
- });
77
- // Logs: "fetchData took 123ms"
65
+ timeAgo(new Date('2024-01-01')); // "3 months ago"
66
+ friendlyDuration(150, 'en', true); // { key: 'common:duration.hoursAndMinutesShort', vars: {...} }
67
+ await delay(1000); // Wait 1 second
68
+ await measure('fetchData', () => fetch('/api')); // Logs timing
78
69
  ```
79
70
 
80
- ### http/cookie
71
+ ### @regardio/js/http
81
72
 
82
- Browser cookie get/set helpers.
73
+ HTTP, cookie, and routing utilities.
83
74
 
84
75
  ```ts
85
- import { getCookieValue, setCookieValue } from '@regardio/js/http/cookie';
76
+ import {
77
+ getCookieValue,
78
+ setCookieValue,
79
+ createDomain,
80
+ getCleanUrl,
81
+ isRouteActive,
82
+ } from '@regardio/js/http';
86
83
 
87
84
  setCookieValue('theme', 'dark', { path: '/', secure: true });
88
85
  const theme = getCookieValue('theme');
89
- ```
90
-
91
- ### http/domain
92
-
93
- Domain extraction from requests (proxy-aware).
94
-
95
- ```ts
96
- import { createDomain } from '@regardio/js/http/domain';
97
-
98
86
  const domain = createDomain(request); // "https://example.com"
87
+ isRouteActive('/account', '/account/settings', false); // true
99
88
  ```
100
89
 
101
- ### http/request-helpers
90
+ ### @regardio/js/intl
102
91
 
103
- URL cleaning utilities.
92
+ Server-side language detection for i18n.
104
93
 
105
94
  ```ts
106
- import { getCleanUrl } from '@regardio/js/http/request-helpers';
107
-
108
- getCleanUrl(request); // URL without search params
109
- ```
110
-
111
- ### intl/language-detector
112
-
113
- Server-side language detection for i18n (works with Lingui).
114
-
115
- ```ts
116
- import { LanguageDetector } from '@regardio/js/intl/language-detector';
95
+ import { LanguageDetector } from '@regardio/js/intl';
117
96
 
118
97
  const detector = new LanguageDetector({
119
98
  supportedLanguages: ['en', 'de'],
@@ -123,66 +102,38 @@ const detector = new LanguageDetector({
123
102
  const locale = await detector.detect(request);
124
103
  ```
125
104
 
126
- ### intl/locale
105
+ ### @regardio/js/assert
127
106
 
128
- Client locale extraction from Accept-Language header.
107
+ Runtime assertion and validation utilities.
129
108
 
130
109
  ```ts
131
- import { getClientLocales } from '@regardio/js/intl/locale';
132
-
133
- const locales = getClientLocales(request); // ['en-US', 'de']
134
- ```
135
-
136
- ### time/time
137
-
138
- Time formatting utilities.
139
-
140
- ```ts
141
- import { timeAgo, friendlyDuration, oneWeekFromNow } from '@regardio/js/time/time';
142
-
143
- timeAgo(new Date('2024-01-01')); // "3 months ago"
144
- friendlyDuration(150, 'en', true); // { key: 'common:duration.hoursAndMinutesShort', vars: {...} }
145
- const expires = oneWeekFromNow();
146
- ```
147
-
148
- ### validation/invariant
149
-
150
- Runtime assertion utilities.
151
-
152
- ```ts
153
- import { invariant, invariantResponse } from '@regardio/js/validation/invariant';
110
+ import { invariant, invariantResponse, verifyAccept } from '@regardio/js/assert';
154
111
 
155
112
  invariant(user, 'User not found'); // Throws Error if falsy
156
- invariantResponse(user, 'User not found', { status: 404 }); // Throws Response if falsy
113
+ invariantResponse(user, 'User not found', { status: 404 }); // Throws Response
114
+ verifyAccept('image/png', 'image/*'); // true
157
115
  ```
158
116
 
159
- ### validation/verify-file-accept
117
+ ### @regardio/js/encoding
160
118
 
161
- MIME type validation for file uploads.
119
+ Binary encoding utilities.
162
120
 
163
121
  ```ts
164
- import { verifyAccept } from '@regardio/js/validation/verify-file-accept';
122
+ import { urlBase64ToUint8Array } from '@regardio/js/encoding';
165
123
 
166
- verifyAccept('image/png', 'image/*'); // true
167
- verifyAccept('video/mp4', 'image/*'); // false
124
+ const bytes = urlBase64ToUint8Array(vapidPublicKey);
168
125
  ```
169
126
 
170
127
  ## Module Overview
171
128
 
172
129
  | Module | Description |
173
130
  |--------|-------------|
174
- | `async/delay` | Promise-based delay utility |
175
- | `browser/base64` | URL-safe base64 to Uint8Array conversion |
176
- | `format/bytes` | Human-readable byte formatting |
177
- | `format/measure` | Performance measurement with logging |
178
- | `http/cookie` | Browser cookie get/set helpers |
179
- | `http/domain` | Domain extraction from requests (proxy-aware) |
180
- | `http/request-helpers` | URL cleaning utilities |
181
- | `intl/language-detector` | Server-side language detection for i18n |
182
- | `intl/locale` | Client locale extraction from headers |
183
- | `time/time` | Time formatting and date utilities |
184
- | `validation/invariant` | Runtime assertion utilities |
185
- | `validation/verify-file-accept` | MIME type validation for file uploads |
131
+ | `@regardio/js/text` | String manipulation, formatting, author parsing |
132
+ | `@regardio/js/time` | Time formatting, delays, performance measurement |
133
+ | `@regardio/js/http` | Cookies, domain extraction, route matching |
134
+ | `@regardio/js/intl` | Server-side language detection for i18n |
135
+ | `@regardio/js/assert` | Runtime assertions and MIME validation |
136
+ | `@regardio/js/encoding` | Base64 and binary conversions |
186
137
 
187
138
  ## Contributing
188
139
 
@@ -31,4 +31,13 @@ declare function invariant(condition: unknown, message: string | (() => string))
31
31
  */
32
32
  declare function invariantResponse(condition: unknown, message: string | (() => string), responseInit?: ResponseInit): asserts condition;
33
33
 
34
- export { invariant, invariantResponse };
34
+ /**
35
+ * Check if a mime type matches the set given in accept
36
+ *
37
+ * @param type the mime type to test, ex image/png
38
+ * @param accept the mime types to accept, ex audio/*,video/*,image/png
39
+ * @returns true if the mime is accepted, false otherwise
40
+ */
41
+ declare function verifyAccept(type: string, accept: string): boolean;
42
+
43
+ export { invariant, invariantResponse, verifyAccept };
@@ -1,4 +1,4 @@
1
- // src/validation/invariant.ts
1
+ // src/assert/invariant.ts
2
2
  function invariant(condition, message) {
3
3
  if (!condition) {
4
4
  throw new Error(typeof message === "function" ? message() : message);
@@ -13,4 +13,10 @@ function invariantResponse(condition, message, responseInit) {
13
13
  }
14
14
  }
15
15
 
16
- export { invariant, invariantResponse };
16
+ // src/assert/verify-file-accept.ts
17
+ function verifyAccept(type, accept) {
18
+ const allowed = accept.split(",").map((x) => x.trim());
19
+ return allowed.includes(type) || allowed.includes(`${type.split("/")[0]}/*`);
20
+ }
21
+
22
+ export { invariant, invariantResponse, verifyAccept };
@@ -1,4 +1,4 @@
1
- // src/browser/base64.ts
1
+ // src/encoding/base64.ts
2
2
  var urlBase64ToUint8Array = (base64String) => {
3
3
  const padding = "=".repeat((4 - base64String.length % 4) % 4);
4
4
  const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Helper function to set cookies in a more controlled way
3
+ * @param name - The name of the cookie
4
+ * @param value - The value to set
5
+ * @param options - Cookie options
6
+ */
7
+ declare function setCookieValue(name: string, value: string, options?: {
8
+ expires?: Date;
9
+ path?: string;
10
+ sameSite?: string;
11
+ secure?: boolean;
12
+ domain?: string;
13
+ }): void;
14
+ /**
15
+ * Get a cookie value by name
16
+ * @param name - The name of the cookie to get
17
+ * @returns The cookie value or null if not found
18
+ */
19
+ declare function getCookieValue(name: string): string | null;
20
+
21
+ /**
22
+ * Helper utility used to extract the domain from the request even if it's
23
+ * behind a proxy. This is useful for sitemaps and other things.
24
+ * @param request Request object
25
+ * @returns Current domain
26
+ */
27
+ declare const createDomain: (request: Request) => string;
28
+
29
+ /**
30
+ * @name isRouteActive
31
+ * @description A function to check if a route is active. This is used to
32
+ * @param end
33
+ * @param path
34
+ * @param currentPath
35
+ */
36
+ declare function isRouteActive(path: string, currentPath: string, end?: boolean | ((path: string) => boolean) | undefined): boolean;
37
+ /**
38
+ * @name checkIfRouteIsActive
39
+ * @description A function to check if a route is active. This is used to
40
+ * highlight the active link in the navigation.
41
+ * @param targetLink - The link to check against
42
+ * @param currentRoute - the current route
43
+ * @param depth - how far down should segments be matched?
44
+ */
45
+ declare function checkIfRouteIsActive(targetLink: string, currentRoute: string, depth?: number): boolean;
46
+
47
+ declare function getCleanUrl(request: Request): string;
48
+
49
+ export { checkIfRouteIsActive, createDomain, getCleanUrl, getCookieValue, isRouteActive, setCookieValue };
@@ -0,0 +1,126 @@
1
+ // src/http/cookie.ts
2
+ function setCookieValue(name, value, options = {}) {
3
+ if (typeof window === "undefined") {
4
+ console.warn("Cannot set cookie on server side");
5
+ return;
6
+ }
7
+ let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
8
+ if (options.expires) {
9
+ cookieString += `; expires=${options.expires.toUTCString()}`;
10
+ }
11
+ if (options.path) {
12
+ cookieString += `; path=${options.path}`;
13
+ }
14
+ if (options.sameSite) {
15
+ cookieString += `; SameSite=${options.sameSite}`;
16
+ }
17
+ if (options.secure) {
18
+ cookieString += "; Secure";
19
+ }
20
+ if (options.domain) {
21
+ cookieString += `; domain=${options.domain}`;
22
+ }
23
+ try {
24
+ document.cookie = cookieString;
25
+ } catch (error) {
26
+ if (error instanceof Error) {
27
+ console.error(`Failed to set cookie '${name}':`, error.message);
28
+ } else {
29
+ console.error(`Failed to set cookie '${name}': Unknown error`);
30
+ }
31
+ }
32
+ }
33
+ function getCookieValue(name) {
34
+ if (typeof window === "undefined") {
35
+ console.warn("Cannot get cookie on server side");
36
+ return null;
37
+ }
38
+ const value = `; ${document.cookie}`;
39
+ const parts = value.split(`; ${name}=`);
40
+ if (parts.length === 2) {
41
+ const cookieValue = parts.pop()?.split(";").shift();
42
+ return cookieValue ? decodeURIComponent(cookieValue) : null;
43
+ }
44
+ return null;
45
+ }
46
+
47
+ // src/http/domain.ts
48
+ var createDomain = (request) => {
49
+ const headers = request.headers;
50
+ const maybeProto = headers.get("x-forwarded-proto");
51
+ const maybeHost = headers.get("host");
52
+ const url = new URL(request.url);
53
+ if (maybeProto) {
54
+ return `${maybeProto}://${maybeHost ?? url.host}`;
55
+ }
56
+ if (url.hostname === "localhost") {
57
+ return `http://${url.host}`;
58
+ }
59
+ return `https://${url.host}`;
60
+ };
61
+
62
+ // src/http/is-route-active.ts
63
+ var ROOT_PATH = "/";
64
+ function isRouteActive(path, currentPath, end) {
65
+ if (path === currentPath) {
66
+ return true;
67
+ }
68
+ if (typeof end === "function") {
69
+ return !end(currentPath);
70
+ }
71
+ const defaultEnd = end ?? true;
72
+ const oneLevelDeep = 1;
73
+ const threeLevelsDeep = 3;
74
+ const depth = defaultEnd ? oneLevelDeep : threeLevelsDeep;
75
+ return checkIfRouteIsActive(path, currentPath, depth);
76
+ }
77
+ function checkIfRouteIsActive(targetLink, currentRoute, depth = 1) {
78
+ const currentRoutePath = currentRoute.split("?")[0] ?? "";
79
+ if (!isRoot(currentRoutePath) && isRoot(targetLink)) {
80
+ return false;
81
+ }
82
+ if (!currentRoutePath.includes(targetLink)) {
83
+ return false;
84
+ }
85
+ const isSameRoute = targetLink === currentRoutePath;
86
+ if (isSameRoute) {
87
+ return true;
88
+ }
89
+ return hasMatchingSegments(targetLink, currentRoutePath, depth);
90
+ }
91
+ function splitIntoSegments(href) {
92
+ return href.split("/").filter(Boolean);
93
+ }
94
+ function hasMatchingSegments(targetLink, currentRoute, depth) {
95
+ const segments = splitIntoSegments(targetLink);
96
+ const matchingSegments = numberOfMatchingSegments(currentRoute, segments);
97
+ if (targetLink === currentRoute) {
98
+ return true;
99
+ }
100
+ return matchingSegments > segments.length - (depth - 1);
101
+ }
102
+ function numberOfMatchingSegments(href, segments) {
103
+ let count = 0;
104
+ for (const segment of splitIntoSegments(href)) {
105
+ if (segments.includes(segment)) {
106
+ count += 1;
107
+ } else {
108
+ return count;
109
+ }
110
+ }
111
+ return count;
112
+ }
113
+ function isRoot(path) {
114
+ return path === ROOT_PATH;
115
+ }
116
+
117
+ // src/http/request-helpers.ts
118
+ function getCleanUrl(request) {
119
+ const url = new URL(request.url);
120
+ url.searchParams.forEach((_, key) => {
121
+ url.searchParams.delete(key);
122
+ });
123
+ return url.toString();
124
+ }
125
+
126
+ export { checkIfRouteIsActive, createDomain, getCleanUrl, getCookieValue, isRouteActive, setCookieValue };
@@ -0,0 +1,46 @@
1
+ declare function formatBytes(bytes: number, decimals?: number): string;
2
+
3
+ /**
4
+ * Replace straight quotes with typographically correct quotes for the given locale.
5
+ *
6
+ * @param text - The text containing straight quotes
7
+ * @param locale - The locale to use for quote style (e.g., 'en', 'de', 'fr')
8
+ * @returns Text with typographic quotes
9
+ *
10
+ * @example
11
+ * typographicQuotes('"Hello"', 'en') // → '"Hello"'
12
+ * typographicQuotes('"Hello"', 'de') // → '„Hello"'
13
+ * typographicQuotes('"Hello"', 'fr') // → '« Hello »'
14
+ */
15
+ declare function typographicQuotes(text: string, locale: string): string;
16
+ declare function toBoolean(value: string | boolean | null | undefined): boolean;
17
+ /**
18
+ * Replace &shy; HTML entity with Unicode soft hyphen
19
+ */
20
+ declare function replaceShyInString(input: string): string;
21
+ /**
22
+ * Split text into sentences
23
+ */
24
+ declare function splitIntoSentences(text: string): string[];
25
+ /**
26
+ * Split text into words
27
+ */
28
+ declare function splitIntoWords(text: string): string[];
29
+ /**
30
+ * Truncate text to a maximum length
31
+ */
32
+ declare function truncateText(text: string, maxLength: number, suffix?: string): string;
33
+ /**
34
+ * Author info parsed from a string like "Name <email> (url)"
35
+ */
36
+ type AuthorInfo = {
37
+ name?: string;
38
+ email?: string;
39
+ url?: string;
40
+ };
41
+ /**
42
+ * Parse an author string in the format "Name <email> (url)"
43
+ */
44
+ declare function parseAuthorString(input: string): AuthorInfo;
45
+
46
+ export { type AuthorInfo, formatBytes, parseAuthorString, replaceShyInString, splitIntoSentences, splitIntoWords, toBoolean, truncateText, typographicQuotes };
@@ -0,0 +1,109 @@
1
+ // src/text/bytes.ts
2
+ function formatBytes(bytes, decimals = 2) {
3
+ if (!+bytes) {
4
+ return "0 Bytes";
5
+ }
6
+ const k = 1e3;
7
+ const dm = decimals < 0 ? 0 : decimals;
8
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
9
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
10
+ return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
11
+ }
12
+
13
+ // src/text/text.ts
14
+ var quoteStyles = {
15
+ cs: { close: "\u201D", closeSingle: "\u2019", open: "\u201E", openSingle: "\u201A" },
16
+ // Danish, Norwegian - » « › ‹
17
+ da: { close: "\xAB", closeSingle: "\u203A", open: "\xBB", openSingle: "\u2039" },
18
+ // German (Germany, Austria) - „ " ‚ '
19
+ de: { close: "\u201D", closeSingle: "\u2019", open: "\u201E", openSingle: "\u201A" },
20
+ // German (Switzerland) - « » ‹ ›
21
+ "de-ch": { close: "\xBB", closeSingle: "\u203A", open: "\xAB", openSingle: "\u2039" },
22
+ // English (US, UK, etc.) - " " ' '
23
+ en: { close: "\u201D", closeSingle: "\u2019", open: "\u201C", openSingle: "\u2018" },
24
+ // Spanish, Italian, Portuguese - « » " "
25
+ es: { close: "\xBB", closeSingle: "\u201D", open: "\xAB", openSingle: "\u201C" },
26
+ fi: { close: "\u201D", closeSingle: "\u2019", open: "\u201D", openSingle: "\u2019" },
27
+ // French - « » ‹ › (with spaces)
28
+ fr: { close: " \xBB", closeSingle: " \u203A", open: "\xAB ", openSingle: "\u2039 " },
29
+ hu: { close: "\u201D", closeSingle: "\u2019", open: "\u201E", openSingle: "\u201A" },
30
+ it: { close: "\xBB", closeSingle: "\u201D", open: "\xAB", openSingle: "\u201C" },
31
+ // Japanese - 「 」 『 』
32
+ ja: { close: "\u300D", closeSingle: "\u300F", open: "\u300C", openSingle: "\u300E" },
33
+ // Dutch - ' ' ' '
34
+ nl: { close: "\u2019", closeSingle: "\u2019", open: "\u2018", openSingle: "\u2018" },
35
+ no: { close: "\xAB", closeSingle: "\u203A", open: "\xBB", openSingle: "\u2039" },
36
+ // Polish, Czech, Hungarian - „ " ‚ '
37
+ pl: { close: "\u201D", closeSingle: "\u2019", open: "\u201E", openSingle: "\u201A" },
38
+ pt: { close: "\xBB", closeSingle: "\u201D", open: "\xAB", openSingle: "\u201C" },
39
+ // Russian - « » ‚ '
40
+ ru: { close: "\xBB", closeSingle: "\u2019", open: "\xAB", openSingle: "\u201A" },
41
+ // Swedish, Finnish - " " ' '
42
+ sv: { close: "\u201D", closeSingle: "\u2019", open: "\u201D", openSingle: "\u2019" },
43
+ // Chinese - 「 」 『 』
44
+ zh: { close: "\u300D", closeSingle: "\u300F", open: "\u300C", openSingle: "\u300E" }
45
+ };
46
+ function getQuoteStyle(locale) {
47
+ const normalized = locale.toLowerCase();
48
+ const exactMatch = quoteStyles[normalized];
49
+ if (exactMatch) {
50
+ return exactMatch;
51
+ }
52
+ const baseLanguage = normalized.split("-")[0];
53
+ if (baseLanguage) {
54
+ const baseMatch = quoteStyles[baseLanguage];
55
+ if (baseMatch) {
56
+ return baseMatch;
57
+ }
58
+ }
59
+ return quoteStyles.en;
60
+ }
61
+ function typographicQuotes(text, locale) {
62
+ const style = getQuoteStyle(locale);
63
+ let result = text.replace(/"([^"]*)"/g, `${style.open}$1${style.close}`);
64
+ result = result.replace(/'([^']*)'/g, `${style.openSingle}$1${style.closeSingle}`);
65
+ return result;
66
+ }
67
+ function toBoolean(value) {
68
+ if (typeof value === "boolean") {
69
+ return value;
70
+ }
71
+ return value === "true" || value === "1";
72
+ }
73
+ function replaceShyInString(input) {
74
+ return input.replace(/&shy;/g, "\xAD");
75
+ }
76
+ function splitIntoSentences(text) {
77
+ return text.split(/(?<=[.!?])\s+/);
78
+ }
79
+ function splitIntoWords(text) {
80
+ return text.split(/\s+/);
81
+ }
82
+ function truncateText(text, maxLength, suffix = "...") {
83
+ if (text.length <= maxLength) {
84
+ return text;
85
+ }
86
+ return text.slice(0, maxLength - suffix.length) + suffix;
87
+ }
88
+ var authorRegex = /^(.*?)\s*(?:<([^>]+)>)?\s*(?:\(([^)]+)\))?$/;
89
+ function parseAuthorString(input) {
90
+ const match = input.match(authorRegex);
91
+ if (match) {
92
+ const [, name, email, url] = match;
93
+ const result = {};
94
+ if (email) {
95
+ result.email = email;
96
+ }
97
+ const trimmedName = name?.trim();
98
+ if (trimmedName) {
99
+ result.name = trimmedName;
100
+ }
101
+ if (url) {
102
+ result.url = url;
103
+ }
104
+ return result;
105
+ }
106
+ return {};
107
+ }
108
+
109
+ export { formatBytes, parseAuthorString, replaceShyInString, splitIntoSentences, splitIntoWords, toBoolean, truncateText, typographicQuotes };
@@ -1,3 +1,11 @@
1
+ /** Delays using a promise
2
+ * @param ms Milisecods of delay
3
+ * @return Promise
4
+ */
5
+ declare function delay(ms: number): Promise<unknown>;
6
+
7
+ declare function measure<Result>(key: string, callback: () => Result | Promise<Result>): Promise<Result>;
8
+
1
9
  declare function timeAgo(input: Date | string): string;
2
10
  declare const oneWeekFromNow: () => Date;
3
11
  declare const oneDayFromNow: () => Date;
@@ -37,4 +45,4 @@ declare function friendlyDuration(minutes: number | null, locale: string, short?
37
45
  } | null;
38
46
  declare const dateTimeInUnix: (date: number) => number;
39
47
 
40
- export { dateTimeInUnix, friendlyDuration, oneDayFromNow, oneMinuteFromNow, oneWeekFromNow, timeAgo };
48
+ export { dateTimeInUnix, delay, friendlyDuration, measure, oneDayFromNow, oneMinuteFromNow, oneWeekFromNow, timeAgo };
@@ -1,3 +1,25 @@
1
+ // src/time/delay.ts
2
+ function delay(ms) {
3
+ return new Promise((resolve) => {
4
+ return setTimeout(resolve, ms);
5
+ });
6
+ }
7
+
8
+ // src/time/measure.ts
9
+ async function measure(key, callback) {
10
+ const start = Date.now();
11
+ try {
12
+ let result = callback();
13
+ if (result instanceof Promise) {
14
+ result = await result;
15
+ }
16
+ return result;
17
+ } finally {
18
+ const end = Date.now();
19
+ console.log(`${key} took ${end - start}ms`);
20
+ }
21
+ }
22
+
1
23
  // src/time/time.ts
2
24
  function timeAgo(input) {
3
25
  const date = new Date(input);
@@ -132,4 +154,4 @@ var dateTimeInUnix = (date) => {
132
154
  return Math.floor(date / 1e3);
133
155
  };
134
156
 
135
- export { dateTimeInUnix, friendlyDuration, oneDayFromNow, oneMinuteFromNow, oneWeekFromNow, timeAgo };
157
+ export { dateTimeInUnix, delay, friendlyDuration, measure, oneDayFromNow, oneMinuteFromNow, oneWeekFromNow, timeAgo };
package/package.json CHANGED
@@ -10,56 +10,36 @@
10
10
  },
11
11
  "description": "Regardio JavaScript utilities",
12
12
  "devDependencies": {
13
- "@regardio/dev": "1.10.1",
13
+ "@regardio/dev": "1.10.2",
14
14
  "@total-typescript/ts-reset": "0.6.1",
15
15
  "@types/node": "25.0.3",
16
16
  "tsup": "8.5.1",
17
17
  "vitest": "4.0.16"
18
18
  },
19
19
  "exports": {
20
- "./async/delay": {
21
- "import": "./dist/async/delay.js",
22
- "types": "./dist/async/delay.d.ts"
20
+ "./assert": {
21
+ "import": "./dist/assert/index.js",
22
+ "types": "./dist/assert/index.d.ts"
23
23
  },
24
- "./browser/base64": {
25
- "import": "./dist/browser/base64.js",
26
- "types": "./dist/browser/base64.d.ts"
24
+ "./encoding": {
25
+ "import": "./dist/encoding/index.js",
26
+ "types": "./dist/encoding/index.d.ts"
27
27
  },
28
- "./format/bytes": {
29
- "import": "./dist/format/bytes.js",
30
- "types": "./dist/format/bytes.d.ts"
28
+ "./http": {
29
+ "import": "./dist/http/index.js",
30
+ "types": "./dist/http/index.d.ts"
31
31
  },
32
- "./format/measure": {
33
- "import": "./dist/format/measure.js",
34
- "types": "./dist/format/measure.d.ts"
32
+ "./intl": {
33
+ "import": "./dist/intl/index.js",
34
+ "types": "./dist/intl/index.d.ts"
35
35
  },
36
- "./http/cookie": {
37
- "import": "./dist/http/cookie.js",
38
- "types": "./dist/http/cookie.d.ts"
36
+ "./text": {
37
+ "import": "./dist/text/index.js",
38
+ "types": "./dist/text/index.d.ts"
39
39
  },
40
- "./http/domain": {
41
- "import": "./dist/http/domain.js",
42
- "types": "./dist/http/domain.d.ts"
43
- },
44
- "./http/request-helpers": {
45
- "import": "./dist/http/request-helpers.js",
46
- "types": "./dist/http/request-helpers.d.ts"
47
- },
48
- "./intl/language-detector": {
49
- "import": "./dist/intl/language-detector.js",
50
- "types": "./dist/intl/language-detector.d.ts"
51
- },
52
- "./time/time": {
53
- "import": "./dist/time/time.js",
54
- "types": "./dist/time/time.d.ts"
55
- },
56
- "./validation/invariant": {
57
- "import": "./dist/validation/invariant.js",
58
- "types": "./dist/validation/invariant.d.ts"
59
- },
60
- "./validation/verify-file-accept": {
61
- "import": "./dist/validation/verify-file-accept.js",
62
- "types": "./dist/validation/verify-file-accept.d.ts"
40
+ "./time": {
41
+ "import": "./dist/time/index.js",
42
+ "types": "./dist/time/index.d.ts"
63
43
  }
64
44
  },
65
45
  "files": ["dist"],
@@ -105,5 +85,5 @@
105
85
  },
106
86
  "sideEffects": false,
107
87
  "type": "module",
108
- "version": "0.4.2"
88
+ "version": "0.5.0"
109
89
  }
@@ -1,7 +0,0 @@
1
- /** Delays using a promise
2
- * @param ms Milisecods of delay
3
- * @return Promise
4
- */
5
- declare function delay(ms: number): Promise<unknown>;
6
-
7
- export { delay };
@@ -1,8 +0,0 @@
1
- // src/async/delay.ts
2
- function delay(ms) {
3
- return new Promise((resolve) => {
4
- return setTimeout(resolve, ms);
5
- });
6
- }
7
-
8
- export { delay };
@@ -1,3 +0,0 @@
1
- declare function formatBytes(bytes: number, decimals?: number): string;
2
-
3
- export { formatBytes };
@@ -1,13 +0,0 @@
1
- // src/format/bytes.ts
2
- function formatBytes(bytes, decimals = 2) {
3
- if (!+bytes) {
4
- return "0 Bytes";
5
- }
6
- const k = 1e3;
7
- const dm = decimals < 0 ? 0 : decimals;
8
- const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
9
- const i = Math.floor(Math.log(bytes) / Math.log(k));
10
- return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
11
- }
12
-
13
- export { formatBytes };
@@ -1,3 +0,0 @@
1
- declare function measure<Result>(key: string, callback: () => Result | Promise<Result>): Promise<Result>;
2
-
3
- export { measure };
@@ -1,16 +0,0 @@
1
- // src/format/measure.ts
2
- async function measure(key, callback) {
3
- const start = Date.now();
4
- try {
5
- let result = callback();
6
- if (result instanceof Promise) {
7
- result = await result;
8
- }
9
- return result;
10
- } finally {
11
- const end = Date.now();
12
- console.log(`${key} took ${end - start}ms`);
13
- }
14
- }
15
-
16
- export { measure };
@@ -1,21 +0,0 @@
1
- /**
2
- * Helper function to set cookies in a more controlled way
3
- * @param name - The name of the cookie
4
- * @param value - The value to set
5
- * @param options - Cookie options
6
- */
7
- declare function setCookieValue(name: string, value: string, options?: {
8
- expires?: Date;
9
- path?: string;
10
- sameSite?: string;
11
- secure?: boolean;
12
- domain?: string;
13
- }): void;
14
- /**
15
- * Get a cookie value by name
16
- * @param name - The name of the cookie to get
17
- * @returns The cookie value or null if not found
18
- */
19
- declare function getCookieValue(name: string): string | null;
20
-
21
- export { getCookieValue, setCookieValue };
@@ -1,47 +0,0 @@
1
- // src/http/cookie.ts
2
- function setCookieValue(name, value, options = {}) {
3
- if (typeof window === "undefined") {
4
- console.warn("Cannot set cookie on server side");
5
- return;
6
- }
7
- let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
8
- if (options.expires) {
9
- cookieString += `; expires=${options.expires.toUTCString()}`;
10
- }
11
- if (options.path) {
12
- cookieString += `; path=${options.path}`;
13
- }
14
- if (options.sameSite) {
15
- cookieString += `; SameSite=${options.sameSite}`;
16
- }
17
- if (options.secure) {
18
- cookieString += "; Secure";
19
- }
20
- if (options.domain) {
21
- cookieString += `; domain=${options.domain}`;
22
- }
23
- try {
24
- document.cookie = cookieString;
25
- } catch (error) {
26
- if (error instanceof Error) {
27
- console.error(`Failed to set cookie '${name}':`, error.message);
28
- } else {
29
- console.error(`Failed to set cookie '${name}': Unknown error`);
30
- }
31
- }
32
- }
33
- function getCookieValue(name) {
34
- if (typeof window === "undefined") {
35
- console.warn("Cannot get cookie on server side");
36
- return null;
37
- }
38
- const value = `; ${document.cookie}`;
39
- const parts = value.split(`; ${name}=`);
40
- if (parts.length === 2) {
41
- const cookieValue = parts.pop()?.split(";").shift();
42
- return cookieValue ? decodeURIComponent(cookieValue) : null;
43
- }
44
- return null;
45
- }
46
-
47
- export { getCookieValue, setCookieValue };
@@ -1,9 +0,0 @@
1
- /**
2
- * Helper utility used to extract the domain from the request even if it's
3
- * behind a proxy. This is useful for sitemaps and other things.
4
- * @param request Request object
5
- * @returns Current domain
6
- */
7
- declare const createDomain: (request: Request) => string;
8
-
9
- export { createDomain };
@@ -1,16 +0,0 @@
1
- // src/http/domain.ts
2
- var createDomain = (request) => {
3
- const headers = request.headers;
4
- const maybeProto = headers.get("x-forwarded-proto");
5
- const maybeHost = headers.get("host");
6
- const url = new URL(request.url);
7
- if (maybeProto) {
8
- return `${maybeProto}://${maybeHost ?? url.host}`;
9
- }
10
- if (url.hostname === "localhost") {
11
- return `http://${url.host}`;
12
- }
13
- return `https://${url.host}`;
14
- };
15
-
16
- export { createDomain };
@@ -1,3 +0,0 @@
1
- declare function getCleanUrl(request: Request): string;
2
-
3
- export { getCleanUrl };
@@ -1,10 +0,0 @@
1
- // src/http/request-helpers.ts
2
- function getCleanUrl(request) {
3
- const url = new URL(request.url);
4
- url.searchParams.forEach((_, key) => {
5
- url.searchParams.delete(key);
6
- });
7
- return url.toString();
8
- }
9
-
10
- export { getCleanUrl };
@@ -1,10 +0,0 @@
1
- /**
2
- * Check if a mime type matches the set given in accept
3
- *
4
- * @param type the mime type to test, ex image/png
5
- * @param accept the mime types to accept, ex audio/*,video/*,image/png
6
- * @returns true if the mime is accepted, false otherwise
7
- */
8
- declare function verifyAccept(type: string, accept: string): boolean;
9
-
10
- export { verifyAccept };
@@ -1,7 +0,0 @@
1
- // src/validation/verify-file-accept.ts
2
- function verifyAccept(type, accept) {
3
- const allowed = accept.split(",").map((x) => x.trim());
4
- return allowed.includes(type) || allowed.includes(`${type.split("/")[0]}/*`);
5
- }
6
-
7
- export { verifyAccept };
File without changes