@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/dist/index.d.ts
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
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
|
+
/**
|
|
100
|
+
* General type utilities for TypeScript.
|
|
101
|
+
*
|
|
102
|
+
* @module utilities
|
|
103
|
+
*/
|
|
104
|
+
/**
|
|
105
|
+
* Makes specific properties of a type required.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* interface Config { host?: string; port?: number; }
|
|
110
|
+
* type RequiredHost = RequiredKeys<Config, "host">;
|
|
111
|
+
* // { host: string; port?: number; }
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
type RequiredKeys<
|
|
115
|
+
T,
|
|
116
|
+
K extends keyof T
|
|
117
|
+
> = T & Required<Pick<T, K>>;
|
|
118
|
+
/**
|
|
119
|
+
* Makes specific properties of a type optional.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* interface User { id: string; name: string; email: string; }
|
|
124
|
+
* type PartialUser = OptionalKeys<User, "email">;
|
|
125
|
+
* // { id: string; name: string; email?: string; }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
type OptionalKeys<
|
|
129
|
+
T,
|
|
130
|
+
K extends keyof T
|
|
131
|
+
> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
132
|
+
/**
|
|
133
|
+
* Deeply makes all properties readonly.
|
|
134
|
+
*/
|
|
135
|
+
type DeepReadonly<T> = { readonly [P in keyof T] : T[P] extends object ? DeepReadonly<T[P]> : T[P] };
|
|
136
|
+
/**
|
|
137
|
+
* Deeply makes all properties partial (optional).
|
|
138
|
+
*/
|
|
139
|
+
type DeepPartial<T> = { [P in keyof T]? : T[P] extends object ? DeepPartial<T[P]> : T[P] };
|
|
140
|
+
/**
|
|
141
|
+
* Creates a type that requires at least one of the specified keys.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* type Props = AtLeastOne<{ a?: string; b?: number; c?: boolean }, "a" | "b">;
|
|
146
|
+
* // Must have at least 'a' or 'b'
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
type AtLeastOne<
|
|
150
|
+
T,
|
|
151
|
+
Keys extends keyof T = keyof T
|
|
152
|
+
> = Omit<T, Keys> & { [K in Keys]-? : Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys];
|
|
153
|
+
/**
|
|
154
|
+
* Creates a type that requires exactly one of the specified keys.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* type Auth = ExactlyOne<{ token?: string; apiKey?: string }, "token" | "apiKey">;
|
|
159
|
+
* // Must have exactly one of 'token' or 'apiKey', not both
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
type ExactlyOne<
|
|
163
|
+
T,
|
|
164
|
+
Keys extends keyof T = keyof T
|
|
165
|
+
> = Omit<T, Keys> & { [K in Keys]-? : Required<Pick<T, K>> & Partial<Record<Exclude<Keys, K>, never>> }[Keys];
|
|
166
|
+
/**
|
|
167
|
+
* Extracts the element type from an array type.
|
|
168
|
+
*/
|
|
169
|
+
type ElementOf<T> = T extends readonly (infer E)[] ? E : never;
|
|
170
|
+
/**
|
|
171
|
+
* Creates a union type from object values.
|
|
172
|
+
*/
|
|
173
|
+
type ValueOf<T> = T[keyof T];
|
|
174
|
+
/**
|
|
175
|
+
* Makes a type's properties mutable (removes readonly).
|
|
176
|
+
*/
|
|
177
|
+
type Mutable<T> = { -readonly [P in keyof T] : T[P] };
|
|
178
|
+
/**
|
|
179
|
+
* Asserts at compile time that a type is never (exhaustiveness check).
|
|
180
|
+
*
|
|
181
|
+
* @param value - The value that should be never
|
|
182
|
+
* @throws Error - Always throws if reached at runtime
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* type Status = "pending" | "done";
|
|
187
|
+
* function handle(status: Status) {
|
|
188
|
+
* switch (status) {
|
|
189
|
+
* case "pending": return "...";
|
|
190
|
+
* case "done": return "ok";
|
|
191
|
+
* default: return assertNever(status);
|
|
192
|
+
* }
|
|
193
|
+
* }
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
declare function assertNever(value: never): never;
|
|
197
|
+
/**
|
|
198
|
+
* Collection type utilities.
|
|
199
|
+
*
|
|
200
|
+
* @module collections
|
|
201
|
+
*/
|
|
202
|
+
/**
|
|
203
|
+
* A non-empty array type that guarantees at least one element.
|
|
204
|
+
*/
|
|
205
|
+
type NonEmptyArray<T> = [T, ...T[]];
|
|
206
|
+
/**
|
|
207
|
+
* Type guard for checking if an array is non-empty.
|
|
208
|
+
*
|
|
209
|
+
* @param array - The array to check
|
|
210
|
+
* @returns True if the array has at least one element
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const items: string[] = getItems();
|
|
215
|
+
* if (isNonEmptyArray(items)) {
|
|
216
|
+
* const first = items[0]; // Type is string, not string | undefined
|
|
217
|
+
* }
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
declare function isNonEmptyArray<T>(array: T[]): array is NonEmptyArray<T>;
|
|
221
|
+
/**
|
|
222
|
+
* Creates a non-empty array, throwing if the input is empty.
|
|
223
|
+
*
|
|
224
|
+
* @param array - The array to convert
|
|
225
|
+
* @returns A non-empty array
|
|
226
|
+
* @throws Error - If the array is empty
|
|
227
|
+
* @throws Error - Not implemented yet
|
|
228
|
+
*/
|
|
229
|
+
declare function toNonEmptyArray<T>(array: T[]): NonEmptyArray<T>;
|
|
230
|
+
/**
|
|
231
|
+
* Gets the first element of a non-empty array.
|
|
232
|
+
*
|
|
233
|
+
* @param array - A non-empty array
|
|
234
|
+
* @returns The first element (guaranteed to exist)
|
|
235
|
+
*/
|
|
236
|
+
declare function first<T>(array: NonEmptyArray<T>): T;
|
|
237
|
+
/**
|
|
238
|
+
* Gets the last element of a non-empty array.
|
|
239
|
+
*
|
|
240
|
+
* @param array - A non-empty array
|
|
241
|
+
* @returns The last element (guaranteed to exist)
|
|
242
|
+
*/
|
|
243
|
+
declare function last<T>(array: NonEmptyArray<T>): T;
|
|
244
|
+
/**
|
|
245
|
+
* Groups array elements by a key function.
|
|
246
|
+
*
|
|
247
|
+
* @param items - The items to group
|
|
248
|
+
* @param keyFn - Function to extract the grouping key
|
|
249
|
+
* @returns A map of keys to arrays of items
|
|
250
|
+
* @throws Error - Not implemented yet
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```typescript
|
|
254
|
+
* const users = [{ name: "Alice", role: "admin" }, { name: "Bob", role: "user" }];
|
|
255
|
+
* const byRole = groupBy(users, (u) => u.role);
|
|
256
|
+
* // Map { "admin" => [{ name: "Alice", ... }], "user" => [{ name: "Bob", ... }] }
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
declare function groupBy<
|
|
260
|
+
T,
|
|
261
|
+
K extends string | number | symbol
|
|
262
|
+
>(items: T[], keyFn: (item: T) => K): Map<K, NonEmptyArray<T>>;
|
|
263
|
+
import { ValidationError } from "@outfitter/contracts";
|
|
264
|
+
import { Result } from "better-result";
|
|
265
|
+
/**
|
|
266
|
+
* Creates a branded type by adding a unique brand to a base type.
|
|
267
|
+
* This enables nominal typing for primitive types.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* type UserId = Branded<string, "UserId">;
|
|
272
|
+
* type PostId = Branded<string, "PostId">;
|
|
273
|
+
*
|
|
274
|
+
* // These are now incompatible at compile time
|
|
275
|
+
* const userId: UserId = brand<string, "UserId">("user_123");
|
|
276
|
+
* const postId: PostId = brand<string, "PostId">("post_456");
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
type Branded<
|
|
280
|
+
T,
|
|
281
|
+
Brand extends string
|
|
282
|
+
> = T & {
|
|
283
|
+
readonly __brand: Brand;
|
|
284
|
+
};
|
|
285
|
+
/**
|
|
286
|
+
* Brands a value with a nominal type marker.
|
|
287
|
+
*
|
|
288
|
+
* @param value - The value to brand
|
|
289
|
+
* @returns The branded value
|
|
290
|
+
* @throws Error - Not implemented yet
|
|
291
|
+
*/
|
|
292
|
+
declare function brand<
|
|
293
|
+
T,
|
|
294
|
+
Brand extends string
|
|
295
|
+
>(value: T): Branded<T, Brand>;
|
|
296
|
+
/**
|
|
297
|
+
* Removes the brand from a branded type, returning the underlying type.
|
|
298
|
+
*
|
|
299
|
+
* @param value - The branded value
|
|
300
|
+
* @returns The unbranded value
|
|
301
|
+
* @throws Error - Not implemented yet
|
|
302
|
+
*/
|
|
303
|
+
declare function unbrand<
|
|
304
|
+
T,
|
|
305
|
+
Brand extends string
|
|
306
|
+
>(value: Branded<T, Brand>): T;
|
|
307
|
+
/**
|
|
308
|
+
* Type helper to extract the underlying type from a branded type.
|
|
309
|
+
*/
|
|
310
|
+
type Unbrand<T> = T extends Branded<infer U, string> ? U : T;
|
|
311
|
+
/**
|
|
312
|
+
* Type helper to extract the brand string from a branded type.
|
|
313
|
+
*/
|
|
314
|
+
type BrandOf<T> = T extends Branded<unknown, infer B> ? B : never;
|
|
315
|
+
/**
|
|
316
|
+
* A positive integer (value > 0, must be a finite integer).
|
|
317
|
+
*/
|
|
318
|
+
type PositiveInt = Branded<number, "PositiveInt">;
|
|
319
|
+
/**
|
|
320
|
+
* A non-empty string (trimmed length > 0).
|
|
321
|
+
*/
|
|
322
|
+
type NonEmptyString = Branded<string, "NonEmptyString">;
|
|
323
|
+
/**
|
|
324
|
+
* An email address (basic format validation: contains @ and . in domain).
|
|
325
|
+
*/
|
|
326
|
+
type Email = Branded<string, "Email">;
|
|
327
|
+
/**
|
|
328
|
+
* A UUID string (UUID v4 format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
|
|
329
|
+
*/
|
|
330
|
+
type UUID = Branded<string, "UUID">;
|
|
331
|
+
/**
|
|
332
|
+
* Creates a PositiveInt from a number, validating that it is a positive integer.
|
|
333
|
+
*
|
|
334
|
+
* @param value - The number to validate
|
|
335
|
+
* @returns Result containing PositiveInt on success, ValidationError on failure
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```typescript
|
|
339
|
+
* const result = positiveInt(42);
|
|
340
|
+
* if (Result.isOk(result)) {
|
|
341
|
+
* console.log(result.value); // 42 as PositiveInt
|
|
342
|
+
* }
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
declare function positiveInt(value: number): Result<PositiveInt, InstanceType<typeof ValidationError>>;
|
|
346
|
+
/**
|
|
347
|
+
* Creates a NonEmptyString from a string, validating that it has content after trimming.
|
|
348
|
+
*
|
|
349
|
+
* @param value - The string to validate
|
|
350
|
+
* @returns Result containing NonEmptyString on success, ValidationError on failure
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* const result = nonEmptyString("hello");
|
|
355
|
+
* if (Result.isOk(result)) {
|
|
356
|
+
* console.log(result.value); // "hello" as NonEmptyString
|
|
357
|
+
* }
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
declare function nonEmptyString(value: string): Result<NonEmptyString, InstanceType<typeof ValidationError>>;
|
|
361
|
+
/**
|
|
362
|
+
* Creates an Email from a string, validating basic email format.
|
|
363
|
+
*
|
|
364
|
+
* @param value - The string to validate
|
|
365
|
+
* @returns Result containing Email on success, ValidationError on failure
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```typescript
|
|
369
|
+
* const result = email("user@example.com");
|
|
370
|
+
* if (Result.isOk(result)) {
|
|
371
|
+
* console.log(result.value); // "user@example.com" as Email
|
|
372
|
+
* }
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
declare function email(value: string): Result<Email, InstanceType<typeof ValidationError>>;
|
|
376
|
+
/**
|
|
377
|
+
* Creates a UUID from a string, validating UUID v4 format.
|
|
378
|
+
*
|
|
379
|
+
* @param value - The string to validate
|
|
380
|
+
* @returns Result containing UUID on success, ValidationError on failure
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ```typescript
|
|
384
|
+
* const result = uuid("550e8400-e29b-41d4-a716-446655440000");
|
|
385
|
+
* if (Result.isOk(result)) {
|
|
386
|
+
* console.log(result.value); // UUID branded string
|
|
387
|
+
* }
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
declare function uuid(value: string): Result<UUID, InstanceType<typeof ValidationError>>;
|
|
391
|
+
/**
|
|
392
|
+
* A short identifier string, typically 7-12 characters.
|
|
393
|
+
* Used for human-readable references (e.g., commit SHAs, session IDs).
|
|
394
|
+
*/
|
|
395
|
+
type ShortId = Branded<string, "ShortId">;
|
|
396
|
+
/**
|
|
397
|
+
* Options for short ID generation.
|
|
398
|
+
*/
|
|
399
|
+
interface ShortIdOptions {
|
|
400
|
+
/** Length of the generated ID. Default: 8 */
|
|
401
|
+
length?: number;
|
|
402
|
+
/** Character set to use. Default: alphanumeric */
|
|
403
|
+
charset?: "alphanumeric" | "hex" | "base62";
|
|
404
|
+
/** Optional prefix to prepend */
|
|
405
|
+
prefix?: string;
|
|
406
|
+
}
|
|
407
|
+
declare function shortId(options?: ShortIdOptions): ShortId;
|
|
408
|
+
/**
|
|
409
|
+
* Validates that a string matches the short ID format.
|
|
410
|
+
*
|
|
411
|
+
* @param value - The string to validate
|
|
412
|
+
* @param options - Validation options (must match generation options)
|
|
413
|
+
* @returns True if the string is a valid short ID
|
|
414
|
+
* @throws Error - Not implemented yet
|
|
415
|
+
*/
|
|
416
|
+
declare function isShortId(value: string, options?: ShortIdOptions): value is ShortId;
|
|
417
|
+
/**
|
|
418
|
+
* Generates a deterministic short ID from input using Bun.hash().
|
|
419
|
+
* Unlike shortId() which generates random IDs, hashId() always produces
|
|
420
|
+
* the same output for the same input.
|
|
421
|
+
*
|
|
422
|
+
* @param input - String to hash
|
|
423
|
+
* @param length - Output length (default: 5)
|
|
424
|
+
* @returns URL-safe alphanumeric hash (base62)
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* ```typescript
|
|
428
|
+
* hashId("user-123") // "a7b2c" (always same for same input)
|
|
429
|
+
* hashId("user-123", 8) // "a7b2c3d1"
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
declare function hashId(input: string, length?: number): string;
|
|
433
|
+
/**
|
|
434
|
+
* Type guard utilities for runtime type checking.
|
|
435
|
+
*
|
|
436
|
+
* @module guards
|
|
437
|
+
*/
|
|
438
|
+
/**
|
|
439
|
+
* Type guard for checking if a value is defined (not null or undefined).
|
|
440
|
+
*
|
|
441
|
+
* @param value - The value to check
|
|
442
|
+
* @returns True if the value is not null or undefined
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* ```typescript
|
|
446
|
+
* const items = [1, null, 2, undefined, 3];
|
|
447
|
+
* const defined = items.filter(isDefined); // [1, 2, 3]
|
|
448
|
+
* ```
|
|
449
|
+
*/
|
|
450
|
+
declare function isDefined<T>(value: T | null | undefined): value is T;
|
|
451
|
+
/**
|
|
452
|
+
* Type guard for checking if a value is a non-empty string.
|
|
453
|
+
*
|
|
454
|
+
* @param value - The value to check
|
|
455
|
+
* @returns True if the value is a string with length > 0
|
|
456
|
+
*/
|
|
457
|
+
declare function isNonEmptyString(value: unknown): value is string;
|
|
458
|
+
/**
|
|
459
|
+
* Type guard for checking if a value is a plain object.
|
|
460
|
+
*
|
|
461
|
+
* @param value - The value to check
|
|
462
|
+
* @returns True if the value is a plain object (not null, array, or other object types)
|
|
463
|
+
*/
|
|
464
|
+
declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
465
|
+
/**
|
|
466
|
+
* Type guard for checking if a value has a specific property.
|
|
467
|
+
*
|
|
468
|
+
* @param value - The value to check
|
|
469
|
+
* @param key - The property key to look for
|
|
470
|
+
* @returns True if the value is an object with the specified property
|
|
471
|
+
*/
|
|
472
|
+
declare function hasProperty<K extends string>(value: unknown, key: K): value is Record<K, unknown>;
|
|
473
|
+
/**
|
|
474
|
+
* Creates a type guard function for a specific type using a predicate.
|
|
475
|
+
*
|
|
476
|
+
* @param predicate - A function that returns true if the value matches the type
|
|
477
|
+
* @returns A type guard function
|
|
478
|
+
* @throws Error - Not implemented yet
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```typescript
|
|
482
|
+
* interface User { name: string; age: number; }
|
|
483
|
+
* const isUser = createGuard<User>(
|
|
484
|
+
* (v) => hasProperty(v, "name") && hasProperty(v, "age")
|
|
485
|
+
* );
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
488
|
+
declare function createGuard<T>(predicate: (value: unknown) => boolean): (value: unknown) => value is T;
|
|
489
|
+
/**
|
|
490
|
+
* Asserts that a value matches a type guard, throwing if it doesn't.
|
|
491
|
+
*
|
|
492
|
+
* @param value - The value to assert
|
|
493
|
+
* @param guard - The type guard to use
|
|
494
|
+
* @param message - Optional error message
|
|
495
|
+
* @throws Error - If the value doesn't match the guard
|
|
496
|
+
* @throws Error - Not implemented yet
|
|
497
|
+
*/
|
|
498
|
+
declare function assertType<T>(value: unknown, guard: (value: unknown) => value is T, message?: string): asserts value is T;
|
|
499
|
+
export { uuid, unbrand, toNonEmptyArray, shortId, positiveInt, nonEmptyString, last, isShortId, isPlainObject, isNonEmptyString, isNonEmptyArray, isDefined, hashId, hasProperty, groupBy, first, email, createGuard, brand, assertType, assertNever, ValueOf, Unbrand, UUID, ShortIdOptions, ShortId, RequiredKeys, PositiveInt, OptionalKeys, NonEmptyString, NonEmptyArray, Mutable, ExactlyOne, Email, ElementOf, DeepSet, DeepReadonly, DeepPartial, DeepKeys, DeepGet, Branded, BrandOf, AtLeastOne };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
assertNever
|
|
4
|
+
} from "./utilities.js";
|
|
5
|
+
import {
|
|
6
|
+
first,
|
|
7
|
+
groupBy,
|
|
8
|
+
isNonEmptyArray,
|
|
9
|
+
last,
|
|
10
|
+
toNonEmptyArray
|
|
11
|
+
} from "./collections.js";
|
|
12
|
+
import {
|
|
13
|
+
isShortId,
|
|
14
|
+
shortId
|
|
15
|
+
} from "./short-id.js";
|
|
16
|
+
import {
|
|
17
|
+
hashId
|
|
18
|
+
} from "./hash-id.js";
|
|
19
|
+
import {
|
|
20
|
+
brand,
|
|
21
|
+
email,
|
|
22
|
+
nonEmptyString,
|
|
23
|
+
positiveInt,
|
|
24
|
+
unbrand,
|
|
25
|
+
uuid
|
|
26
|
+
} from "./branded.js";
|
|
27
|
+
import {
|
|
28
|
+
assertType,
|
|
29
|
+
createGuard,
|
|
30
|
+
hasProperty,
|
|
31
|
+
isDefined,
|
|
32
|
+
isNonEmptyString,
|
|
33
|
+
isPlainObject
|
|
34
|
+
} from "./guards.js";
|
|
35
|
+
export {
|
|
36
|
+
uuid,
|
|
37
|
+
unbrand,
|
|
38
|
+
toNonEmptyArray,
|
|
39
|
+
shortId,
|
|
40
|
+
positiveInt,
|
|
41
|
+
nonEmptyString,
|
|
42
|
+
last,
|
|
43
|
+
isShortId,
|
|
44
|
+
isPlainObject,
|
|
45
|
+
isNonEmptyString,
|
|
46
|
+
isNonEmptyArray,
|
|
47
|
+
isDefined,
|
|
48
|
+
hashId,
|
|
49
|
+
hasProperty,
|
|
50
|
+
groupBy,
|
|
51
|
+
first,
|
|
52
|
+
email,
|
|
53
|
+
createGuard,
|
|
54
|
+
brand,
|
|
55
|
+
assertType,
|
|
56
|
+
assertNever
|
|
57
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a branded type by adding a unique brand to a base type.
|
|
3
|
+
* This enables nominal typing for primitive types.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* type UserId = Branded<string, "UserId">;
|
|
8
|
+
* type PostId = Branded<string, "PostId">;
|
|
9
|
+
*
|
|
10
|
+
* // These are now incompatible at compile time
|
|
11
|
+
* const userId: UserId = brand<string, "UserId">("user_123");
|
|
12
|
+
* const postId: PostId = brand<string, "PostId">("post_456");
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
type Branded<
|
|
16
|
+
T,
|
|
17
|
+
Brand extends string
|
|
18
|
+
> = T & {
|
|
19
|
+
readonly __brand: Brand;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* A short identifier string, typically 7-12 characters.
|
|
23
|
+
* Used for human-readable references (e.g., commit SHAs, session IDs).
|
|
24
|
+
*/
|
|
25
|
+
type ShortId = Branded<string, "ShortId">;
|
|
26
|
+
/**
|
|
27
|
+
* Options for short ID generation.
|
|
28
|
+
*/
|
|
29
|
+
interface ShortIdOptions {
|
|
30
|
+
/** Length of the generated ID. Default: 8 */
|
|
31
|
+
length?: number;
|
|
32
|
+
/** Character set to use. Default: alphanumeric */
|
|
33
|
+
charset?: "alphanumeric" | "hex" | "base62";
|
|
34
|
+
/** Optional prefix to prepend */
|
|
35
|
+
prefix?: string;
|
|
36
|
+
}
|
|
37
|
+
declare function shortId(options?: ShortIdOptions): ShortId;
|
|
38
|
+
/**
|
|
39
|
+
* Validates that a string matches the short ID format.
|
|
40
|
+
*
|
|
41
|
+
* @param value - The string to validate
|
|
42
|
+
* @param options - Validation options (must match generation options)
|
|
43
|
+
* @returns True if the string is a valid short ID
|
|
44
|
+
* @throws Error - Not implemented yet
|
|
45
|
+
*/
|
|
46
|
+
declare function isShortId(value: string, options?: ShortIdOptions): value is ShortId;
|
|
47
|
+
export { shortId, isShortId, ShortIdOptions, ShortId };
|
package/dist/short-id.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/types/src/short-id.ts
|
|
3
|
+
var CHARSETS = {
|
|
4
|
+
alphanumeric: "0123456789abcdefghijklmnopqrstuvwxyz",
|
|
5
|
+
hex: "0123456789abcdef",
|
|
6
|
+
base62: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
7
|
+
};
|
|
8
|
+
function shortId(options) {
|
|
9
|
+
const length = options?.length ?? 8;
|
|
10
|
+
const charset = CHARSETS[options?.charset ?? "alphanumeric"];
|
|
11
|
+
const prefix = options?.prefix ?? "";
|
|
12
|
+
const bytes = new Uint8Array(length);
|
|
13
|
+
crypto.getRandomValues(bytes);
|
|
14
|
+
let result = "";
|
|
15
|
+
for (const byte of bytes) {
|
|
16
|
+
result += charset[byte % charset.length];
|
|
17
|
+
}
|
|
18
|
+
return prefix + result;
|
|
19
|
+
}
|
|
20
|
+
function isShortId(value, options) {
|
|
21
|
+
const length = options?.length ?? 8;
|
|
22
|
+
const charset = CHARSETS[options?.charset ?? "alphanumeric"];
|
|
23
|
+
const prefix = options?.prefix ?? "";
|
|
24
|
+
if (prefix && !value.startsWith(prefix)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const idPart = prefix ? value.slice(prefix.length) : value;
|
|
28
|
+
if (idPart.length !== length) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
for (const char of idPart) {
|
|
32
|
+
if (!charset.includes(char)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
shortId,
|
|
40
|
+
isShortId
|
|
41
|
+
};
|