@stackframe/stack-shared 2.7.20 → 2.7.21
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/CHANGELOG.md +6 -0
- package/dist/crud.js +76 -0
- package/dist/hooks/use-strict-memo.js +75 -0
- package/dist/known-errors.js +1 -1
- package/dist/utils/arrays.js +75 -1
- package/dist/utils/caches.js +33 -0
- package/dist/utils/compile-time.js +17 -0
- package/dist/utils/dates.js +4 -4
- package/dist/utils/functions.js +15 -0
- package/dist/utils/html.js +28 -0
- package/dist/utils/http.js +29 -0
- package/dist/utils/ips.js +29 -0
- package/dist/utils/json.js +135 -0
- package/dist/utils/maps.js +145 -0
- package/dist/utils/objects.js +69 -0
- package/dist/utils/promises.js +201 -1
- package/dist/utils/proxies.js +72 -0
- package/dist/utils/react.js +82 -0
- package/dist/utils/results.js +241 -5
- package/dist/utils/strings.js +223 -2
- package/dist/utils/unicode.js +13 -0
- package/dist/utils/urls.js +115 -0
- package/dist/utils/uuids.js +30 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/dist/crud.js
CHANGED
|
@@ -52,3 +52,79 @@ export function createCrud(options) {
|
|
|
52
52
|
hasDelete: !!admin.deleteSchema,
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
+
import { yupObject, yupString } from './schema-fields';
|
|
56
|
+
import.meta.vitest?.test("createCrud", ({ expect }) => {
|
|
57
|
+
// Test with empty options
|
|
58
|
+
const emptyCrud = createCrud({});
|
|
59
|
+
expect(emptyCrud.hasCreate).toBe(false);
|
|
60
|
+
expect(emptyCrud.hasRead).toBe(false);
|
|
61
|
+
expect(emptyCrud.hasUpdate).toBe(false);
|
|
62
|
+
expect(emptyCrud.hasDelete).toBe(false);
|
|
63
|
+
expect(emptyCrud.client.createSchema).toBeUndefined();
|
|
64
|
+
expect(emptyCrud.server.createSchema).toBeUndefined();
|
|
65
|
+
expect(emptyCrud.admin.createSchema).toBeUndefined();
|
|
66
|
+
// Test with client schemas only
|
|
67
|
+
const mockSchema = yupObject().shape({
|
|
68
|
+
name: yupString().defined(),
|
|
69
|
+
});
|
|
70
|
+
const clientOnlyCrud = createCrud({
|
|
71
|
+
clientCreateSchema: mockSchema,
|
|
72
|
+
clientReadSchema: mockSchema,
|
|
73
|
+
});
|
|
74
|
+
expect(clientOnlyCrud.hasCreate).toBe(true);
|
|
75
|
+
expect(clientOnlyCrud.hasRead).toBe(true);
|
|
76
|
+
expect(clientOnlyCrud.hasUpdate).toBe(false);
|
|
77
|
+
expect(clientOnlyCrud.hasDelete).toBe(false);
|
|
78
|
+
expect(clientOnlyCrud.client.createSchema).toBe(mockSchema);
|
|
79
|
+
expect(clientOnlyCrud.server.createSchema).toBe(mockSchema);
|
|
80
|
+
expect(clientOnlyCrud.admin.createSchema).toBe(mockSchema);
|
|
81
|
+
// Test with server overrides
|
|
82
|
+
const serverSchema = yupObject().shape({
|
|
83
|
+
name: yupString().defined(),
|
|
84
|
+
internalField: yupString().defined(),
|
|
85
|
+
});
|
|
86
|
+
const serverOverrideCrud = createCrud({
|
|
87
|
+
clientCreateSchema: mockSchema,
|
|
88
|
+
serverCreateSchema: serverSchema,
|
|
89
|
+
});
|
|
90
|
+
expect(serverOverrideCrud.hasCreate).toBe(true);
|
|
91
|
+
expect(serverOverrideCrud.client.createSchema).toBe(mockSchema);
|
|
92
|
+
expect(serverOverrideCrud.server.createSchema).toBe(serverSchema);
|
|
93
|
+
expect(serverOverrideCrud.admin.createSchema).toBe(serverSchema);
|
|
94
|
+
// Test with admin overrides
|
|
95
|
+
const adminSchema = yupObject().shape({
|
|
96
|
+
name: yupString().defined(),
|
|
97
|
+
internalField: yupString().defined(),
|
|
98
|
+
adminField: yupString().defined(),
|
|
99
|
+
});
|
|
100
|
+
const fullOverrideCrud = createCrud({
|
|
101
|
+
clientCreateSchema: mockSchema,
|
|
102
|
+
serverCreateSchema: serverSchema,
|
|
103
|
+
adminCreateSchema: adminSchema,
|
|
104
|
+
});
|
|
105
|
+
expect(fullOverrideCrud.hasCreate).toBe(true);
|
|
106
|
+
expect(fullOverrideCrud.client.createSchema).toBe(mockSchema);
|
|
107
|
+
expect(fullOverrideCrud.server.createSchema).toBe(serverSchema);
|
|
108
|
+
expect(fullOverrideCrud.admin.createSchema).toBe(adminSchema);
|
|
109
|
+
// Test with documentation
|
|
110
|
+
const crudWithDocs = createCrud({
|
|
111
|
+
clientCreateSchema: mockSchema,
|
|
112
|
+
docs: {
|
|
113
|
+
clientCreate: {
|
|
114
|
+
summary: "Create a resource",
|
|
115
|
+
description: "Creates a new resource",
|
|
116
|
+
tags: ["resources"],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
expect(crudWithDocs.client.createDocs).toEqual({
|
|
121
|
+
summary: "Create a resource",
|
|
122
|
+
description: "Creates a new resource",
|
|
123
|
+
tags: ["resources"],
|
|
124
|
+
});
|
|
125
|
+
expect(crudWithDocs.server.createDocs).toEqual({
|
|
126
|
+
summary: "Create a resource",
|
|
127
|
+
description: "Creates a new resource",
|
|
128
|
+
tags: ["resources"],
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -27,6 +27,41 @@ function unwrapFromInner(dependencies, inner) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
import.meta.vitest?.test("unwrapFromInner", ({ expect }) => {
|
|
31
|
+
// Test with empty dependencies and non-nested map
|
|
32
|
+
const nonNestedMap = { isNotNestedMap: true, value: "test" };
|
|
33
|
+
const result1 = unwrapFromInner([], nonNestedMap);
|
|
34
|
+
expect(result1.status).toBe("ok");
|
|
35
|
+
if (result1.status === "ok") {
|
|
36
|
+
expect(result1.data).toBe("test");
|
|
37
|
+
}
|
|
38
|
+
// Test with non-empty dependencies and non-nested map (should error)
|
|
39
|
+
expect(unwrapFromInner(["key"], nonNestedMap).status).toBe("error");
|
|
40
|
+
// Test with empty dependencies and nested map (should error)
|
|
41
|
+
const nestedMap = new Map([["key", { isNotNestedMap: true, value: "test" }]]);
|
|
42
|
+
expect(unwrapFromInner([], nestedMap).status).toBe("error");
|
|
43
|
+
// Test with matching dependencies and nested map
|
|
44
|
+
const result2 = unwrapFromInner(["key"], nestedMap);
|
|
45
|
+
expect(result2.status).toBe("ok");
|
|
46
|
+
if (result2.status === "ok") {
|
|
47
|
+
expect(result2.data).toBe("test");
|
|
48
|
+
}
|
|
49
|
+
// Test with non-matching dependencies and nested map
|
|
50
|
+
expect(unwrapFromInner(["wrongKey"], nestedMap).status).toBe("error");
|
|
51
|
+
// Test with deeply nested map
|
|
52
|
+
const deeplyNestedMap = new Map([
|
|
53
|
+
["key1", new Map([
|
|
54
|
+
["key2", { isNotNestedMap: true, value: "nested" }]
|
|
55
|
+
])]
|
|
56
|
+
]);
|
|
57
|
+
const result3 = unwrapFromInner(["key1", "key2"], deeplyNestedMap);
|
|
58
|
+
expect(result3.status).toBe("ok");
|
|
59
|
+
if (result3.status === "ok") {
|
|
60
|
+
expect(result3.data).toBe("nested");
|
|
61
|
+
}
|
|
62
|
+
// Test with partial match in deeply nested map
|
|
63
|
+
expect(unwrapFromInner(["key1", "wrongKey"], deeplyNestedMap).status).toBe("error");
|
|
64
|
+
});
|
|
30
65
|
function wrapToInner(dependencies, value) {
|
|
31
66
|
if (dependencies.length === 0) {
|
|
32
67
|
return { isNotNestedMap: true, value };
|
|
@@ -39,6 +74,46 @@ function wrapToInner(dependencies, value) {
|
|
|
39
74
|
const mapType = isWeak ? WeakMap : Map;
|
|
40
75
|
return new mapType([[key, inner]]);
|
|
41
76
|
}
|
|
77
|
+
import.meta.vitest?.test("wrapToInner", ({ expect }) => {
|
|
78
|
+
// Test with empty dependencies
|
|
79
|
+
const emptyResult = wrapToInner([], "test");
|
|
80
|
+
expect(emptyResult).toEqual({ isNotNestedMap: true, value: "test" });
|
|
81
|
+
// Test with single string dependency
|
|
82
|
+
const singleResult = wrapToInner(["key"], "test");
|
|
83
|
+
expect(singleResult instanceof Map).toBe(true);
|
|
84
|
+
// Need to cast to access Map methods
|
|
85
|
+
const singleMap = singleResult;
|
|
86
|
+
expect(singleMap.get("key")).toEqual({ isNotNestedMap: true, value: "test" });
|
|
87
|
+
// Test with multiple string dependencies
|
|
88
|
+
const multiResult = wrapToInner(["key1", "key2"], "test");
|
|
89
|
+
expect(multiResult instanceof Map).toBe(true);
|
|
90
|
+
// Need to cast to access Map methods
|
|
91
|
+
const multiMap = multiResult;
|
|
92
|
+
const innerMap = multiMap.get("key1");
|
|
93
|
+
expect(innerMap instanceof Map).toBe(true);
|
|
94
|
+
expect(innerMap.get("key2")).toEqual({ isNotNestedMap: true, value: "test" });
|
|
95
|
+
// Test with object dependency (should use WeakMap)
|
|
96
|
+
const obj = { test: true };
|
|
97
|
+
const objResult = wrapToInner([obj], "test");
|
|
98
|
+
expect(objResult instanceof WeakMap).toBe(true);
|
|
99
|
+
// Need to cast to access WeakMap methods
|
|
100
|
+
const objMap = objResult;
|
|
101
|
+
expect(objMap.get(obj)).toEqual({ isNotNestedMap: true, value: "test" });
|
|
102
|
+
// Test with unregistered symbol dependency (should use WeakMap)
|
|
103
|
+
const symbolObj = Symbol("test");
|
|
104
|
+
const symbolResult = wrapToInner([symbolObj], "test");
|
|
105
|
+
expect(symbolResult instanceof WeakMap).toBe(true);
|
|
106
|
+
// Need to cast to access WeakMap methods
|
|
107
|
+
const symbolMap = symbolResult;
|
|
108
|
+
expect(symbolMap.get(symbolObj)).toEqual({ isNotNestedMap: true, value: "test" });
|
|
109
|
+
// Test with registered symbol dependency (should use Map)
|
|
110
|
+
const registeredSymbol = Symbol.for("test");
|
|
111
|
+
const registeredSymbolResult = wrapToInner([registeredSymbol], "test");
|
|
112
|
+
expect(registeredSymbolResult instanceof Map).toBe(true);
|
|
113
|
+
// Need to cast to access Map methods
|
|
114
|
+
const registeredSymbolMap = registeredSymbolResult;
|
|
115
|
+
expect(registeredSymbolMap.get(registeredSymbol)).toEqual({ isNotNestedMap: true, value: "test" });
|
|
116
|
+
});
|
|
42
117
|
/**
|
|
43
118
|
* Like memo, but minimizes recomputation of the value at all costs (instead of useMemo which recomputes whenever the renderer feels like it).
|
|
44
119
|
*
|
package/dist/known-errors.js
CHANGED
|
@@ -54,7 +54,7 @@ function createKnownErrorConstructor(SuperClass, errorCode, create, constructorA
|
|
|
54
54
|
// @ts-expect-error this is not a mixin, but TS detects it as one
|
|
55
55
|
class KnownErrorImpl extends SuperClass {
|
|
56
56
|
constructor(...args) {
|
|
57
|
-
// @ts-expect-error
|
|
57
|
+
// @ts-expect-error DEVIN, STOP REMOVING THIS LINE YOU DUMBASS, YES TYPESCRIPT DOES THROW AN ERROR IF YOU DON'T HAVE IT, NO THIS IS NOT "UNUSED", AND YES YOU ARE BREAKING OUR CI PIPELINE EVERY TIME YOU DO IT
|
|
58
58
|
super(...createFn(...args));
|
|
59
59
|
this.name = `KnownError<${errorCode}>`;
|
|
60
60
|
this.constructorArgs = args;
|
package/dist/utils/arrays.js
CHANGED
|
@@ -2,9 +2,23 @@ import { remainder } from "./math";
|
|
|
2
2
|
export function typedIncludes(arr, item) {
|
|
3
3
|
return arr.includes(item);
|
|
4
4
|
}
|
|
5
|
+
import.meta.vitest?.test("typedIncludes", ({ expect }) => {
|
|
6
|
+
const arr = [1, 2, 3];
|
|
7
|
+
expect(typedIncludes(arr, 1)).toBe(true);
|
|
8
|
+
expect(typedIncludes(arr, 4)).toBe(false);
|
|
9
|
+
expect(typedIncludes(arr, "1")).toBe(false);
|
|
10
|
+
const strArr = ["a", "b", "c"];
|
|
11
|
+
expect(typedIncludes(strArr, "a")).toBe(true);
|
|
12
|
+
expect(typedIncludes(strArr, "d")).toBe(false);
|
|
13
|
+
});
|
|
5
14
|
export function enumerate(arr) {
|
|
6
15
|
return arr.map((item, index) => [index, item]);
|
|
7
16
|
}
|
|
17
|
+
import.meta.vitest?.test("enumerate", ({ expect }) => {
|
|
18
|
+
expect(enumerate([])).toEqual([]);
|
|
19
|
+
expect(enumerate([1, 2, 3])).toEqual([[0, 1], [1, 2], [2, 3]]);
|
|
20
|
+
expect(enumerate(["a", "b", "c"])).toEqual([[0, "a"], [1, "b"], [2, "c"]]);
|
|
21
|
+
});
|
|
8
22
|
export function isShallowEqual(a, b) {
|
|
9
23
|
if (a.length !== b.length)
|
|
10
24
|
return false;
|
|
@@ -53,6 +67,22 @@ export function groupBy(arr, key) {
|
|
|
53
67
|
}
|
|
54
68
|
return result;
|
|
55
69
|
}
|
|
70
|
+
import.meta.vitest?.test("groupBy", ({ expect }) => {
|
|
71
|
+
expect(groupBy([], (x) => x)).toEqual(new Map());
|
|
72
|
+
const numbers = [1, 2, 3, 4, 5, 6];
|
|
73
|
+
const grouped = groupBy(numbers, (n) => n % 2 === 0 ? "even" : "odd");
|
|
74
|
+
expect(grouped.get("even")).toEqual([2, 4, 6]);
|
|
75
|
+
expect(grouped.get("odd")).toEqual([1, 3, 5]);
|
|
76
|
+
// Check the actual lengths of the words to ensure our test is correct
|
|
77
|
+
const words = ["apple", "banana", "cherry", "date", "elderberry"];
|
|
78
|
+
console.log("Word lengths:", words.map(w => `${w}: ${w.length}`));
|
|
79
|
+
const byLength = groupBy(words, (w) => w.length);
|
|
80
|
+
// Adjust expectations based on actual word lengths
|
|
81
|
+
expect(byLength.get(5)).toEqual(["apple"]);
|
|
82
|
+
expect(byLength.get(6)).toEqual(["banana", "cherry"]);
|
|
83
|
+
expect(byLength.get(4)).toEqual(["date"]);
|
|
84
|
+
expect(byLength.get(10)).toEqual(["elderberry"]);
|
|
85
|
+
});
|
|
56
86
|
export function range(startInclusive, endExclusive, step) {
|
|
57
87
|
if (endExclusive === undefined) {
|
|
58
88
|
endExclusive = startInclusive;
|
|
@@ -75,12 +105,30 @@ import.meta.vitest?.test("range", ({ expect }) => {
|
|
|
75
105
|
expect(range(0, 10, 3)).toEqual([0, 3, 6, 9]);
|
|
76
106
|
});
|
|
77
107
|
export function rotateLeft(arr, n) {
|
|
108
|
+
if (arr.length === 0)
|
|
109
|
+
return [];
|
|
78
110
|
const index = remainder(n, arr.length);
|
|
79
|
-
return [...arr.slice(
|
|
111
|
+
return [...arr.slice(index), ...arr.slice(0, index)];
|
|
80
112
|
}
|
|
113
|
+
import.meta.vitest?.test("rotateLeft", ({ expect }) => {
|
|
114
|
+
expect(rotateLeft([], 1)).toEqual([]);
|
|
115
|
+
expect(rotateLeft([1, 2, 3, 4, 5], 0)).toEqual([1, 2, 3, 4, 5]);
|
|
116
|
+
expect(rotateLeft([1, 2, 3, 4, 5], 1)).toEqual([2, 3, 4, 5, 1]);
|
|
117
|
+
expect(rotateLeft([1, 2, 3, 4, 5], 3)).toEqual([4, 5, 1, 2, 3]);
|
|
118
|
+
expect(rotateLeft([1, 2, 3, 4, 5], 5)).toEqual([1, 2, 3, 4, 5]);
|
|
119
|
+
expect(rotateLeft([1, 2, 3, 4, 5], 6)).toEqual([2, 3, 4, 5, 1]);
|
|
120
|
+
});
|
|
81
121
|
export function rotateRight(arr, n) {
|
|
82
122
|
return rotateLeft(arr, -n);
|
|
83
123
|
}
|
|
124
|
+
import.meta.vitest?.test("rotateRight", ({ expect }) => {
|
|
125
|
+
expect(rotateRight([], 1)).toEqual([]);
|
|
126
|
+
expect(rotateRight([1, 2, 3, 4, 5], 0)).toEqual([1, 2, 3, 4, 5]);
|
|
127
|
+
expect(rotateRight([1, 2, 3, 4, 5], 1)).toEqual([5, 1, 2, 3, 4]);
|
|
128
|
+
expect(rotateRight([1, 2, 3, 4, 5], 3)).toEqual([3, 4, 5, 1, 2]);
|
|
129
|
+
expect(rotateRight([1, 2, 3, 4, 5], 5)).toEqual([1, 2, 3, 4, 5]);
|
|
130
|
+
expect(rotateRight([1, 2, 3, 4, 5], 6)).toEqual([5, 1, 2, 3, 4]);
|
|
131
|
+
});
|
|
84
132
|
export function shuffle(arr) {
|
|
85
133
|
const result = [...arr];
|
|
86
134
|
for (let i = result.length - 1; i > 0; i--) {
|
|
@@ -89,9 +137,35 @@ export function shuffle(arr) {
|
|
|
89
137
|
}
|
|
90
138
|
return result;
|
|
91
139
|
}
|
|
140
|
+
import.meta.vitest?.test("shuffle", ({ expect }) => {
|
|
141
|
+
// Test empty array
|
|
142
|
+
expect(shuffle([])).toEqual([]);
|
|
143
|
+
// Test single element array
|
|
144
|
+
expect(shuffle([1])).toEqual([1]);
|
|
145
|
+
// Test that shuffle returns a new array
|
|
146
|
+
const original = [1, 2, 3, 4, 5];
|
|
147
|
+
const shuffled = shuffle(original);
|
|
148
|
+
expect(shuffled).not.toBe(original);
|
|
149
|
+
// Test that all elements are preserved
|
|
150
|
+
expect(shuffled.sort((a, b) => a - b)).toEqual(original);
|
|
151
|
+
// Test with a larger array to ensure randomness
|
|
152
|
+
// This is a probabilistic test, but it's very unlikely to fail
|
|
153
|
+
const large = Array.from({ length: 100 }, (_, i) => i);
|
|
154
|
+
const shuffledLarge = shuffle(large);
|
|
155
|
+
expect(shuffledLarge).not.toEqual(large);
|
|
156
|
+
expect(shuffledLarge.sort((a, b) => a - b)).toEqual(large);
|
|
157
|
+
});
|
|
92
158
|
export function outerProduct(arr1, arr2) {
|
|
93
159
|
return arr1.flatMap((item1) => arr2.map((item2) => [item1, item2]));
|
|
94
160
|
}
|
|
161
|
+
import.meta.vitest?.test("outerProduct", ({ expect }) => {
|
|
162
|
+
expect(outerProduct([], [])).toEqual([]);
|
|
163
|
+
expect(outerProduct([1], [])).toEqual([]);
|
|
164
|
+
expect(outerProduct([], [1])).toEqual([]);
|
|
165
|
+
expect(outerProduct([1], [2])).toEqual([[1, 2]]);
|
|
166
|
+
expect(outerProduct([1, 2], [3, 4])).toEqual([[1, 3], [1, 4], [2, 3], [2, 4]]);
|
|
167
|
+
expect(outerProduct(["a", "b"], [1, 2])).toEqual([["a", 1], ["a", 2], ["b", 1], ["b", 2]]);
|
|
168
|
+
});
|
|
95
169
|
export function unique(arr) {
|
|
96
170
|
return [...new Set(arr)];
|
|
97
171
|
}
|
package/dist/utils/caches.js
CHANGED
|
@@ -16,6 +16,39 @@ export function cacheFunction(f) {
|
|
|
16
16
|
return value;
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
+
import.meta.vitest?.test("cacheFunction", ({ expect }) => {
|
|
20
|
+
// Test with a simple function
|
|
21
|
+
let callCount = 0;
|
|
22
|
+
const add = (a, b) => {
|
|
23
|
+
callCount++;
|
|
24
|
+
return a + b;
|
|
25
|
+
};
|
|
26
|
+
const cachedAdd = cacheFunction(add);
|
|
27
|
+
// First call should execute the function
|
|
28
|
+
expect(cachedAdd(1, 2)).toBe(3);
|
|
29
|
+
expect(callCount).toBe(1);
|
|
30
|
+
// Second call with same args should use cached result
|
|
31
|
+
expect(cachedAdd(1, 2)).toBe(3);
|
|
32
|
+
expect(callCount).toBe(1);
|
|
33
|
+
// Call with different args should execute the function again
|
|
34
|
+
expect(cachedAdd(2, 3)).toBe(5);
|
|
35
|
+
expect(callCount).toBe(2);
|
|
36
|
+
// Test with a function that returns objects
|
|
37
|
+
let objectCallCount = 0;
|
|
38
|
+
const createObject = (id) => {
|
|
39
|
+
objectCallCount++;
|
|
40
|
+
return { id };
|
|
41
|
+
};
|
|
42
|
+
const cachedCreateObject = cacheFunction(createObject);
|
|
43
|
+
// First call should execute the function
|
|
44
|
+
const obj1 = cachedCreateObject(1);
|
|
45
|
+
expect(obj1).toEqual({ id: 1 });
|
|
46
|
+
expect(objectCallCount).toBe(1);
|
|
47
|
+
// Second call with same args should use cached result
|
|
48
|
+
const obj2 = cachedCreateObject(1);
|
|
49
|
+
expect(obj2).toBe(obj1); // Same reference
|
|
50
|
+
expect(objectCallCount).toBe(1);
|
|
51
|
+
});
|
|
19
52
|
export class AsyncCache {
|
|
20
53
|
constructor(_fetcher, _options = {}) {
|
|
21
54
|
this._fetcher = _fetcher;
|
|
@@ -6,3 +6,20 @@
|
|
|
6
6
|
export function scrambleDuringCompileTime(t) {
|
|
7
7
|
return t;
|
|
8
8
|
}
|
|
9
|
+
import.meta.vitest?.test("scrambleDuringCompileTime", ({ expect }) => {
|
|
10
|
+
// Test with primitive values
|
|
11
|
+
expect(scrambleDuringCompileTime(42)).toBe(42);
|
|
12
|
+
expect(scrambleDuringCompileTime("hello")).toBe("hello");
|
|
13
|
+
expect(scrambleDuringCompileTime(true)).toBe(true);
|
|
14
|
+
expect(scrambleDuringCompileTime(null)).toBe(null);
|
|
15
|
+
expect(scrambleDuringCompileTime(undefined)).toBe(undefined);
|
|
16
|
+
// Test with objects (reference equality)
|
|
17
|
+
const obj = { a: 1 };
|
|
18
|
+
expect(scrambleDuringCompileTime(obj)).toBe(obj);
|
|
19
|
+
// Test with arrays (reference equality)
|
|
20
|
+
const arr = [1, 2, 3];
|
|
21
|
+
expect(scrambleDuringCompileTime(arr)).toBe(arr);
|
|
22
|
+
// Test with functions (reference equality)
|
|
23
|
+
const fn = () => "test";
|
|
24
|
+
expect(scrambleDuringCompileTime(fn)).toBe(fn);
|
|
25
|
+
});
|
package/dist/utils/dates.js
CHANGED
|
@@ -4,13 +4,13 @@ export function isWeekend(date) {
|
|
|
4
4
|
}
|
|
5
5
|
import.meta.vitest?.test("isWeekend", ({ expect }) => {
|
|
6
6
|
// Sunday (day 0)
|
|
7
|
-
expect(isWeekend(new Date(
|
|
7
|
+
expect(isWeekend(new Date(2023, 0, 1))).toBe(true);
|
|
8
8
|
// Saturday (day 6)
|
|
9
|
-
expect(isWeekend(new Date(
|
|
9
|
+
expect(isWeekend(new Date(2023, 0, 7))).toBe(true);
|
|
10
10
|
// Monday (day 1)
|
|
11
|
-
expect(isWeekend(new Date(
|
|
11
|
+
expect(isWeekend(new Date(2023, 0, 2))).toBe(false);
|
|
12
12
|
// Friday (day 5)
|
|
13
|
-
expect(isWeekend(new Date(
|
|
13
|
+
expect(isWeekend(new Date(2023, 0, 6))).toBe(false);
|
|
14
14
|
});
|
|
15
15
|
const agoUnits = [
|
|
16
16
|
[60, 'second'],
|
package/dist/utils/functions.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
export function identity(t) {
|
|
2
2
|
return t;
|
|
3
3
|
}
|
|
4
|
+
import.meta.vitest?.test("identity", ({ expect }) => {
|
|
5
|
+
expect(identity(1)).toBe(1);
|
|
6
|
+
expect(identity("test")).toBe("test");
|
|
7
|
+
expect(identity(null)).toBe(null);
|
|
8
|
+
expect(identity(undefined)).toBe(undefined);
|
|
9
|
+
const obj = { a: 1 };
|
|
10
|
+
expect(identity(obj)).toBe(obj);
|
|
11
|
+
});
|
|
4
12
|
export function identityArgs(...args) {
|
|
5
13
|
return args;
|
|
6
14
|
}
|
|
15
|
+
import.meta.vitest?.test("identityArgs", ({ expect }) => {
|
|
16
|
+
expect(identityArgs()).toEqual([]);
|
|
17
|
+
expect(identityArgs(1)).toEqual([1]);
|
|
18
|
+
expect(identityArgs(1, 2, 3)).toEqual([1, 2, 3]);
|
|
19
|
+
expect(identityArgs("a", "b", "c")).toEqual(["a", "b", "c"]);
|
|
20
|
+
expect(identityArgs(null, undefined)).toEqual([null, undefined]);
|
|
21
|
+
});
|
package/dist/utils/html.js
CHANGED
|
@@ -7,6 +7,34 @@ export function escapeHtml(unsafe) {
|
|
|
7
7
|
.replace(/"/g, """)
|
|
8
8
|
.replace(/'/g, "'");
|
|
9
9
|
}
|
|
10
|
+
import.meta.vitest?.test("escapeHtml", ({ expect }) => {
|
|
11
|
+
// Test with empty string
|
|
12
|
+
expect(escapeHtml("")).toBe("");
|
|
13
|
+
// Test with string without special characters
|
|
14
|
+
expect(escapeHtml("hello world")).toBe("hello world");
|
|
15
|
+
// Test with special characters
|
|
16
|
+
expect(escapeHtml("<div>")).toBe("<div>");
|
|
17
|
+
expect(escapeHtml("a & b")).toBe("a & b");
|
|
18
|
+
expect(escapeHtml('a "quoted" string')).toBe("a "quoted" string");
|
|
19
|
+
expect(escapeHtml("it's a test")).toBe("it's a test");
|
|
20
|
+
// Test with multiple special characters
|
|
21
|
+
expect(escapeHtml("<a href=\"test\">It's a link</a>")).toBe("<a href="test">It's a link</a>");
|
|
22
|
+
});
|
|
10
23
|
export function html(strings, ...values) {
|
|
11
24
|
return templateIdentity(strings, ...values.map(v => escapeHtml(`${v}`)));
|
|
12
25
|
}
|
|
26
|
+
import.meta.vitest?.test("html", ({ expect }) => {
|
|
27
|
+
// Test with no interpolation
|
|
28
|
+
expect(html `simple string`).toBe("simple string");
|
|
29
|
+
// Test with string interpolation
|
|
30
|
+
expect(html `Hello, ${"world"}!`).toBe("Hello, world!");
|
|
31
|
+
// Test with number interpolation
|
|
32
|
+
expect(html `Count: ${42}`).toBe("Count: 42");
|
|
33
|
+
// Test with HTML special characters in interpolated values
|
|
34
|
+
expect(html `<div>${"<script>"}</div>`).toBe("<div><script></div>");
|
|
35
|
+
// Test with multiple interpolations
|
|
36
|
+
expect(html `${1} + ${2} = ${"<3"}`).toBe("1 + 2 = <3");
|
|
37
|
+
// Test with object interpolation
|
|
38
|
+
const obj = { toString: () => "<object>" };
|
|
39
|
+
expect(html `Object: ${obj}`).toBe("Object: <object>");
|
|
40
|
+
});
|
package/dist/utils/http.js
CHANGED
|
@@ -51,8 +51,37 @@ export function decodeBasicAuthorizationHeader(value) {
|
|
|
51
51
|
const split = decoded.split(':');
|
|
52
52
|
return [split[0], split.slice(1).join(':')];
|
|
53
53
|
}
|
|
54
|
+
import.meta.vitest?.test("decodeBasicAuthorizationHeader", ({ expect }) => {
|
|
55
|
+
// Test with valid Basic Authorization header
|
|
56
|
+
const username = "user";
|
|
57
|
+
const password = "pass";
|
|
58
|
+
const encoded = encodeBasicAuthorizationHeader(username, password);
|
|
59
|
+
expect(decodeBasicAuthorizationHeader(encoded)).toEqual([username, password]);
|
|
60
|
+
// Test with password containing colons
|
|
61
|
+
const complexPassword = "pass:with:colons";
|
|
62
|
+
const encodedComplex = encodeBasicAuthorizationHeader(username, complexPassword);
|
|
63
|
+
expect(decodeBasicAuthorizationHeader(encodedComplex)).toEqual([username, complexPassword]);
|
|
64
|
+
// Test with invalid headers
|
|
65
|
+
expect(decodeBasicAuthorizationHeader("NotBasic dXNlcjpwYXNz")).toBe(null); // Wrong type
|
|
66
|
+
expect(decodeBasicAuthorizationHeader("Basic")).toBe(null); // Missing encoded part
|
|
67
|
+
expect(decodeBasicAuthorizationHeader("Basic not-base64")).toBe(null); // Not base64
|
|
68
|
+
expect(decodeBasicAuthorizationHeader("Basic dXNlcjpwYXNz extra")).toBe(null); // Extra parts
|
|
69
|
+
});
|
|
54
70
|
export function encodeBasicAuthorizationHeader(id, password) {
|
|
55
71
|
if (id.includes(':'))
|
|
56
72
|
throw new Error("Basic authorization header id cannot contain ':'");
|
|
57
73
|
return `Basic ${encodeBase64(new TextEncoder().encode(`${id}:${password}`))}`;
|
|
58
74
|
}
|
|
75
|
+
import.meta.vitest?.test("encodeBasicAuthorizationHeader", ({ expect }) => {
|
|
76
|
+
// Test with simple username and password
|
|
77
|
+
const encoded = encodeBasicAuthorizationHeader("user", "pass");
|
|
78
|
+
expect(encoded).toMatch(/^Basic [A-Za-z0-9+/=]+$/); // Should start with "Basic " followed by base64
|
|
79
|
+
// Test with empty password
|
|
80
|
+
const encodedEmptyPass = encodeBasicAuthorizationHeader("user", "");
|
|
81
|
+
expect(encodedEmptyPass).toMatch(/^Basic [A-Za-z0-9+/=]+$/);
|
|
82
|
+
// Test with password containing special characters
|
|
83
|
+
const encodedSpecialChars = encodeBasicAuthorizationHeader("user", "p@ss!w0rd");
|
|
84
|
+
expect(encodedSpecialChars).toMatch(/^Basic [A-Za-z0-9+/=]+$/);
|
|
85
|
+
// Test with username containing colon should throw
|
|
86
|
+
expect(() => encodeBasicAuthorizationHeader("user:name", "pass")).toThrow();
|
|
87
|
+
});
|
package/dist/utils/ips.js
CHANGED
|
@@ -2,8 +2,37 @@ import ipRegex from "ip-regex";
|
|
|
2
2
|
export function isIpAddress(ip) {
|
|
3
3
|
return ipRegex({ exact: true }).test(ip);
|
|
4
4
|
}
|
|
5
|
+
import.meta.vitest?.test("isIpAddress", ({ expect }) => {
|
|
6
|
+
// Test valid IPv4 addresses
|
|
7
|
+
expect(isIpAddress("192.168.1.1")).toBe(true);
|
|
8
|
+
expect(isIpAddress("127.0.0.1")).toBe(true);
|
|
9
|
+
expect(isIpAddress("0.0.0.0")).toBe(true);
|
|
10
|
+
expect(isIpAddress("255.255.255.255")).toBe(true);
|
|
11
|
+
// Test valid IPv6 addresses
|
|
12
|
+
expect(isIpAddress("::1")).toBe(true);
|
|
13
|
+
expect(isIpAddress("2001:db8::")).toBe(true);
|
|
14
|
+
expect(isIpAddress("2001:db8:85a3:8d3:1319:8a2e:370:7348")).toBe(true);
|
|
15
|
+
// Test invalid IP addresses
|
|
16
|
+
expect(isIpAddress("")).toBe(false);
|
|
17
|
+
expect(isIpAddress("not an ip")).toBe(false);
|
|
18
|
+
expect(isIpAddress("256.256.256.256")).toBe(false);
|
|
19
|
+
expect(isIpAddress("192.168.1")).toBe(false);
|
|
20
|
+
expect(isIpAddress("192.168.1.1.1")).toBe(false);
|
|
21
|
+
expect(isIpAddress("2001:db8::xyz")).toBe(false);
|
|
22
|
+
});
|
|
5
23
|
export function assertIpAddress(ip) {
|
|
6
24
|
if (!isIpAddress(ip)) {
|
|
7
25
|
throw new Error(`Invalid IP address: ${ip}`);
|
|
8
26
|
}
|
|
9
27
|
}
|
|
28
|
+
import.meta.vitest?.test("assertIpAddress", ({ expect }) => {
|
|
29
|
+
// Test with valid IPv4 address
|
|
30
|
+
expect(() => assertIpAddress("192.168.1.1")).not.toThrow();
|
|
31
|
+
// Test with valid IPv6 address
|
|
32
|
+
expect(() => assertIpAddress("::1")).not.toThrow();
|
|
33
|
+
// Test with invalid IP addresses
|
|
34
|
+
expect(() => assertIpAddress("")).toThrow("Invalid IP address: ");
|
|
35
|
+
expect(() => assertIpAddress("not an ip")).toThrow("Invalid IP address: not an ip");
|
|
36
|
+
expect(() => assertIpAddress("256.256.256.256")).toThrow("Invalid IP address: 256.256.256.256");
|
|
37
|
+
expect(() => assertIpAddress("192.168.1")).toThrow("Invalid IP address: 192.168.1");
|
|
38
|
+
});
|
package/dist/utils/json.js
CHANGED
|
@@ -18,9 +18,144 @@ export function isJson(value) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
import.meta.vitest?.test("isJson", ({ expect }) => {
|
|
22
|
+
// Test primitive values
|
|
23
|
+
expect(isJson(null)).toBe(true);
|
|
24
|
+
expect(isJson(true)).toBe(true);
|
|
25
|
+
expect(isJson(false)).toBe(true);
|
|
26
|
+
expect(isJson(123)).toBe(true);
|
|
27
|
+
expect(isJson("string")).toBe(true);
|
|
28
|
+
// Test arrays
|
|
29
|
+
expect(isJson([])).toBe(true);
|
|
30
|
+
expect(isJson([1, 2, 3])).toBe(true);
|
|
31
|
+
expect(isJson(["a", "b", "c"])).toBe(true);
|
|
32
|
+
expect(isJson([1, "a", true, null])).toBe(true);
|
|
33
|
+
expect(isJson([1, [2, 3], { a: "b" }])).toBe(true);
|
|
34
|
+
// Test objects
|
|
35
|
+
expect(isJson({})).toBe(true);
|
|
36
|
+
expect(isJson({ a: 1, b: 2 })).toBe(true);
|
|
37
|
+
expect(isJson({ a: "string", b: true, c: null })).toBe(true);
|
|
38
|
+
expect(isJson({ a: [1, 2, 3], b: { c: "d" } })).toBe(true);
|
|
39
|
+
// Test invalid JSON values
|
|
40
|
+
expect(isJson(undefined)).toBe(false);
|
|
41
|
+
expect(isJson(() => { })).toBe(false);
|
|
42
|
+
expect(isJson(Symbol())).toBe(false);
|
|
43
|
+
expect(isJson(BigInt(123))).toBe(false);
|
|
44
|
+
// Test arrays with invalid JSON values
|
|
45
|
+
expect(isJson([1, undefined, 3])).toBe(false);
|
|
46
|
+
expect(isJson([1, () => { }, 3])).toBe(false);
|
|
47
|
+
// Test objects with invalid JSON values
|
|
48
|
+
expect(isJson({ a: 1, b: undefined })).toBe(false);
|
|
49
|
+
expect(isJson({ a: 1, b: () => { } })).toBe(false);
|
|
50
|
+
});
|
|
21
51
|
export function parseJson(json) {
|
|
22
52
|
return Result.fromThrowing(() => JSON.parse(json));
|
|
23
53
|
}
|
|
54
|
+
import.meta.vitest?.test("parseJson", ({ expect }) => {
|
|
55
|
+
// Test valid JSON strings
|
|
56
|
+
const nullResult = parseJson("null");
|
|
57
|
+
expect(nullResult.status).toBe("ok");
|
|
58
|
+
if (nullResult.status === "ok") {
|
|
59
|
+
expect(nullResult.data).toBe(null);
|
|
60
|
+
}
|
|
61
|
+
const trueResult = parseJson("true");
|
|
62
|
+
expect(trueResult.status).toBe("ok");
|
|
63
|
+
if (trueResult.status === "ok") {
|
|
64
|
+
expect(trueResult.data).toBe(true);
|
|
65
|
+
}
|
|
66
|
+
const numberResult = parseJson("123");
|
|
67
|
+
expect(numberResult.status).toBe("ok");
|
|
68
|
+
if (numberResult.status === "ok") {
|
|
69
|
+
expect(numberResult.data).toBe(123);
|
|
70
|
+
}
|
|
71
|
+
const stringResult = parseJson('"string"');
|
|
72
|
+
expect(stringResult.status).toBe("ok");
|
|
73
|
+
if (stringResult.status === "ok") {
|
|
74
|
+
expect(stringResult.data).toBe("string");
|
|
75
|
+
}
|
|
76
|
+
const emptyArrayResult = parseJson("[]");
|
|
77
|
+
expect(emptyArrayResult.status).toBe("ok");
|
|
78
|
+
if (emptyArrayResult.status === "ok") {
|
|
79
|
+
expect(emptyArrayResult.data).toEqual([]);
|
|
80
|
+
}
|
|
81
|
+
const arrayResult = parseJson("[1,2,3]");
|
|
82
|
+
expect(arrayResult.status).toBe("ok");
|
|
83
|
+
if (arrayResult.status === "ok") {
|
|
84
|
+
expect(arrayResult.data).toEqual([1, 2, 3]);
|
|
85
|
+
}
|
|
86
|
+
const emptyObjectResult = parseJson("{}");
|
|
87
|
+
expect(emptyObjectResult.status).toBe("ok");
|
|
88
|
+
if (emptyObjectResult.status === "ok") {
|
|
89
|
+
expect(emptyObjectResult.data).toEqual({});
|
|
90
|
+
}
|
|
91
|
+
const objectResult = parseJson('{"a":1,"b":"string"}');
|
|
92
|
+
expect(objectResult.status).toBe("ok");
|
|
93
|
+
if (objectResult.status === "ok") {
|
|
94
|
+
expect(objectResult.data).toEqual({ a: 1, b: "string" });
|
|
95
|
+
}
|
|
96
|
+
// Test invalid JSON strings
|
|
97
|
+
expect(parseJson("").status).toBe("error");
|
|
98
|
+
expect(parseJson("undefined").status).toBe("error");
|
|
99
|
+
expect(parseJson("{").status).toBe("error");
|
|
100
|
+
expect(parseJson('{"a":1,}').status).toBe("error");
|
|
101
|
+
expect(parseJson("function(){}").status).toBe("error");
|
|
102
|
+
});
|
|
24
103
|
export function stringifyJson(json) {
|
|
25
104
|
return Result.fromThrowing(() => JSON.stringify(json));
|
|
26
105
|
}
|
|
106
|
+
import.meta.vitest?.test("stringifyJson", ({ expect }) => {
|
|
107
|
+
// Test primitive values
|
|
108
|
+
const nullResult = stringifyJson(null);
|
|
109
|
+
expect(nullResult.status).toBe("ok");
|
|
110
|
+
if (nullResult.status === "ok") {
|
|
111
|
+
expect(nullResult.data).toBe("null");
|
|
112
|
+
}
|
|
113
|
+
const trueResult = stringifyJson(true);
|
|
114
|
+
expect(trueResult.status).toBe("ok");
|
|
115
|
+
if (trueResult.status === "ok") {
|
|
116
|
+
expect(trueResult.data).toBe("true");
|
|
117
|
+
}
|
|
118
|
+
const numberResult = stringifyJson(123);
|
|
119
|
+
expect(numberResult.status).toBe("ok");
|
|
120
|
+
if (numberResult.status === "ok") {
|
|
121
|
+
expect(numberResult.data).toBe("123");
|
|
122
|
+
}
|
|
123
|
+
const stringResult = stringifyJson("string");
|
|
124
|
+
expect(stringResult.status).toBe("ok");
|
|
125
|
+
if (stringResult.status === "ok") {
|
|
126
|
+
expect(stringResult.data).toBe('"string"');
|
|
127
|
+
}
|
|
128
|
+
// Test arrays
|
|
129
|
+
const emptyArrayResult = stringifyJson([]);
|
|
130
|
+
expect(emptyArrayResult.status).toBe("ok");
|
|
131
|
+
if (emptyArrayResult.status === "ok") {
|
|
132
|
+
expect(emptyArrayResult.data).toBe("[]");
|
|
133
|
+
}
|
|
134
|
+
const arrayResult = stringifyJson([1, 2, 3]);
|
|
135
|
+
expect(arrayResult.status).toBe("ok");
|
|
136
|
+
if (arrayResult.status === "ok") {
|
|
137
|
+
expect(arrayResult.data).toBe("[1,2,3]");
|
|
138
|
+
}
|
|
139
|
+
// Test objects
|
|
140
|
+
const emptyObjectResult = stringifyJson({});
|
|
141
|
+
expect(emptyObjectResult.status).toBe("ok");
|
|
142
|
+
if (emptyObjectResult.status === "ok") {
|
|
143
|
+
expect(emptyObjectResult.data).toBe("{}");
|
|
144
|
+
}
|
|
145
|
+
const objectResult = stringifyJson({ a: 1, b: "string" });
|
|
146
|
+
expect(objectResult.status).toBe("ok");
|
|
147
|
+
if (objectResult.status === "ok") {
|
|
148
|
+
expect(objectResult.data).toBe('{"a":1,"b":"string"}');
|
|
149
|
+
}
|
|
150
|
+
// Test nested structures
|
|
151
|
+
const nested = { a: [1, 2, 3], b: { c: "d" } };
|
|
152
|
+
const nestedResult = stringifyJson(nested);
|
|
153
|
+
expect(nestedResult.status).toBe("ok");
|
|
154
|
+
if (nestedResult.status === "ok") {
|
|
155
|
+
expect(nestedResult.data).toBe('{"a":[1,2,3],"b":{"c":"d"}}');
|
|
156
|
+
}
|
|
157
|
+
// Test circular references (should error)
|
|
158
|
+
const circular = { a: 1 };
|
|
159
|
+
circular.self = circular;
|
|
160
|
+
expect(stringifyJson(circular).status).toBe("error");
|
|
161
|
+
});
|