awesome-key-factory 1.0.0
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/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +77 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +420 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bhaskar Sharma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# awesome-key-factory
|
|
2
|
+
|
|
3
|
+
A type-safe key factory for managing react-query keys with full TypeScript support.
|
|
4
|
+
|
|
5
|
+
## 📚 Documentation
|
|
6
|
+
|
|
7
|
+
- **[Documentation Site](https://bhaskar20.github.io/awesome-key-factory/)** - Beautiful, interactive documentation built with VitePress (recommended)
|
|
8
|
+
- **[Full Documentation](./DOCUMENTATION.md)** - Complete guide with all features and examples
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
yarn add awesome-key-factory
|
|
14
|
+
# or
|
|
15
|
+
npm install awesome-key-factory
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Basic Example
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { createKeyFactory } from 'awesome-key-factory';
|
|
24
|
+
|
|
25
|
+
const keys = createKeyFactory('baseKey', {
|
|
26
|
+
a: (params: {}) => ['key1', 'key2'],
|
|
27
|
+
b: {
|
|
28
|
+
c: ['key3', 'key4'],
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Access any level
|
|
33
|
+
keys.a({}) // => ['baseKey', 'a', 'key1', 'key2']
|
|
34
|
+
keys.b.c() // => ['baseKey', 'b', 'c', 'key3', 'key4']
|
|
35
|
+
keys.b() // => ['baseKey', 'b'] (any level can be called as a function)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### With Parameters
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const queryKeys = createKeyFactory('app', {
|
|
42
|
+
users: {
|
|
43
|
+
all: () => [],
|
|
44
|
+
detail: (params: { id: string }) => [params.id],
|
|
45
|
+
posts: (params: { userId: string }) => [params.userId, 'posts'],
|
|
46
|
+
},
|
|
47
|
+
posts: {
|
|
48
|
+
all: () => [],
|
|
49
|
+
detail: (params: { id: string }) => [params.id],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Usage in react-query
|
|
54
|
+
useQuery({
|
|
55
|
+
queryKey: queryKeys.users.detail({ id: '123' }),
|
|
56
|
+
queryFn: () => fetchUser('123'),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
useQuery({
|
|
60
|
+
queryKey: queryKeys.users.posts({ userId: '456' }),
|
|
61
|
+
queryFn: () => fetchUserPosts('456'),
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Array Shorthand
|
|
66
|
+
|
|
67
|
+
You can use arrays as a shorthand for functions that return static keys:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const keys = createKeyFactory('shop', {
|
|
71
|
+
products: {
|
|
72
|
+
list: ['all'], // equivalent to () => ['all']
|
|
73
|
+
byId: (params: { id: string }) => [params.id],
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
keys.products.list() // => ['shop', 'products', 'list', 'all']
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Deep Nesting
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const keys = createKeyFactory('api', {
|
|
84
|
+
v1: {
|
|
85
|
+
users: {
|
|
86
|
+
posts: {
|
|
87
|
+
comments: (params: { postId: string }) => [params.postId],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
keys.v1.users.posts.comments({ postId: '123' })
|
|
94
|
+
// => ['api', 'v1', 'users', 'posts', 'comments', '123']
|
|
95
|
+
|
|
96
|
+
keys.v1.users.posts() // => ['api', 'v1', 'users', 'posts']
|
|
97
|
+
keys.v1.users() // => ['api', 'v1', 'users']
|
|
98
|
+
keys.v1() // => ['api', 'v1']
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Features
|
|
102
|
+
|
|
103
|
+
- ✅ **Fully TypeScript typed** - Get autocomplete and type safety for all your keys
|
|
104
|
+
- ✅ **Nested key access** - Access nested keys through the full path (`keys.b.c`)
|
|
105
|
+
- ✅ **Function parameters** - Pass typed parameters to your key functions
|
|
106
|
+
- ✅ **Array shorthand** - Use arrays for static key lists
|
|
107
|
+
- ✅ **Any level access** - Call any level in the hierarchy as a function to get its path
|
|
108
|
+
|
|
109
|
+
## API
|
|
110
|
+
|
|
111
|
+
### `createKeyFactory<BaseKey, Schema>(baseKey, schema)`
|
|
112
|
+
|
|
113
|
+
Creates a type-safe key factory.
|
|
114
|
+
|
|
115
|
+
**Parameters:**
|
|
116
|
+
- `baseKey` (string): The base key that will be prepended to all generated keys
|
|
117
|
+
- `schema` (object): An object defining the key structure with nested objects and functions
|
|
118
|
+
|
|
119
|
+
**Returns:** A factory object where each level can be accessed as a function
|
|
120
|
+
|
|
121
|
+
## Documentation
|
|
122
|
+
|
|
123
|
+
For complete documentation, examples, and best practices, see:
|
|
124
|
+
|
|
125
|
+
- **[Documentation](https://bhaskar20.github.io/awesome-key-factory/)** - Beautiful, modern documentation site with search, dark mode, and more
|
|
126
|
+
- **[DOCUMENTATION.md](./DOCUMENTATION.md)** - Comprehensive markdown documentation
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type helper to check if a function has no parameters
|
|
3
|
+
*/
|
|
4
|
+
type HasNoParams<T> = T extends () => any ? true : false;
|
|
5
|
+
/**
|
|
6
|
+
* Type helper to extract the parameter type from a function
|
|
7
|
+
*/
|
|
8
|
+
type ParamsOf<T> = T extends (params: infer P) => any ? P : T extends () => any ? Record<string, never> : never;
|
|
9
|
+
/**
|
|
10
|
+
* Type helper to create function signature with optional params when Record<string, never>
|
|
11
|
+
* Functions with no parameters can be called with 0 args or with an empty object
|
|
12
|
+
*/
|
|
13
|
+
type FunctionWithParams<P, R, T> = HasNoParams<T> extends true ? (() => R) & ((params?: Record<string, never>) => R) : P extends Record<string, never> ? (params?: P) => R : (params: P) => R;
|
|
14
|
+
/**
|
|
15
|
+
* Type helper to extract the return type from a function
|
|
16
|
+
*/
|
|
17
|
+
type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never;
|
|
18
|
+
/**
|
|
19
|
+
* Type helper to get the key array type from a function or array
|
|
20
|
+
*/
|
|
21
|
+
type KeyArray<T> = T extends (...args: any[]) => infer R ? R extends readonly (infer K)[] ? K[] : never : T extends readonly (infer K)[] ? K[] : never;
|
|
22
|
+
/**
|
|
23
|
+
* Check if a type is an array
|
|
24
|
+
*/
|
|
25
|
+
type IsArray<T> = T extends readonly (infer _)[] ? true : false;
|
|
26
|
+
/**
|
|
27
|
+
* Get the function type for a specific key in the schema
|
|
28
|
+
*/
|
|
29
|
+
type GetKeyFunction<T, K extends string, BaseKey extends string, Path extends readonly string[] = []> = K extends keyof T ? T[K] extends (...args: any[]) => any ? FunctionWithParams<ParamsOf<T[K]>, [
|
|
30
|
+
...Path,
|
|
31
|
+
BaseKey,
|
|
32
|
+
K,
|
|
33
|
+
...KeyArray<ReturnOf<T[K]>>
|
|
34
|
+
], T[K]> : IsArray<T[K]> extends true ? (params?: Record<string, never>) => [...Path, BaseKey, K, ...KeyArray<T[K]>] : T[K] extends Record<string, any> ? KeyFactory<T[K], BaseKey, [...Path, BaseKey, K]> & {
|
|
35
|
+
(): [...Path, BaseKey, K];
|
|
36
|
+
} : never : never;
|
|
37
|
+
/**
|
|
38
|
+
* Union of all string keys in a type
|
|
39
|
+
*/
|
|
40
|
+
type StringKeys<T> = Extract<keyof T, string>;
|
|
41
|
+
/**
|
|
42
|
+
* Union of all function types for bracket notation access
|
|
43
|
+
*/
|
|
44
|
+
type BracketAccess<T, BaseKey extends string, Path extends readonly string[] = []> = {
|
|
45
|
+
[K in StringKeys<T>]: GetKeyFunction<T, K, BaseKey, Path>;
|
|
46
|
+
}[StringKeys<T>];
|
|
47
|
+
/**
|
|
48
|
+
* Recursive type to transform the input schema into the output factory type
|
|
49
|
+
*/
|
|
50
|
+
type KeyFactory<T, BaseKey extends string, Path extends readonly string[] = []> = {
|
|
51
|
+
[K in keyof T]: T[K] extends (...args: any[]) => any ? FunctionWithParams<ParamsOf<T[K]>, [
|
|
52
|
+
...Path,
|
|
53
|
+
BaseKey,
|
|
54
|
+
K & string,
|
|
55
|
+
...KeyArray<ReturnOf<T[K]>>
|
|
56
|
+
], T[K]> : IsArray<T[K]> extends true ? (params?: Record<string, never>) => [...Path, BaseKey, K & string, ...KeyArray<T[K]>] : T[K] extends Record<string, any> ? KeyFactory<T[K], BaseKey, [...Path, BaseKey, K & string]> & {
|
|
57
|
+
(): [...Path, BaseKey, K & string];
|
|
58
|
+
} : never;
|
|
59
|
+
} & {
|
|
60
|
+
[K in StringKeys<T>]: GetKeyFunction<T, K, BaseKey, Path>;
|
|
61
|
+
} & {
|
|
62
|
+
[key: string]: BracketAccess<T, BaseKey, Path> | any;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Creates a type-safe key factory for react-query keys
|
|
66
|
+
*
|
|
67
|
+
* @param baseKey - The base key that will be prepended to all generated keys
|
|
68
|
+
* @param schema - An object defining the key structure with nested objects and functions
|
|
69
|
+
* @returns A factory object where each level can be accessed as a function
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const keys = createKeyFactory("baseKey", {
|
|
74
|
+
* a: (params: {}) => ["key1", "key2"],
|
|
75
|
+
* b: {
|
|
76
|
+
* c: ["key3", "key4"]
|
|
77
|
+
* }
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* keys.a({}) // => ["baseKey", "a", "key1", "key2"]
|
|
81
|
+
* keys.b.c({}) // => ["baseKey", "b", "c", "key3", "key4"]
|
|
82
|
+
* keys.b() // => ["baseKey", "b"]
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare function createKeyFactory<BaseKey extends string, Schema extends Record<string, any>>(baseKey: BaseKey, schema: Schema): KeyFactory<Schema, BaseKey>;
|
|
86
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createKeyFactory = createKeyFactory;
|
|
4
|
+
/**
|
|
5
|
+
* Creates a type-safe key factory for react-query keys
|
|
6
|
+
*
|
|
7
|
+
* @param baseKey - The base key that will be prepended to all generated keys
|
|
8
|
+
* @param schema - An object defining the key structure with nested objects and functions
|
|
9
|
+
* @returns A factory object where each level can be accessed as a function
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const keys = createKeyFactory("baseKey", {
|
|
14
|
+
* a: (params: {}) => ["key1", "key2"],
|
|
15
|
+
* b: {
|
|
16
|
+
* c: ["key3", "key4"]
|
|
17
|
+
* }
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* keys.a({}) // => ["baseKey", "a", "key1", "key2"]
|
|
21
|
+
* keys.b.c({}) // => ["baseKey", "b", "c", "key3", "key4"]
|
|
22
|
+
* keys.b() // => ["baseKey", "b"]
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function createKeyFactory(baseKey, schema) {
|
|
26
|
+
function createFactory(currentSchema, currentBaseKey, currentPath = []) {
|
|
27
|
+
const factory = {};
|
|
28
|
+
// Add a function to get the current path
|
|
29
|
+
factory.__call = () => [...currentPath, currentBaseKey];
|
|
30
|
+
for (const key in currentSchema) {
|
|
31
|
+
const value = currentSchema[key];
|
|
32
|
+
if (typeof value === "function") {
|
|
33
|
+
// It's a function that returns keys
|
|
34
|
+
factory[key] = (params) => {
|
|
35
|
+
const keys = value(params);
|
|
36
|
+
return [...currentPath, currentBaseKey, key, ...keys];
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
else if (typeof value === "object" &&
|
|
40
|
+
value !== null &&
|
|
41
|
+
!Array.isArray(value)) {
|
|
42
|
+
// It's a nested object
|
|
43
|
+
const nestedFactory = createFactory(value, key, [
|
|
44
|
+
...currentPath,
|
|
45
|
+
currentBaseKey,
|
|
46
|
+
]);
|
|
47
|
+
// Also add a direct accessor that returns the path to this level
|
|
48
|
+
factory[key] = Object.assign(() => [...currentPath, currentBaseKey, key], nestedFactory);
|
|
49
|
+
}
|
|
50
|
+
else if (Array.isArray(value)) {
|
|
51
|
+
// It's an array of keys (shorthand for a function that returns the array)
|
|
52
|
+
factory[key] = (_params) => [
|
|
53
|
+
...currentPath,
|
|
54
|
+
currentBaseKey,
|
|
55
|
+
key,
|
|
56
|
+
...value,
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Make the factory callable
|
|
61
|
+
return new Proxy(factory, {
|
|
62
|
+
get(target, prop) {
|
|
63
|
+
if (prop === "__call") {
|
|
64
|
+
return target.__call;
|
|
65
|
+
}
|
|
66
|
+
if (prop in target) {
|
|
67
|
+
return target[prop];
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Key "${String(prop)}" does not exist at this level. Access it through the proper hierarchy.`);
|
|
70
|
+
},
|
|
71
|
+
apply(target, _thisArg, _argumentsList) {
|
|
72
|
+
return target.__call();
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return createFactory(schema, baseKey);
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
describe("createKeyFactory", () => {
|
|
5
|
+
describe("basic functionality", () => {
|
|
6
|
+
it("should create keys with base key and function", () => {
|
|
7
|
+
const keys = (0, index_1.createKeyFactory)("baseKey", {
|
|
8
|
+
a: (_params) => ["key1", "key2"],
|
|
9
|
+
});
|
|
10
|
+
expect(keys.a({})).toEqual(["baseKey", "a", "key1", "key2"]);
|
|
11
|
+
});
|
|
12
|
+
it("should use params to create keys dynamically", () => {
|
|
13
|
+
const keys = (0, index_1.createKeyFactory)("baseKey", {
|
|
14
|
+
a: (params) => [params.id, params.status, "key1"],
|
|
15
|
+
});
|
|
16
|
+
expect(keys.a({ id: "123", status: "active" })).toEqual([
|
|
17
|
+
"baseKey",
|
|
18
|
+
"a",
|
|
19
|
+
"123",
|
|
20
|
+
"active",
|
|
21
|
+
"key1",
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
it("should create keys with nested objects", () => {
|
|
25
|
+
const keys = (0, index_1.createKeyFactory)("baseKey", {
|
|
26
|
+
b: {
|
|
27
|
+
c: ["key3", "key4"],
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
expect(keys.b.c()).toEqual(["baseKey", "b", "c", "key3", "key4"]);
|
|
31
|
+
});
|
|
32
|
+
it("should allow accessing intermediate levels", () => {
|
|
33
|
+
const keys = (0, index_1.createKeyFactory)("baseKey", {
|
|
34
|
+
b: {
|
|
35
|
+
c: ["key3", "key4"],
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
expect(keys.b()).toEqual(["baseKey", "b"]);
|
|
39
|
+
});
|
|
40
|
+
it("should handle the example from the requirements", () => {
|
|
41
|
+
const keys = (0, index_1.createKeyFactory)("baseKey", {
|
|
42
|
+
a: () => ["key1", "key2"],
|
|
43
|
+
b: {
|
|
44
|
+
c: () => ["key3", "key4"],
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
expect(keys.a({})).toEqual(["baseKey", "a", "key1", "key2"]);
|
|
48
|
+
expect(keys.b.c()).toEqual(["baseKey", "b", "c", "key3", "key4"]);
|
|
49
|
+
expect(() => keys.c()).toThrow();
|
|
50
|
+
expect(keys.b()).toEqual(["baseKey", "b"]);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe("function parameters", () => {
|
|
54
|
+
it("should pass parameters to key functions", () => {
|
|
55
|
+
const keys = (0, index_1.createKeyFactory)("users", {
|
|
56
|
+
byId: (params) => [params.id, params.type],
|
|
57
|
+
});
|
|
58
|
+
expect(keys.byId({ id: "123", type: "admin" })).toEqual([
|
|
59
|
+
"users",
|
|
60
|
+
"byId",
|
|
61
|
+
"123",
|
|
62
|
+
"admin",
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
it("should handle empty parameters", () => {
|
|
66
|
+
const keys = (0, index_1.createKeyFactory)("posts", {
|
|
67
|
+
all: (_params) => [],
|
|
68
|
+
});
|
|
69
|
+
expect(keys.all({})).toEqual(["posts", "all"]);
|
|
70
|
+
});
|
|
71
|
+
it("should handle multiple parameters", () => {
|
|
72
|
+
const keys = (0, index_1.createKeyFactory)("users", {
|
|
73
|
+
posts: {
|
|
74
|
+
byId: (params) => [params.userId, params.postId],
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
expect(keys.posts.byId({ userId: "1", postId: "2" })).toEqual([
|
|
78
|
+
"users",
|
|
79
|
+
"posts",
|
|
80
|
+
"byId",
|
|
81
|
+
"1",
|
|
82
|
+
"2",
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe("nested structures", () => {
|
|
87
|
+
it("should handle deeply nested objects", () => {
|
|
88
|
+
const keys = (0, index_1.createKeyFactory)("app", {
|
|
89
|
+
users: {
|
|
90
|
+
posts: {
|
|
91
|
+
comments: ["all"],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
expect(keys.users.posts.comments()).toEqual([
|
|
96
|
+
"app",
|
|
97
|
+
"users",
|
|
98
|
+
"posts",
|
|
99
|
+
"comments",
|
|
100
|
+
"all",
|
|
101
|
+
]);
|
|
102
|
+
expect(keys.users.posts()).toEqual(["app", "users", "posts"]);
|
|
103
|
+
expect(keys.users()).toEqual(["app", "users"]);
|
|
104
|
+
});
|
|
105
|
+
it("should handle multiple nested levels with functions", () => {
|
|
106
|
+
const keys = (0, index_1.createKeyFactory)("api", {
|
|
107
|
+
v1: {
|
|
108
|
+
users: (params) => [params.id],
|
|
109
|
+
posts: {
|
|
110
|
+
list: (params) => [params.page.toString()],
|
|
111
|
+
detail: (params) => [params.id],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
expect(keys.v1.users({ id: "123" })).toEqual([
|
|
116
|
+
"api",
|
|
117
|
+
"v1",
|
|
118
|
+
"users",
|
|
119
|
+
"123",
|
|
120
|
+
]);
|
|
121
|
+
expect(keys.v1.posts.list({ page: 1 })).toEqual([
|
|
122
|
+
"api",
|
|
123
|
+
"v1",
|
|
124
|
+
"posts",
|
|
125
|
+
"list",
|
|
126
|
+
"1",
|
|
127
|
+
]);
|
|
128
|
+
expect(keys.v1.posts.detail({ id: "456" })).toEqual([
|
|
129
|
+
"api",
|
|
130
|
+
"v1",
|
|
131
|
+
"posts",
|
|
132
|
+
"detail",
|
|
133
|
+
"456",
|
|
134
|
+
]);
|
|
135
|
+
expect(keys.v1.posts()).toEqual(["api", "v1", "posts"]);
|
|
136
|
+
expect(keys.v1()).toEqual(["api", "v1"]);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe("array shorthand", () => {
|
|
140
|
+
it("should handle array shorthand syntax", () => {
|
|
141
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
142
|
+
items: ["list"],
|
|
143
|
+
});
|
|
144
|
+
expect(keys.items()).toEqual(["base", "items", "list"]);
|
|
145
|
+
});
|
|
146
|
+
it("should handle multiple items in array", () => {
|
|
147
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
148
|
+
items: ["list", "all"],
|
|
149
|
+
});
|
|
150
|
+
expect(keys.items()).toEqual(["base", "items", "list", "all"]);
|
|
151
|
+
});
|
|
152
|
+
it("should handle empty arrays", () => {
|
|
153
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
154
|
+
items: [],
|
|
155
|
+
});
|
|
156
|
+
expect(keys.items()).toEqual(["base", "items"]);
|
|
157
|
+
});
|
|
158
|
+
it("should handle array shorthand in nested structures", () => {
|
|
159
|
+
const keys = (0, index_1.createKeyFactory)("app", {
|
|
160
|
+
users: {
|
|
161
|
+
list: ["all"],
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
expect(keys.users.list()).toEqual(["app", "users", "list", "all"]);
|
|
165
|
+
});
|
|
166
|
+
it("should handle array shorthand with numeric string values", () => {
|
|
167
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
168
|
+
items: ["1", "2", "3"],
|
|
169
|
+
});
|
|
170
|
+
expect(keys.items()).toEqual(["base", "items", "1", "2", "3"]);
|
|
171
|
+
});
|
|
172
|
+
it("should handle array shorthand in deeply nested structures", () => {
|
|
173
|
+
const keys = (0, index_1.createKeyFactory)("api", {
|
|
174
|
+
v1: {
|
|
175
|
+
users: {
|
|
176
|
+
posts: {
|
|
177
|
+
comments: ["all"],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
expect(keys.v1.users.posts.comments()).toEqual([
|
|
183
|
+
"api",
|
|
184
|
+
"v1",
|
|
185
|
+
"users",
|
|
186
|
+
"posts",
|
|
187
|
+
"comments",
|
|
188
|
+
"all",
|
|
189
|
+
]);
|
|
190
|
+
expect(keys.v1.users.posts()).toEqual(["api", "v1", "users", "posts"]);
|
|
191
|
+
expect(keys.v1.users()).toEqual(["api", "v1", "users"]);
|
|
192
|
+
expect(keys.v1()).toEqual(["api", "v1"]);
|
|
193
|
+
});
|
|
194
|
+
it("should handle array shorthand with special characters in array values", () => {
|
|
195
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
196
|
+
items: ["key-1", "key_2", "key.3"],
|
|
197
|
+
});
|
|
198
|
+
expect(keys.items()).toEqual(["base", "items", "key-1", "key_2", "key.3"]);
|
|
199
|
+
});
|
|
200
|
+
it("should handle multiple array shorthand keys at the same level", () => {
|
|
201
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
202
|
+
a: ["1"],
|
|
203
|
+
b: ["2"],
|
|
204
|
+
c: ["3"],
|
|
205
|
+
});
|
|
206
|
+
expect(keys.a()).toEqual(["base", "a", "1"]);
|
|
207
|
+
expect(keys.b()).toEqual(["base", "b", "2"]);
|
|
208
|
+
expect(keys.c()).toEqual(["base", "c", "3"]);
|
|
209
|
+
});
|
|
210
|
+
it("should handle array shorthand with bracket notation access", () => {
|
|
211
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
212
|
+
"key-1": ["value1"],
|
|
213
|
+
"key_2": ["value2"],
|
|
214
|
+
});
|
|
215
|
+
expect(keys["key-1"]()).toEqual(["base", "key-1", "value1"]);
|
|
216
|
+
expect(keys["key_2"]()).toEqual(["base", "key_2", "value2"]);
|
|
217
|
+
});
|
|
218
|
+
it("should handle array shorthand with long arrays", () => {
|
|
219
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
220
|
+
items: ["a", "b", "c", "d", "e", "f", "g"],
|
|
221
|
+
});
|
|
222
|
+
expect(keys.items()).toEqual([
|
|
223
|
+
"base",
|
|
224
|
+
"items",
|
|
225
|
+
"a",
|
|
226
|
+
"b",
|
|
227
|
+
"c",
|
|
228
|
+
"d",
|
|
229
|
+
"e",
|
|
230
|
+
"f",
|
|
231
|
+
"g",
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
it("should handle array shorthand with single character values", () => {
|
|
235
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
236
|
+
items: ["x", "y", "z"],
|
|
237
|
+
});
|
|
238
|
+
expect(keys.items()).toEqual(["base", "items", "x", "y", "z"]);
|
|
239
|
+
});
|
|
240
|
+
it("should handle array shorthand with empty string values", () => {
|
|
241
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
242
|
+
items: ["", "value"],
|
|
243
|
+
});
|
|
244
|
+
expect(keys.items()).toEqual(["base", "items", "", "value"]);
|
|
245
|
+
});
|
|
246
|
+
it("should handle array shorthand at root level with nested objects", () => {
|
|
247
|
+
const keys = (0, index_1.createKeyFactory)("app", {
|
|
248
|
+
root: ["level"],
|
|
249
|
+
nested: {
|
|
250
|
+
deep: ["value"],
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
expect(keys.root()).toEqual(["app", "root", "level"]);
|
|
254
|
+
expect(keys.nested.deep()).toEqual(["app", "nested", "deep", "value"]);
|
|
255
|
+
expect(keys.nested()).toEqual(["app", "nested"]);
|
|
256
|
+
});
|
|
257
|
+
it("should handle array shorthand with params parameter (should be optional)", () => {
|
|
258
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
259
|
+
items: ["list"],
|
|
260
|
+
});
|
|
261
|
+
// Array shorthand should accept optional params
|
|
262
|
+
expect(keys.items()).toEqual(["base", "items", "list"]);
|
|
263
|
+
expect(keys.items({})).toEqual(["base", "items", "list"]);
|
|
264
|
+
});
|
|
265
|
+
it("should handle array shorthand in multiple nested levels", () => {
|
|
266
|
+
const keys = (0, index_1.createKeyFactory)("shop", {
|
|
267
|
+
products: {
|
|
268
|
+
list: ["all"],
|
|
269
|
+
featured: ["featured"],
|
|
270
|
+
categories: {
|
|
271
|
+
electronics: ["items"],
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
cart: ["items"],
|
|
275
|
+
});
|
|
276
|
+
expect(keys.products.list()).toEqual(["shop", "products", "list", "all"]);
|
|
277
|
+
expect(keys.products.featured()).toEqual([
|
|
278
|
+
"shop",
|
|
279
|
+
"products",
|
|
280
|
+
"featured",
|
|
281
|
+
"featured",
|
|
282
|
+
]);
|
|
283
|
+
expect(keys.products.categories.electronics()).toEqual([
|
|
284
|
+
"shop",
|
|
285
|
+
"products",
|
|
286
|
+
"categories",
|
|
287
|
+
"electronics",
|
|
288
|
+
"items",
|
|
289
|
+
]);
|
|
290
|
+
expect(keys.cart()).toEqual(["shop", "cart", "items"]);
|
|
291
|
+
expect(keys.products.categories()).toEqual([
|
|
292
|
+
"shop",
|
|
293
|
+
"products",
|
|
294
|
+
"categories",
|
|
295
|
+
]);
|
|
296
|
+
expect(keys.products()).toEqual(["shop", "products"]);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
describe("complex real-world scenarios", () => {
|
|
300
|
+
it("should work with a typical react-query setup", () => {
|
|
301
|
+
const queryKeys = (0, index_1.createKeyFactory)("app", {
|
|
302
|
+
users: {
|
|
303
|
+
all: () => [],
|
|
304
|
+
lists: () => ["list"],
|
|
305
|
+
detail: (params) => [params.id],
|
|
306
|
+
posts: (params) => [params.userId, "posts"],
|
|
307
|
+
},
|
|
308
|
+
posts: {
|
|
309
|
+
all: () => [],
|
|
310
|
+
detail: (params) => [params.id],
|
|
311
|
+
comments: (params) => [params.postId, "comments"],
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
expect(queryKeys.users.all({})).toEqual(["app", "users", "all"]);
|
|
315
|
+
expect(queryKeys.users.lists({})).toEqual([
|
|
316
|
+
"app",
|
|
317
|
+
"users",
|
|
318
|
+
"lists",
|
|
319
|
+
"list",
|
|
320
|
+
]);
|
|
321
|
+
expect(queryKeys.users.detail({ id: "123" })).toEqual([
|
|
322
|
+
"app",
|
|
323
|
+
"users",
|
|
324
|
+
"detail",
|
|
325
|
+
"123",
|
|
326
|
+
]);
|
|
327
|
+
expect(queryKeys.users.posts({ userId: "456" })).toEqual([
|
|
328
|
+
"app",
|
|
329
|
+
"users",
|
|
330
|
+
"posts",
|
|
331
|
+
"456",
|
|
332
|
+
"posts",
|
|
333
|
+
]);
|
|
334
|
+
expect(queryKeys.users()).toEqual(["app", "users"]);
|
|
335
|
+
expect(queryKeys.posts.all({})).toEqual(["app", "posts", "all"]);
|
|
336
|
+
expect(queryKeys.posts.detail({ id: "789" })).toEqual([
|
|
337
|
+
"app",
|
|
338
|
+
"posts",
|
|
339
|
+
"detail",
|
|
340
|
+
"789",
|
|
341
|
+
]);
|
|
342
|
+
expect(queryKeys.posts.comments({ postId: "101" })).toEqual([
|
|
343
|
+
"app",
|
|
344
|
+
"posts",
|
|
345
|
+
"comments",
|
|
346
|
+
"101",
|
|
347
|
+
"comments",
|
|
348
|
+
]);
|
|
349
|
+
expect(queryKeys.posts()).toEqual(["app", "posts"]);
|
|
350
|
+
});
|
|
351
|
+
it("should handle mixed function and array syntax", () => {
|
|
352
|
+
const keys = (0, index_1.createKeyFactory)("shop", {
|
|
353
|
+
products: {
|
|
354
|
+
list: ["all"],
|
|
355
|
+
byCategory: (params) => [params.category],
|
|
356
|
+
byId: (params) => [params.id],
|
|
357
|
+
},
|
|
358
|
+
cart: ["items"],
|
|
359
|
+
});
|
|
360
|
+
expect(keys.products.list()).toEqual(["shop", "products", "list", "all"]);
|
|
361
|
+
expect(keys.products.byCategory({ category: "electronics" })).toEqual([
|
|
362
|
+
"shop",
|
|
363
|
+
"products",
|
|
364
|
+
"byCategory",
|
|
365
|
+
"electronics",
|
|
366
|
+
]);
|
|
367
|
+
expect(keys.products.byId({ id: "123" })).toEqual([
|
|
368
|
+
"shop",
|
|
369
|
+
"products",
|
|
370
|
+
"byId",
|
|
371
|
+
"123",
|
|
372
|
+
]);
|
|
373
|
+
expect(keys.products()).toEqual(["shop", "products"]);
|
|
374
|
+
expect(keys.cart()).toEqual(["shop", "cart", "items"]);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
describe("edge cases", () => {
|
|
378
|
+
it("should handle single level with no nesting", () => {
|
|
379
|
+
const keys = (0, index_1.createKeyFactory)("simple", {
|
|
380
|
+
item: () => ["single"],
|
|
381
|
+
});
|
|
382
|
+
expect(keys.item({})).toEqual(["simple", "item", "single"]);
|
|
383
|
+
});
|
|
384
|
+
it("should handle multiple keys at the same level", () => {
|
|
385
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
386
|
+
a: () => ["1"],
|
|
387
|
+
b: () => ["2"],
|
|
388
|
+
c: () => ["3"],
|
|
389
|
+
});
|
|
390
|
+
expect(keys.a({})).toEqual(["base", "a", "1"]);
|
|
391
|
+
expect(keys.b({})).toEqual(["base", "b", "2"]);
|
|
392
|
+
expect(keys.c({})).toEqual(["base", "c", "3"]);
|
|
393
|
+
});
|
|
394
|
+
it("should handle numeric and special characters in keys", () => {
|
|
395
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
396
|
+
"key-1": () => ["value"],
|
|
397
|
+
key_2: () => ["value2"],
|
|
398
|
+
});
|
|
399
|
+
expect(keys["key-1"]({})).toEqual(["base", "key-1", "value"]);
|
|
400
|
+
expect(keys["key_2"]({})).toEqual(["base", "key_2", "value2"]);
|
|
401
|
+
});
|
|
402
|
+
it("should support bracket notation with proper types for function parameters", () => {
|
|
403
|
+
const keys = (0, index_1.createKeyFactory)("base", {
|
|
404
|
+
"key-with-params": (params) => [params.id, params.type],
|
|
405
|
+
"another-key": () => ["static"],
|
|
406
|
+
});
|
|
407
|
+
// Bracket notation should work with proper types
|
|
408
|
+
const keyWithParams = keys["key-with-params"];
|
|
409
|
+
const anotherKey = keys["another-key"];
|
|
410
|
+
// TypeScript should know the parameter types
|
|
411
|
+
expect(keyWithParams({ id: "123", type: "admin" })).toEqual([
|
|
412
|
+
"base",
|
|
413
|
+
"key-with-params",
|
|
414
|
+
"123",
|
|
415
|
+
"admin",
|
|
416
|
+
]);
|
|
417
|
+
expect(anotherKey({})).toEqual(["base", "another-key", "static"]);
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "awesome-key-factory",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A type-safe key factory for managing react-query keys",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "jest",
|
|
10
|
+
"prepublishOnly": "yarn build && yarn test",
|
|
11
|
+
"prepack": "yarn build",
|
|
12
|
+
"publish": "npm publish",
|
|
13
|
+
"publish:dry-run": "npm publish --dry-run",
|
|
14
|
+
"publish:public": "npm publish --access public",
|
|
15
|
+
"publish:beta": "npm publish --tag beta",
|
|
16
|
+
"publish:next": "npm publish --tag next",
|
|
17
|
+
"version:patch": "yarn version --patch --no-git-tag-version",
|
|
18
|
+
"version:minor": "yarn version --minor --no-git-tag-version",
|
|
19
|
+
"version:major": "yarn version --major --no-git-tag-version",
|
|
20
|
+
"release:patch": "yarn version:patch && yarn publish:public && git push && git push --tags",
|
|
21
|
+
"release:minor": "yarn version:minor && yarn publish:public && git push && git push --tags",
|
|
22
|
+
"release:major": "yarn version:major && yarn publish:public && git push && git push --tags",
|
|
23
|
+
"lint": "eslint . --ext .ts",
|
|
24
|
+
"lint:fix": "eslint . --ext .ts --fix",
|
|
25
|
+
"type-check": "tsc --noEmit",
|
|
26
|
+
"check": "yarn type-check && yarn lint",
|
|
27
|
+
"docs:dev": "cd vitepress-docs && yarn dev",
|
|
28
|
+
"docs:build": "cd vitepress-docs && yarn build",
|
|
29
|
+
"docs:preview": "cd vitepress-docs && yarn preview"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"react-query",
|
|
33
|
+
"query-keys",
|
|
34
|
+
"typescript",
|
|
35
|
+
"type-safe"
|
|
36
|
+
],
|
|
37
|
+
"author": "",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/bhaskar20/awesome-key-factory.git"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://bhaskar20.github.io/awesome-key-factory/",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/bhaskar20/awesome-key-factory/issues"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@astrojs/starlight": "^0.37.3",
|
|
49
|
+
"@types/jest": "^29.5.0",
|
|
50
|
+
"@types/node": "^20.0.0",
|
|
51
|
+
"@typescript-eslint/eslint-plugin": "^8.53.0",
|
|
52
|
+
"@typescript-eslint/parser": "^8.53.0",
|
|
53
|
+
"astro": "^5.16.10",
|
|
54
|
+
"eslint": "^9.39.2",
|
|
55
|
+
"eslint-config-prettier": "^10.1.8",
|
|
56
|
+
"jest": "^29.5.0",
|
|
57
|
+
"ts-jest": "^29.1.0",
|
|
58
|
+
"typescript": "^5.0.0",
|
|
59
|
+
"typescript-eslint": "^8.53.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"typescript": "^4.0.0 || ^5.0.0"
|
|
63
|
+
},
|
|
64
|
+
"files": [
|
|
65
|
+
"dist"
|
|
66
|
+
],
|
|
67
|
+
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8"
|
|
68
|
+
}
|