@outfitter/types 0.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/branded.d.ts +129 -0
- package/dist/branded.js +68 -0
- package/dist/collections.d.ts +67 -0
- package/dist/collections.js +37 -0
- package/dist/deep.d.ts +99 -0
- package/dist/deep.js +1 -0
- package/dist/guards.d.ts +67 -0
- package/dist/guards.js +30 -0
- package/dist/hash-id.d.ts +17 -0
- package/dist/hash-id.js +25 -0
- package/dist/index.d.ts +499 -0
- package/dist/index.js +57 -0
- package/dist/short-id.d.ts +47 -0
- package/dist/short-id.js +41 -0
- package/dist/utilities.d.ts +99 -0
- package/dist/utilities.js +8 -0
- package/package.json +92 -0
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { ValidationError } from "@outfitter/contracts";
|
|
2
|
+
import { Result } from "better-result";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a branded type by adding a unique brand to a base type.
|
|
5
|
+
* This enables nominal typing for primitive types.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* type UserId = Branded<string, "UserId">;
|
|
10
|
+
* type PostId = Branded<string, "PostId">;
|
|
11
|
+
*
|
|
12
|
+
* // These are now incompatible at compile time
|
|
13
|
+
* const userId: UserId = brand<string, "UserId">("user_123");
|
|
14
|
+
* const postId: PostId = brand<string, "PostId">("post_456");
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
type Branded<
|
|
18
|
+
T,
|
|
19
|
+
Brand extends string
|
|
20
|
+
> = T & {
|
|
21
|
+
readonly __brand: Brand;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Brands a value with a nominal type marker.
|
|
25
|
+
*
|
|
26
|
+
* @param value - The value to brand
|
|
27
|
+
* @returns The branded value
|
|
28
|
+
* @throws Error - Not implemented yet
|
|
29
|
+
*/
|
|
30
|
+
declare function brand<
|
|
31
|
+
T,
|
|
32
|
+
Brand extends string
|
|
33
|
+
>(value: T): Branded<T, Brand>;
|
|
34
|
+
/**
|
|
35
|
+
* Removes the brand from a branded type, returning the underlying type.
|
|
36
|
+
*
|
|
37
|
+
* @param value - The branded value
|
|
38
|
+
* @returns The unbranded value
|
|
39
|
+
* @throws Error - Not implemented yet
|
|
40
|
+
*/
|
|
41
|
+
declare function unbrand<
|
|
42
|
+
T,
|
|
43
|
+
Brand extends string
|
|
44
|
+
>(value: Branded<T, Brand>): T;
|
|
45
|
+
/**
|
|
46
|
+
* Type helper to extract the underlying type from a branded type.
|
|
47
|
+
*/
|
|
48
|
+
type Unbrand<T> = T extends Branded<infer U, string> ? U : T;
|
|
49
|
+
/**
|
|
50
|
+
* Type helper to extract the brand string from a branded type.
|
|
51
|
+
*/
|
|
52
|
+
type BrandOf<T> = T extends Branded<unknown, infer B> ? B : never;
|
|
53
|
+
/**
|
|
54
|
+
* A positive integer (value > 0, must be a finite integer).
|
|
55
|
+
*/
|
|
56
|
+
type PositiveInt = Branded<number, "PositiveInt">;
|
|
57
|
+
/**
|
|
58
|
+
* A non-empty string (trimmed length > 0).
|
|
59
|
+
*/
|
|
60
|
+
type NonEmptyString = Branded<string, "NonEmptyString">;
|
|
61
|
+
/**
|
|
62
|
+
* An email address (basic format validation: contains @ and . in domain).
|
|
63
|
+
*/
|
|
64
|
+
type Email = Branded<string, "Email">;
|
|
65
|
+
/**
|
|
66
|
+
* A UUID string (UUID v4 format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
|
|
67
|
+
*/
|
|
68
|
+
type UUID = Branded<string, "UUID">;
|
|
69
|
+
/**
|
|
70
|
+
* Creates a PositiveInt from a number, validating that it is a positive integer.
|
|
71
|
+
*
|
|
72
|
+
* @param value - The number to validate
|
|
73
|
+
* @returns Result containing PositiveInt on success, ValidationError on failure
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const result = positiveInt(42);
|
|
78
|
+
* if (Result.isOk(result)) {
|
|
79
|
+
* console.log(result.value); // 42 as PositiveInt
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function positiveInt(value: number): Result<PositiveInt, InstanceType<typeof ValidationError>>;
|
|
84
|
+
/**
|
|
85
|
+
* Creates a NonEmptyString from a string, validating that it has content after trimming.
|
|
86
|
+
*
|
|
87
|
+
* @param value - The string to validate
|
|
88
|
+
* @returns Result containing NonEmptyString on success, ValidationError on failure
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const result = nonEmptyString("hello");
|
|
93
|
+
* if (Result.isOk(result)) {
|
|
94
|
+
* console.log(result.value); // "hello" as NonEmptyString
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
declare function nonEmptyString(value: string): Result<NonEmptyString, InstanceType<typeof ValidationError>>;
|
|
99
|
+
/**
|
|
100
|
+
* Creates an Email from a string, validating basic email format.
|
|
101
|
+
*
|
|
102
|
+
* @param value - The string to validate
|
|
103
|
+
* @returns Result containing Email on success, ValidationError on failure
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const result = email("user@example.com");
|
|
108
|
+
* if (Result.isOk(result)) {
|
|
109
|
+
* console.log(result.value); // "user@example.com" as Email
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
declare function email(value: string): Result<Email, InstanceType<typeof ValidationError>>;
|
|
114
|
+
/**
|
|
115
|
+
* Creates a UUID from a string, validating UUID v4 format.
|
|
116
|
+
*
|
|
117
|
+
* @param value - The string to validate
|
|
118
|
+
* @returns Result containing UUID on success, ValidationError on failure
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const result = uuid("550e8400-e29b-41d4-a716-446655440000");
|
|
123
|
+
* if (Result.isOk(result)) {
|
|
124
|
+
* console.log(result.value); // UUID branded string
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
declare function uuid(value: string): Result<UUID, InstanceType<typeof ValidationError>>;
|
|
129
|
+
export { uuid, unbrand, positiveInt, nonEmptyString, email, brand, Unbrand, UUID, PositiveInt, NonEmptyString, Email, Branded, BrandOf };
|
package/dist/branded.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/types/src/branded.ts
|
|
3
|
+
import { ValidationError } from "@outfitter/contracts";
|
|
4
|
+
import { Result } from "better-result";
|
|
5
|
+
function brand(value) {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
function unbrand(value) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function positiveInt(value) {
|
|
12
|
+
if (!Number.isFinite(value)) {
|
|
13
|
+
return Result.err(new ValidationError({
|
|
14
|
+
message: "Value must be a finite number",
|
|
15
|
+
field: "value"
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
if (!Number.isInteger(value)) {
|
|
19
|
+
return Result.err(new ValidationError({
|
|
20
|
+
message: "Value must be an integer",
|
|
21
|
+
field: "value"
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
if (value <= 0) {
|
|
25
|
+
return Result.err(new ValidationError({
|
|
26
|
+
message: "Value must be greater than 0",
|
|
27
|
+
field: "value"
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
return Result.ok(brand(value));
|
|
31
|
+
}
|
|
32
|
+
function nonEmptyString(value) {
|
|
33
|
+
if (value.trim().length === 0) {
|
|
34
|
+
return Result.err(new ValidationError({
|
|
35
|
+
message: "String must not be empty or whitespace-only",
|
|
36
|
+
field: "value"
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
return Result.ok(brand(value));
|
|
40
|
+
}
|
|
41
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
42
|
+
function email(value) {
|
|
43
|
+
if (!EMAIL_REGEX.test(value)) {
|
|
44
|
+
return Result.err(new ValidationError({
|
|
45
|
+
message: "Invalid email format",
|
|
46
|
+
field: "value"
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
return Result.ok(brand(value));
|
|
50
|
+
}
|
|
51
|
+
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
52
|
+
function uuid(value) {
|
|
53
|
+
if (!UUID_REGEX.test(value)) {
|
|
54
|
+
return Result.err(new ValidationError({
|
|
55
|
+
message: "Invalid UUID v4 format",
|
|
56
|
+
field: "value"
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
return Result.ok(brand(value));
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
uuid,
|
|
63
|
+
unbrand,
|
|
64
|
+
positiveInt,
|
|
65
|
+
nonEmptyString,
|
|
66
|
+
email,
|
|
67
|
+
brand
|
|
68
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection type utilities.
|
|
3
|
+
*
|
|
4
|
+
* @module collections
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* A non-empty array type that guarantees at least one element.
|
|
8
|
+
*/
|
|
9
|
+
type NonEmptyArray<T> = [T, ...T[]];
|
|
10
|
+
/**
|
|
11
|
+
* Type guard for checking if an array is non-empty.
|
|
12
|
+
*
|
|
13
|
+
* @param array - The array to check
|
|
14
|
+
* @returns True if the array has at least one element
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const items: string[] = getItems();
|
|
19
|
+
* if (isNonEmptyArray(items)) {
|
|
20
|
+
* const first = items[0]; // Type is string, not string | undefined
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare function isNonEmptyArray<T>(array: T[]): array is NonEmptyArray<T>;
|
|
25
|
+
/**
|
|
26
|
+
* Creates a non-empty array, throwing if the input is empty.
|
|
27
|
+
*
|
|
28
|
+
* @param array - The array to convert
|
|
29
|
+
* @returns A non-empty array
|
|
30
|
+
* @throws Error - If the array is empty
|
|
31
|
+
* @throws Error - Not implemented yet
|
|
32
|
+
*/
|
|
33
|
+
declare function toNonEmptyArray<T>(array: T[]): NonEmptyArray<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Gets the first element of a non-empty array.
|
|
36
|
+
*
|
|
37
|
+
* @param array - A non-empty array
|
|
38
|
+
* @returns The first element (guaranteed to exist)
|
|
39
|
+
*/
|
|
40
|
+
declare function first<T>(array: NonEmptyArray<T>): T;
|
|
41
|
+
/**
|
|
42
|
+
* Gets the last element of a non-empty array.
|
|
43
|
+
*
|
|
44
|
+
* @param array - A non-empty array
|
|
45
|
+
* @returns The last element (guaranteed to exist)
|
|
46
|
+
*/
|
|
47
|
+
declare function last<T>(array: NonEmptyArray<T>): T;
|
|
48
|
+
/**
|
|
49
|
+
* Groups array elements by a key function.
|
|
50
|
+
*
|
|
51
|
+
* @param items - The items to group
|
|
52
|
+
* @param keyFn - Function to extract the grouping key
|
|
53
|
+
* @returns A map of keys to arrays of items
|
|
54
|
+
* @throws Error - Not implemented yet
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const users = [{ name: "Alice", role: "admin" }, { name: "Bob", role: "user" }];
|
|
59
|
+
* const byRole = groupBy(users, (u) => u.role);
|
|
60
|
+
* // Map { "admin" => [{ name: "Alice", ... }], "user" => [{ name: "Bob", ... }] }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function groupBy<
|
|
64
|
+
T,
|
|
65
|
+
K extends string | number | symbol
|
|
66
|
+
>(items: T[], keyFn: (item: T) => K): Map<K, NonEmptyArray<T>>;
|
|
67
|
+
export { toNonEmptyArray, last, isNonEmptyArray, groupBy, first, NonEmptyArray };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/types/src/collections.ts
|
|
3
|
+
function isNonEmptyArray(array) {
|
|
4
|
+
return array.length > 0;
|
|
5
|
+
}
|
|
6
|
+
function toNonEmptyArray(array) {
|
|
7
|
+
if (array.length === 0) {
|
|
8
|
+
throw new Error("Array is empty");
|
|
9
|
+
}
|
|
10
|
+
return array;
|
|
11
|
+
}
|
|
12
|
+
function first(array) {
|
|
13
|
+
return array[0];
|
|
14
|
+
}
|
|
15
|
+
function last(array) {
|
|
16
|
+
return array.at(-1);
|
|
17
|
+
}
|
|
18
|
+
function groupBy(items, keyFn) {
|
|
19
|
+
const result = new Map;
|
|
20
|
+
for (const item of items) {
|
|
21
|
+
const key = keyFn(item);
|
|
22
|
+
const existing = result.get(key);
|
|
23
|
+
if (existing) {
|
|
24
|
+
existing.push(item);
|
|
25
|
+
} else {
|
|
26
|
+
result.set(key, [item]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
toNonEmptyArray,
|
|
33
|
+
last,
|
|
34
|
+
isNonEmptyArray,
|
|
35
|
+
groupBy,
|
|
36
|
+
first
|
|
37
|
+
};
|
package/dist/deep.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep path type utilities for type-safe dot-notation access.
|
|
3
|
+
*
|
|
4
|
+
* These utilities enable type-safe access to nested object properties using
|
|
5
|
+
* dot-notation string paths, commonly used in configuration systems and
|
|
6
|
+
* state management.
|
|
7
|
+
*
|
|
8
|
+
* @module deep
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Recursively extracts all valid dot-notation paths from an object type.
|
|
12
|
+
*
|
|
13
|
+
* This type generates a union of all possible paths through an object,
|
|
14
|
+
* including intermediate object paths and final leaf paths.
|
|
15
|
+
*
|
|
16
|
+
* Arrays are treated as leaf nodes - the type does not recurse into array
|
|
17
|
+
* indices (e.g., "items.0" is not generated for `{ items: string[] }`).
|
|
18
|
+
*
|
|
19
|
+
* @typeParam T - The object type to extract paths from
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* type Config = { database: { host: string; port: number }; debug: boolean };
|
|
24
|
+
* type Keys = DeepKeys<Config>;
|
|
25
|
+
* // "database" | "database.host" | "database.port" | "debug"
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* // Arrays are leaf nodes
|
|
31
|
+
* type WithArray = { items: string[]; nested: { list: number[] } };
|
|
32
|
+
* type Keys = DeepKeys<WithArray>;
|
|
33
|
+
* // "items" | "nested" | "nested.list"
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
type DeepKeys<T> = T extends object ? T extends readonly unknown[] ? never : { [K in keyof T & string] : NonNullable<T[K]> extends object ? NonNullable<T[K]> extends readonly unknown[] ? K : K | `${K}.${DeepKeys<NonNullable<T[K]>>}` : K }[keyof T & string] : never;
|
|
37
|
+
/**
|
|
38
|
+
* Gets the type at a specific dot-notation path.
|
|
39
|
+
*
|
|
40
|
+
* Given an object type and a path string, this type resolves to the
|
|
41
|
+
* type of the value at that path. Returns `never` for invalid paths.
|
|
42
|
+
*
|
|
43
|
+
* @typeParam T - The object type to access
|
|
44
|
+
* @typeParam P - The dot-notation path string
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* type Config = { database: { host: string; port: number } };
|
|
49
|
+
* type Host = DeepGet<Config, "database.host">; // string
|
|
50
|
+
* type Port = DeepGet<Config, "database.port">; // number
|
|
51
|
+
* type DB = DeepGet<Config, "database">; // { host: string; port: number }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* type Config = { database: { host: string } };
|
|
57
|
+
* type Invalid = DeepGet<Config, "database.invalid">; // never
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
type DeepGet<
|
|
61
|
+
T,
|
|
62
|
+
P extends string
|
|
63
|
+
> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? DeepGet<T[K], Rest> : never : P extends keyof T ? T[P] : never;
|
|
64
|
+
/**
|
|
65
|
+
* Creates a new type with the value at path P replaced with V.
|
|
66
|
+
*
|
|
67
|
+
* This type is useful for typing immutable update functions that modify
|
|
68
|
+
* values at specific paths while preserving the rest of the object structure.
|
|
69
|
+
*
|
|
70
|
+
* Returns `never` if the path is invalid.
|
|
71
|
+
*
|
|
72
|
+
* @typeParam T - The original object type
|
|
73
|
+
* @typeParam P - The dot-notation path to the value to replace
|
|
74
|
+
* @typeParam V - The new type for the value at the path
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* type Config = { database: { host: string; port: number } };
|
|
79
|
+
*
|
|
80
|
+
* // Replace port type from number to string
|
|
81
|
+
* type Updated = DeepSet<Config, "database.port", string>;
|
|
82
|
+
* // { database: { host: string; port: string } }
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* type State = { user: { name: string; age: number } };
|
|
88
|
+
*
|
|
89
|
+
* // Replace entire nested object
|
|
90
|
+
* type Updated = DeepSet<State, "user", { id: number }>;
|
|
91
|
+
* // { user: { id: number } }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
type DeepSet<
|
|
95
|
+
T,
|
|
96
|
+
P extends string,
|
|
97
|
+
V
|
|
98
|
+
> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? { [Key in keyof T] : Key extends K ? DeepSet<T[Key], Rest, V> : T[Key] } : never : P extends keyof T ? { [Key in keyof T] : Key extends P ? V : T[Key] } : never;
|
|
99
|
+
export { DeepSet, DeepKeys, DeepGet };
|
package/dist/deep.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// @bun
|
package/dist/guards.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type guard utilities for runtime type checking.
|
|
3
|
+
*
|
|
4
|
+
* @module guards
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Type guard for checking if a value is defined (not null or undefined).
|
|
8
|
+
*
|
|
9
|
+
* @param value - The value to check
|
|
10
|
+
* @returns True if the value is not null or undefined
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const items = [1, null, 2, undefined, 3];
|
|
15
|
+
* const defined = items.filter(isDefined); // [1, 2, 3]
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
declare function isDefined<T>(value: T | null | undefined): value is T;
|
|
19
|
+
/**
|
|
20
|
+
* Type guard for checking if a value is a non-empty string.
|
|
21
|
+
*
|
|
22
|
+
* @param value - The value to check
|
|
23
|
+
* @returns True if the value is a string with length > 0
|
|
24
|
+
*/
|
|
25
|
+
declare function isNonEmptyString(value: unknown): value is string;
|
|
26
|
+
/**
|
|
27
|
+
* Type guard for checking if a value is a plain object.
|
|
28
|
+
*
|
|
29
|
+
* @param value - The value to check
|
|
30
|
+
* @returns True if the value is a plain object (not null, array, or other object types)
|
|
31
|
+
*/
|
|
32
|
+
declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* Type guard for checking if a value has a specific property.
|
|
35
|
+
*
|
|
36
|
+
* @param value - The value to check
|
|
37
|
+
* @param key - The property key to look for
|
|
38
|
+
* @returns True if the value is an object with the specified property
|
|
39
|
+
*/
|
|
40
|
+
declare function hasProperty<K extends string>(value: unknown, key: K): value is Record<K, unknown>;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a type guard function for a specific type using a predicate.
|
|
43
|
+
*
|
|
44
|
+
* @param predicate - A function that returns true if the value matches the type
|
|
45
|
+
* @returns A type guard function
|
|
46
|
+
* @throws Error - Not implemented yet
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* interface User { name: string; age: number; }
|
|
51
|
+
* const isUser = createGuard<User>(
|
|
52
|
+
* (v) => hasProperty(v, "name") && hasProperty(v, "age")
|
|
53
|
+
* );
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare function createGuard<T>(predicate: (value: unknown) => boolean): (value: unknown) => value is T;
|
|
57
|
+
/**
|
|
58
|
+
* Asserts that a value matches a type guard, throwing if it doesn't.
|
|
59
|
+
*
|
|
60
|
+
* @param value - The value to assert
|
|
61
|
+
* @param guard - The type guard to use
|
|
62
|
+
* @param message - Optional error message
|
|
63
|
+
* @throws Error - If the value doesn't match the guard
|
|
64
|
+
* @throws Error - Not implemented yet
|
|
65
|
+
*/
|
|
66
|
+
declare function assertType<T>(value: unknown, guard: (value: unknown) => value is T, message?: string): asserts value is T;
|
|
67
|
+
export { isPlainObject, isNonEmptyString, isDefined, hasProperty, createGuard, assertType };
|
package/dist/guards.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/types/src/guards.ts
|
|
3
|
+
function isDefined(value) {
|
|
4
|
+
return value !== null && value !== undefined;
|
|
5
|
+
}
|
|
6
|
+
function isNonEmptyString(value) {
|
|
7
|
+
return typeof value === "string" && value.length > 0;
|
|
8
|
+
}
|
|
9
|
+
function isPlainObject(value) {
|
|
10
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
11
|
+
}
|
|
12
|
+
function hasProperty(value, key) {
|
|
13
|
+
return isPlainObject(value) && key in value;
|
|
14
|
+
}
|
|
15
|
+
function createGuard(predicate) {
|
|
16
|
+
return (value) => predicate(value);
|
|
17
|
+
}
|
|
18
|
+
function assertType(value, guard, message) {
|
|
19
|
+
if (!guard(value)) {
|
|
20
|
+
throw new Error(message ?? "Type assertion failed");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
isPlainObject,
|
|
25
|
+
isNonEmptyString,
|
|
26
|
+
isDefined,
|
|
27
|
+
hasProperty,
|
|
28
|
+
createGuard,
|
|
29
|
+
assertType
|
|
30
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a deterministic short ID from input using Bun.hash().
|
|
3
|
+
* Unlike shortId() which generates random IDs, hashId() always produces
|
|
4
|
+
* the same output for the same input.
|
|
5
|
+
*
|
|
6
|
+
* @param input - String to hash
|
|
7
|
+
* @param length - Output length (default: 5)
|
|
8
|
+
* @returns URL-safe alphanumeric hash (base62)
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* hashId("user-123") // "a7b2c" (always same for same input)
|
|
13
|
+
* hashId("user-123", 8) // "a7b2c3d1"
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
declare function hashId(input: string, length?: number): string;
|
|
17
|
+
export { hashId };
|
package/dist/hash-id.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/types/src/hash-id.ts
|
|
3
|
+
var BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
4
|
+
function toBase62(num) {
|
|
5
|
+
if (num === 0n)
|
|
6
|
+
return "0";
|
|
7
|
+
const base = BigInt(BASE62.length);
|
|
8
|
+
let result = "";
|
|
9
|
+
let value = num;
|
|
10
|
+
while (value > 0n) {
|
|
11
|
+
const remainder = Number(value % base);
|
|
12
|
+
result = BASE62[remainder] + result;
|
|
13
|
+
value /= base;
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
function hashId(input, length = 5) {
|
|
18
|
+
const hash = Bun.hash(input);
|
|
19
|
+
const base62 = toBase62(BigInt(hash));
|
|
20
|
+
const padded = base62.padStart(length, "0");
|
|
21
|
+
return padded.slice(0, length);
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
hashId
|
|
25
|
+
};
|