@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.
@@ -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 };
@@ -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
+ };