@regardio/js 0.2.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/LICENSE +9 -0
- package/README.md +156 -0
- package/dist/async/delay.d.ts +2 -0
- package/dist/async/delay.d.ts.map +1 -0
- package/dist/async/delay.js +5 -0
- package/dist/async/delay.test.d.ts +2 -0
- package/dist/async/delay.test.d.ts.map +1 -0
- package/dist/async/delay.test.js +35 -0
- package/dist/browser/base64.d.ts +2 -0
- package/dist/browser/base64.d.ts.map +1 -0
- package/dist/browser/base64.js +10 -0
- package/dist/format/bytes.d.ts +2 -0
- package/dist/format/bytes.d.ts.map +1 -0
- package/dist/format/bytes.js +10 -0
- package/dist/format/bytes.test.d.ts +2 -0
- package/dist/format/bytes.test.d.ts.map +1 -0
- package/dist/format/bytes.test.js +49 -0
- package/dist/format/measure.d.ts +2 -0
- package/dist/format/measure.d.ts.map +1 -0
- package/dist/format/measure.js +14 -0
- package/dist/format/measure.test.d.ts +2 -0
- package/dist/format/measure.test.d.ts.map +1 -0
- package/dist/format/measure.test.js +50 -0
- package/dist/http/cookie.d.ts +9 -0
- package/dist/http/cookie.d.ts.map +1 -0
- package/dist/http/cookie.js +46 -0
- package/dist/http/domain.d.ts +2 -0
- package/dist/http/domain.d.ts.map +1 -0
- package/dist/http/domain.js +13 -0
- package/dist/http/domain.test.d.ts +2 -0
- package/dist/http/domain.test.d.ts.map +1 -0
- package/dist/http/domain.test.js +29 -0
- package/dist/http/request-helpers.d.ts +2 -0
- package/dist/http/request-helpers.d.ts.map +1 -0
- package/dist/http/request-helpers.js +7 -0
- package/dist/http/request-helpers.test.d.ts +2 -0
- package/dist/http/request-helpers.test.d.ts.map +1 -0
- package/dist/http/request-helpers.test.js +37 -0
- package/dist/intl/language-detector.d.ts +33 -0
- package/dist/intl/language-detector.d.ts.map +1 -0
- package/dist/intl/language-detector.js +114 -0
- package/dist/intl/language-detector.test.d.ts +2 -0
- package/dist/intl/language-detector.test.d.ts.map +1 -0
- package/dist/intl/language-detector.test.js +186 -0
- package/dist/intl/locale.d.ts +4 -0
- package/dist/intl/locale.d.ts.map +1 -0
- package/dist/intl/locale.js +22 -0
- package/dist/intl/locale.test.d.ts +2 -0
- package/dist/intl/locale.test.d.ts.map +1 -0
- package/dist/intl/locale.test.js +75 -0
- package/dist/time/time.d.ts +39 -0
- package/dist/time/time.d.ts.map +1 -0
- package/dist/time/time.js +132 -0
- package/dist/time/time.test.d.ts +2 -0
- package/dist/time/time.test.d.ts.map +1 -0
- package/dist/time/time.test.js +202 -0
- package/dist/validation/invariant.d.ts +3 -0
- package/dist/validation/invariant.d.ts.map +1 -0
- package/dist/validation/invariant.js +13 -0
- package/dist/validation/invariant.test.d.ts +2 -0
- package/dist/validation/invariant.test.d.ts.map +1 -0
- package/dist/validation/invariant.test.js +110 -0
- package/dist/validation/verify-file-accept.d.ts +2 -0
- package/dist/validation/verify-file-accept.d.ts.map +1 -0
- package/dist/validation/verify-file-accept.js +4 -0
- package/dist/validation/verify-file-accept.test.d.ts +2 -0
- package/dist/validation/verify-file-accept.test.d.ts.map +1 -0
- package/dist/validation/verify-file-accept.test.js +71 -0
- package/package.json +114 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright © 2025 Regardio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# @regardio/js
|
|
2
|
+
|
|
3
|
+
> **TypeScript utilities for Regardio applications**
|
|
4
|
+
|
|
5
|
+
A collection of lightweight, tree-shakeable utility functions for common tasks
|
|
6
|
+
like HTTP handling, internationalization, time formatting, and validation.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add @regardio/js
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Modules
|
|
15
|
+
|
|
16
|
+
### async/delay
|
|
17
|
+
|
|
18
|
+
Promise-based delay utility.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { delay } from '@regardio/js/async/delay';
|
|
22
|
+
|
|
23
|
+
await delay(1000); // Wait 1 second
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### browser/base64
|
|
27
|
+
|
|
28
|
+
URL-safe base64 to Uint8Array conversion (useful for Web Push).
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { urlBase64ToUint8Array } from '@regardio/js/browser/base64';
|
|
32
|
+
|
|
33
|
+
const bytes = urlBase64ToUint8Array(vapidPublicKey);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### format/bytes
|
|
37
|
+
|
|
38
|
+
Human-readable byte formatting.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { formatBytes } from '@regardio/js/format/bytes';
|
|
42
|
+
|
|
43
|
+
formatBytes(1500000); // "1.5 MB"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### format/measure
|
|
47
|
+
|
|
48
|
+
Performance measurement with logging.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { measure } from '@regardio/js/format/measure';
|
|
52
|
+
|
|
53
|
+
const result = await measure('fetchData', async () => {
|
|
54
|
+
return await fetch('/api/data');
|
|
55
|
+
});
|
|
56
|
+
// Logs: "fetchData took 123ms"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### http/cookie
|
|
60
|
+
|
|
61
|
+
Browser cookie get/set helpers.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { getCookieValue, setCookieValue } from '@regardio/js/http/cookie';
|
|
65
|
+
|
|
66
|
+
setCookieValue('theme', 'dark', { path: '/', secure: true });
|
|
67
|
+
const theme = getCookieValue('theme');
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### http/domain
|
|
71
|
+
|
|
72
|
+
Domain extraction from requests (proxy-aware).
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { createDomain } from '@regardio/js/http/domain';
|
|
76
|
+
|
|
77
|
+
const domain = createDomain(request); // "https://example.com"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### http/request-helpers
|
|
81
|
+
|
|
82
|
+
URL cleaning utilities.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { getCleanUrl } from '@regardio/js/http/request-helpers';
|
|
86
|
+
|
|
87
|
+
getCleanUrl(request); // URL without search params
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### intl/language-detector
|
|
91
|
+
|
|
92
|
+
Server-side language detection for i18n (works with Lingui).
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { LanguageDetector } from '@regardio/js/intl/language-detector';
|
|
96
|
+
|
|
97
|
+
const detector = new LanguageDetector({
|
|
98
|
+
supportedLanguages: ['en', 'de'],
|
|
99
|
+
fallbackLanguage: 'en',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const locale = await detector.detect(request);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### intl/locale
|
|
106
|
+
|
|
107
|
+
Client locale extraction from Accept-Language header.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { getClientLocales } from '@regardio/js/intl/locale';
|
|
111
|
+
|
|
112
|
+
const locales = getClientLocales(request); // ['en-US', 'de']
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### time/time
|
|
116
|
+
|
|
117
|
+
Time formatting utilities.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { timeAgo, friendlyDuration, oneWeekFromNow } from '@regardio/js/time/time';
|
|
121
|
+
|
|
122
|
+
timeAgo(new Date('2024-01-01')); // "3 months ago"
|
|
123
|
+
friendlyDuration(150, 'en', true); // { key: 'common:duration.hoursAndMinutesShort', vars: {...} }
|
|
124
|
+
const expires = oneWeekFromNow();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### validation/invariant
|
|
128
|
+
|
|
129
|
+
Runtime assertion utilities.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { invariant, invariantResponse } from '@regardio/js/validation/invariant';
|
|
133
|
+
|
|
134
|
+
invariant(user, 'User not found'); // Throws Error if falsy
|
|
135
|
+
invariantResponse(user, 'User not found', { status: 404 }); // Throws Response if falsy
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### validation/verify-file-accept
|
|
139
|
+
|
|
140
|
+
MIME type validation for file uploads.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { verifyAccept } from '@regardio/js/validation/verify-file-accept';
|
|
144
|
+
|
|
145
|
+
verifyAccept('image/png', 'image/*'); // true
|
|
146
|
+
verifyAccept('video/mp4', 'image/*'); // false
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
**MIT License** - Free to use in commercial and open source projects.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
*Part of the [Regardio Ensemble](https://regard.io/ensemble) toolkit for
|
|
156
|
+
collective well-being.*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delay.d.ts","sourceRoot":"","sources":["../../src/async/delay.ts"],"names":[],"mappings":"AAIA,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,oBAI/B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delay.test.d.ts","sourceRoot":"","sources":["../../src/async/delay.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { delay } from './delay';
|
|
3
|
+
describe('delay', () => {
|
|
4
|
+
test('should return a promise', () => {
|
|
5
|
+
const result = delay(0);
|
|
6
|
+
expect(result).toBeInstanceOf(Promise);
|
|
7
|
+
});
|
|
8
|
+
test('should resolve after the specified time', async () => {
|
|
9
|
+
vi.useFakeTimers();
|
|
10
|
+
const promise = delay(100);
|
|
11
|
+
vi.advanceTimersByTime(100);
|
|
12
|
+
await expect(promise).resolves.toBeUndefined();
|
|
13
|
+
vi.useRealTimers();
|
|
14
|
+
});
|
|
15
|
+
test('should not resolve before the specified time', async () => {
|
|
16
|
+
vi.useFakeTimers();
|
|
17
|
+
let resolved = false;
|
|
18
|
+
delay(100).then(() => {
|
|
19
|
+
resolved = true;
|
|
20
|
+
});
|
|
21
|
+
vi.advanceTimersByTime(50);
|
|
22
|
+
expect(resolved).toBe(false);
|
|
23
|
+
vi.advanceTimersByTime(50);
|
|
24
|
+
await Promise.resolve();
|
|
25
|
+
expect(resolved).toBe(true);
|
|
26
|
+
vi.useRealTimers();
|
|
27
|
+
});
|
|
28
|
+
test('should work with 0ms delay', async () => {
|
|
29
|
+
vi.useFakeTimers();
|
|
30
|
+
const promise = delay(0);
|
|
31
|
+
vi.advanceTimersByTime(0);
|
|
32
|
+
await expect(promise).resolves.toBeUndefined();
|
|
33
|
+
vi.useRealTimers();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../../src/browser/base64.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,qBAAqB,GAAI,cAAc,MAAM,KAAG,UAW5D,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const urlBase64ToUint8Array = (base64String) => {
|
|
2
|
+
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
|
3
|
+
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
|
4
|
+
const rawData = window.atob(base64);
|
|
5
|
+
const outputArray = new Uint8Array(rawData.length);
|
|
6
|
+
for (let i = 0; i < rawData.length; ++i) {
|
|
7
|
+
outputArray[i] = rawData.charCodeAt(i);
|
|
8
|
+
}
|
|
9
|
+
return outputArray;
|
|
10
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bytes.d.ts","sourceRoot":"","sources":["../../src/format/bytes.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAI,UAetD"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function formatBytes(bytes, decimals = 2) {
|
|
2
|
+
if (!+bytes) {
|
|
3
|
+
return '0 Bytes';
|
|
4
|
+
}
|
|
5
|
+
const k = 1000;
|
|
6
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
7
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
|
8
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
9
|
+
return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bytes.test.d.ts","sourceRoot":"","sources":["../../src/format/bytes.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { formatBytes } from './bytes';
|
|
3
|
+
describe('formatBytes', () => {
|
|
4
|
+
test('should return "0 Bytes" for 0', () => {
|
|
5
|
+
expect(formatBytes(0)).toBe('0 Bytes');
|
|
6
|
+
});
|
|
7
|
+
test('should return "0 Bytes" for NaN', () => {
|
|
8
|
+
expect(formatBytes(Number.NaN)).toBe('0 Bytes');
|
|
9
|
+
});
|
|
10
|
+
test('should format bytes correctly', () => {
|
|
11
|
+
expect(formatBytes(1)).toBe('1 Bytes');
|
|
12
|
+
expect(formatBytes(500)).toBe('500 Bytes');
|
|
13
|
+
expect(formatBytes(999)).toBe('999 Bytes');
|
|
14
|
+
});
|
|
15
|
+
test('should format kilobytes correctly (base 10)', () => {
|
|
16
|
+
expect(formatBytes(1000)).toBe('1 KB');
|
|
17
|
+
expect(formatBytes(1500)).toBe('1.5 KB');
|
|
18
|
+
expect(formatBytes(999000)).toBe('999 KB');
|
|
19
|
+
});
|
|
20
|
+
test('should format megabytes correctly', () => {
|
|
21
|
+
expect(formatBytes(1000000)).toBe('1 MB');
|
|
22
|
+
expect(formatBytes(1500000)).toBe('1.5 MB');
|
|
23
|
+
expect(formatBytes(999000000)).toBe('999 MB');
|
|
24
|
+
});
|
|
25
|
+
test('should format gigabytes correctly', () => {
|
|
26
|
+
expect(formatBytes(1000000000)).toBe('1 GB');
|
|
27
|
+
expect(formatBytes(1500000000)).toBe('1.5 GB');
|
|
28
|
+
});
|
|
29
|
+
test('should format terabytes correctly', () => {
|
|
30
|
+
expect(formatBytes(1000000000000)).toBe('1 TB');
|
|
31
|
+
});
|
|
32
|
+
test('should format petabytes correctly', () => {
|
|
33
|
+
expect(formatBytes(1000000000000000)).toBe('1 PB');
|
|
34
|
+
});
|
|
35
|
+
test('should respect custom decimal places', () => {
|
|
36
|
+
expect(formatBytes(1234, 0)).toBe('1 KB');
|
|
37
|
+
expect(formatBytes(1234, 1)).toBe('1.2 KB');
|
|
38
|
+
expect(formatBytes(1234, 2)).toBe('1.23 KB');
|
|
39
|
+
expect(formatBytes(1234, 3)).toBe('1.234 KB');
|
|
40
|
+
});
|
|
41
|
+
test('should handle negative decimal places as 0', () => {
|
|
42
|
+
expect(formatBytes(1234, -1)).toBe('1 KB');
|
|
43
|
+
expect(formatBytes(1234, -5)).toBe('1 KB');
|
|
44
|
+
});
|
|
45
|
+
test('should handle large numbers', () => {
|
|
46
|
+
expect(formatBytes(1e21)).toBe('1 ZB');
|
|
47
|
+
expect(formatBytes(1e24)).toBe('1 YB');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure.d.ts","sourceRoot":"","sources":["../../src/format/measure.ts"],"names":[],"mappings":"AAAA,wBAAsB,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,mBAc1F"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export async function measure(key, callback) {
|
|
2
|
+
const start = Date.now();
|
|
3
|
+
try {
|
|
4
|
+
let result = callback();
|
|
5
|
+
if (result instanceof Promise) {
|
|
6
|
+
result = await result;
|
|
7
|
+
}
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
finally {
|
|
11
|
+
const end = Date.now();
|
|
12
|
+
console.log(`${key} took ${end - start}ms`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure.test.d.ts","sourceRoot":"","sources":["../../src/format/measure.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { measure } from './measure';
|
|
3
|
+
describe('measure', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
6
|
+
});
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
vi.restoreAllMocks();
|
|
9
|
+
});
|
|
10
|
+
test('should return the result of a synchronous callback', async () => {
|
|
11
|
+
const result = await measure('test', () => 42);
|
|
12
|
+
expect(result).toBe(42);
|
|
13
|
+
});
|
|
14
|
+
test('should return the result of an async callback', async () => {
|
|
15
|
+
const result = await measure('test', async () => 'async result');
|
|
16
|
+
expect(result).toBe('async result');
|
|
17
|
+
});
|
|
18
|
+
test('should log the execution time with the key', async () => {
|
|
19
|
+
vi.spyOn(Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(1050);
|
|
20
|
+
await measure('myOperation', () => 'result');
|
|
21
|
+
expect(console.log).toHaveBeenCalledWith('myOperation took 50ms');
|
|
22
|
+
});
|
|
23
|
+
test('should log even if the callback throws', async () => {
|
|
24
|
+
vi.spyOn(Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(1100);
|
|
25
|
+
await expect(measure('failingOp', () => {
|
|
26
|
+
throw new Error('test error');
|
|
27
|
+
})).rejects.toThrow('test error');
|
|
28
|
+
expect(console.log).toHaveBeenCalledWith('failingOp took 100ms');
|
|
29
|
+
});
|
|
30
|
+
test('should log even if async callback rejects', async () => {
|
|
31
|
+
vi.spyOn(Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(1200);
|
|
32
|
+
await expect(measure('asyncFail', () => {
|
|
33
|
+
return Promise.reject(new Error('async error'));
|
|
34
|
+
})).rejects.toThrow('async error');
|
|
35
|
+
expect(console.log).toHaveBeenCalledWith('asyncFail took 200ms');
|
|
36
|
+
});
|
|
37
|
+
test('should handle callbacks returning undefined', async () => {
|
|
38
|
+
const result = await measure('voidOp', () => undefined);
|
|
39
|
+
expect(result).toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
test('should handle callbacks returning null', async () => {
|
|
42
|
+
const result = await measure('nullOp', () => null);
|
|
43
|
+
expect(result).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
test('should handle callbacks returning objects', async () => {
|
|
46
|
+
const obj = { foo: 'bar', num: 123 };
|
|
47
|
+
const result = await measure('objectOp', () => obj);
|
|
48
|
+
expect(result).toEqual(obj);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function setCookieValue(name: string, value: string, options?: {
|
|
2
|
+
expires?: Date;
|
|
3
|
+
path?: string;
|
|
4
|
+
sameSite?: string;
|
|
5
|
+
secure?: boolean;
|
|
6
|
+
domain?: string;
|
|
7
|
+
}): void;
|
|
8
|
+
export declare function getCookieValue(name: string): string | null;
|
|
9
|
+
//# sourceMappingURL=cookie.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../../src/http/cookie.ts"],"names":[],"mappings":"AAMA,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACZ,GACL,IAAI,CAuCN;AAOD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAe1D"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export function setCookieValue(name, value, options = {}) {
|
|
2
|
+
if (typeof window === 'undefined') {
|
|
3
|
+
console.warn('Cannot set cookie on server side');
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
7
|
+
if (options.expires) {
|
|
8
|
+
cookieString += `; expires=${options.expires.toUTCString()}`;
|
|
9
|
+
}
|
|
10
|
+
if (options.path) {
|
|
11
|
+
cookieString += `; path=${options.path}`;
|
|
12
|
+
}
|
|
13
|
+
if (options.sameSite) {
|
|
14
|
+
cookieString += `; SameSite=${options.sameSite}`;
|
|
15
|
+
}
|
|
16
|
+
if (options.secure) {
|
|
17
|
+
cookieString += '; Secure';
|
|
18
|
+
}
|
|
19
|
+
if (options.domain) {
|
|
20
|
+
cookieString += `; domain=${options.domain}`;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
document.cookie = cookieString;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof Error) {
|
|
27
|
+
console.error(`Failed to set cookie '${name}':`, error.message);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.error(`Failed to set cookie '${name}': Unknown error`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function getCookieValue(name) {
|
|
35
|
+
if (typeof window === 'undefined') {
|
|
36
|
+
console.warn('Cannot get cookie on server side');
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const value = `; ${document.cookie}`;
|
|
40
|
+
const parts = value.split(`; ${name}=`);
|
|
41
|
+
if (parts.length === 2) {
|
|
42
|
+
const cookieValue = parts.pop()?.split(';').shift();
|
|
43
|
+
return cookieValue ? decodeURIComponent(cookieValue) : null;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../src/http/domain.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,KAAG,MAmB/C,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const createDomain = (request) => {
|
|
2
|
+
const headers = request.headers;
|
|
3
|
+
const maybeProto = headers.get('x-forwarded-proto');
|
|
4
|
+
const maybeHost = headers.get('host');
|
|
5
|
+
const url = new URL(request.url);
|
|
6
|
+
if (maybeProto) {
|
|
7
|
+
return `${maybeProto}://${maybeHost ?? url.host}`;
|
|
8
|
+
}
|
|
9
|
+
if (url.hostname === 'localhost') {
|
|
10
|
+
return `http://${url.host}`;
|
|
11
|
+
}
|
|
12
|
+
return `https://${url.host}`;
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.test.d.ts","sourceRoot":"","sources":["../../src/http/domain.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { createDomain } from './domain';
|
|
3
|
+
describe('createDomain', () => {
|
|
4
|
+
test('handles request behind proxy with x-forwarded-proto', () => {
|
|
5
|
+
const request = new Request('http://example.com', {
|
|
6
|
+
headers: {
|
|
7
|
+
host: 'example.com',
|
|
8
|
+
'x-forwarded-proto': 'https',
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
expect(createDomain(request)).toBe('https://example.com');
|
|
12
|
+
});
|
|
13
|
+
test('handles localhost development environment', () => {
|
|
14
|
+
const request = new Request('http://localhost:3000/path');
|
|
15
|
+
expect(createDomain(request)).toBe('http://localhost:3000');
|
|
16
|
+
});
|
|
17
|
+
test('handles production environment', () => {
|
|
18
|
+
const request = new Request('https://production.com/path');
|
|
19
|
+
expect(createDomain(request)).toBe('https://production.com');
|
|
20
|
+
});
|
|
21
|
+
test('uses URL host when x-forwarded-proto exists but host header is missing', () => {
|
|
22
|
+
const request = new Request('http://example.com', {
|
|
23
|
+
headers: {
|
|
24
|
+
'x-forwarded-proto': 'https',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
expect(createDomain(request)).toBe('https://example.com');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-helpers.d.ts","sourceRoot":"","sources":["../../src/http/request-helpers.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,UAQ3C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-helpers.test.d.ts","sourceRoot":"","sources":["../../src/http/request-helpers.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { getCleanUrl } from './request-helpers';
|
|
3
|
+
describe('getCleanUrl', () => {
|
|
4
|
+
test('should return URL without search params', () => {
|
|
5
|
+
const request = new Request('https://example.com/path?foo=bar');
|
|
6
|
+
expect(getCleanUrl(request)).toBe('https://example.com/path');
|
|
7
|
+
});
|
|
8
|
+
test('should return URL unchanged if no search params', () => {
|
|
9
|
+
const request = new Request('https://example.com/path');
|
|
10
|
+
expect(getCleanUrl(request)).toBe('https://example.com/path');
|
|
11
|
+
});
|
|
12
|
+
test('should preserve the path', () => {
|
|
13
|
+
const request = new Request('https://example.com/some/deep/path?param=value');
|
|
14
|
+
expect(getCleanUrl(request)).toBe('https://example.com/some/deep/path');
|
|
15
|
+
});
|
|
16
|
+
test('should preserve the hash', () => {
|
|
17
|
+
const request = new Request('https://example.com/path?foo=bar#section');
|
|
18
|
+
const result = getCleanUrl(request);
|
|
19
|
+
expect(result).toBe('https://example.com/path#section');
|
|
20
|
+
});
|
|
21
|
+
test('should handle root path', () => {
|
|
22
|
+
const request = new Request('https://example.com/?foo=bar');
|
|
23
|
+
expect(getCleanUrl(request)).toBe('https://example.com/');
|
|
24
|
+
});
|
|
25
|
+
test('should handle multiple search params', () => {
|
|
26
|
+
const request = new Request('https://example.com/page?a=1&a=2&a=3');
|
|
27
|
+
expect(getCleanUrl(request)).toBe('https://example.com/page');
|
|
28
|
+
});
|
|
29
|
+
test('should handle URL with port', () => {
|
|
30
|
+
const request = new Request('https://example.com:8080/path?foo=bar');
|
|
31
|
+
expect(getCleanUrl(request)).toBe('https://example.com:8080/path');
|
|
32
|
+
});
|
|
33
|
+
test('should handle localhost', () => {
|
|
34
|
+
const request = new Request('http://localhost:3000/api?debug=true');
|
|
35
|
+
expect(getCleanUrl(request)).toBe('http://localhost:3000/api');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Cookie, SessionStorage } from 'react-router';
|
|
2
|
+
export interface LanguageDetectorOption {
|
|
3
|
+
supportedLanguages: string[];
|
|
4
|
+
fallbackLanguage: string;
|
|
5
|
+
cookie?: Cookie;
|
|
6
|
+
sessionStorage?: SessionStorage;
|
|
7
|
+
sessionKey?: string;
|
|
8
|
+
searchParamKey?: string;
|
|
9
|
+
order?: Array<'urlPath' | 'searchParams' | 'cookie' | 'session' | 'header'>;
|
|
10
|
+
}
|
|
11
|
+
export interface LanguageDetectorLinguiOptions {
|
|
12
|
+
detection: LanguageDetectorOption;
|
|
13
|
+
}
|
|
14
|
+
export declare class LanguageDetectorLingui {
|
|
15
|
+
private detector;
|
|
16
|
+
private options;
|
|
17
|
+
constructor(options: LanguageDetectorLinguiOptions);
|
|
18
|
+
getLocale(request: Request): Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
export declare class LanguageDetector {
|
|
21
|
+
private options;
|
|
22
|
+
constructor(options: LanguageDetectorOption);
|
|
23
|
+
detect(request: Request): Promise<string>;
|
|
24
|
+
private isSessionOnly;
|
|
25
|
+
private isCookieOnly;
|
|
26
|
+
private fromUrlPath;
|
|
27
|
+
private fromSearchParams;
|
|
28
|
+
private fromCookie;
|
|
29
|
+
private fromSessionStorage;
|
|
30
|
+
private fromHeader;
|
|
31
|
+
private fromSupported;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=language-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"language-detector.d.ts","sourceRoot":"","sources":["../../src/intl/language-detector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG3D,MAAM,WAAW,sBAAsB;IAMrC,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAM7B,gBAAgB,EAAE,MAAM,CAAC;IAKzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAMhB,cAAc,CAAC,EAAE,cAAc,CAAC;IAMhC,UAAU,CAAC,EAAE,MAAM,CAAC;IAOpB,cAAc,CAAC,EAAE,MAAM,CAAC;IAWxB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC;CAC7E;AAED,MAAM,WAAW,6BAA6B;IAC5C,SAAS,EAAE,sBAAsB,CAAC;CACnC;AAED,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,OAAO,CAAgC;gBAEnC,OAAO,EAAE,6BAA6B;IAgBrC,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;CAG1D;AAOD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAyB;gBAE5B,OAAO,EAAE,sBAAsB;IAM9B,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAgCtD,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,gBAAgB;YAQV,UAAU;YAcV,kBAAkB;IAYhC,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,aAAa;CAYtB"}
|