Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 71e48d9

Browse files
refactor: improved type safety
1 parent 04ad189 commit 71e48d9

File tree

1 file changed

+40
-42
lines changed

1 file changed

+40
-42
lines changed

‎src/index.ts

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
11
export type QueryKeyPart = string | number | boolean | object | undefined;
2-
export type QueryKey = QueryKeyPart[];
3-
export type QueryKeyBuilder<Args extends unknown[] = []> = (
4-
...args: Args
5-
) => QueryKey;
6-
export type QueryKeyRegistry = Record<string, QueryKeyBuilder<any>>;
2+
export type QueryKey = readonly QueryKeyPart[];
3+
4+
export type QueryKeyBuilder<
5+
Args extends unknown[] = [],
6+
Return extends QueryKey = QueryKey
7+
> = (...args: Args) => Return;
8+
9+
export type QueryKeyRegistry = Record<string, QueryKeyBuilder<any, any>>;
10+
11+
export function createQueryKey<T extends readonly QueryKeyPart[]>(
12+
...parts: T
13+
): T {
14+
return parts;
15+
}
16+
17+
type ValidFunction<T> = T extends (...args: any[]) => readonly QueryKeyPart[]
18+
? T
19+
: never;
20+
21+
type ValidateKeyMap<T> = {
22+
[K in keyof T]: T[K] extends (...args: any[]) => any
23+
? ValidFunction<T[K]>
24+
: T[K] extends object
25+
? ValidateKeyMap<T[K]>
26+
: never;
27+
};
728

829
export class QueryKeyManager {
930
private static registry: QueryKeyRegistry = {};
1031
private static keyNames: Set<string> = new Set();
1132

12-
/**
13-
* Creates a new query key with collision protection
14-
*
15-
* @param name Unique key identifier (dot notation recommended for namespacing)
16-
* @param keyMap Object containing query key builder functions
17-
* @returns Type-safe query key builder functions with proper inference
18-
*
19-
* @example
20-
* const userKeys = QueryKeyManager.create('user', {
21-
* profile: (userId: string) => ['user', 'profile', userId] as const,
22-
* settings: (userId: string, section?: string) => ['user', 'settings', userId, section] as const
23-
* } as const);
24-
*
25-
* // Types are properly inferred:
26-
* // userKeys.profile: (userId: string) => QueryKey
27-
* // userKeys.settings: (userId: string, section?: string) => QueryKey
28-
*/
2933
static create<
30-
const KeyMap extends Record<string, (...args: any[]) => QueryKey>
31-
>(name: string, keyMap: KeyMap): KeyMap {
32-
// Runtime duplicate check
34+
const KeyMap extends Record<string, QueryKeyBuilder<any, any> | object>
35+
>(name: string, keyMap: KeyMap & ValidateKeyMap<KeyMap>): KeyMap {
3336
if (this.keyNames.has(name)) {
3437
if (process.env.NODE_ENV !== "production") {
3538
throw new Error(`QueryKeyManager: Key name "${name}" already exists`);
@@ -39,33 +42,31 @@ export class QueryKeyManager {
3942

4043
this.keyNames.add(name);
4144

42-
// Register each key builder (type-erased for storage)
43-
for (const [key, builder] of Object.entries(keyMap)) {
44-
const fullKey = `${name}.${key}`;
45-
this.registry[fullKey] = builder as QueryKeyBuilder<any>;
46-
}
45+
const register = (prefix: string, node: Record<string, any>) => {
46+
for (const [key, value] of Object.entries(node)) {
47+
const fullKey = `${prefix}.${key}`;
48+
if (typeof value === "function") {
49+
this.registry[fullKey] = value as QueryKeyBuilder<any, any>;
50+
} else if (value && typeof value === "object") {
51+
register(fullKey, value);
52+
}
53+
}
54+
};
55+
56+
register(name, keyMap);
4757

4858
return keyMap;
4959
}
5060

51-
/**
52-
* Retrieves all registered query key builders
53-
*
54-
* @returns Readonly registry object with all query key builders
55-
*/
5661
static getQueryKeys(): Readonly<QueryKeyRegistry> {
5762
return Object.freeze({ ...this.registry });
5863
}
5964

60-
/**
61-
* Clears all registered keys (primarily for testing)
62-
*/
6365
static clearRegistry() {
6466
this.registry = {};
6567
this.keyNames.clear();
6668
}
6769

68-
// Private constructor to prevent instantiation
6970
private constructor() {}
7071
}
7172

@@ -76,11 +77,8 @@ export function migrateLegacyKeys<T extends (...args: any[]) => QueryKey>(
7677
if (process.env.NODE_ENV !== "production") {
7778
console.warn(`Migrating from legacy key: ${legacyKey}`);
7879
}
79-
80-
// Runtime check to prevent duplicate registration
8180
if (QueryKeyManager.getQueryKeys()[legacyKey]) {
8281
throw new Error(`Legacy key ${legacyKey} conflicts with new keys`);
8382
}
84-
8583
return newBuilder;
8684
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /