reslib 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/README.md +298 -0
- package/build/auth/index.d.ts +2034 -0
- package/build/auth/index.js +5 -0
- package/build/auth/types.d.ts +465 -0
- package/build/auth/types.js +1 -0
- package/build/countries/countries.d.ts +1454 -0
- package/build/countries/countries.js +1 -0
- package/build/countries/index.d.ts +159 -0
- package/build/countries/index.js +5 -0
- package/build/countries/types.d.ts +65 -0
- package/build/countries/types.js +1 -0
- package/build/currency/currencies.d.ts +8 -0
- package/build/currency/currencies.js +1 -0
- package/build/currency/index.d.ts +51 -0
- package/build/currency/index.js +5 -0
- package/build/currency/session.d.ts +23 -0
- package/build/currency/session.js +5 -0
- package/build/currency/types.d.ts +1039 -0
- package/build/currency/types.js +1 -0
- package/build/currency/utils.d.ts +25 -0
- package/build/currency/utils.js +1 -0
- package/build/i18n/index.d.ts +640 -0
- package/build/i18n/index.js +5 -0
- package/build/inputFormatter/index.d.ts +396 -0
- package/build/inputFormatter/index.js +5 -0
- package/build/inputFormatter/types.d.ts +544 -0
- package/build/inputFormatter/types.js +1 -0
- package/build/logger/index.d.ts +235 -0
- package/build/logger/index.js +5 -0
- package/build/observable/index.d.ts +329 -0
- package/build/observable/index.js +1 -0
- package/build/platform/index.d.ts +32 -0
- package/build/platform/index.js +1 -0
- package/build/resources/ResourcePaginationHelper.d.ts +537 -0
- package/build/resources/ResourcePaginationHelper.js +2 -0
- package/build/resources/decorators/create.decorator.d.ts +20 -0
- package/build/resources/decorators/create.decorator.js +1 -0
- package/build/resources/decorators/index.d.ts +41 -0
- package/build/resources/decorators/index.js +1 -0
- package/build/resources/fields/index.d.ts +33 -0
- package/build/resources/fields/index.js +1 -0
- package/build/resources/filters.d.ts +62 -0
- package/build/resources/filters.js +1 -0
- package/build/resources/index.d.ts +854 -0
- package/build/resources/index.js +6 -0
- package/build/resources/types/filters.d.ts +508 -0
- package/build/resources/types/filters.js +1 -0
- package/build/resources/types/index.d.ts +4138 -0
- package/build/resources/types/index.js +1 -0
- package/build/session/index.d.ts +1474 -0
- package/build/session/index.js +1 -0
- package/build/translations/auth.en.d.ts +3 -0
- package/build/translations/auth.en.js +1 -0
- package/build/translations/countries.en.d.ts +6 -0
- package/build/translations/countries.en.js +1 -0
- package/build/translations/currencies.en.d.ts +5 -0
- package/build/translations/currencies.en.js +1 -0
- package/build/translations/date.en.d.ts +19 -0
- package/build/translations/date.en.js +1 -0
- package/build/translations/index.d.ts +1583 -0
- package/build/translations/index.js +5 -0
- package/build/translations/resources.en.d.ts +6 -0
- package/build/translations/resources.en.js +1 -0
- package/build/translations/validator.en.d.ts +104 -0
- package/build/translations/validator.en.js +5 -0
- package/build/types/date.d.ts +44 -0
- package/build/types/date.js +1 -0
- package/build/types/dictionary.d.ts +29 -0
- package/build/types/dictionary.js +1 -0
- package/build/types/i18n.d.ts +121 -0
- package/build/types/i18n.js +1 -0
- package/build/types/index.d.ts +145 -0
- package/build/types/index.js +1 -0
- package/build/utils/areEquals.d.ts +19 -0
- package/build/utils/areEquals.js +1 -0
- package/build/utils/date/dateHelper.d.ts +371 -0
- package/build/utils/date/dateHelper.js +5 -0
- package/build/utils/date/index.d.ts +212 -0
- package/build/utils/date/index.js +5 -0
- package/build/utils/date/isDateObj.d.ts +14 -0
- package/build/utils/date/isDateObj.js +1 -0
- package/build/utils/debounce.d.ts +52 -0
- package/build/utils/debounce.js +1 -0
- package/build/utils/defaultArray.d.ts +18 -0
- package/build/utils/defaultArray.js +1 -0
- package/build/utils/defaultBool.d.ts +14 -0
- package/build/utils/defaultBool.js +1 -0
- package/build/utils/defaultStr.d.ts +17 -0
- package/build/utils/defaultStr.js +1 -0
- package/build/utils/defaultVal.d.ts +18 -0
- package/build/utils/defaultVal.js +1 -0
- package/build/utils/dom/index.d.ts +65 -0
- package/build/utils/dom/index.js +1 -0
- package/build/utils/dom/isDOMElement.d.ts +11 -0
- package/build/utils/dom/isDOMElement.js +1 -0
- package/build/utils/file/index.d.ts +26 -0
- package/build/utils/file/index.js +1 -0
- package/build/utils/global.d.ts +53 -0
- package/build/utils/global.js +1 -0
- package/build/utils/image.d.ts +56 -0
- package/build/utils/image.js +1 -0
- package/build/utils/index.d.ts +39 -0
- package/build/utils/index.js +6 -0
- package/build/utils/interpolate.d.ts +105 -0
- package/build/utils/interpolate.js +1 -0
- package/build/utils/isEmail.d.ts +57 -0
- package/build/utils/isEmail.js +1 -0
- package/build/utils/isEmpty.d.ts +18 -0
- package/build/utils/isEmpty.js +1 -0
- package/build/utils/isNonNullString.d.ts +17 -0
- package/build/utils/isNonNullString.js +1 -0
- package/build/utils/isNullable.d.ts +7 -0
- package/build/utils/isNullable.js +1 -0
- package/build/utils/isNumber.d.ts +36 -0
- package/build/utils/isNumber.js +1 -0
- package/build/utils/isPrimitive.d.ts +16 -0
- package/build/utils/isPrimitive.js +1 -0
- package/build/utils/isPromise.d.ts +14 -0
- package/build/utils/isPromise.js +1 -0
- package/build/utils/isRegex.d.ts +15 -0
- package/build/utils/isRegex.js +1 -0
- package/build/utils/isTime.d.ts +18 -0
- package/build/utils/isTime.js +1 -0
- package/build/utils/json.d.ts +224 -0
- package/build/utils/json.js +1 -0
- package/build/utils/numbers.d.ts +148 -0
- package/build/utils/numbers.js +5 -0
- package/build/utils/object.d.ts +567 -0
- package/build/utils/object.js +1 -0
- package/build/utils/sort.d.ts +67 -0
- package/build/utils/sort.js +1 -0
- package/build/utils/string.d.ts +165 -0
- package/build/utils/string.js +1 -0
- package/build/utils/stringify.d.ts +23 -0
- package/build/utils/stringify.js +1 -0
- package/build/utils/uniqid.d.ts +18 -0
- package/build/utils/uniqid.js +1 -0
- package/build/utils/uri/index.d.ts +333 -0
- package/build/utils/uri/index.js +2 -0
- package/build/validator/index.d.ts +4 -0
- package/build/validator/index.js +6 -0
- package/build/validator/rules/array.d.ts +848 -0
- package/build/validator/rules/array.js +5 -0
- package/build/validator/rules/boolean.d.ts +87 -0
- package/build/validator/rules/boolean.js +5 -0
- package/build/validator/rules/date.d.ts +551 -0
- package/build/validator/rules/date.js +5 -0
- package/build/validator/rules/default.d.ts +367 -0
- package/build/validator/rules/default.js +5 -0
- package/build/validator/rules/enum.d.ts +155 -0
- package/build/validator/rules/enum.js +5 -0
- package/build/validator/rules/file.d.ts +356 -0
- package/build/validator/rules/file.js +5 -0
- package/build/validator/rules/format.d.ts +2825 -0
- package/build/validator/rules/format.js +6 -0
- package/build/validator/rules/index.d.ts +16 -0
- package/build/validator/rules/index.js +6 -0
- package/build/validator/rules/multiRules.d.ts +475 -0
- package/build/validator/rules/multiRules.js +5 -0
- package/build/validator/rules/numeric.d.ts +1135 -0
- package/build/validator/rules/numeric.js +5 -0
- package/build/validator/rules/string.d.ts +504 -0
- package/build/validator/rules/string.js +5 -0
- package/build/validator/rules/target.d.ts +137 -0
- package/build/validator/rules/target.js +5 -0
- package/build/validator/rules/utils.d.ts +1 -0
- package/build/validator/rules/utils.js +1 -0
- package/build/validator/rulesMarkers.d.ts +11 -0
- package/build/validator/rulesMarkers.js +1 -0
- package/build/validator/types.d.ts +2906 -0
- package/build/validator/types.js +1 -0
- package/build/validator/validator.d.ts +3692 -0
- package/build/validator/validator.js +5 -0
- package/lib/cjs/auth.js +1 -0
- package/lib/cjs/countries.js +1 -0
- package/lib/cjs/currency.js +1 -0
- package/lib/cjs/i18n.js +1 -0
- package/lib/cjs/inputFormatter.js +1 -0
- package/lib/cjs/logger.js +1 -0
- package/lib/cjs/observable.js +1 -0
- package/lib/cjs/platform.js +1 -0
- package/lib/cjs/resources.js +1 -0
- package/lib/cjs/session.js +1 -0
- package/lib/cjs/types.js +1 -0
- package/lib/cjs/utils.js +1 -0
- package/lib/cjs/validator.js +1 -0
- package/lib/esm/auth.mjs +1 -0
- package/lib/esm/countries.mjs +1 -0
- package/lib/esm/currency.mjs +1 -0
- package/lib/esm/i18n.mjs +1 -0
- package/lib/esm/inputFormatter.mjs +1 -0
- package/lib/esm/logger.mjs +1 -0
- package/lib/esm/observable.mjs +1 -0
- package/lib/esm/platform.mjs +1 -0
- package/lib/esm/resources.mjs +1 -0
- package/lib/esm/session.mjs +1 -0
- package/lib/esm/types.mjs +1 -0
- package/lib/esm/utils.mjs +1 -0
- package/lib/esm/validator.mjs +1 -0
- package/package.json +244 -0
|
@@ -0,0 +1,4138 @@
|
|
|
1
|
+
import { AuthPerm } from '../../auth/types';
|
|
2
|
+
import { InputFormatterOptions } from '../../inputFormatter/types';
|
|
3
|
+
import { Dictionary, UppercaseFirst } from '../../types/dictionary';
|
|
4
|
+
import { MongoQuery, ResourceQueryOrderBy } from './filters';
|
|
5
|
+
export * from './filters';
|
|
6
|
+
export interface FieldBase<TFieldType extends FieldType = FieldType, TValueType = any> extends Partial<ResourceActionTupleObject<ResourceName>>, Omit<InputFormatterOptions<TFieldType, TValueType>, 'value' | 'type'> {
|
|
7
|
+
/**
|
|
8
|
+
* The type of the field.
|
|
9
|
+
*
|
|
10
|
+
* @description
|
|
11
|
+
* This property specifies the type of the field, such as "text", "number", or "date".
|
|
12
|
+
*
|
|
13
|
+
* @default "text"
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const textField: FieldBase = {
|
|
18
|
+
* type: 'text'
|
|
19
|
+
* };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
type: TFieldType;
|
|
23
|
+
/**
|
|
24
|
+
* The name of the field.
|
|
25
|
+
*
|
|
26
|
+
* @description
|
|
27
|
+
* This property specifies the unique name or identifier for the field.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const textField: FieldBase = {
|
|
32
|
+
* name: 'textField'
|
|
33
|
+
* };
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
name?: string;
|
|
37
|
+
/**
|
|
38
|
+
* The name of the field in the database table.
|
|
39
|
+
*
|
|
40
|
+
* @description
|
|
41
|
+
* This property specifies the name of the field as it appears in the database table.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const textField: FieldBase = {
|
|
46
|
+
* databaseName: 'text_field'
|
|
47
|
+
* };
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
databaseName?: string;
|
|
51
|
+
/**
|
|
52
|
+
* The name of the field's table or collection in the database.
|
|
53
|
+
*
|
|
54
|
+
* @description
|
|
55
|
+
* This property specifies the name of the table or collection that contains the field in the database.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const textField: FieldBase = {
|
|
60
|
+
* databaseTableName: 'text_fields'
|
|
61
|
+
* };
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
databaseTableName?: string;
|
|
65
|
+
/***
|
|
66
|
+
* weatherr the field is a primary key or not
|
|
67
|
+
*/
|
|
68
|
+
primaryKey?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* weatherr the field is rendable or not
|
|
71
|
+
* It is used to determine if the field should be rendered or not.
|
|
72
|
+
*/
|
|
73
|
+
rendable?: boolean;
|
|
74
|
+
/***
|
|
75
|
+
* weatherr the field is readonly or not
|
|
76
|
+
*/
|
|
77
|
+
readOnly?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* weatherr the field is disabled or not
|
|
80
|
+
*/
|
|
81
|
+
disabled?: boolean;
|
|
82
|
+
/***
|
|
83
|
+
* weatherr the field is unique for the resource or not
|
|
84
|
+
*/
|
|
85
|
+
unique?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* weatherr the field is required or not
|
|
88
|
+
*/
|
|
89
|
+
required?: boolean;
|
|
90
|
+
/***
|
|
91
|
+
* the min length of the field
|
|
92
|
+
*/
|
|
93
|
+
minLength?: number;
|
|
94
|
+
/**
|
|
95
|
+
* the max length of the field
|
|
96
|
+
*/
|
|
97
|
+
maxLength?: number;
|
|
98
|
+
/**
|
|
99
|
+
* the length of the field
|
|
100
|
+
*/
|
|
101
|
+
length?: number;
|
|
102
|
+
/***
|
|
103
|
+
* whether the field is visible or not
|
|
104
|
+
*/
|
|
105
|
+
visible?: boolean;
|
|
106
|
+
/***
|
|
107
|
+
* The permission associated with the field. This permission is used to determine if the field will be rendered or not.
|
|
108
|
+
*/
|
|
109
|
+
perm?: AuthPerm;
|
|
110
|
+
/**
|
|
111
|
+
* The default value of the field.
|
|
112
|
+
*/
|
|
113
|
+
defaultValue?: TValueType;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* ## FieldMap Interface
|
|
117
|
+
*
|
|
118
|
+
* A global interface for defining field mappings in the resource system.
|
|
119
|
+
* This interface serves as a central registry for all available field types
|
|
120
|
+
* and their configurations, enabling type-safe field definitions across the application.
|
|
121
|
+
*
|
|
122
|
+
* ### Purpose
|
|
123
|
+
* The `FieldMap` interface is designed for **module augmentation**, allowing developers
|
|
124
|
+
* to extend it with custom field definitions. Each key represents a field type (e.g., "text", "number"),
|
|
125
|
+
* and each value must extend the `FieldBase` interface, ensuring consistent structure
|
|
126
|
+
* and type safety for all field configurations.
|
|
127
|
+
*
|
|
128
|
+
* ### How It Works
|
|
129
|
+
* - **Empty by Design**: The interface is intentionally empty in the core library.
|
|
130
|
+
* - **Module Augmentation**: Developers extend it via `declare module` statements.
|
|
131
|
+
* - **Type Safety**: The `Field<T>` type uses `FieldMap[T]` to enforce that field
|
|
132
|
+
* configurations match their declared types.
|
|
133
|
+
* - **Extensibility**: New field types can be added without modifying the core library.
|
|
134
|
+
*
|
|
135
|
+
* ### Template Parameters
|
|
136
|
+
* This interface doesn't use generic parameters itself, but the values must conform to `FieldBase<TFieldType, ValueType>`.
|
|
137
|
+
*
|
|
138
|
+
* ### Examples
|
|
139
|
+
*
|
|
140
|
+
* #### Basic Module Augmentation
|
|
141
|
+
* ```typescript
|
|
142
|
+
* // In your application's types file (e.g., types.ts or global.d.ts)
|
|
143
|
+
* import "reslib/resources";
|
|
144
|
+
*
|
|
145
|
+
* declare module "reslib/resources/resources" {
|
|
146
|
+
* interface FieldMap {
|
|
147
|
+
* // Define a text field type
|
|
148
|
+
* text: FieldBase<"text", string>;
|
|
149
|
+
*
|
|
150
|
+
* // Define a number field type
|
|
151
|
+
* number: FieldBase<"number", number>;
|
|
152
|
+
*
|
|
153
|
+
* // Define an email field type
|
|
154
|
+
* email: FieldBase<"email", string>;
|
|
155
|
+
* }
|
|
156
|
+
* }
|
|
157
|
+
* ```
|
|
158
|
+
*
|
|
159
|
+
* #### Advanced FieldMeta Configuration
|
|
160
|
+
* ```typescript
|
|
161
|
+
* declare module "reslib/resources/resources" {
|
|
162
|
+
* interface FieldMap {
|
|
163
|
+
* // Text field with validation
|
|
164
|
+
* text: FieldBase<"text", string> & {
|
|
165
|
+
* minLength?: number;
|
|
166
|
+
* maxLength?: number;
|
|
167
|
+
* pattern?: RegExp;
|
|
168
|
+
* };
|
|
169
|
+
*
|
|
170
|
+
* // Number field with range constraints
|
|
171
|
+
* number: FieldBase<"number", number> & {
|
|
172
|
+
* min?: number;
|
|
173
|
+
* max?: number;
|
|
174
|
+
* step?: number;
|
|
175
|
+
* };
|
|
176
|
+
*
|
|
177
|
+
* // Select field with predefined options
|
|
178
|
+
* select: FieldBase<"select", string> & {
|
|
179
|
+
* options: Array<{ label: string; value: string }>;
|
|
180
|
+
* multiple?: boolean;
|
|
181
|
+
* };
|
|
182
|
+
*
|
|
183
|
+
* // Date field with format options
|
|
184
|
+
* date: FieldBase<"date", Date> & {
|
|
185
|
+
* format?: string;
|
|
186
|
+
* minDate?: Date;
|
|
187
|
+
* maxDate?: Date;
|
|
188
|
+
* };
|
|
189
|
+
* }
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* #### Using Defined Fields
|
|
194
|
+
* ```typescript
|
|
195
|
+
* // After augmentation, you can create type-safe fields
|
|
196
|
+
* const userFields: Fields = {
|
|
197
|
+
* username: {
|
|
198
|
+
* type: "text", // ✓ TypeScript knows this is valid
|
|
199
|
+
* name: "username",
|
|
200
|
+
* required: true,
|
|
201
|
+
* minLength: 3, // ✓ Additional properties available
|
|
202
|
+
* maxLength: 50,
|
|
203
|
+
* forCreate: { required: true },
|
|
204
|
+
* forUpdate: { required: false }
|
|
205
|
+
* },
|
|
206
|
+
* age: {
|
|
207
|
+
* type: "number", // ✓ TypeScript knows this is valid
|
|
208
|
+
* name: "age",
|
|
209
|
+
* min: 0, // ✓ Range constraints available
|
|
210
|
+
* max: 150,
|
|
211
|
+
* forFilter: { allowRange: true }
|
|
212
|
+
* },
|
|
213
|
+
* email: {
|
|
214
|
+
* type: "email", // ✓ TypeScript knows this is valid
|
|
215
|
+
* name: "email",
|
|
216
|
+
* required: true,
|
|
217
|
+
* forCreate: { required: true }
|
|
218
|
+
* }
|
|
219
|
+
* };
|
|
220
|
+
*
|
|
221
|
+
* // TypeScript will catch invalid field types:
|
|
222
|
+
* // const invalidField: Field = { type: "invalid" }; // ✗ Error: "invalid" not in FieldMap
|
|
223
|
+
* ```
|
|
224
|
+
*
|
|
225
|
+
* #### Conditional FieldMeta Extensions
|
|
226
|
+
* ```typescript
|
|
227
|
+
* declare module "reslib/resources/resources" {
|
|
228
|
+
* interface FieldMap {
|
|
229
|
+
* // Conditional field that changes behavior based on context
|
|
230
|
+
* conditional: FieldBase<"conditional", any> & {
|
|
231
|
+
* condition: (context: any) => boolean;
|
|
232
|
+
* trueField: keyof FieldMap;
|
|
233
|
+
* falseField: keyof FieldMap;
|
|
234
|
+
* };
|
|
235
|
+
* }
|
|
236
|
+
* }
|
|
237
|
+
* ```
|
|
238
|
+
*
|
|
239
|
+
* ### Best Practices
|
|
240
|
+
* - **Consistent Naming**: Use lowercase strings for field type keys (e.g., "text", "number").
|
|
241
|
+
* - **Extensive Properties**: Add as many type-specific properties as needed for your use case.
|
|
242
|
+
* - **Documentation**: Document custom field types clearly for team members.
|
|
243
|
+
* - **Versioning**: Consider the impact on existing field definitions when adding new properties.
|
|
244
|
+
* - **Validation**: Use TypeScript's type system to enforce field constraints at compile time.
|
|
245
|
+
*
|
|
246
|
+
* ### Integration with Other Types
|
|
247
|
+
* - **Field<T>**: Uses `FieldMap[T]` to create type-safe field configurations.
|
|
248
|
+
* - **FieldType**: Derives from `keyof FieldMap` to constrain valid field types.
|
|
249
|
+
* - **FieldBase**: Provides the base structure that all field definitions must extend.
|
|
250
|
+
*
|
|
251
|
+
* ### Migration Notes
|
|
252
|
+
* If you're upgrading from a version where field types were defined differently,
|
|
253
|
+
* update your module augmentations to use this new structure for better type safety.
|
|
254
|
+
*
|
|
255
|
+
* @interface FieldMap
|
|
256
|
+
* @public
|
|
257
|
+
*
|
|
258
|
+
* @see {@link FieldBase} - The base interface that field definitions must extend
|
|
259
|
+
* @see {@link Field} - How field configurations are created from this map
|
|
260
|
+
* @see {@link FieldType} - The union type of all defined field types
|
|
261
|
+
* @example
|
|
262
|
+
* ```typescript
|
|
263
|
+
* // Complete example of extending FieldMap
|
|
264
|
+
* declare module "reslib/resources/resources" {
|
|
265
|
+
* interface FieldMap {
|
|
266
|
+
* text: FieldBase<"text", string> & {
|
|
267
|
+
* minLength?: number;
|
|
268
|
+
* maxLength?: number;
|
|
269
|
+
* placeholder?: string;
|
|
270
|
+
* };
|
|
271
|
+
*
|
|
272
|
+
* number: FieldBase<"number", number> & {
|
|
273
|
+
* min?: number;
|
|
274
|
+
* max?: number;
|
|
275
|
+
* step?: number;
|
|
276
|
+
* };
|
|
277
|
+
*
|
|
278
|
+
* boolean: FieldBase<"boolean", boolean>;
|
|
279
|
+
*
|
|
280
|
+
* date: FieldBase<"date", Date> & {
|
|
281
|
+
* format?: string;
|
|
282
|
+
* };
|
|
283
|
+
*
|
|
284
|
+
* select: FieldBase<"select", string> & {
|
|
285
|
+
* options: Array<{ label: string; value: any }>;
|
|
286
|
+
* multiple?: boolean;
|
|
287
|
+
* };
|
|
288
|
+
* }
|
|
289
|
+
* }
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
export interface FieldMap {
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* ## FieldActionsMap Interface
|
|
296
|
+
*
|
|
297
|
+
* A global interface defining the action contexts available for field configurations.
|
|
298
|
+
* This interface serves as a central registry for all possible field action types
|
|
299
|
+
* that can have different validation rules, requirements, or behaviors.
|
|
300
|
+
*
|
|
301
|
+
* ### Purpose
|
|
302
|
+
* The `FieldActionsMap` interface defines the different contexts in which fields
|
|
303
|
+
* can be used within the resource system. Each key represents an action context
|
|
304
|
+
* (e.g., "create", "update", "filter"), and the corresponding string value is used
|
|
305
|
+
* for type-level operations and transformations.
|
|
306
|
+
*
|
|
307
|
+
* ### How It Works
|
|
308
|
+
* - **Action Contexts**: Defines the different operations where field behavior might differ
|
|
309
|
+
* - **Type Generation**: Used by `Field<T>` to generate action-specific field overrides
|
|
310
|
+
* like `forCreate`, `forUpdate`, `forCreateOrUpdate`, and `forFilter`
|
|
311
|
+
* - **Module Augmentation**: Can be extended via `declare module` for custom action contexts
|
|
312
|
+
* - **String Values**: The string values are primarily used for TypeScript type manipulation
|
|
313
|
+
*
|
|
314
|
+
* ### Default Action Contexts
|
|
315
|
+
*
|
|
316
|
+
* - **create**: FieldMeta configuration when creating new resource instances
|
|
317
|
+
* - **update**: FieldMeta configuration when updating existing resource instances
|
|
318
|
+
* - **createOrUpdate**: FieldMeta configuration for operations that can create or update
|
|
319
|
+
* - **filter**: FieldMeta configuration for filtering/searching resource instances
|
|
320
|
+
*
|
|
321
|
+
* ### Template Parameters
|
|
322
|
+
* This interface doesn't use generic parameters itself, but serves as a foundation
|
|
323
|
+
* for generating action-specific field configurations.
|
|
324
|
+
*
|
|
325
|
+
* ### Examples
|
|
326
|
+
*
|
|
327
|
+
* #### Basic Usage in FieldMeta Definitions
|
|
328
|
+
* ```typescript
|
|
329
|
+
* // Fields can have different configurations for different actions
|
|
330
|
+
* const userFields: Fields = {
|
|
331
|
+
* password: {
|
|
332
|
+
* type: "text",
|
|
333
|
+
* required: true,
|
|
334
|
+
* minLength: 8,
|
|
335
|
+
* // Different requirements for create vs update
|
|
336
|
+
* forCreate: { required: true }, // Password required when creating
|
|
337
|
+
* forUpdate: { required: false } // Password optional when updating
|
|
338
|
+
* },
|
|
339
|
+
* email: {
|
|
340
|
+
* type: "email",
|
|
341
|
+
* required: true,
|
|
342
|
+
* unique: true,
|
|
343
|
+
* // Email validation differs by context
|
|
344
|
+
* forCreate: { required: true, unique: true },
|
|
345
|
+
* forUpdate: { required: true, unique: true },
|
|
346
|
+
* forFilter: { required: false } // Email optional for filtering
|
|
347
|
+
* }
|
|
348
|
+
* };
|
|
349
|
+
* ```
|
|
350
|
+
*
|
|
351
|
+
* #### Module Augmentation for Custom Actions
|
|
352
|
+
* ```typescript
|
|
353
|
+
* // In your application's types file
|
|
354
|
+
* import "reslib/resources/resources";
|
|
355
|
+
*
|
|
356
|
+
* declare module "reslib/resources/resources" {
|
|
357
|
+
* interface FieldActionsMap {
|
|
358
|
+
* // Add custom action contexts
|
|
359
|
+
* import: string; // For bulk import operations
|
|
360
|
+
* export: string; // For data export configurations
|
|
361
|
+
* validate: string; // For validation-only contexts
|
|
362
|
+
* preview: string; // For preview/display contexts
|
|
363
|
+
* }
|
|
364
|
+
* }
|
|
365
|
+
*
|
|
366
|
+
* // Now you can use these in field definitions
|
|
367
|
+
* const productFields: Fields = {
|
|
368
|
+
* sku: {
|
|
369
|
+
* type: "text",
|
|
370
|
+
* required: true,
|
|
371
|
+
* forCreate: { required: true },
|
|
372
|
+
* forUpdate: { required: true },
|
|
373
|
+
* forImport: { required: true, pattern: "^[A-Z0-9]+$" }, // Custom validation for imports
|
|
374
|
+
* forExport: { required: false } // SKU optional in exports
|
|
375
|
+
* }
|
|
376
|
+
* };
|
|
377
|
+
* ```
|
|
378
|
+
*
|
|
379
|
+
* #### Advanced FieldMeta Behavior Overrides
|
|
380
|
+
* ```typescript
|
|
381
|
+
* declare module "reslib/resources/resources" {
|
|
382
|
+
* interface FieldActionsMap {
|
|
383
|
+
* bulkUpdate: string;
|
|
384
|
+
* softDelete: string;
|
|
385
|
+
* archive: string;
|
|
386
|
+
* }
|
|
387
|
+
* }
|
|
388
|
+
*
|
|
389
|
+
* const documentFields: Fields = {
|
|
390
|
+
* status: {
|
|
391
|
+
* type: "select",
|
|
392
|
+
* options: ["draft", "published", "archived"],
|
|
393
|
+
* required: true,
|
|
394
|
+
* forCreate: { required: true, defaultValue: "draft" },
|
|
395
|
+
* forUpdate: { required: true },
|
|
396
|
+
* forBulkUpdate: { required: false }, // Optional in bulk operations
|
|
397
|
+
* forSoftDelete: { readOnly: true }, // Can't change status when soft deleting
|
|
398
|
+
* forArchive: { required: true, allowedValues: ["archived"] } // Only allow archive status
|
|
399
|
+
* },
|
|
400
|
+
* deletedAt: {
|
|
401
|
+
* type: "date",
|
|
402
|
+
* readOnly: true,
|
|
403
|
+
* forSoftDelete: { required: true }, // Must set deletion timestamp
|
|
404
|
+
* forArchive: { required: false } // Optional for archiving
|
|
405
|
+
* }
|
|
406
|
+
* };
|
|
407
|
+
* ```
|
|
408
|
+
*
|
|
409
|
+
* #### Conditional FieldMeta Requirements
|
|
410
|
+
* ```typescript
|
|
411
|
+
* const orderFields: Fields = {
|
|
412
|
+
* paymentMethod: {
|
|
413
|
+
* type: "select",
|
|
414
|
+
* options: ["credit_card", "paypal", "bank_transfer"],
|
|
415
|
+
* forCreate: { required: true },
|
|
416
|
+
* forUpdate: { required: false }, // Can't change payment method after creation
|
|
417
|
+
* forFilter: { required: false }
|
|
418
|
+
* },
|
|
419
|
+
* creditCardNumber: {
|
|
420
|
+
* type: "text",
|
|
421
|
+
* pattern: "^\\d{16}$",
|
|
422
|
+
* forCreate: {
|
|
423
|
+
* required: true,
|
|
424
|
+
* // Only required if payment method is credit card
|
|
425
|
+
* conditionalRequired: (data) => data.paymentMethod === "credit_card"
|
|
426
|
+
* },
|
|
427
|
+
* forUpdate: { readOnly: true }, // Never updatable for security
|
|
428
|
+
* forFilter: { required: false }
|
|
429
|
+
* }
|
|
430
|
+
* };
|
|
431
|
+
* ```
|
|
432
|
+
*
|
|
433
|
+
* ### Best Practices
|
|
434
|
+
* - **Consistent Naming**: Use lowercase action names (e.g., "create", "update")
|
|
435
|
+
* - **Semantic Actions**: Choose action names that clearly describe their purpose
|
|
436
|
+
* - **Minimal Overrides**: Only override field properties when behavior genuinely differs
|
|
437
|
+
* - **Security Considerations**: Use action contexts to enforce security rules
|
|
438
|
+
* (e.g., making fields read-only in certain contexts)
|
|
439
|
+
* - **Documentation**: Document custom action contexts for team members
|
|
440
|
+
*
|
|
441
|
+
* ### Integration with Other Types
|
|
442
|
+
* - **Field<T>**: Uses `FieldActionsMap` to generate action-specific properties
|
|
443
|
+
* - **FieldMap**: Provides the base field configurations that can be overridden
|
|
444
|
+
* - **UppercaseFirst**: Used to transform action names (e.g., "create" → "Create" → "forCreate")
|
|
445
|
+
*
|
|
446
|
+
* ### Migration Notes
|
|
447
|
+
* When adding new action contexts, ensure that existing field definitions are
|
|
448
|
+
* compatible. Consider providing default behaviors for new actions to maintain
|
|
449
|
+
* backward compatibility.
|
|
450
|
+
*
|
|
451
|
+
* @interface FieldActionsMap
|
|
452
|
+
* @public
|
|
453
|
+
*
|
|
454
|
+
* @see {@link Field} - How action contexts are used in field definitions
|
|
455
|
+
* @see {@link FieldMap} - The base field configurations that can be overridden
|
|
456
|
+
* @see {@link UppercaseFirst} - Utility for transforming action names
|
|
457
|
+
* @example
|
|
458
|
+
* ```typescript
|
|
459
|
+
* // Complete example of extending FieldActionsMap
|
|
460
|
+
* declare module "reslib/resources/resources" {
|
|
461
|
+
* interface FieldActionsMap {
|
|
462
|
+
* // Standard CRUD operations
|
|
463
|
+
* create: string;
|
|
464
|
+
* update: string;
|
|
465
|
+
* createOrUpdate: string;
|
|
466
|
+
* filter: string;
|
|
467
|
+
*
|
|
468
|
+
* // Custom business logic actions
|
|
469
|
+
* approve: string; // For approval workflows
|
|
470
|
+
* reject: string; // For rejection workflows
|
|
471
|
+
* publish: string; // For content publishing
|
|
472
|
+
* archive: string; // For archiving operations
|
|
473
|
+
*
|
|
474
|
+
* // Data operations
|
|
475
|
+
* import: string; // For bulk import
|
|
476
|
+
* export: string; // For data export
|
|
477
|
+
* migrate: string; // For data migration
|
|
478
|
+
*
|
|
479
|
+
* // Administrative actions
|
|
480
|
+
* adminUpdate: string; // For admin-only updates
|
|
481
|
+
* systemUpdate: string; // For system-generated updates
|
|
482
|
+
* }
|
|
483
|
+
* }
|
|
484
|
+
*
|
|
485
|
+
* // Usage in field definitions
|
|
486
|
+
* const contentFields: Fields = {
|
|
487
|
+
* publishedAt: {
|
|
488
|
+
* type: "date",
|
|
489
|
+
* readOnly: true,
|
|
490
|
+
* forCreate: { required: false },
|
|
491
|
+
* forPublish: { required: true }, // Must set when publishing
|
|
492
|
+
* forUpdate: { required: false },
|
|
493
|
+
* forAdminUpdate: { required: false } // Admins can modify
|
|
494
|
+
* },
|
|
495
|
+
* approvalStatus: {
|
|
496
|
+
* type: "select",
|
|
497
|
+
* options: ["pending", "approved", "rejected"],
|
|
498
|
+
* defaultValue: "pending",
|
|
499
|
+
* forCreate: { required: false },
|
|
500
|
+
* forApprove: { required: true, allowedValues: ["approved"] },
|
|
501
|
+
* forReject: { required: true, allowedValues: ["rejected"] },
|
|
502
|
+
* forUpdate: { readOnly: true } // Status changes through specific actions
|
|
503
|
+
* }
|
|
504
|
+
* };
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
export interface FieldActionsMap {
|
|
508
|
+
create: string;
|
|
509
|
+
update: string;
|
|
510
|
+
createOrUpdate: string;
|
|
511
|
+
filter: string;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* ## Field Type
|
|
515
|
+
*
|
|
516
|
+
* The core type representing a field configuration in the resource system.
|
|
517
|
+
* This conditional type combines base field properties with action-specific overrides,
|
|
518
|
+
* enabling flexible and type-safe field definitions across different operations.
|
|
519
|
+
*
|
|
520
|
+
* ### Purpose
|
|
521
|
+
* The `Field` type serves as the primary interface for defining fields in resource schemas.
|
|
522
|
+
* It provides a structured way to specify field behavior, validation rules, and operation-specific
|
|
523
|
+
* customizations, ensuring type safety and consistency throughout the application.
|
|
524
|
+
*
|
|
525
|
+
* ### How It Works
|
|
526
|
+
* This type uses TypeScript's conditional types and mapped types to dynamically construct
|
|
527
|
+
* field configurations based on the field type and available actions:
|
|
528
|
+
*
|
|
529
|
+
* 1. **Base Configuration**: Inherits all properties from the specific field type in `FieldMap[T]`
|
|
530
|
+
* 2. **Action Overrides**: Adds optional properties for each action in `FieldActionsMap`
|
|
531
|
+
* 3. **Type Safety**: Ensures only valid field types and action contexts are used
|
|
532
|
+
* 4. **Extensibility**: Supports module augmentation for custom field types and actions
|
|
533
|
+
*
|
|
534
|
+
* ### Template Parameters
|
|
535
|
+
* - **T**: The field type (extends `FieldType`, which is `keyof FieldMap`). Defaults to `FieldType`.
|
|
536
|
+
*
|
|
537
|
+
* ### Type Construction
|
|
538
|
+
* ```typescript
|
|
539
|
+
* Field<T> = FieldMap[T] extends FieldBase
|
|
540
|
+
* ? FieldMap[T] & {
|
|
541
|
+
* [key in keyof FieldActionsMap as `for${UppercaseFirst<key>}`]?: Partial<FieldMap[keyof FieldMap]>
|
|
542
|
+
* }
|
|
543
|
+
* : never
|
|
544
|
+
* ```
|
|
545
|
+
*
|
|
546
|
+
* This creates a type that includes:
|
|
547
|
+
* - All base properties from `FieldMap[T]` (e.g., `type`, `name`, `required`, field-specific properties)
|
|
548
|
+
* - Optional action-specific overrides like `forCreate`, `forUpdate`, `forFilter`, etc.
|
|
549
|
+
*
|
|
550
|
+
* ### Examples
|
|
551
|
+
*
|
|
552
|
+
* #### Basic Field Definition
|
|
553
|
+
* ```typescript
|
|
554
|
+
* // Define a text field with basic properties
|
|
555
|
+
* const usernameField: Field<"text"> = {
|
|
556
|
+
* type: "text",
|
|
557
|
+
* name: "username",
|
|
558
|
+
* required: true,
|
|
559
|
+
* minLength: 3,
|
|
560
|
+
* maxLength: 50,
|
|
561
|
+
* placeholder: "Enter username"
|
|
562
|
+
* };
|
|
563
|
+
* ```
|
|
564
|
+
*
|
|
565
|
+
* #### Field with Action-Specific Overrides
|
|
566
|
+
* ```typescript
|
|
567
|
+
* // Field that behaves differently for different actions
|
|
568
|
+
* const passwordField: Field<"text"> = {
|
|
569
|
+
* type: "text",
|
|
570
|
+
* name: "password",
|
|
571
|
+
* // Base configuration
|
|
572
|
+
* minLength: 8,
|
|
573
|
+
* pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$",
|
|
574
|
+
*
|
|
575
|
+
* // Action-specific overrides
|
|
576
|
+
* forCreate: {
|
|
577
|
+
* required: true, // Password required when creating
|
|
578
|
+
* minLength: 8 // Minimum length for creation
|
|
579
|
+
* },
|
|
580
|
+
* forUpdate: {
|
|
581
|
+
* required: false, // Password optional when updating
|
|
582
|
+
* minLength: 0 // No minimum when updating (keep existing)
|
|
583
|
+
* },
|
|
584
|
+
* forFilter: {
|
|
585
|
+
* required: false, // Not used in filtering
|
|
586
|
+
* minLength: 0
|
|
587
|
+
* }
|
|
588
|
+
* };
|
|
589
|
+
* ```
|
|
590
|
+
*
|
|
591
|
+
* #### Complex Field with Multiple Overrides
|
|
592
|
+
* ```typescript
|
|
593
|
+
* const statusField: Field<"select"> = {
|
|
594
|
+
* type: "select",
|
|
595
|
+
* name: "status",
|
|
596
|
+
* required: true,
|
|
597
|
+
* options: ["draft", "published", "archived"],
|
|
598
|
+
* defaultValue: "draft",
|
|
599
|
+
*
|
|
600
|
+
* // Different validation rules per action
|
|
601
|
+
* forCreate: {
|
|
602
|
+
* required: false, // Optional during creation
|
|
603
|
+
* defaultValue: "draft" // Default to draft
|
|
604
|
+
* },
|
|
605
|
+
* forUpdate: {
|
|
606
|
+
* required: true, // Required during updates
|
|
607
|
+
* allowedValues: ["published", "archived"] // Can't go back to draft
|
|
608
|
+
* },
|
|
609
|
+
* forFilter: {
|
|
610
|
+
* required: false, // Optional in filters
|
|
611
|
+
* multiple: true // Allow multiple values in filters
|
|
612
|
+
* }
|
|
613
|
+
* };
|
|
614
|
+
* ```
|
|
615
|
+
*
|
|
616
|
+
* #### Field Collection Usage
|
|
617
|
+
* ```typescript
|
|
618
|
+
* // Using fields in a resource definition
|
|
619
|
+
* const userFields: Fields = {
|
|
620
|
+
* id: {
|
|
621
|
+
* type: "uuid",
|
|
622
|
+
* name: "id",
|
|
623
|
+
* primaryKey: true,
|
|
624
|
+
* readOnly: true
|
|
625
|
+
* },
|
|
626
|
+
* username: {
|
|
627
|
+
* type: "text",
|
|
628
|
+
* name: "username",
|
|
629
|
+
* required: true,
|
|
630
|
+
* minLength: 3,
|
|
631
|
+
* maxLength: 50,
|
|
632
|
+
* forCreate: { required: true },
|
|
633
|
+
* forUpdate: { required: true }
|
|
634
|
+
* },
|
|
635
|
+
* email: {
|
|
636
|
+
* type: "email",
|
|
637
|
+
* name: "email",
|
|
638
|
+
* required: true,
|
|
639
|
+
* unique: true,
|
|
640
|
+
* forCreate: { required: true, unique: true },
|
|
641
|
+
* forUpdate: { required: true, unique: true }
|
|
642
|
+
* },
|
|
643
|
+
* role: {
|
|
644
|
+
* type: "select",
|
|
645
|
+
* name: "role",
|
|
646
|
+
* options: ["admin", "user", "moderator"],
|
|
647
|
+
* defaultValue: "user",
|
|
648
|
+
* forCreate: { required: false },
|
|
649
|
+
* forUpdate: { required: true, allowedValues: ["admin", "user"] }
|
|
650
|
+
* }
|
|
651
|
+
* };
|
|
652
|
+
* ```
|
|
653
|
+
*
|
|
654
|
+
* #### Type-Safe Field Access
|
|
655
|
+
* ```typescript
|
|
656
|
+
* // TypeScript ensures type safety
|
|
657
|
+
* function processField<T extends FieldType>(field: Field<T>) {
|
|
658
|
+
* // field.type is guaranteed to be T
|
|
659
|
+
* // field.name is always available
|
|
660
|
+
* // Action overrides are optional and type-safe
|
|
661
|
+
*
|
|
662
|
+
* if (field.forCreate) {
|
|
663
|
+
* console.log("Create-specific config:", field.forCreate);
|
|
664
|
+
* }
|
|
665
|
+
*
|
|
666
|
+
* if (field.forUpdate) {
|
|
667
|
+
* console.log("Update-specific config:", field.forUpdate);
|
|
668
|
+
* }
|
|
669
|
+
* }
|
|
670
|
+
* ```
|
|
671
|
+
*
|
|
672
|
+
* #### Advanced Usage with Custom Field Types
|
|
673
|
+
* ```typescript
|
|
674
|
+
* // After extending FieldMap with custom types
|
|
675
|
+
* declare module "reslib/resources/resources" {
|
|
676
|
+
* interface FieldMap {
|
|
677
|
+
* richText: FieldBase<"richText", string> & {
|
|
678
|
+
* toolbar: string[];
|
|
679
|
+
* maxLength: number;
|
|
680
|
+
* allowedFormats: string[];
|
|
681
|
+
* };
|
|
682
|
+
* dateRange: FieldBase<"dateRange", { start: Date; end: Date }> & {
|
|
683
|
+
* minDate?: Date;
|
|
684
|
+
* maxDate?: Date;
|
|
685
|
+
* format: string;
|
|
686
|
+
* };
|
|
687
|
+
* }
|
|
688
|
+
* }
|
|
689
|
+
*
|
|
690
|
+
* // Now you can use these custom types
|
|
691
|
+
* const contentField: Field<"richText"> = {
|
|
692
|
+
* type: "richText",
|
|
693
|
+
* name: "content",
|
|
694
|
+
* toolbar: ["bold", "italic", "link"],
|
|
695
|
+
* maxLength: 10000,
|
|
696
|
+
* allowedFormats: ["html", "markdown"],
|
|
697
|
+
* forCreate: { required: true },
|
|
698
|
+
* forUpdate: { required: false }
|
|
699
|
+
* };
|
|
700
|
+
*
|
|
701
|
+
* const dateRangeField: Field<"dateRange"> = {
|
|
702
|
+
* type: "dateRange",
|
|
703
|
+
* name: "eventDates",
|
|
704
|
+
* format: "YYYY-MM-DD",
|
|
705
|
+
* minDate: new Date(),
|
|
706
|
+
* forFilter: { required: false }
|
|
707
|
+
* };
|
|
708
|
+
* ```
|
|
709
|
+
*
|
|
710
|
+
* ### Action-Specific Properties
|
|
711
|
+
* The `Field` type automatically generates properties for each action defined in `FieldActionsMap`:
|
|
712
|
+
*
|
|
713
|
+
* - **`forCreate`**: Configuration when creating new resource instances
|
|
714
|
+
* - **`forUpdate`**: Configuration when updating existing resource instances
|
|
715
|
+
* - **`forCreateOrUpdate`**: Configuration for operations that can create or update
|
|
716
|
+
* - **`forFilter`**: Configuration for filtering/searching resource instances
|
|
717
|
+
*
|
|
718
|
+
* Each action property is optional and can contain any subset of properties from any field type,
|
|
719
|
+
* allowing flexible customization of field behavior per operation.
|
|
720
|
+
*
|
|
721
|
+
* ### Best Practices
|
|
722
|
+
* - **Consistent Base Properties**: Define common properties (like `name`, `type`) at the base level
|
|
723
|
+
* - **Minimal Overrides**: Only specify action-specific properties when behavior genuinely differs
|
|
724
|
+
* - **Type Safety**: Leverage TypeScript's inference for field types and action properties
|
|
725
|
+
* - **Documentation**: Document custom field types and their action-specific behaviors
|
|
726
|
+
* - **Validation**: Use action overrides to enforce security rules and business logic
|
|
727
|
+
*
|
|
728
|
+
* ### Integration with Other Types
|
|
729
|
+
* - **`FieldMap`**: Provides the base field type definitions
|
|
730
|
+
* - **`FieldActionsMap`**: Defines available action contexts
|
|
731
|
+
* - **`Fields`**: Collection of fields for resource definitions
|
|
732
|
+
* - **`FieldBase`**: Common base properties for all field types
|
|
733
|
+
* - **`FieldType`**: Union of all valid field type keys
|
|
734
|
+
*
|
|
735
|
+
* ### Migration Notes
|
|
736
|
+
* When upgrading from simpler field definitions, gradually add action-specific overrides
|
|
737
|
+
* to maintain backward compatibility. The base properties remain available and functional
|
|
738
|
+
* even when action overrides are not specified.
|
|
739
|
+
*
|
|
740
|
+
* @type Field
|
|
741
|
+
* @template T - The field type (must be a key of FieldMap)
|
|
742
|
+
* @default FieldType
|
|
743
|
+
* @public
|
|
744
|
+
*
|
|
745
|
+
* @see {@link FieldMap} - The interface defining field type configurations
|
|
746
|
+
* @see {@link FieldActionsMap} - The interface defining action contexts
|
|
747
|
+
* @see {@link FieldBase} - The base properties shared by all field types
|
|
748
|
+
* @see {@link Fields} - How fields are collected into resource schemas
|
|
749
|
+
* @example
|
|
750
|
+
* ```typescript
|
|
751
|
+
* // Complete example of field definitions with action overrides
|
|
752
|
+
* import "reslib/resources/resources";
|
|
753
|
+
*
|
|
754
|
+
* declare module "reslib/resources/resources" {
|
|
755
|
+
* interface FieldMap {
|
|
756
|
+
* text: FieldBase<"text", string> & {
|
|
757
|
+
* minLength?: number;
|
|
758
|
+
* maxLength?: number;
|
|
759
|
+
* pattern?: RegExp;
|
|
760
|
+
* placeholder?: string;
|
|
761
|
+
* };
|
|
762
|
+
* select: FieldBase<"select", string> & {
|
|
763
|
+
* options: string[];
|
|
764
|
+
* multiple?: boolean;
|
|
765
|
+
* defaultValue?: string;
|
|
766
|
+
* };
|
|
767
|
+
* number: FieldBase<"number", number> & {
|
|
768
|
+
* min?: number;
|
|
769
|
+
* max?: number;
|
|
770
|
+
* step?: number;
|
|
771
|
+
* };
|
|
772
|
+
* }
|
|
773
|
+
* }
|
|
774
|
+
*
|
|
775
|
+
* // Define a comprehensive user resource schema
|
|
776
|
+
* const userFields: Fields = {
|
|
777
|
+
* username: {
|
|
778
|
+
* type: "text",
|
|
779
|
+
* name: "username",
|
|
780
|
+
* required: true,
|
|
781
|
+
* minLength: 3,
|
|
782
|
+
* maxLength: 50,
|
|
783
|
+
* pattern: /^[a-zA-Z0-9_]+$/,
|
|
784
|
+
* forCreate: { required: true },
|
|
785
|
+
* forUpdate: { required: true },
|
|
786
|
+
* forFilter: { required: false }
|
|
787
|
+
* } as Field<"text">,
|
|
788
|
+
*
|
|
789
|
+
* email: {
|
|
790
|
+
* type: "email",
|
|
791
|
+
* name: "email",
|
|
792
|
+
* required: true,
|
|
793
|
+
* forCreate: { required: true },
|
|
794
|
+
* forUpdate: { required: true },
|
|
795
|
+
* forFilter: { required: false }
|
|
796
|
+
* } as Field<"text">, // email extends text with validation
|
|
797
|
+
*
|
|
798
|
+
* role: {
|
|
799
|
+
* type: "select",
|
|
800
|
+
* name: "role",
|
|
801
|
+
* options: ["admin", "user", "moderator"],
|
|
802
|
+
* defaultValue: "user",
|
|
803
|
+
* forCreate: { required: false },
|
|
804
|
+
* forUpdate: { required: true },
|
|
805
|
+
* forFilter: { required: false, multiple: true }
|
|
806
|
+
* } as Field<"select">,
|
|
807
|
+
*
|
|
808
|
+
* age: {
|
|
809
|
+
* type: "number",
|
|
810
|
+
* name: "age",
|
|
811
|
+
* min: 0,
|
|
812
|
+
* max: 150,
|
|
813
|
+
* forCreate: { required: false },
|
|
814
|
+
* forUpdate: { required: false },
|
|
815
|
+
* forFilter: { required: false, min: 18, max: 100 }
|
|
816
|
+
* } as Field<"number">
|
|
817
|
+
* };
|
|
818
|
+
*
|
|
819
|
+
* // Type-safe usage
|
|
820
|
+
* function validateField(field: Field, action: keyof FieldActionsMap, value: any): boolean {
|
|
821
|
+
* const actionConfig = field[`for${action.charAt(0).toUpperCase() + action.slice(1)}` as keyof Field];
|
|
822
|
+
*
|
|
823
|
+
* if (actionConfig && 'required' in actionConfig) {
|
|
824
|
+
* if (actionConfig.required && (value === undefined || value === null)) {
|
|
825
|
+
* return false;
|
|
826
|
+
* }
|
|
827
|
+
* }
|
|
828
|
+
*
|
|
829
|
+
* // Additional validation logic...
|
|
830
|
+
* return true;
|
|
831
|
+
* }
|
|
832
|
+
* ```
|
|
833
|
+
*/
|
|
834
|
+
export type Field<T extends FieldType = FieldType> = FieldMap[T] extends FieldBase ? FieldMap[T] & {
|
|
835
|
+
[key in keyof FieldActionsMap as `for${UppercaseFirst<key>}`]?: Partial<FieldMap[keyof FieldMap]>;
|
|
836
|
+
} : never;
|
|
837
|
+
/**
|
|
838
|
+
* Type representing a collection of fields where each key is a string and each value is an Field.
|
|
839
|
+
* This type is used to define the structure of fields for a resource or form.
|
|
840
|
+
*
|
|
841
|
+
* This type provides a flexible way to define multiple fields with their configurations,
|
|
842
|
+
* validation rules, and action-specific overrides. It's commonly used when defining
|
|
843
|
+
* the schema for resources, forms, or data validation.
|
|
844
|
+
*
|
|
845
|
+
* @type Fields
|
|
846
|
+
*
|
|
847
|
+
* @example
|
|
848
|
+
* ```typescript
|
|
849
|
+
* // Basic field collection for a user resource
|
|
850
|
+
* const userFields: Fields = {
|
|
851
|
+
* username: {
|
|
852
|
+
* type: "text",
|
|
853
|
+
* name: "username",
|
|
854
|
+
* required: true,
|
|
855
|
+
* minLength: 3,
|
|
856
|
+
* maxLength: 50,
|
|
857
|
+
* forCreate: { required: true },
|
|
858
|
+
* forUpdate: { required: false }
|
|
859
|
+
* },
|
|
860
|
+
* email: {
|
|
861
|
+
* type: "email",
|
|
862
|
+
* name: "email",
|
|
863
|
+
* required: true,
|
|
864
|
+
* unique: true,
|
|
865
|
+
* forCreate: { required: true },
|
|
866
|
+
* forUpdate: { required: true }
|
|
867
|
+
* },
|
|
868
|
+
* age: {
|
|
869
|
+
* type: "number",
|
|
870
|
+
* name: "age",
|
|
871
|
+
* required: false,
|
|
872
|
+
* min: 0,
|
|
873
|
+
* max: 150,
|
|
874
|
+
* forFilter: { allowRange: true }
|
|
875
|
+
* }
|
|
876
|
+
* };
|
|
877
|
+
* ```
|
|
878
|
+
*
|
|
879
|
+
* @example
|
|
880
|
+
* ```typescript
|
|
881
|
+
* // Dynamic field collection based on user permissions
|
|
882
|
+
* function createFieldsForUser(userRole: string): Fields {
|
|
883
|
+
* const baseFields: Fields = {
|
|
884
|
+
* id: { type: "uuid", name: "id", primaryKey: true, readOnly: true },
|
|
885
|
+
* createdAt: { type: "date", name: "createdAt", readOnly: true },
|
|
886
|
+
* updatedAt: { type: "date", name: "updatedAt", readOnly: true }
|
|
887
|
+
* };
|
|
888
|
+
*
|
|
889
|
+
* if (userRole === 'admin') {
|
|
890
|
+
* return {
|
|
891
|
+
* ...baseFields,
|
|
892
|
+
* adminNotes: {
|
|
893
|
+
* type: "textarea",
|
|
894
|
+
* name: "adminNotes",
|
|
895
|
+
* required: false,
|
|
896
|
+
* maxLength: 1000
|
|
897
|
+
* },
|
|
898
|
+
* isActive: {
|
|
899
|
+
* type: "boolean",
|
|
900
|
+
* name: "isActive",
|
|
901
|
+
* required: true,
|
|
902
|
+
* defaultValue: true
|
|
903
|
+
* }
|
|
904
|
+
* };
|
|
905
|
+
* }
|
|
906
|
+
*
|
|
907
|
+
* return baseFields;
|
|
908
|
+
* }
|
|
909
|
+
* ```
|
|
910
|
+
*
|
|
911
|
+
* @example
|
|
912
|
+
* ```typescript
|
|
913
|
+
* // Form validation using field collection
|
|
914
|
+
* function validateFormData(fields: Fields, data: Record<string, any>): ValidationResult {
|
|
915
|
+
* const errors: string[] = [];
|
|
916
|
+
*
|
|
917
|
+
* for (const [fieldName, fieldConfig] of Object.entries(fields)) {
|
|
918
|
+
* const value = data[fieldName];
|
|
919
|
+
*
|
|
920
|
+
* // Check required fields
|
|
921
|
+
* if (fieldConfig.required && (value === undefined || value === null || value === '')) {
|
|
922
|
+
* errors.push(`${fieldConfig.name || fieldName} is required`);
|
|
923
|
+
* }
|
|
924
|
+
*
|
|
925
|
+
* // Check string length constraints
|
|
926
|
+
* if (typeof value === 'string') {
|
|
927
|
+
* if (fieldConfig.minLength && value.length < fieldConfig.minLength) {
|
|
928
|
+
* errors.push(`${fieldName} must be at least ${fieldConfig.minLength} characters`);
|
|
929
|
+
* }
|
|
930
|
+
* if (fieldConfig.maxLength && value.length > fieldConfig.maxLength) {
|
|
931
|
+
* errors.push(`${fieldName} must be at most ${fieldConfig.maxLength} characters`);
|
|
932
|
+
* }
|
|
933
|
+
* }
|
|
934
|
+
* }
|
|
935
|
+
*
|
|
936
|
+
* return { isValid: errors.length === 0, errors };
|
|
937
|
+
* }
|
|
938
|
+
* ```
|
|
939
|
+
*/
|
|
940
|
+
export type Fields = Record<string, Field>;
|
|
941
|
+
/**
|
|
942
|
+
* Type representing the union of all possible field types defined in FieldMap.
|
|
943
|
+
* This type is used to constrain field types to only those defined in the field map.
|
|
944
|
+
*
|
|
945
|
+
* This type ensures type safety by only allowing field types that have been
|
|
946
|
+
* explicitly defined in the FieldMap interface. It prevents typos and ensures
|
|
947
|
+
* that all field types are known and properly configured.
|
|
948
|
+
*
|
|
949
|
+
* @type FieldType
|
|
950
|
+
*
|
|
951
|
+
* @example
|
|
952
|
+
* ```typescript
|
|
953
|
+
* // Basic usage - defining a field with a valid type
|
|
954
|
+
* const textField: FieldBase<"text"> = {
|
|
955
|
+
* type: "text", // ✓ Valid - "text" is in FieldMap
|
|
956
|
+
* name: "username",
|
|
957
|
+
* required: true
|
|
958
|
+
* };
|
|
959
|
+
*
|
|
960
|
+
* const emailField: FieldBase<"email"> = {
|
|
961
|
+
* type: "email", // ✓ Valid - "email" is in FieldMap
|
|
962
|
+
* name: "userEmail",
|
|
963
|
+
* required: true
|
|
964
|
+
* };
|
|
965
|
+
* ```
|
|
966
|
+
*
|
|
967
|
+
* @example
|
|
968
|
+
* ```typescript
|
|
969
|
+
* // Type-safe field type checking
|
|
970
|
+
* function createField<T extends FieldType>(
|
|
971
|
+
* type: T,
|
|
972
|
+
* config: Omit<FieldBase<T>, 'type'>
|
|
973
|
+
* ): FieldBase<T> {
|
|
974
|
+
* return { type, ...config };
|
|
975
|
+
* }
|
|
976
|
+
*
|
|
977
|
+
* // Usage with type safety
|
|
978
|
+
* const numberField = createField("number", {
|
|
979
|
+
* name: "age",
|
|
980
|
+
* required: true,
|
|
981
|
+
* min: 0,
|
|
982
|
+
* max: 150
|
|
983
|
+
* });
|
|
984
|
+
*
|
|
985
|
+
* const dateField = createField("date", {
|
|
986
|
+
* name: "birthDate",
|
|
987
|
+
* required: false
|
|
988
|
+
* });
|
|
989
|
+
*
|
|
990
|
+
* // This would cause a TypeScript error:
|
|
991
|
+
* // const invalidField = createField("invalidType", { name: "test" });
|
|
992
|
+
* // Error: "invalidType" is not assignable to FieldType
|
|
993
|
+
* ```
|
|
994
|
+
*
|
|
995
|
+
* @example
|
|
996
|
+
* ```typescript
|
|
997
|
+
* // Dynamic field type validation
|
|
998
|
+
* function isValidFieldType(type: string): type is FieldType {
|
|
999
|
+
* const validTypes: FieldType[] = ["text", "number", "boolean", "date", "email"];
|
|
1000
|
+
* return validTypes.includes(type as FieldType);
|
|
1001
|
+
* }
|
|
1002
|
+
*
|
|
1003
|
+
* // Usage in runtime validation
|
|
1004
|
+
* function validateFieldType(input: string): FieldType {
|
|
1005
|
+
* if (!isValidFieldType(input)) {
|
|
1006
|
+
* throw new Error(`Invalid field type: ${input}. Valid types are: text, number, boolean, date, email`);
|
|
1007
|
+
* }
|
|
1008
|
+
* return input;
|
|
1009
|
+
* }
|
|
1010
|
+
*
|
|
1011
|
+
* // Safe usage
|
|
1012
|
+
* const fieldType = validateFieldType("text"); // ✓ Valid
|
|
1013
|
+
* // const invalidType = validateFieldType("invalid"); // ✗ Throws error
|
|
1014
|
+
* ```
|
|
1015
|
+
*/
|
|
1016
|
+
export type FieldType = keyof FieldMap;
|
|
1017
|
+
/**
|
|
1018
|
+
* ## Resources Interface
|
|
1019
|
+
*
|
|
1020
|
+
* A global interface for defining resource configurations in the application.
|
|
1021
|
+
* This interface serves as a central registry for all available resources and their
|
|
1022
|
+
* metadata, enabling type-safe resource management across the entire codebase.
|
|
1023
|
+
*
|
|
1024
|
+
* ### Purpose
|
|
1025
|
+
* The `Resources` interface is designed for **module augmentation**, allowing developers
|
|
1026
|
+
* to extend it with custom resource definitions. Each key represents a resource name
|
|
1027
|
+
* (e.g., "users", "posts", "products"), and each value must conform to the `ResourceBase` interface,
|
|
1028
|
+
* ensuring consistent structure and type safety for all resource configurations.
|
|
1029
|
+
*
|
|
1030
|
+
* ### How It Works
|
|
1031
|
+
* - **Empty by Design**: The interface is intentionally empty in the core library.
|
|
1032
|
+
* - **Module Augmentation**: Developers extend it via `declare module` statements.
|
|
1033
|
+
* - **Type Safety**: The `ResourceConfig<T>`, `ResourceName`, and other types use `Resources[K]`
|
|
1034
|
+
* to enforce that resource configurations match their declared types.
|
|
1035
|
+
* - **Extensibility**: New resources can be added without modifying the core library.
|
|
1036
|
+
*
|
|
1037
|
+
* ### Template Parameters
|
|
1038
|
+
* This interface doesn't use generic parameters itself, but the values must conform to `ResourceBase<Name, DataType, TPrimaryKey, Actions>`.
|
|
1039
|
+
*
|
|
1040
|
+
* ### Examples
|
|
1041
|
+
*
|
|
1042
|
+
* #### Basic Module Augmentation
|
|
1043
|
+
* ```typescript
|
|
1044
|
+
* // In your application's types file (e.g., types.ts or global.d.ts)
|
|
1045
|
+
* import "reslib/resources";
|
|
1046
|
+
*
|
|
1047
|
+
* declare module "reslib/resources" {
|
|
1048
|
+
* interface Resources {
|
|
1049
|
+
* // Define a users resource
|
|
1050
|
+
* users: {
|
|
1051
|
+
* actions: {
|
|
1052
|
+
* read: { label: "Read User" };
|
|
1053
|
+
* create: { label: "Create User" };
|
|
1054
|
+
* update: { label: "Update User" };
|
|
1055
|
+
* delete: { label: "Delete User" };
|
|
1056
|
+
* }
|
|
1057
|
+
* };
|
|
1058
|
+
*
|
|
1059
|
+
* // Define a posts resource
|
|
1060
|
+
* posts: {
|
|
1061
|
+
* actions: {
|
|
1062
|
+
* read: { label: "Read Post" };
|
|
1063
|
+
* create: { label: "Create Post" };
|
|
1064
|
+
* publish: { label: "Publish Post" };
|
|
1065
|
+
* archive: { label: "Archive Post" };
|
|
1066
|
+
* }
|
|
1067
|
+
* };
|
|
1068
|
+
* }
|
|
1069
|
+
* }
|
|
1070
|
+
* ```
|
|
1071
|
+
*
|
|
1072
|
+
* #### Advanced Resource Configuration
|
|
1073
|
+
* ```typescript
|
|
1074
|
+
* declare module "reslib/resources" {
|
|
1075
|
+
* interface Resources {
|
|
1076
|
+
* // User resource with comprehensive actions
|
|
1077
|
+
* users: {
|
|
1078
|
+
* actions: {
|
|
1079
|
+
* read: { label: "Read User", title: "View user details" };
|
|
1080
|
+
* create: { label: "Create User", title: "Add new user" };
|
|
1081
|
+
* update: { label: "Update User", title: "Modify user data" };
|
|
1082
|
+
* delete: { label: "Delete User", title: "Remove user" };
|
|
1083
|
+
* activate: { label: "Activate User", title: "Enable user account" };
|
|
1084
|
+
* deactivate: { label: "Deactivate User", title: "Disable user account" };
|
|
1085
|
+
* resetPassword: { label: "Reset Password", title: "Send password reset" };
|
|
1086
|
+
* }
|
|
1087
|
+
* };
|
|
1088
|
+
*
|
|
1089
|
+
* // Product resource with inventory management
|
|
1090
|
+
* products: {
|
|
1091
|
+
* actions: {
|
|
1092
|
+
* read: { label: "View Product" };
|
|
1093
|
+
* create: { label: "Add Product" };
|
|
1094
|
+
* update: { label: "Edit Product" };
|
|
1095
|
+
* delete: { label: "Remove Product" };
|
|
1096
|
+
* restock: { label: "Restock Product" };
|
|
1097
|
+
* discontinue: { label: "Discontinue Product" };
|
|
1098
|
+
* }
|
|
1099
|
+
* };
|
|
1100
|
+
*
|
|
1101
|
+
* // Order resource with workflow actions
|
|
1102
|
+
* orders: {
|
|
1103
|
+
* actions: {
|
|
1104
|
+
* read: { label: "View Order" };
|
|
1105
|
+
* create: { label: "Create Order" };
|
|
1106
|
+
* update: { label: "Update Order" };
|
|
1107
|
+
* cancel: { label: "Cancel Order" };
|
|
1108
|
+
* ship: { label: "Ship Order" };
|
|
1109
|
+
* deliver: { label: "Mark Delivered" };
|
|
1110
|
+
* refund: { label: "Process Refund" };
|
|
1111
|
+
* }
|
|
1112
|
+
* };
|
|
1113
|
+
* }
|
|
1114
|
+
* }
|
|
1115
|
+
* ```
|
|
1116
|
+
*
|
|
1117
|
+
* #### Using Defined Resources
|
|
1118
|
+
* ```typescript
|
|
1119
|
+
* // After augmentation, you can use type-safe resource operations
|
|
1120
|
+
* import { ResourceConfig, ResourceName, ResourceActions } from "reslib/resources";
|
|
1121
|
+
*
|
|
1122
|
+
* // Type-safe resource access
|
|
1123
|
+
* type UserResource = ResourceConfig<"users">;
|
|
1124
|
+
* type PostResource = ResourceConfig<"posts">;
|
|
1125
|
+
*
|
|
1126
|
+
* // Type-safe action names
|
|
1127
|
+
* type UserActionNames = ResourceActionName<"users">;
|
|
1128
|
+
* // Result: "read" | "create" | "update" | "delete" | "activate" | "deactivate" | "resetPassword"
|
|
1129
|
+
*
|
|
1130
|
+
* // Type-safe action access
|
|
1131
|
+
* type UserActions = ResourceActions<"users">;
|
|
1132
|
+
* // Result: Complete actions record for users resource
|
|
1133
|
+
*
|
|
1134
|
+
* // Runtime usage with type safety
|
|
1135
|
+
* function processUserAction(action: UserActionNames, userId: string) {
|
|
1136
|
+
* console.log(`Processing ${action} for user ${userId}`);
|
|
1137
|
+
*
|
|
1138
|
+
* // TypeScript ensures action is a valid user action
|
|
1139
|
+
* switch (action) {
|
|
1140
|
+
* case "create":
|
|
1141
|
+
* return createUser();
|
|
1142
|
+
* case "activate":
|
|
1143
|
+
* return activateUser(userId);
|
|
1144
|
+
* case "resetPassword":
|
|
1145
|
+
* return resetUserPassword(userId);
|
|
1146
|
+
* // TypeScript will catch invalid actions
|
|
1147
|
+
* }
|
|
1148
|
+
* }
|
|
1149
|
+
* ```
|
|
1150
|
+
*
|
|
1151
|
+
* #### Resource Registry Implementation
|
|
1152
|
+
* ```typescript
|
|
1153
|
+
* // Create a type-safe resource registry
|
|
1154
|
+
* class ResourceRegistry {
|
|
1155
|
+
* private resources = new Map<ResourceName, ResourceConfig<ResourceName>>();
|
|
1156
|
+
*
|
|
1157
|
+
* register<TResourceName extends ResourceName>(
|
|
1158
|
+
* name: TResourceName,
|
|
1159
|
+
* config: ResourceConfig<TResourceName>
|
|
1160
|
+
* ) {
|
|
1161
|
+
* this.resources.set(name, config);
|
|
1162
|
+
* }
|
|
1163
|
+
*
|
|
1164
|
+
* get<TResourceName extends ResourceName>(
|
|
1165
|
+
* name: TResourceName
|
|
1166
|
+
* ): ResourceConfig<TResourceName> | undefined {
|
|
1167
|
+
* return this.resources.get(name) as ResourceConfig<TResourceName>;
|
|
1168
|
+
* }
|
|
1169
|
+
*
|
|
1170
|
+
* getAllActions<TResourceName extends ResourceName>(
|
|
1171
|
+
* name: TResourceName
|
|
1172
|
+
* ): ResourceActions<TResourceName> {
|
|
1173
|
+
* const resource = this.get(name);
|
|
1174
|
+
* if (!resource) throw new Error(`Resource ${name} not found`);
|
|
1175
|
+
* return resource.actions;
|
|
1176
|
+
* }
|
|
1177
|
+
* }
|
|
1178
|
+
*
|
|
1179
|
+
* // Usage
|
|
1180
|
+
* const registry = new ResourceRegistry();
|
|
1181
|
+
*
|
|
1182
|
+
* registry.register("users", {
|
|
1183
|
+
* actions: {
|
|
1184
|
+
* read: { label: "Read User" },
|
|
1185
|
+
* create: { label: "Create User" },
|
|
1186
|
+
* update: { label: "Update User" }
|
|
1187
|
+
* }
|
|
1188
|
+
* });
|
|
1189
|
+
*
|
|
1190
|
+
* const userActions = registry.getAllActions("users");
|
|
1191
|
+
* // TypeScript knows this contains only user actions
|
|
1192
|
+
* ```
|
|
1193
|
+
*
|
|
1194
|
+
* #### Permission System Integration
|
|
1195
|
+
* ```typescript
|
|
1196
|
+
* // Define permissions based on resource actions
|
|
1197
|
+
* type Permission<TResourceName extends ResourceName> = {
|
|
1198
|
+
* resource: TResourceName;
|
|
1199
|
+
* action: ResourceActionName<TResourceName>;
|
|
1200
|
+
* granted: boolean;
|
|
1201
|
+
* };
|
|
1202
|
+
*
|
|
1203
|
+
* class PermissionManager {
|
|
1204
|
+
* private permissions = new Map<string, Permission<ResourceName>>();
|
|
1205
|
+
*
|
|
1206
|
+
* grant<TResourceName extends ResourceName>(
|
|
1207
|
+
* resource: TResourceName,
|
|
1208
|
+
* action: ResourceActionName<TResourceName>
|
|
1209
|
+
* ) {
|
|
1210
|
+
* const key = `${resource}:${action}`;
|
|
1211
|
+
* this.permissions.set(key, { resource, action, granted: true });
|
|
1212
|
+
* }
|
|
1213
|
+
*
|
|
1214
|
+
* check<TResourceName extends ResourceName>(
|
|
1215
|
+
* resource: TResourceName,
|
|
1216
|
+
* action: ResourceActionName<TResourceName>
|
|
1217
|
+
* ): boolean {
|
|
1218
|
+
* const key = `${resource}:${action}`;
|
|
1219
|
+
* return this.permissions.get(key)?.granted ?? false;
|
|
1220
|
+
* }
|
|
1221
|
+
* }
|
|
1222
|
+
*
|
|
1223
|
+
* // Usage
|
|
1224
|
+
* const permManager = new PermissionManager();
|
|
1225
|
+
*
|
|
1226
|
+
* permManager.grant("users", "create");
|
|
1227
|
+
* permManager.grant("users", "update");
|
|
1228
|
+
* permManager.grant("posts", "publish");
|
|
1229
|
+
*
|
|
1230
|
+
* // Type-safe permission checks
|
|
1231
|
+
* if (permManager.check("users", "create")) {
|
|
1232
|
+
* // User can create users
|
|
1233
|
+
* }
|
|
1234
|
+
*
|
|
1235
|
+
* // This would cause a TypeScript error:
|
|
1236
|
+
* // permManager.check("users", "invalidAction"); // ✗ Error: not a valid user action
|
|
1237
|
+
* ```
|
|
1238
|
+
*
|
|
1239
|
+
* ### Best Practices
|
|
1240
|
+
* - **Consistent Naming**: Use lowercase, plural resource names (e.g., "users", "products").
|
|
1241
|
+
* - **Comprehensive Actions**: Define all relevant actions for each resource's lifecycle.
|
|
1242
|
+
* - **Documentation**: Document custom resources and their actions clearly for team members.
|
|
1243
|
+
* - **Versioning**: Consider the impact on existing code when adding new resources or actions.
|
|
1244
|
+
* - **Validation**: Use TypeScript's type system to enforce resource constraints at compile time.
|
|
1245
|
+
*
|
|
1246
|
+
* ### Integration with Other Types
|
|
1247
|
+
* - **ResourceConfig<T>**: Provides type-safe access to individual resource configurations.
|
|
1248
|
+
* - **ResourceName**: Union type of all defined resource names.
|
|
1249
|
+
* - **ResourceActions<T>**: Type-safe access to a resource's actions.
|
|
1250
|
+
* - **ResourceActionName<T>**: Union type of action names for a specific resource.
|
|
1251
|
+
* - **ResourceBase**: The base interface that all resource definitions must implement.
|
|
1252
|
+
*
|
|
1253
|
+
* ### Migration Notes
|
|
1254
|
+
* If you're upgrading from a version where resources were defined differently,
|
|
1255
|
+
* update your module augmentations to use this new structure for better type safety.
|
|
1256
|
+
* Existing code using the old structure may need to be updated to match the new interface.
|
|
1257
|
+
*
|
|
1258
|
+
* @interface Resources
|
|
1259
|
+
* @public
|
|
1260
|
+
*
|
|
1261
|
+
* @see {@link ResourceBase} - The base interface that resource definitions must implement
|
|
1262
|
+
* @see {@link ResourceConfig} - How to access individual resource configurations
|
|
1263
|
+
* @see {@link ResourceName} - The union type of all defined resource names
|
|
1264
|
+
* @see {@link ResourceActions} - How to access resource actions with type safety
|
|
1265
|
+
* @example
|
|
1266
|
+
* ```typescript
|
|
1267
|
+
* // Complete example of extending Resources
|
|
1268
|
+
* declare module "reslib/resources" {
|
|
1269
|
+
* interface Resources {
|
|
1270
|
+
* // User management resource
|
|
1271
|
+
* users: {
|
|
1272
|
+
* actions: {
|
|
1273
|
+
* read: { label: "Read User" };
|
|
1274
|
+
* create: { label: "Create User" };
|
|
1275
|
+
* update: { label: "Update User" };
|
|
1276
|
+
* delete: { label: "Delete User" };
|
|
1277
|
+
* activate: { label: "Activate User" };
|
|
1278
|
+
* deactivate: { label: "Deactivate User" };
|
|
1279
|
+
* }
|
|
1280
|
+
* };
|
|
1281
|
+
*
|
|
1282
|
+
* // Content management resource
|
|
1283
|
+
* posts: {
|
|
1284
|
+
* actions: {
|
|
1285
|
+
* read: { label: "Read Post" };
|
|
1286
|
+
* create: { label: "Create Post" };
|
|
1287
|
+
* update: { label: "Update Post" };
|
|
1288
|
+
* delete: { label: "Delete Post" };
|
|
1289
|
+
* publish: { label: "Publish Post" };
|
|
1290
|
+
* archive: { label: "Archive Post" };
|
|
1291
|
+
* }
|
|
1292
|
+
* };
|
|
1293
|
+
*
|
|
1294
|
+
* // E-commerce resource
|
|
1295
|
+
* products: {
|
|
1296
|
+
* actions: {
|
|
1297
|
+
* read: { label: "View Product" };
|
|
1298
|
+
* create: { label: "Add Product" };
|
|
1299
|
+
* update: { label: "Edit Product" };
|
|
1300
|
+
* delete: { label: "Remove Product" };
|
|
1301
|
+
* restock: { label: "Restock Product" };
|
|
1302
|
+
* discontinue: { label: "Discontinue Product" };
|
|
1303
|
+
* }
|
|
1304
|
+
* };
|
|
1305
|
+
*
|
|
1306
|
+
* // Order management resource
|
|
1307
|
+
* orders: {
|
|
1308
|
+
* actions: {
|
|
1309
|
+
* read: { label: "View Order" };
|
|
1310
|
+
* create: { label: "Create Order" };
|
|
1311
|
+
* update: { label: "Update Order" };
|
|
1312
|
+
* cancel: { label: "Cancel Order" };
|
|
1313
|
+
* ship: { label: "Ship Order" };
|
|
1314
|
+
* deliver: { label: "Mark Delivered" };
|
|
1315
|
+
* refund: { label: "Process Refund" };
|
|
1316
|
+
* }
|
|
1317
|
+
* };
|
|
1318
|
+
* }
|
|
1319
|
+
* }
|
|
1320
|
+
*
|
|
1321
|
+
* // Usage examples with type safety
|
|
1322
|
+
* type UserResource = ResourceConfig<"users">;
|
|
1323
|
+
* type UserActions = ResourceActionName<"users">;
|
|
1324
|
+
*
|
|
1325
|
+
* function handleUserAction(action: UserActions, userId: string) {
|
|
1326
|
+
* // TypeScript ensures action is a valid user action
|
|
1327
|
+
* console.log(`Handling ${action} for user ${userId}`);
|
|
1328
|
+
* }
|
|
1329
|
+
*
|
|
1330
|
+
* // Valid usage
|
|
1331
|
+
* handleUserAction("create", "123");
|
|
1332
|
+
* handleUserAction("activate", "456");
|
|
1333
|
+
*
|
|
1334
|
+
* // Invalid usage (TypeScript error)
|
|
1335
|
+
* // handleUserAction("invalidAction", "123"); // ✗ Error
|
|
1336
|
+
* ```
|
|
1337
|
+
*/
|
|
1338
|
+
export interface Resources {
|
|
1339
|
+
}
|
|
1340
|
+
type ValidatedResources = {
|
|
1341
|
+
[K in keyof Resources]: ValidateResource<Resources[K]>;
|
|
1342
|
+
};
|
|
1343
|
+
type ValidateResource<T> = T extends ResourceBase ? T : never;
|
|
1344
|
+
/**
|
|
1345
|
+
* ## ResourceName Type
|
|
1346
|
+
*
|
|
1347
|
+
* A union type representing all valid resource names defined in the global Resources interface.
|
|
1348
|
+
* This type provides compile-time safety by ensuring that only resources that have been
|
|
1349
|
+
* properly defined and validated can be referenced throughout the application.
|
|
1350
|
+
*
|
|
1351
|
+
* ### Purpose
|
|
1352
|
+
* The `ResourceName` type serves as the foundation for type-safe resource operations by
|
|
1353
|
+
* creating a union of all resource names that have been defined via module augmentation.
|
|
1354
|
+
* It ensures that developers can only reference resources that actually exist and conform
|
|
1355
|
+
* to the expected structure, preventing typos and invalid resource references.
|
|
1356
|
+
*
|
|
1357
|
+
* ### How It Works
|
|
1358
|
+
* - **Module Augmentation**: Resources are defined by extending the `Resources` interface
|
|
1359
|
+
* - **Validation**: `ValidatedResources` ensures each resource implements `ResourceBase`
|
|
1360
|
+
* - **Key Extraction**: `keyof ValidatedResources` creates a union of all valid resource names
|
|
1361
|
+
* - **Type Safety**: Any operation using `ResourceName` is guaranteed to reference a valid resource
|
|
1362
|
+
*
|
|
1363
|
+
* ### Type Construction
|
|
1364
|
+
* ```typescript
|
|
1365
|
+
* ResourceName = keyof ValidatedResources
|
|
1366
|
+
* ```
|
|
1367
|
+
*
|
|
1368
|
+
* Where `ValidatedResources` maps each resource to its validated configuration.
|
|
1369
|
+
*
|
|
1370
|
+
* ### Examples
|
|
1371
|
+
*
|
|
1372
|
+
* #### Basic Resource Definition and Usage
|
|
1373
|
+
* ```typescript
|
|
1374
|
+
* // Define resources via module augmentation
|
|
1375
|
+
* declare module "reslib/resources" {
|
|
1376
|
+
* interface Resources {
|
|
1377
|
+
* users: {
|
|
1378
|
+
* actions: {
|
|
1379
|
+
* read: { label: "Read User" };
|
|
1380
|
+
* create: { label: "Create User" };
|
|
1381
|
+
* update: { label: "Update User" };
|
|
1382
|
+
* }
|
|
1383
|
+
* };
|
|
1384
|
+
* posts: {
|
|
1385
|
+
* actions: {
|
|
1386
|
+
* read: { label: "Read Post" };
|
|
1387
|
+
* create: { label: "Create Post" };
|
|
1388
|
+
* publish: { label: "Publish Post" };
|
|
1389
|
+
* }
|
|
1390
|
+
* };
|
|
1391
|
+
* products: {
|
|
1392
|
+
* actions: {
|
|
1393
|
+
* read: { label: "View Product" };
|
|
1394
|
+
* create: { label: "Add Product" };
|
|
1395
|
+
* update: { label: "Edit Product" };
|
|
1396
|
+
* }
|
|
1397
|
+
* };
|
|
1398
|
+
* }
|
|
1399
|
+
* }
|
|
1400
|
+
*
|
|
1401
|
+
* // ResourceName automatically becomes: "users" | "posts" | "products"
|
|
1402
|
+
* type AvailableResources = ResourceName; // "users" | "posts" | "products"
|
|
1403
|
+
*
|
|
1404
|
+
* // Type-safe resource operations
|
|
1405
|
+
* function getResourceConfig(name: ResourceName): ResourceConfig<ResourceName> {
|
|
1406
|
+
* // TypeScript ensures name is a valid resource
|
|
1407
|
+
* return getResourceConfiguration(name);
|
|
1408
|
+
* }
|
|
1409
|
+
*
|
|
1410
|
+
* getResourceConfig("users"); // ✓ Valid
|
|
1411
|
+
* getResourceConfig("posts"); // ✓ Valid
|
|
1412
|
+
* getResourceConfig("products"); // ✓ Valid
|
|
1413
|
+
* // getResourceConfig("invalid"); // ✗ TypeScript error
|
|
1414
|
+
* ```
|
|
1415
|
+
*
|
|
1416
|
+
* #### Generic Functions with Resource Names
|
|
1417
|
+
* ```typescript
|
|
1418
|
+
* // Generic function that works with any resource
|
|
1419
|
+
* function createResourceHandler<T extends ResourceName>(resourceName: T) {
|
|
1420
|
+
* return {
|
|
1421
|
+
* name: resourceName,
|
|
1422
|
+
* config: {} as ResourceConfig<T>,
|
|
1423
|
+
* actions: [] as ResourceActionName<T>[]
|
|
1424
|
+
* };
|
|
1425
|
+
* }
|
|
1426
|
+
*
|
|
1427
|
+
* // Usage with type safety
|
|
1428
|
+
* const userHandler = createResourceHandler("users");
|
|
1429
|
+
* // TypeScript knows this is specifically for users
|
|
1430
|
+
*
|
|
1431
|
+
* const postHandler = createResourceHandler("posts");
|
|
1432
|
+
* // TypeScript knows this is specifically for posts
|
|
1433
|
+
* ```
|
|
1434
|
+
*
|
|
1435
|
+
* #### Resource Registry with Type Safety
|
|
1436
|
+
* ```typescript
|
|
1437
|
+
* // Type-safe resource registry
|
|
1438
|
+
* class ResourceRegistry {
|
|
1439
|
+
* private resources = new Map<ResourceName, ResourceConfig<ResourceName>>();
|
|
1440
|
+
*
|
|
1441
|
+
* register<T extends ResourceName>(
|
|
1442
|
+
* name: T,
|
|
1443
|
+
* config: ResourceConfig<T>
|
|
1444
|
+
* ): void {
|
|
1445
|
+
* this.resources.set(name, config);
|
|
1446
|
+
* }
|
|
1447
|
+
*
|
|
1448
|
+
* get<T extends ResourceName>(name: T): ResourceConfig<T> | undefined {
|
|
1449
|
+
* return this.resources.get(name) as ResourceConfig<T> | undefined;
|
|
1450
|
+
* }
|
|
1451
|
+
*
|
|
1452
|
+
* has(name: ResourceName): boolean {
|
|
1453
|
+
* return this.resources.has(name);
|
|
1454
|
+
* }
|
|
1455
|
+
*
|
|
1456
|
+
* getAllResourceNames(): ResourceName[] {
|
|
1457
|
+
* return Array.from(this.resources.keys());
|
|
1458
|
+
* }
|
|
1459
|
+
* }
|
|
1460
|
+
*
|
|
1461
|
+
* // Usage
|
|
1462
|
+
* const registry = new ResourceRegistry();
|
|
1463
|
+
*
|
|
1464
|
+
* registry.register("users", {
|
|
1465
|
+
* actions: {
|
|
1466
|
+
* read: { label: "Read User" },
|
|
1467
|
+
* create: { label: "Create User" }
|
|
1468
|
+
* }
|
|
1469
|
+
* });
|
|
1470
|
+
*
|
|
1471
|
+
* registry.register("posts", {
|
|
1472
|
+
* actions: {
|
|
1473
|
+
* read: { label: "Read Post" },
|
|
1474
|
+
* create: { label: "Create Post" }
|
|
1475
|
+
* }
|
|
1476
|
+
* });
|
|
1477
|
+
*
|
|
1478
|
+
* // Type-safe access
|
|
1479
|
+
* const userConfig = registry.get("users"); // ResourceConfig<"users">
|
|
1480
|
+
* const postConfig = registry.get("posts"); // ResourceConfig<"posts">
|
|
1481
|
+
* ```
|
|
1482
|
+
*
|
|
1483
|
+
* #### Permission System with Resource Names
|
|
1484
|
+
* ```typescript
|
|
1485
|
+
* // Permission type using ResourceName
|
|
1486
|
+
* type ResourcePermission = {
|
|
1487
|
+
* resource: ResourceName;
|
|
1488
|
+
* action: string; // Could be ResourceActionName<ResourceName> for full type safety
|
|
1489
|
+
* granted: boolean;
|
|
1490
|
+
* };
|
|
1491
|
+
*
|
|
1492
|
+
* class PermissionManager {
|
|
1493
|
+
* private permissions = new Map<string, ResourcePermission>();
|
|
1494
|
+
*
|
|
1495
|
+
* grantPermission(resource: ResourceName, action: string): void {
|
|
1496
|
+
* const key = `${resource}:${action}`;
|
|
1497
|
+
* this.permissions.set(key, { resource, action, granted: true });
|
|
1498
|
+
* }
|
|
1499
|
+
*
|
|
1500
|
+
* checkPermission(resource: ResourceName, action: string): boolean {
|
|
1501
|
+
* const key = `${resource}:${action}`;
|
|
1502
|
+
* return this.permissions.get(key)?.granted ?? false;
|
|
1503
|
+
* }
|
|
1504
|
+
*
|
|
1505
|
+
* getAllPermissionsForResource(resource: ResourceName): ResourcePermission[] {
|
|
1506
|
+
* return Array.from(this.permissions.values())
|
|
1507
|
+
* .filter(perm => perm.resource === resource);
|
|
1508
|
+
* }
|
|
1509
|
+
* }
|
|
1510
|
+
*
|
|
1511
|
+
* // Usage
|
|
1512
|
+
* const permManager = new PermissionManager();
|
|
1513
|
+
*
|
|
1514
|
+
* permManager.grantPermission("users", "read");
|
|
1515
|
+
* permManager.grantPermission("users", "create");
|
|
1516
|
+
* permManager.grantPermission("posts", "read");
|
|
1517
|
+
*
|
|
1518
|
+
* permManager.checkPermission("users", "read"); // true
|
|
1519
|
+
* permManager.checkPermission("users", "delete"); // false
|
|
1520
|
+
* permManager.checkPermission("invalid", "read"); // ✗ TypeScript error
|
|
1521
|
+
* ```
|
|
1522
|
+
*
|
|
1523
|
+
* #### API Route Generation
|
|
1524
|
+
* ```typescript
|
|
1525
|
+
* // Generate API routes for all defined resources
|
|
1526
|
+
* function generateApiRoutes(resources: ResourceName[]): string[] {
|
|
1527
|
+
* const routes: string[] = [];
|
|
1528
|
+
*
|
|
1529
|
+
* for (const resource of resources) {
|
|
1530
|
+
* routes.push(`/api/${resource}`);
|
|
1531
|
+
* routes.push(`/api/${resource}/:id`);
|
|
1532
|
+
* }
|
|
1533
|
+
*
|
|
1534
|
+
* return routes;
|
|
1535
|
+
* }
|
|
1536
|
+
*
|
|
1537
|
+
* // Usage
|
|
1538
|
+
* const allRoutes = generateApiRoutes(["users", "posts", "products"]);
|
|
1539
|
+
* // Result: ["/api/users", "/api/users/:id", "/api/posts", "/api/posts/:id", ...]
|
|
1540
|
+
* ```
|
|
1541
|
+
*
|
|
1542
|
+
* ### Best Practices
|
|
1543
|
+
* - **Module Augmentation**: Always define resources via `declare module` to extend the `Resources` interface
|
|
1544
|
+
* - **Consistent Naming**: Use lowercase, plural resource names (e.g., "users", "products", "orders")
|
|
1545
|
+
* - **Type Constraints**: Use `T extends ResourceName` in generic functions for type safety
|
|
1546
|
+
* - **Validation**: Resources must implement `ResourceBase` to be included in `ResourceName`
|
|
1547
|
+
* - **Documentation**: Document custom resources and their purposes for team members
|
|
1548
|
+
*
|
|
1549
|
+
* ### Integration with Other Types
|
|
1550
|
+
* - **`Resources`**: The global interface that defines all resource configurations
|
|
1551
|
+
* - **`ResourceConfig<T>`**: Provides type-safe access to individual resource configurations
|
|
1552
|
+
* - **`ResourceActionName<T>`**: Union type of action names for a specific resource
|
|
1553
|
+
* - **`ResourceActions<T>`**: Type-safe access to a resource's actions
|
|
1554
|
+
* - **`ResourceBase`**: The base interface that all resources must implement
|
|
1555
|
+
*
|
|
1556
|
+
* ### Migration Notes
|
|
1557
|
+
* When adding new resources, ensure they are defined via module augmentation of the `Resources`
|
|
1558
|
+
* interface. The `ResourceName` type will automatically include new resources without requiring
|
|
1559
|
+
* code changes elsewhere. Existing code using string literals for resource names can be updated
|
|
1560
|
+
* to use `ResourceName` for better type safety.
|
|
1561
|
+
*
|
|
1562
|
+
* @type ResourceName
|
|
1563
|
+
* @public
|
|
1564
|
+
*
|
|
1565
|
+
* @see {@link Resources} - The global interface defining all resource configurations
|
|
1566
|
+
* @see {@link ResourceConfig} - How to access individual resource configurations
|
|
1567
|
+
* @see {@link ResourceActionName} - Action names for specific resources
|
|
1568
|
+
* @example
|
|
1569
|
+
* ```typescript
|
|
1570
|
+
* // Complete example of using ResourceName for type safety
|
|
1571
|
+
* import "reslib/resources";
|
|
1572
|
+
*
|
|
1573
|
+
* declare module "reslib/resources" {
|
|
1574
|
+
* interface Resources {
|
|
1575
|
+
* users: {
|
|
1576
|
+
* actions: {
|
|
1577
|
+
* read: { label: "Read User" };
|
|
1578
|
+
* create: { label: "Create User" };
|
|
1579
|
+
* update: { label: "Update User" };
|
|
1580
|
+
* delete: { label: "Delete User" };
|
|
1581
|
+
* };
|
|
1582
|
+
* permissions: {
|
|
1583
|
+
* adminOnly: string[];
|
|
1584
|
+
* publicAccess: string[];
|
|
1585
|
+
* };
|
|
1586
|
+
* };
|
|
1587
|
+
*
|
|
1588
|
+
* posts: {
|
|
1589
|
+
* actions: {
|
|
1590
|
+
* read: { label: "Read Post" };
|
|
1591
|
+
* create: { label: "Create Post" };
|
|
1592
|
+
* publish: { label: "Publish Post" };
|
|
1593
|
+
* archive: { label: "Archive Post" };
|
|
1594
|
+
* };
|
|
1595
|
+
* categories: string[];
|
|
1596
|
+
* };
|
|
1597
|
+
*
|
|
1598
|
+
* products: {
|
|
1599
|
+
* actions: {
|
|
1600
|
+
* read: { label: "View Product" };
|
|
1601
|
+
* create: { label: "Add Product" };
|
|
1602
|
+
* update: { label: "Edit Product" };
|
|
1603
|
+
* discontinue: { label: "Discontinue Product" };
|
|
1604
|
+
* };
|
|
1605
|
+
* pricing: {
|
|
1606
|
+
* currency: string;
|
|
1607
|
+
* taxRate: number;
|
|
1608
|
+
* };
|
|
1609
|
+
* };
|
|
1610
|
+
* }
|
|
1611
|
+
* }
|
|
1612
|
+
*
|
|
1613
|
+
* // ResourceName is automatically: "users" | "posts" | "products"
|
|
1614
|
+
* type AvailableResources = ResourceName;
|
|
1615
|
+
*
|
|
1616
|
+
* // Type-safe resource operations
|
|
1617
|
+
* function processResource<T extends ResourceName>(name: T) {
|
|
1618
|
+
* console.log(`Processing resource: ${name}`);
|
|
1619
|
+
*
|
|
1620
|
+
* // TypeScript knows the exact configuration available
|
|
1621
|
+
* const config = {} as ResourceConfig<T>;
|
|
1622
|
+
*
|
|
1623
|
+
* // TypeScript knows the exact actions available
|
|
1624
|
+
* const actions = [] as ResourceActionName<T>[];
|
|
1625
|
+
*
|
|
1626
|
+
* return { name, config, actions };
|
|
1627
|
+
* }
|
|
1628
|
+
*
|
|
1629
|
+
* // Usage with full type safety
|
|
1630
|
+
* const userProcessor = processResource("users");
|
|
1631
|
+
* // userProcessor.name: "users"
|
|
1632
|
+
* // userProcessor.config: ResourceConfig<"users">
|
|
1633
|
+
* // userProcessor.actions: ResourceActionName<"users">[]
|
|
1634
|
+
*
|
|
1635
|
+
* const postProcessor = processResource("posts");
|
|
1636
|
+
* // postProcessor.name: "posts"
|
|
1637
|
+
* // postProcessor.config: ResourceConfig<"posts">
|
|
1638
|
+
* // postProcessor.actions: ResourceActionName<"posts">[]
|
|
1639
|
+
*
|
|
1640
|
+
* // Invalid usage (TypeScript errors)
|
|
1641
|
+
* // processResource("invalidResource"); // ✗ Error: not in ResourceName
|
|
1642
|
+
* ```
|
|
1643
|
+
*/
|
|
1644
|
+
export type ResourceName = keyof ValidatedResources;
|
|
1645
|
+
/**
|
|
1646
|
+
* ## ResourceConfig Type
|
|
1647
|
+
*
|
|
1648
|
+
* A type-safe accessor for individual resource configurations from the global Resources interface.
|
|
1649
|
+
* This type provides compile-time guarantees that resource configurations conform to the expected structure
|
|
1650
|
+
* and enables type-safe operations on specific resources throughout the application.
|
|
1651
|
+
*
|
|
1652
|
+
* ### Purpose
|
|
1653
|
+
* The `ResourceConfig` type serves as a bridge between the global `Resources` interface and individual
|
|
1654
|
+
* resource operations. It ensures that when you reference a specific resource by name, you get the
|
|
1655
|
+
* exact configuration defined for that resource, with full type safety and IntelliSense support.
|
|
1656
|
+
*
|
|
1657
|
+
* ### How It Works
|
|
1658
|
+
* This type uses TypeScript's indexed access types to extract the configuration for a specific resource:
|
|
1659
|
+
*
|
|
1660
|
+
* 1. **Type Validation**: Leverages `ValidatedResources` to ensure the resource configuration
|
|
1661
|
+
* implements the `ResourceBase` interface
|
|
1662
|
+
* 2. **Indexed Access**: Uses `ValidatedResources[TResourceName]` to extract the specific resource config
|
|
1663
|
+
* 3. **Type Safety**: Provides compile-time guarantees about the structure and available actions
|
|
1664
|
+
* 4. **Extensibility**: Automatically reflects changes when new resources are added via module augmentation
|
|
1665
|
+
*
|
|
1666
|
+
* ### Template Parameters
|
|
1667
|
+
* - **TResourceName**: The name of the resource (must be a key of the Resources interface).
|
|
1668
|
+
* This parameter is constrained to `ResourceName`, ensuring only defined resources can be accessed.
|
|
1669
|
+
*
|
|
1670
|
+
* ### Type Construction
|
|
1671
|
+
* ```typescript
|
|
1672
|
+
* ResourceConfig<TResourceName> = ValidatedResources[TResourceName]
|
|
1673
|
+
* ```
|
|
1674
|
+
*
|
|
1675
|
+
* Where `ValidatedResources` ensures each resource configuration extends `ResourceBase`.
|
|
1676
|
+
*
|
|
1677
|
+
* ### Examples
|
|
1678
|
+
*
|
|
1679
|
+
* #### Basic Resource Access
|
|
1680
|
+
* ```typescript
|
|
1681
|
+
* // After defining resources via module augmentation
|
|
1682
|
+
* declare module "reslib/resources" {
|
|
1683
|
+
* interface Resources {
|
|
1684
|
+
* users: {
|
|
1685
|
+
* actions: {
|
|
1686
|
+
* read: { label: "Read User" };
|
|
1687
|
+
* create: { label: "Create User" };
|
|
1688
|
+
* update: { label: "Update User" };
|
|
1689
|
+
* }
|
|
1690
|
+
* };
|
|
1691
|
+
* posts: {
|
|
1692
|
+
* actions: {
|
|
1693
|
+
* read: { label: "Read Post" };
|
|
1694
|
+
* create: { label: "Create Post" };
|
|
1695
|
+
* publish: { label: "Publish Post" };
|
|
1696
|
+
* }
|
|
1697
|
+
* };
|
|
1698
|
+
* }
|
|
1699
|
+
* }
|
|
1700
|
+
*
|
|
1701
|
+
* // Type-safe access to resource configurations
|
|
1702
|
+
* type UserConfig = ResourceConfig<"users">;
|
|
1703
|
+
* // Result: The complete configuration object for the users resource
|
|
1704
|
+
*
|
|
1705
|
+
* type PostConfig = ResourceConfig<"posts">;
|
|
1706
|
+
* // Result: The complete configuration object for the posts resource
|
|
1707
|
+
* ```
|
|
1708
|
+
*
|
|
1709
|
+
* #### Type-Safe Resource Operations
|
|
1710
|
+
* ```typescript
|
|
1711
|
+
* // Function that works with any resource configuration
|
|
1712
|
+
* function createResourceHandler<TResourceName extends ResourceName>(
|
|
1713
|
+
* resourceName: TResourceName,
|
|
1714
|
+
* config: ResourceConfig<TResourceName>
|
|
1715
|
+
* ) {
|
|
1716
|
+
* console.log(`Setting up handlers for ${resourceName}`);
|
|
1717
|
+
*
|
|
1718
|
+
* // TypeScript knows the exact actions available for this resource
|
|
1719
|
+
* for (const actionName in config.actions) {
|
|
1720
|
+
* console.log(`Action: ${actionName}`);
|
|
1721
|
+
* // actionName is typed as keyof config.actions
|
|
1722
|
+
* }
|
|
1723
|
+
*
|
|
1724
|
+
* return {
|
|
1725
|
+
* getConfig: (): ResourceConfig<TResourceName> => config,
|
|
1726
|
+
* getActionNames: (): (keyof ResourceConfig<TResourceName>['actions'])[] => {
|
|
1727
|
+
* return Object.keys(config.actions) as (keyof ResourceConfig<TResourceName>['actions'])[];
|
|
1728
|
+
* }
|
|
1729
|
+
* };
|
|
1730
|
+
* }
|
|
1731
|
+
*
|
|
1732
|
+
* // Usage with type safety
|
|
1733
|
+
* const userHandler = createResourceHandler("users", {
|
|
1734
|
+
* actions: {
|
|
1735
|
+
* read: { label: "Read User" },
|
|
1736
|
+
* create: { label: "Create User" },
|
|
1737
|
+
* update: { label: "Update User" }
|
|
1738
|
+
* }
|
|
1739
|
+
* });
|
|
1740
|
+
*
|
|
1741
|
+
* // TypeScript ensures only valid user actions are used
|
|
1742
|
+
* const userActions = userHandler.getActionNames();
|
|
1743
|
+
* // Result: ("read" | "create" | "update")[]
|
|
1744
|
+
* ```
|
|
1745
|
+
*
|
|
1746
|
+
* #### Resource Registry with Type Safety
|
|
1747
|
+
* ```typescript
|
|
1748
|
+
* // Type-safe resource registry
|
|
1749
|
+
* class ResourceRegistry {
|
|
1750
|
+
* private resources = new Map<ResourceName, ResourceConfig<ResourceName>>();
|
|
1751
|
+
*
|
|
1752
|
+
* register<TResourceName extends ResourceName>(
|
|
1753
|
+
* name: TResourceName,
|
|
1754
|
+
* config: ResourceConfig<TResourceName>
|
|
1755
|
+
* ) {
|
|
1756
|
+
* this.resources.set(name, config);
|
|
1757
|
+
* }
|
|
1758
|
+
*
|
|
1759
|
+
* get<TResourceName extends ResourceName>(
|
|
1760
|
+
* name: TResourceName
|
|
1761
|
+
* ): ResourceConfig<TResourceName> | undefined {
|
|
1762
|
+
* return this.resources.get(name) as ResourceConfig<TResourceName> | undefined;
|
|
1763
|
+
* }
|
|
1764
|
+
*
|
|
1765
|
+
* getAllActions<TResourceName extends ResourceName>(
|
|
1766
|
+
* name: TResourceName
|
|
1767
|
+
* ): ResourceConfig<TResourceName>['actions'] {
|
|
1768
|
+
* const resource = this.get(name);
|
|
1769
|
+
* if (!resource) throw new Error(`Resource ${name} not found`);
|
|
1770
|
+
* return resource.actions;
|
|
1771
|
+
* }
|
|
1772
|
+
* }
|
|
1773
|
+
*
|
|
1774
|
+
* // Usage
|
|
1775
|
+
* const registry = new ResourceRegistry();
|
|
1776
|
+
*
|
|
1777
|
+
* registry.register("users", {
|
|
1778
|
+
* actions: {
|
|
1779
|
+
* read: { label: "Read User" },
|
|
1780
|
+
* create: { label: "Create User" },
|
|
1781
|
+
* update: { label: "Update User" }
|
|
1782
|
+
* }
|
|
1783
|
+
* });
|
|
1784
|
+
*
|
|
1785
|
+
* const userConfig = registry.get("users");
|
|
1786
|
+
* // TypeScript knows this is ResourceConfig<"users">
|
|
1787
|
+
*
|
|
1788
|
+
* const userActions = registry.getAllActions("users");
|
|
1789
|
+
* // TypeScript knows this contains only user actions
|
|
1790
|
+
* ```
|
|
1791
|
+
*
|
|
1792
|
+
* #### Advanced Configuration with Custom Properties
|
|
1793
|
+
* ```typescript
|
|
1794
|
+
* // Resources with additional configuration properties
|
|
1795
|
+
* declare module "reslib/resources" {
|
|
1796
|
+
* interface Resources {
|
|
1797
|
+
* users: {
|
|
1798
|
+
* actions: {
|
|
1799
|
+
* read: { label: "Read User" };
|
|
1800
|
+
* create: { label: "Create User" };
|
|
1801
|
+
* update: { label: "Update User" };
|
|
1802
|
+
* };
|
|
1803
|
+
* // Custom properties
|
|
1804
|
+
* permissions: {
|
|
1805
|
+
* adminOnly: string[];
|
|
1806
|
+
* publicAccess: string[];
|
|
1807
|
+
* };
|
|
1808
|
+
* validationRules: {
|
|
1809
|
+
* passwordMinLength: number;
|
|
1810
|
+
* requireEmailVerification: boolean;
|
|
1811
|
+
* };
|
|
1812
|
+
* };
|
|
1813
|
+
* }
|
|
1814
|
+
* }
|
|
1815
|
+
*
|
|
1816
|
+
* // Type-safe access to custom properties
|
|
1817
|
+
* function configureUserResource(config: ResourceConfig<"users">) {
|
|
1818
|
+
* // TypeScript knows about custom properties
|
|
1819
|
+
* console.log("Admin actions:", config.permissions.adminOnly);
|
|
1820
|
+
* console.log("Password min length:", config.validationRules.passwordMinLength);
|
|
1821
|
+
*
|
|
1822
|
+
* // Standard actions are still available
|
|
1823
|
+
* const actions = config.actions;
|
|
1824
|
+
* // actions is typed with user-specific actions
|
|
1825
|
+
* }
|
|
1826
|
+
*
|
|
1827
|
+
* const userConfig: ResourceConfig<"users"> = {
|
|
1828
|
+
* actions: {
|
|
1829
|
+
* read: { label: "Read User" },
|
|
1830
|
+
* create: { label: "Create User" },
|
|
1831
|
+
* update: { label: "Update User" }
|
|
1832
|
+
* },
|
|
1833
|
+
* permissions: {
|
|
1834
|
+
* adminOnly: ["delete", "ban"],
|
|
1835
|
+
* publicAccess: ["read", "create"]
|
|
1836
|
+
* },
|
|
1837
|
+
* validationRules: {
|
|
1838
|
+
* passwordMinLength: 8,
|
|
1839
|
+
* requireEmailVerification: true
|
|
1840
|
+
* }
|
|
1841
|
+
* };
|
|
1842
|
+
*
|
|
1843
|
+
* configureUserResource(userConfig);
|
|
1844
|
+
* ```
|
|
1845
|
+
*
|
|
1846
|
+
* #### Generic Resource Processing
|
|
1847
|
+
* ```typescript
|
|
1848
|
+
* // Generic function that processes any resource
|
|
1849
|
+
* function processResource<TResourceName extends ResourceName>(
|
|
1850
|
+
* name: TResourceName,
|
|
1851
|
+
* config: ResourceConfig<TResourceName>,
|
|
1852
|
+
* processor: (config: ResourceConfig<TResourceName>) => void
|
|
1853
|
+
* ) {
|
|
1854
|
+
* console.log(`Processing resource: ${name}`);
|
|
1855
|
+
* processor(config);
|
|
1856
|
+
* }
|
|
1857
|
+
*
|
|
1858
|
+
* // Usage with different resources
|
|
1859
|
+
* processResource("users", userConfig, (config) => {
|
|
1860
|
+
* // config is typed as ResourceConfig<"users">
|
|
1861
|
+
* console.log("User actions:", Object.keys(config.actions));
|
|
1862
|
+
* });
|
|
1863
|
+
*
|
|
1864
|
+
* processResource("posts", postConfig, (config) => {
|
|
1865
|
+
* // config is typed as ResourceConfig<"posts">
|
|
1866
|
+
* console.log("Post actions:", Object.keys(config.actions));
|
|
1867
|
+
* });
|
|
1868
|
+
* ```
|
|
1869
|
+
*
|
|
1870
|
+
* ### Best Practices
|
|
1871
|
+
* - **Type Constraints**: Always use `TResourceName extends ResourceName` to constrain resource names
|
|
1872
|
+
* - **Generic Functions**: Use generics to create reusable functions that work with any resource type
|
|
1873
|
+
* - **Module Augmentation**: Define resources via module augmentation for better organization
|
|
1874
|
+
* - **Type Safety**: Leverage TypeScript's inference to avoid manual type assertions
|
|
1875
|
+
* - **Consistent Naming**: Use lowercase, plural resource names (e.g., "users", "products")
|
|
1876
|
+
* - **Documentation**: Document custom resource properties for team members
|
|
1877
|
+
*
|
|
1878
|
+
* ### Integration with Other Types
|
|
1879
|
+
* - **`Resources`**: The global interface that `ResourceConfig` accesses
|
|
1880
|
+
* - **`ResourceName`**: Union type of all defined resource names
|
|
1881
|
+
* - **`ResourceActions<TResourceName>`**: Type-safe access to a resource's actions
|
|
1882
|
+
* - **`ResourceActionName<TResourceName>`**: Union type of action names for a specific resource
|
|
1883
|
+
* - **`ResourceBase`**: The base interface that all resource configurations must implement
|
|
1884
|
+
*
|
|
1885
|
+
* ### Migration Notes
|
|
1886
|
+
* When upgrading from direct resource object usage, replace explicit types with `ResourceConfig<TResourceName>`
|
|
1887
|
+
* to gain type safety and automatic updates when resource definitions change. The type provides the same
|
|
1888
|
+
* runtime behavior while adding compile-time guarantees.
|
|
1889
|
+
*
|
|
1890
|
+
* @type ResourceConfig
|
|
1891
|
+
* @template TResourceName - The name of the resource (must be a key of Resources)
|
|
1892
|
+
* @public
|
|
1893
|
+
*
|
|
1894
|
+
* @see {@link Resources} - The global interface defining all resource configurations
|
|
1895
|
+
* @see {@link ResourceName} - Union type of all defined resource names
|
|
1896
|
+
* @see {@link ResourceActions} - How to access resource actions with type safety
|
|
1897
|
+
* @see {@link ResourceBase} - The base interface that resource definitions must implement
|
|
1898
|
+
* @example
|
|
1899
|
+
* ```typescript
|
|
1900
|
+
* // Complete example of using ResourceConfig
|
|
1901
|
+
* import "reslib/resources";
|
|
1902
|
+
*
|
|
1903
|
+
* declare module "reslib/resources" {
|
|
1904
|
+
* interface Resources {
|
|
1905
|
+
* users: {
|
|
1906
|
+
* actions: {
|
|
1907
|
+
* read: { label: "Read User" };
|
|
1908
|
+
* create: { label: "Create User" };
|
|
1909
|
+
* update: { label: "Update User" };
|
|
1910
|
+
* delete: { label: "Delete User" };
|
|
1911
|
+
* };
|
|
1912
|
+
* permissions: {
|
|
1913
|
+
* adminOnly: string[];
|
|
1914
|
+
* publicAccess: string[];
|
|
1915
|
+
* };
|
|
1916
|
+
* };
|
|
1917
|
+
*
|
|
1918
|
+
* products: {
|
|
1919
|
+
* actions: {
|
|
1920
|
+
* read: { label: "View Product" };
|
|
1921
|
+
* create: { label: "Add Product" };
|
|
1922
|
+
* update: { label: "Edit Product" };
|
|
1923
|
+
* discontinue: { label: "Discontinue Product" };
|
|
1924
|
+
* };
|
|
1925
|
+
* categories: string[];
|
|
1926
|
+
* pricing: {
|
|
1927
|
+
* currency: string;
|
|
1928
|
+
* taxRate: number;
|
|
1929
|
+
* };
|
|
1930
|
+
* };
|
|
1931
|
+
* }
|
|
1932
|
+
* }
|
|
1933
|
+
*
|
|
1934
|
+
* // Type-safe resource configuration access
|
|
1935
|
+
* type UserConfig = ResourceConfig<"users">;
|
|
1936
|
+
* type ProductConfig = ResourceConfig<"products">;
|
|
1937
|
+
*
|
|
1938
|
+
* // Function that works with any resource configuration
|
|
1939
|
+
* function setupResource<TResourceName extends ResourceName>(
|
|
1940
|
+
* name: TResourceName,
|
|
1941
|
+
* config: ResourceConfig<TResourceName>
|
|
1942
|
+
* ) {
|
|
1943
|
+
* console.log(`Setting up ${name} resource`);
|
|
1944
|
+
*
|
|
1945
|
+
* // Access standard properties
|
|
1946
|
+
* const actions = Object.keys(config.actions);
|
|
1947
|
+
* console.log(`Available actions: ${actions.join(', ')}`);
|
|
1948
|
+
*
|
|
1949
|
+
* // Access resource-specific properties with type safety
|
|
1950
|
+
* if (name === "users") {
|
|
1951
|
+
* const userConfig = config as ResourceConfig<"users">;
|
|
1952
|
+
* console.log(`Admin actions: ${userConfig.permissions.adminOnly.join(', ')}`);
|
|
1953
|
+
* } else if (name === "products") {
|
|
1954
|
+
* const productConfig = config as ResourceConfig<"products">;
|
|
1955
|
+
* console.log(`Categories: ${productConfig.categories.join(', ')}`);
|
|
1956
|
+
* console.log(`Currency: ${productConfig.pricing.currency}`);
|
|
1957
|
+
* }
|
|
1958
|
+
* }
|
|
1959
|
+
*
|
|
1960
|
+
* // Usage examples
|
|
1961
|
+
* const userResource: ResourceConfig<"users"> = {
|
|
1962
|
+
* actions: {
|
|
1963
|
+
* read: { label: "Read User" },
|
|
1964
|
+
* create: { label: "Create User" },
|
|
1965
|
+
* update: { label: "Update User" },
|
|
1966
|
+
* delete: { label: "Delete User" }
|
|
1967
|
+
* },
|
|
1968
|
+
* permissions: {
|
|
1969
|
+
* adminOnly: ["delete"],
|
|
1970
|
+
* publicAccess: ["read", "create"]
|
|
1971
|
+
* }
|
|
1972
|
+
* };
|
|
1973
|
+
*
|
|
1974
|
+
* const productResource: ResourceConfig<"products"> = {
|
|
1975
|
+
* actions: {
|
|
1976
|
+
* read: { label: "View Product" },
|
|
1977
|
+
* create: { label: "Add Product" },
|
|
1978
|
+
* update: { label: "Edit Product" },
|
|
1979
|
+
* discontinue: { label: "Discontinue Product" }
|
|
1980
|
+
* },
|
|
1981
|
+
* categories: ["electronics", "clothing", "books"],
|
|
1982
|
+
* pricing: {
|
|
1983
|
+
* currency: "USD",
|
|
1984
|
+
* taxRate: 0.08
|
|
1985
|
+
* }
|
|
1986
|
+
* };
|
|
1987
|
+
*
|
|
1988
|
+
* setupResource("users", userResource);
|
|
1989
|
+
* setupResource("products", productResource);
|
|
1990
|
+
* ```
|
|
1991
|
+
*/
|
|
1992
|
+
export type ResourceConfig<TResourceName extends ResourceName> = ValidatedResources[TResourceName];
|
|
1993
|
+
/**
|
|
1994
|
+
* Type representing the action names for a specific resource.
|
|
1995
|
+
* This type extracts the literal action name strings from a resource's actions.
|
|
1996
|
+
*
|
|
1997
|
+
* This type provides compile-time safety by ensuring that only valid action names
|
|
1998
|
+
* for a specific resource can be used. It preserves literal types, enabling better
|
|
1999
|
+
* autocomplete and error detection.
|
|
2000
|
+
*
|
|
2001
|
+
* @type ResourceActionName
|
|
2002
|
+
* @template TResourceName - The name of the resource (optional, defaults to all resources)
|
|
2003
|
+
*
|
|
2004
|
+
* @example
|
|
2005
|
+
* ```typescript
|
|
2006
|
+
* // Basic usage with specific resource
|
|
2007
|
+
* import "reslib/resources";
|
|
2008
|
+
*
|
|
2009
|
+
* declare module "reslib/resources" {
|
|
2010
|
+
* interface Resources {
|
|
2011
|
+
* users: {
|
|
2012
|
+
* actions: {
|
|
2013
|
+
* read: { label: "Read User" };
|
|
2014
|
+
* create: { label: "Create User" };
|
|
2015
|
+
* update: { label: "Update User" };
|
|
2016
|
+
* archive: { label: "Archive User" };
|
|
2017
|
+
* }
|
|
2018
|
+
* };
|
|
2019
|
+
* }
|
|
2020
|
+
* }
|
|
2021
|
+
*
|
|
2022
|
+
* // Type-safe action names for users resource
|
|
2023
|
+
* type UserActionName = ResourceActionName<"users">;
|
|
2024
|
+
* // Result: "read" | "create" | "update" | "archive"
|
|
2025
|
+
*
|
|
2026
|
+
* function performUserAction(action: UserActionName) {
|
|
2027
|
+
* console.log(`Performing ${action} on user`);
|
|
2028
|
+
* }
|
|
2029
|
+
*
|
|
2030
|
+
* performUserAction("read"); // ✓ Valid
|
|
2031
|
+
* performUserAction("create"); // ✓ Valid
|
|
2032
|
+
* // performUserAction("delete"); // ✗ TypeScript error - not a valid user action
|
|
2033
|
+
* ```
|
|
2034
|
+
*
|
|
2035
|
+
* @example
|
|
2036
|
+
* ```typescript
|
|
2037
|
+
* // Generic function with resource-specific actions
|
|
2038
|
+
* function createActionHandler<TResourceName extends ResourceName>(
|
|
2039
|
+
* resourceName: TResourceName,
|
|
2040
|
+
* actionName: ResourceActionName<TResourceName>
|
|
2041
|
+
* ) {
|
|
2042
|
+
* return {
|
|
2043
|
+
* execute: () => console.log(`Executing ${actionName} on ${resourceName}`),
|
|
2044
|
+
* getActionName: (): ResourceActionName<TResourceName> => actionName
|
|
2045
|
+
* };
|
|
2046
|
+
* }
|
|
2047
|
+
*
|
|
2048
|
+
* // Usage with type safety
|
|
2049
|
+
* const userReadHandler = createActionHandler("users", "read");
|
|
2050
|
+
* // TypeScript knows actionName must be a valid user action
|
|
2051
|
+
*
|
|
2052
|
+
* const userCreateHandler = createActionHandler("users", "create");
|
|
2053
|
+
* // TypeScript knows actionName must be a valid user action
|
|
2054
|
+
*
|
|
2055
|
+
* // This would cause a TypeScript error:
|
|
2056
|
+
* // const invalidHandler = createActionHandler("users", "delete");
|
|
2057
|
+
* // Error: "delete" is not assignable to ResourceActionName<"users">
|
|
2058
|
+
* ```
|
|
2059
|
+
*
|
|
2060
|
+
* @example
|
|
2061
|
+
* ```typescript
|
|
2062
|
+
* // Runtime action validation
|
|
2063
|
+
* function isValidAction<TResourceName extends ResourceName>(
|
|
2064
|
+
* resourceName: TResourceName,
|
|
2065
|
+
* actionName: string
|
|
2066
|
+
* ): actionName is ResourceActionName<TResourceName> {
|
|
2067
|
+
* // This would typically check against the resource's defined actions
|
|
2068
|
+
* const validActions = getResourceActions(resourceName);
|
|
2069
|
+
* return validActions.includes(actionName as ResourceActionName<TResourceName>);
|
|
2070
|
+
* }
|
|
2071
|
+
*
|
|
2072
|
+
* // Usage
|
|
2073
|
+
* if (isValidAction("users", "read")) {
|
|
2074
|
+
* // TypeScript now knows actionName is ResourceActionName<"users">
|
|
2075
|
+
* const handler = createActionHandler("users", "read");
|
|
2076
|
+
* }
|
|
2077
|
+
* ```
|
|
2078
|
+
*
|
|
2079
|
+
* @example
|
|
2080
|
+
* ```typescript
|
|
2081
|
+
* // Action name arrays with type safety
|
|
2082
|
+
* function getAllActionNames<TResourceName extends ResourceName>(
|
|
2083
|
+
* resourceName: TResourceName
|
|
2084
|
+
* ): ResourceActionName<TResourceName>[] {
|
|
2085
|
+
* // Implementation would return all action names for the resource
|
|
2086
|
+
* return [] as ResourceActionName<TResourceName>[];
|
|
2087
|
+
* }
|
|
2088
|
+
*
|
|
2089
|
+
* // Usage
|
|
2090
|
+
* const userActions = getAllActionNames("users");
|
|
2091
|
+
* // TypeScript knows userActions contains only valid user action names
|
|
2092
|
+
*
|
|
2093
|
+
* userActions.forEach(action => {
|
|
2094
|
+
* // action is typed as ResourceActionName<"users">
|
|
2095
|
+
* console.log(`User action: ${action}`);
|
|
2096
|
+
* });
|
|
2097
|
+
* ```
|
|
2098
|
+
*/
|
|
2099
|
+
export type ResourceActionName<TResourceName extends ResourceName = ResourceName> = GetResourceActionNames<ResourceConfig<TResourceName>>;
|
|
2100
|
+
/**
|
|
2101
|
+
* @interface ResourceActionTuple
|
|
2102
|
+
* Represents a tuple that contains a resource name and an action name.
|
|
2103
|
+
* This type is a union of two possible tuple formats: `ResourceActionTupleArray` and `ResourceActionTupleObject`.
|
|
2104
|
+
*
|
|
2105
|
+
* @template TResourceName - The name of the resource. Defaults to `ResourceName`.
|
|
2106
|
+
*
|
|
2107
|
+
* @example
|
|
2108
|
+
* ```typescript
|
|
2109
|
+
* // Using ResourceActionTupleArray
|
|
2110
|
+
* const actionTuple: ResourceActionTuple = ["users", "read"];
|
|
2111
|
+
*
|
|
2112
|
+
* // Using ResourceActionTupleObject
|
|
2113
|
+
* const actionTuple: ResourceActionTuple = { resourceName: "users", action: "read" };
|
|
2114
|
+
* ```
|
|
2115
|
+
*
|
|
2116
|
+
* @typeParam TResourceName - The name of the resource.
|
|
2117
|
+
* @default ResourceName
|
|
2118
|
+
*
|
|
2119
|
+
* @typedef {(ResourceActionTupleArray<TResourceName> | ResourceActionTupleObject<TResourceName>)} ResourceActionTuple
|
|
2120
|
+
*
|
|
2121
|
+
* @see {@link ResourceActionTupleArray} for the `ResourceActionTupleArray` type.
|
|
2122
|
+
* @see {@link ResourceActionTupleObject} for the `ResourceActionTupleObject` type.
|
|
2123
|
+
*/
|
|
2124
|
+
export type ResourceActionTuple<TResourceName extends ResourceName> = ResourceActionTupleArray<TResourceName> | ResourceActionTupleObject<TResourceName>;
|
|
2125
|
+
/**
|
|
2126
|
+
* @interface ResourceActionTupleArray
|
|
2127
|
+
* Represents a tuple that contains a resource name and an action name in an array format.
|
|
2128
|
+
* This type is a tuple with two elements: the resource name and the action name.
|
|
2129
|
+
*
|
|
2130
|
+
* @template TResourceName - The name of the resource. Defaults to `ResourceName`.
|
|
2131
|
+
*
|
|
2132
|
+
* @example
|
|
2133
|
+
* ```typescript
|
|
2134
|
+
* const actionTuple: ResourceActionTupleArray = ["users", "read"];
|
|
2135
|
+
* ```
|
|
2136
|
+
*
|
|
2137
|
+
* @typeParam TResourceName - The name of the resource.
|
|
2138
|
+
* @default ResourceName
|
|
2139
|
+
*
|
|
2140
|
+
* @typedef {[TResourceName, ResourceActionName<TResourceName>]} ResourceActionTupleArray
|
|
2141
|
+
*/
|
|
2142
|
+
export type ResourceActionTupleArray<TResourceName extends ResourceName> = [
|
|
2143
|
+
/**
|
|
2144
|
+
* The name of the resource.
|
|
2145
|
+
*
|
|
2146
|
+
* @type {TResourceName}
|
|
2147
|
+
*/
|
|
2148
|
+
TResourceName,
|
|
2149
|
+
/**
|
|
2150
|
+
* The name of the action.
|
|
2151
|
+
*
|
|
2152
|
+
* @type {ResourceActionName<TResourceName>}
|
|
2153
|
+
*/
|
|
2154
|
+
ResourceActionName<TResourceName>
|
|
2155
|
+
];
|
|
2156
|
+
/**
|
|
2157
|
+
* @interface ResourceActionTupleObject
|
|
2158
|
+
* Represents a tuple that contains a resource name and an action name in an object format.
|
|
2159
|
+
* This type is an object with two properties: `resourceName` and `action`.
|
|
2160
|
+
*
|
|
2161
|
+
* @template TResourceName - The name of the resource. Defaults to `ResourceName`.
|
|
2162
|
+
*
|
|
2163
|
+
* @example
|
|
2164
|
+
* ```typescript
|
|
2165
|
+
* const actionTuple: ResourceActionTupleObject = { resourceName: "users", action: "read" };
|
|
2166
|
+
* ```
|
|
2167
|
+
*
|
|
2168
|
+
* @typeParam TResourceName - The name of the resource.
|
|
2169
|
+
* @default ResourceName
|
|
2170
|
+
*
|
|
2171
|
+
* @interface ResourceActionTupleObject
|
|
2172
|
+
*/
|
|
2173
|
+
export interface ResourceActionTupleObject<TResourceName extends ResourceName> {
|
|
2174
|
+
/**
|
|
2175
|
+
* The name of the resource.
|
|
2176
|
+
*
|
|
2177
|
+
* @type {TResourceName}
|
|
2178
|
+
*/
|
|
2179
|
+
resourceName: TResourceName;
|
|
2180
|
+
/**
|
|
2181
|
+
* The name of the action.
|
|
2182
|
+
*
|
|
2183
|
+
* @type {ResourceActionName<TResourceName>}
|
|
2184
|
+
*/
|
|
2185
|
+
action: ResourceActionName<TResourceName>;
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* @interface ResourceAction
|
|
2189
|
+
*
|
|
2190
|
+
* Represents the structure of an action that can be performed on a resource within the application.
|
|
2191
|
+
* This interface defines the essential properties that describe the action, allowing for a
|
|
2192
|
+
* consistent representation of actions across different resources.
|
|
2193
|
+
*
|
|
2194
|
+
* ### Properties
|
|
2195
|
+
*
|
|
2196
|
+
* - `label` (optional): A user-friendly label for the action. This label is typically
|
|
2197
|
+
* displayed in the user interface (UI) to help users understand what the action does.
|
|
2198
|
+
* It should be concise and descriptive.
|
|
2199
|
+
*
|
|
2200
|
+
* - `title` (optional): A short text that appears when the user hovers over the action
|
|
2201
|
+
* in the UI. The title provides extra information about the action, helping users
|
|
2202
|
+
* understand its purpose without cluttering the interface.
|
|
2203
|
+
*
|
|
2204
|
+
* ### Example Usage
|
|
2205
|
+
*
|
|
2206
|
+
* Here is an example of how the `ResourceAction` interface can be utilized:
|
|
2207
|
+
*
|
|
2208
|
+
* ```typescript
|
|
2209
|
+
* // Define a resource action for creating a new document
|
|
2210
|
+
* const createDocumentAction: ResourceAction = {
|
|
2211
|
+
* label: "Create Document",
|
|
2212
|
+
* title: "Click to add a new document."
|
|
2213
|
+
* };
|
|
2214
|
+
*
|
|
2215
|
+
* // Function to display action information
|
|
2216
|
+
* function displayActionInfo(action: ResourceAction) {
|
|
2217
|
+
* console.log(`Action: ${action.label}`);
|
|
2218
|
+
* console.log(`Title: ${action.title}`);
|
|
2219
|
+
* }
|
|
2220
|
+
*
|
|
2221
|
+
* // Example of displaying action information
|
|
2222
|
+
* displayActionInfo(createDocumentAction);
|
|
2223
|
+
* // Output:
|
|
2224
|
+
* // Action: Create Document
|
|
2225
|
+
* // Title: Create a new document in the system
|
|
2226
|
+
* // Tooltip: Click to add a new document.
|
|
2227
|
+
* ```
|
|
2228
|
+
*
|
|
2229
|
+
* ### Notes
|
|
2230
|
+
*
|
|
2231
|
+
* - The `ResourceAction` interface is designed to be flexible, allowing developers to
|
|
2232
|
+
* define actions with varying levels of details based on the needs of their application.
|
|
2233
|
+
* - By providing clear labels, titles, and tooltips, developers can enhance the user
|
|
2234
|
+
* experience and make the application more intuitive.
|
|
2235
|
+
*/
|
|
2236
|
+
export interface ResourceAction {
|
|
2237
|
+
label?: string;
|
|
2238
|
+
title?: string;
|
|
2239
|
+
}
|
|
2240
|
+
type ResourceActionsRecord<TActions> = TActions extends Record<string, ResourceAction> ? TActions & Partial<ResourceDefaultActions> : never;
|
|
2241
|
+
/**
|
|
2242
|
+
* Type representing the actions record for a specific resource.
|
|
2243
|
+
* This type extracts the actions from a resource's definition, ensuring type safety.
|
|
2244
|
+
*
|
|
2245
|
+
* This type provides access to the complete actions object for a resource,
|
|
2246
|
+
* maintaining the exact structure and types as defined in the resource's configuration.
|
|
2247
|
+
* It's useful when you need to work with the entire set of actions for a resource.
|
|
2248
|
+
*
|
|
2249
|
+
* @type ResourceActions
|
|
2250
|
+
* @template TResourceName - The name of the resource
|
|
2251
|
+
*
|
|
2252
|
+
* @example
|
|
2253
|
+
* ```typescript
|
|
2254
|
+
* // Basic usage - getting resource actions type
|
|
2255
|
+
* import "reslib/resources";
|
|
2256
|
+
*
|
|
2257
|
+
* declare module "reslib/resources" {
|
|
2258
|
+
* interface Resources {
|
|
2259
|
+
* users: {
|
|
2260
|
+
* actions: {
|
|
2261
|
+
* read: { label: "Read User", title: "View user details" };
|
|
2262
|
+
* create: { label: "Create User", title: "Add new user" };
|
|
2263
|
+
* update: { label: "Update User", title: "Modify user data" };
|
|
2264
|
+
* archive: { label: "Archive User", title: "Soft delete user" };
|
|
2265
|
+
* }
|
|
2266
|
+
* };
|
|
2267
|
+
* }
|
|
2268
|
+
* }
|
|
2269
|
+
*
|
|
2270
|
+
* // Type-safe access to user actions
|
|
2271
|
+
* type UserActions = ResourceActions<"users">;
|
|
2272
|
+
* // Result: The complete actions record for users resource
|
|
2273
|
+
*
|
|
2274
|
+
* const userActions: UserActions = {
|
|
2275
|
+
* read: { label: "Read User", title: "View user details" },
|
|
2276
|
+
* create: { label: "Create User", title: "Add new user" },
|
|
2277
|
+
* update: { label: "Update User", title: "Modify user data" },
|
|
2278
|
+
* archive: { label: "Archive User", title: "Soft delete user" }
|
|
2279
|
+
* };
|
|
2280
|
+
* ```
|
|
2281
|
+
*
|
|
2282
|
+
* @example
|
|
2283
|
+
* ```typescript
|
|
2284
|
+
* // Function that works with resource actions
|
|
2285
|
+
* function validateResourceActions<TResourceName extends ResourceName>(
|
|
2286
|
+
* resourceName: TResourceName,
|
|
2287
|
+
* actions: ResourceActions<TResourceName>
|
|
2288
|
+
* ): boolean {
|
|
2289
|
+
* // Check if all required actions are present
|
|
2290
|
+
* const requiredActions: (keyof ResourceActions<TResourceName>)[] = ['read', 'create', 'update'];
|
|
2291
|
+
*
|
|
2292
|
+
* return requiredActions.every(action =>
|
|
2293
|
+
* action in actions && actions[action] !== undefined
|
|
2294
|
+
* );
|
|
2295
|
+
* }
|
|
2296
|
+
*
|
|
2297
|
+
* // Usage
|
|
2298
|
+
* const validUserActions = validateResourceActions("users", {
|
|
2299
|
+
* read: { label: "Read" },
|
|
2300
|
+
* create: { label: "Create" },
|
|
2301
|
+
* update: { label: "Update" },
|
|
2302
|
+
* archive: { label: "Archive" }
|
|
2303
|
+
* }); // Returns true
|
|
2304
|
+
* ```
|
|
2305
|
+
*
|
|
2306
|
+
* @example
|
|
2307
|
+
* ```typescript
|
|
2308
|
+
* // Building action handlers with type safety
|
|
2309
|
+
* function createActionHandlers<TResourceName extends ResourceName>(
|
|
2310
|
+
* resourceName: TResourceName,
|
|
2311
|
+
* actions: ResourceActions<TResourceName>
|
|
2312
|
+
* ) {
|
|
2313
|
+
* const handlers: Record<string, () => void> = {};
|
|
2314
|
+
*
|
|
2315
|
+
* // TypeScript knows the exact action names available
|
|
2316
|
+
* for (const actionName in actions) {
|
|
2317
|
+
* handlers[actionName] = () => {
|
|
2318
|
+
* const action = actions[actionName as keyof ResourceActions<TResourceName>];
|
|
2319
|
+
* console.log(`Executing ${action?.label} on ${resourceName}`);
|
|
2320
|
+
* };
|
|
2321
|
+
* }
|
|
2322
|
+
*
|
|
2323
|
+
* return handlers;
|
|
2324
|
+
* }
|
|
2325
|
+
*
|
|
2326
|
+
* // Usage
|
|
2327
|
+
* const userActionHandlers = createActionHandlers("users", {
|
|
2328
|
+
* read: { label: "Read User" },
|
|
2329
|
+
* create: { label: "Create User" },
|
|
2330
|
+
* update: { label: "Update User" }
|
|
2331
|
+
* });
|
|
2332
|
+
*
|
|
2333
|
+
* userActionHandlers.read(); // ✓ Valid - calls read handler
|
|
2334
|
+
* userActionHandlers.create(); // ✓ Valid - calls create handler
|
|
2335
|
+
* // userActionHandlers.delete(); // ✗ TypeScript error - delete not in user actions
|
|
2336
|
+
* ```
|
|
2337
|
+
*
|
|
2338
|
+
* @example
|
|
2339
|
+
* ```typescript
|
|
2340
|
+
* // Permission system based on resource actions
|
|
2341
|
+
* class PermissionManager {
|
|
2342
|
+
* private permissions = new Map<ResourceName, Set<string>>();
|
|
2343
|
+
*
|
|
2344
|
+
* grantPermission<TResourceName extends ResourceName>(
|
|
2345
|
+
* resourceName: TResourceName,
|
|
2346
|
+
* actions: (keyof ResourceActions<TResourceName>)[]
|
|
2347
|
+
* ) {
|
|
2348
|
+
* const current = this.permissions.get(resourceName) || new Set();
|
|
2349
|
+
* actions.forEach(action => current.add(action as string));
|
|
2350
|
+
* this.permissions.set(resourceName, current);
|
|
2351
|
+
* }
|
|
2352
|
+
*
|
|
2353
|
+
* hasPermission<TResourceName extends ResourceName>(
|
|
2354
|
+
* resourceName: TResourceName,
|
|
2355
|
+
* action: keyof ResourceActions<TResourceName>
|
|
2356
|
+
* ): boolean {
|
|
2357
|
+
* const resourcePerms = this.permissions.get(resourceName);
|
|
2358
|
+
* return resourcePerms?.has(action as string) ?? false;
|
|
2359
|
+
* }
|
|
2360
|
+
* }
|
|
2361
|
+
*
|
|
2362
|
+
* // Usage
|
|
2363
|
+
* const permManager = new PermissionManager();
|
|
2364
|
+
* permManager.grantPermission("users", ["read", "create"]);
|
|
2365
|
+
*
|
|
2366
|
+
* permManager.hasPermission("users", "read"); // true
|
|
2367
|
+
* permManager.hasPermission("users", "update"); // false
|
|
2368
|
+
* // permManager.hasPermission("users", "invalid"); // ✗ TypeScript error
|
|
2369
|
+
* ```
|
|
2370
|
+
*/
|
|
2371
|
+
export type ResourceActions<TResourceName extends ResourceName> = Resources[TResourceName] extends {
|
|
2372
|
+
actions: Record<string, ResourceAction>;
|
|
2373
|
+
} ? ResourceActionsRecord<Resources[TResourceName]['actions']> : never;
|
|
2374
|
+
type GetResourceActionNames<TResource extends {
|
|
2375
|
+
actions?: Record<string, ResourceAction>;
|
|
2376
|
+
}> = keyof ResourceActionsRecord<TResource['actions']> & string;
|
|
2377
|
+
/**
|
|
2378
|
+
* ## ResourceDefaultActions Interface
|
|
2379
|
+
*
|
|
2380
|
+
* A foundational interface defining the standard CRUD (Create, Read, Update, Delete) actions
|
|
2381
|
+
* that can be performed on resources, plus an "all" action for comprehensive permissions.
|
|
2382
|
+
* This interface serves as the base set of actions that resource configurations can extend,
|
|
2383
|
+
* override, or supplement with custom actions.
|
|
2384
|
+
*
|
|
2385
|
+
* ### Purpose
|
|
2386
|
+
* The `ResourceDefaultActions` interface establishes a consistent set of core actions that
|
|
2387
|
+
* most resources will support. It provides a standardized foundation for resource management
|
|
2388
|
+
* while allowing flexibility for custom actions specific to particular resources.
|
|
2389
|
+
*
|
|
2390
|
+
* ### How It Works
|
|
2391
|
+
* - **Base Actions**: Defines the five fundamental actions (read, create, update, delete, all)
|
|
2392
|
+
* - **Extensibility**: Used in `ResourceActionsRecord` to create action records that can be extended
|
|
2393
|
+
* - **Type Safety**: Each action is typed as `ResourceAction`, ensuring consistent structure
|
|
2394
|
+
* - **Optional Override**: Resources can override these defaults or add custom actions alongside them
|
|
2395
|
+
*
|
|
2396
|
+
* ### Default Actions
|
|
2397
|
+
*
|
|
2398
|
+
* - **`read`**: Retrieve/view resource data (GET operations)
|
|
2399
|
+
* - **`create`**: Create new resource instances (POST operations)
|
|
2400
|
+
* - **`update`**: Modify existing resource instances (PUT/PATCH operations)
|
|
2401
|
+
* - **`delete`**: Remove resource instances (DELETE operations)
|
|
2402
|
+
* - **`all`**: Perform any action on the resource (wildcard permission)
|
|
2403
|
+
*
|
|
2404
|
+
* ### Template Parameters
|
|
2405
|
+
* This interface doesn't use generic parameters itself, but serves as a foundation for
|
|
2406
|
+
* action-based type operations and resource configurations.
|
|
2407
|
+
*
|
|
2408
|
+
* ### Examples
|
|
2409
|
+
*
|
|
2410
|
+
* #### Basic Resource with Default Actions
|
|
2411
|
+
* ```typescript
|
|
2412
|
+
* // A simple resource using only default actions
|
|
2413
|
+
* const userResource: ResourceBase = {
|
|
2414
|
+
* name: "users",
|
|
2415
|
+
* label: "Users",
|
|
2416
|
+
* actions: {
|
|
2417
|
+
* read: { label: "View Users" },
|
|
2418
|
+
* create: { label: "Create User" },
|
|
2419
|
+
* update: { label: "Update User" },
|
|
2420
|
+
* delete: { label: "Delete User" },
|
|
2421
|
+
* all: { label: "Full User Access" }
|
|
2422
|
+
* }
|
|
2423
|
+
* };
|
|
2424
|
+
* ```
|
|
2425
|
+
*
|
|
2426
|
+
* #### Resource Extending Default Actions
|
|
2427
|
+
* ```typescript
|
|
2428
|
+
* // Resource with default actions plus custom ones
|
|
2429
|
+
* const blogResource: ResourceBase = {
|
|
2430
|
+
* name: "posts",
|
|
2431
|
+
* label: "Blog Posts",
|
|
2432
|
+
* actions: {
|
|
2433
|
+
* // Default actions
|
|
2434
|
+
* read: { label: "View Posts" },
|
|
2435
|
+
* create: { label: "Write Post" },
|
|
2436
|
+
* update: { label: "Edit Post" },
|
|
2437
|
+
* delete: { label: "Delete Post" },
|
|
2438
|
+
* all: { label: "Full Post Access" },
|
|
2439
|
+
*
|
|
2440
|
+
* // Custom actions
|
|
2441
|
+
* publish: { label: "Publish Post" },
|
|
2442
|
+
* archive: { label: "Archive Post" },
|
|
2443
|
+
* feature: { label: "Feature Post" }
|
|
2444
|
+
* }
|
|
2445
|
+
* };
|
|
2446
|
+
* ```
|
|
2447
|
+
*
|
|
2448
|
+
* #### Permission System Using Default Actions
|
|
2449
|
+
* ```typescript
|
|
2450
|
+
* // Permission checking with default actions
|
|
2451
|
+
* class PermissionManager {
|
|
2452
|
+
* private permissions = new Map<string, Set<string>>();
|
|
2453
|
+
*
|
|
2454
|
+
* grantDefaultActions(resourceName: string) {
|
|
2455
|
+
* const key = `resource:${resourceName}`;
|
|
2456
|
+
* const actions = this.permissions.get(key) || new Set();
|
|
2457
|
+
*
|
|
2458
|
+
* // Grant all default actions
|
|
2459
|
+
* actions.add('read');
|
|
2460
|
+
* actions.add('create');
|
|
2461
|
+
* actions.add('update');
|
|
2462
|
+
* actions.add('delete');
|
|
2463
|
+
*
|
|
2464
|
+
* this.permissions.set(key, actions);
|
|
2465
|
+
* }
|
|
2466
|
+
*
|
|
2467
|
+
* hasPermission(resourceName: string, action: keyof ResourceDefaultActions): boolean {
|
|
2468
|
+
* const key = `resource:${resourceName}`;
|
|
2469
|
+
* const actions = this.permissions.get(key);
|
|
2470
|
+
* return actions?.has(action) ?? false;
|
|
2471
|
+
* }
|
|
2472
|
+
* }
|
|
2473
|
+
*
|
|
2474
|
+
* // Usage
|
|
2475
|
+
* const permManager = new PermissionManager();
|
|
2476
|
+
* permManager.grantDefaultActions('users');
|
|
2477
|
+
*
|
|
2478
|
+
* permManager.hasPermission('users', 'read'); // true
|
|
2479
|
+
* permManager.hasPermission('users', 'create'); // true
|
|
2480
|
+
* permManager.hasPermission('users', 'delete'); // true
|
|
2481
|
+
* permManager.hasPermission('users', 'all'); // false (not granted)
|
|
2482
|
+
* ```
|
|
2483
|
+
*
|
|
2484
|
+
* #### UI Component with Action-Based Rendering
|
|
2485
|
+
* ```typescript
|
|
2486
|
+
* // Component that renders different UI based on available actions
|
|
2487
|
+
* interface ResourceActionButtonProps {
|
|
2488
|
+
* resourceName: string;
|
|
2489
|
+
* action: keyof ResourceDefaultActions;
|
|
2490
|
+
* onClick: () => void;
|
|
2491
|
+
* children: React.ReactNode;
|
|
2492
|
+
* }
|
|
2493
|
+
*
|
|
2494
|
+
* function ResourceActionButton({
|
|
2495
|
+
* resourceName,
|
|
2496
|
+
* action,
|
|
2497
|
+
* onClick,
|
|
2498
|
+
* children
|
|
2499
|
+
* }: ResourceActionButtonProps) {
|
|
2500
|
+
* const hasPermission = usePermission(resourceName, action);
|
|
2501
|
+
*
|
|
2502
|
+
* if (!hasPermission) return null;
|
|
2503
|
+
*
|
|
2504
|
+
* return (
|
|
2505
|
+
* <button onClick={onClick} data-action={action}>
|
|
2506
|
+
* {children}
|
|
2507
|
+
* </button>
|
|
2508
|
+
* );
|
|
2509
|
+
* }
|
|
2510
|
+
*
|
|
2511
|
+
* // Usage in a resource management component
|
|
2512
|
+
* function UserManagement() {
|
|
2513
|
+
* return (
|
|
2514
|
+
* <div>
|
|
2515
|
+
* <ResourceActionButton resourceName="users" action="create">
|
|
2516
|
+
* Add User
|
|
2517
|
+
* </ResourceActionButton>
|
|
2518
|
+
*
|
|
2519
|
+
* <ResourceActionButton resourceName="users" action="read">
|
|
2520
|
+
* View Users
|
|
2521
|
+
* </ResourceActionButton>
|
|
2522
|
+
*
|
|
2523
|
+
* <ResourceActionButton resourceName="users" action="update">
|
|
2524
|
+
* Edit User
|
|
2525
|
+
* </ResourceActionButton>
|
|
2526
|
+
*
|
|
2527
|
+
* <ResourceActionButton resourceName="users" action="delete">
|
|
2528
|
+
* Delete User
|
|
2529
|
+
* </ResourceActionButton>
|
|
2530
|
+
* </div>
|
|
2531
|
+
* );
|
|
2532
|
+
* }
|
|
2533
|
+
* ```
|
|
2534
|
+
*
|
|
2535
|
+
* #### API Route Protection
|
|
2536
|
+
* ```typescript
|
|
2537
|
+
* // Middleware for protecting API routes based on default actions
|
|
2538
|
+
* function requireAction(resourceName: string, action: keyof ResourceDefaultActions) {
|
|
2539
|
+
* return (req: Request, res: Response, next: NextFunction) => {
|
|
2540
|
+
* const user = getCurrentUser(req);
|
|
2541
|
+
* const hasPermission = checkUserPermission(user, resourceName, action);
|
|
2542
|
+
*
|
|
2543
|
+
* if (!hasPermission) {
|
|
2544
|
+
* return res.status(403).json({
|
|
2545
|
+
* error: 'Forbidden',
|
|
2546
|
+
* message: `You don't have permission to ${action} ${resourceName}`
|
|
2547
|
+
* });
|
|
2548
|
+
* }
|
|
2549
|
+
*
|
|
2550
|
+
* next();
|
|
2551
|
+
* };
|
|
2552
|
+
* }
|
|
2553
|
+
*
|
|
2554
|
+
* // Usage in Express routes
|
|
2555
|
+
* app.get('/api/users', requireAction('users', 'read'), getUsers);
|
|
2556
|
+
* app.post('/api/users', requireAction('users', 'create'), createUser);
|
|
2557
|
+
* app.put('/api/users/:id', requireAction('users', 'update'), updateUser);
|
|
2558
|
+
* app.delete('/api/users/:id', requireAction('users', 'delete'), deleteUser);
|
|
2559
|
+
* ```
|
|
2560
|
+
*
|
|
2561
|
+
* #### Form Validation Based on Actions
|
|
2562
|
+
* ```typescript
|
|
2563
|
+
* // Form component that adapts based on available actions
|
|
2564
|
+
* interface SmartFormProps<T> {
|
|
2565
|
+
* resourceName: string;
|
|
2566
|
+
* initialData?: T;
|
|
2567
|
+
* onSubmit: (data: T) => void;
|
|
2568
|
+
* }
|
|
2569
|
+
*
|
|
2570
|
+
* function SmartForm<T>({ resourceName, initialData, onSubmit }: SmartFormProps<T>) {
|
|
2571
|
+
* const canCreate = usePermission(resourceName, 'create');
|
|
2572
|
+
* const canUpdate = usePermission(resourceName, 'update');
|
|
2573
|
+
* const isEditing = !!initialData;
|
|
2574
|
+
*
|
|
2575
|
+
* // Don't render form if no appropriate permissions
|
|
2576
|
+
* if (isEditing && !canUpdate) return { error: 'No permission to edit' };
|
|
2577
|
+
* if (!isEditing && !canCreate) return { error: 'No permission to create' };
|
|
2578
|
+
*
|
|
2579
|
+
* return (
|
|
2580
|
+
* <form onSubmit={handleSubmit(onSubmit)}>
|
|
2581
|
+
* <button type="submit">
|
|
2582
|
+
* {isEditing ? 'Update' : 'Create'}
|
|
2583
|
+
* </button>
|
|
2584
|
+
* </form>
|
|
2585
|
+
* );
|
|
2586
|
+
* }
|
|
2587
|
+
* ```
|
|
2588
|
+
*
|
|
2589
|
+
* ### Best Practices
|
|
2590
|
+
* - **Consistent Action Names**: Always use the standard action names (read, create, update, delete, all)
|
|
2591
|
+
* - **Clear Labels**: Provide descriptive labels for each action in resource definitions
|
|
2592
|
+
* - **Permission Granularity**: Use specific actions rather than relying only on "all"
|
|
2593
|
+
* - **Documentation**: Document custom actions that extend beyond the defaults
|
|
2594
|
+
* - **UI Consistency**: Use these actions consistently across different UI components
|
|
2595
|
+
*
|
|
2596
|
+
* ### Integration with Other Types
|
|
2597
|
+
* - **`ResourceActions<T>`**: Uses this interface as the base for resource-specific actions
|
|
2598
|
+
* - **`ResourceActionName<T>`**: Provides type-safe action names for specific resources
|
|
2599
|
+
* - **`ResourceBase`**: Resources can extend or override these default actions
|
|
2600
|
+
* - **`ResourceAction`**: The type definition for individual actions
|
|
2601
|
+
*
|
|
2602
|
+
* ### Migration Notes
|
|
2603
|
+
* When upgrading from custom action definitions, map your existing actions to these standard
|
|
2604
|
+
* names where possible. The "all" action can be used for comprehensive permissions that
|
|
2605
|
+
* previously used different naming conventions.
|
|
2606
|
+
*
|
|
2607
|
+
* @interface ResourceDefaultActions
|
|
2608
|
+
* @public
|
|
2609
|
+
*
|
|
2610
|
+
* @see {@link ResourceActions} - How these actions are used in resource configurations
|
|
2611
|
+
* @see {@link ResourceAction} - The structure of individual actions
|
|
2612
|
+
* @see {@link ResourceBase} - How resources define their available actions
|
|
2613
|
+
* @example
|
|
2614
|
+
* ```typescript
|
|
2615
|
+
* // Complete example of using ResourceDefaultActions in a resource definition
|
|
2616
|
+
* import "reslib/resources";
|
|
2617
|
+
*
|
|
2618
|
+
* declare module "reslib/resources" {
|
|
2619
|
+
* interface Resources {
|
|
2620
|
+
* users: {
|
|
2621
|
+
* actions: ResourceDefaultActions & {
|
|
2622
|
+
* // Extend with custom actions
|
|
2623
|
+
* resetPassword: { label: "Reset Password" };
|
|
2624
|
+
* activate: { label: "Activate Account" };
|
|
2625
|
+
* deactivate: { label: "Deactivate Account" };
|
|
2626
|
+
* }
|
|
2627
|
+
* };
|
|
2628
|
+
*
|
|
2629
|
+
* products: {
|
|
2630
|
+
* actions: ResourceDefaultActions & {
|
|
2631
|
+
* // Product-specific actions
|
|
2632
|
+
* restock: { label: "Restock Product" };
|
|
2633
|
+
* discontinue: { label: "Discontinue Product" };
|
|
2634
|
+
* feature: { label: "Feature Product" };
|
|
2635
|
+
* }
|
|
2636
|
+
* };
|
|
2637
|
+
* }
|
|
2638
|
+
* }
|
|
2639
|
+
*
|
|
2640
|
+
* // Type-safe usage
|
|
2641
|
+
* type UserActions = ResourceActions<"users">;
|
|
2642
|
+
* // Includes: read, create, update, delete, all, resetPassword, activate, deactivate
|
|
2643
|
+
*
|
|
2644
|
+
* type ProductActions = ResourceActions<"products">;
|
|
2645
|
+
* // Includes: read, create, update, delete, all, restock, discontinue, feature
|
|
2646
|
+
*
|
|
2647
|
+
* // Permission checking function
|
|
2648
|
+
* function canPerformAction<T extends ResourceName>(
|
|
2649
|
+
* resource: T,
|
|
2650
|
+
* action: keyof ResourceActions<T>
|
|
2651
|
+
* ): boolean {
|
|
2652
|
+
* // Implementation would check user permissions
|
|
2653
|
+
* return true; // Simplified for example
|
|
2654
|
+
* }
|
|
2655
|
+
*
|
|
2656
|
+
* // Usage
|
|
2657
|
+
* canPerformAction("users", "create"); // ✓ Valid - default action
|
|
2658
|
+
* canPerformAction("users", "resetPassword"); // ✓ Valid - custom action
|
|
2659
|
+
* canPerformAction("users", "restock"); // ✗ TypeScript error - not a user action
|
|
2660
|
+
* ```
|
|
2661
|
+
*/
|
|
2662
|
+
export interface ResourceDefaultActions {
|
|
2663
|
+
/**
|
|
2664
|
+
* The read action for the resource.
|
|
2665
|
+
* This action is used to retrieve a specific resource.
|
|
2666
|
+
*
|
|
2667
|
+
* @type {ResourceAction}
|
|
2668
|
+
* @example
|
|
2669
|
+
* ```typescript
|
|
2670
|
+
* const readAction: ResourceAction = {
|
|
2671
|
+
* label: "Read Resource",
|
|
2672
|
+
* title: "Click to read a specific resource.",
|
|
2673
|
+
* };
|
|
2674
|
+
* ```
|
|
2675
|
+
*/
|
|
2676
|
+
read: ResourceAction;
|
|
2677
|
+
/**
|
|
2678
|
+
* The create action for the resource.
|
|
2679
|
+
* This action is used to create a new resource.
|
|
2680
|
+
*
|
|
2681
|
+
* @type {ResourceAction}
|
|
2682
|
+
* @example
|
|
2683
|
+
* ```typescript
|
|
2684
|
+
* const createAction: ResourceAction = {
|
|
2685
|
+
* label: "Create Resource",
|
|
2686
|
+
* title: "Click to create a new resource.",
|
|
2687
|
+
* };
|
|
2688
|
+
* ```
|
|
2689
|
+
*/
|
|
2690
|
+
create: ResourceAction;
|
|
2691
|
+
/**
|
|
2692
|
+
* The update action for the resource.
|
|
2693
|
+
* This action is used to update a specific resource.
|
|
2694
|
+
*
|
|
2695
|
+
* @type {ResourceAction}
|
|
2696
|
+
* @example
|
|
2697
|
+
* ```typescript
|
|
2698
|
+
* const updateAction: ResourceAction = {
|
|
2699
|
+
* label: "Update Resource",
|
|
2700
|
+
* title: "Click to update a specific resource.",
|
|
2701
|
+
* };
|
|
2702
|
+
* ```
|
|
2703
|
+
*/
|
|
2704
|
+
update: ResourceAction;
|
|
2705
|
+
/**
|
|
2706
|
+
* The delete action for the resource.
|
|
2707
|
+
* This action is used to delete a specific resource.
|
|
2708
|
+
*
|
|
2709
|
+
* @type {ResourceAction}
|
|
2710
|
+
* @example
|
|
2711
|
+
* ```typescript
|
|
2712
|
+
* const deleteAction: ResourceAction = {
|
|
2713
|
+
* label: "Delete Resource",
|
|
2714
|
+
* title: "Click to delete a specific resource.",
|
|
2715
|
+
* };
|
|
2716
|
+
* ```
|
|
2717
|
+
*/
|
|
2718
|
+
delete: ResourceAction;
|
|
2719
|
+
/**
|
|
2720
|
+
* The all action for the resource.
|
|
2721
|
+
* This action is used to perform all actions on the resource.
|
|
2722
|
+
*
|
|
2723
|
+
* @type {ResourceAction}
|
|
2724
|
+
* @example
|
|
2725
|
+
* ```typescript
|
|
2726
|
+
* const allAction: ResourceAction = {
|
|
2727
|
+
* label: "All Actions",
|
|
2728
|
+
* title: "Click to perform all actions on the resource.",
|
|
2729
|
+
* };
|
|
2730
|
+
* ```
|
|
2731
|
+
*/
|
|
2732
|
+
all: ResourceAction;
|
|
2733
|
+
}
|
|
2734
|
+
/**
|
|
2735
|
+
* ## ResourceBase Interface
|
|
2736
|
+
*
|
|
2737
|
+
* The foundational interface that defines the structure and properties required for all resources
|
|
2738
|
+
* in the application. This interface serves as the base contract that all resource definitions
|
|
2739
|
+
* must implement to be considered valid within the resource management system.
|
|
2740
|
+
*
|
|
2741
|
+
* ### Purpose
|
|
2742
|
+
* The `ResourceBase` interface establishes the minimum structure that every resource must have,
|
|
2743
|
+
* ensuring consistency and type safety across all resource definitions. It acts as a constraint
|
|
2744
|
+
* that validates resource configurations during module augmentation, preventing invalid or
|
|
2745
|
+
* incomplete resource definitions from being accepted by the type system.
|
|
2746
|
+
*
|
|
2747
|
+
* ### How It Works
|
|
2748
|
+
* - **Module Augmentation**: Resources are defined by extending the global `Resources` interface
|
|
2749
|
+
* - **Validation**: The `ValidateResource<T>` type ensures each resource implements `ResourceBase`
|
|
2750
|
+
* - **Type Safety**: Only resources that conform to this interface are included in `ResourceName`
|
|
2751
|
+
* - **Extensibility**: Generic parameters allow resources to specify their own action types
|
|
2752
|
+
*
|
|
2753
|
+
* ### Template Parameters
|
|
2754
|
+
* - **TResourceName**: The specific resource name type (extends `ResourceName`)
|
|
2755
|
+
* - **Actions**: The record of actions available for this resource (extends `Record<string, ResourceAction>`)
|
|
2756
|
+
*
|
|
2757
|
+
* ### Examples
|
|
2758
|
+
*
|
|
2759
|
+
* #### Basic Resource Definition
|
|
2760
|
+
* ```typescript
|
|
2761
|
+
* // Define a simple user resource
|
|
2762
|
+
* const userResource: ResourceBase<"users"> = {
|
|
2763
|
+
* name: "users",
|
|
2764
|
+
* label: "Users",
|
|
2765
|
+
* title: "User Management",
|
|
2766
|
+
* actions: {
|
|
2767
|
+
* read: { label: "Read User" },
|
|
2768
|
+
* create: { label: "Create User" },
|
|
2769
|
+
* update: { label: "Update User" }
|
|
2770
|
+
* }
|
|
2771
|
+
* };
|
|
2772
|
+
* ```
|
|
2773
|
+
*
|
|
2774
|
+
* #### Advanced Resource with Custom Actions
|
|
2775
|
+
* ```typescript
|
|
2776
|
+
* // Define a product resource with custom actions
|
|
2777
|
+
* const productResource: ResourceBase<"products", {
|
|
2778
|
+
* read: ResourceAction;
|
|
2779
|
+
* create: ResourceAction;
|
|
2780
|
+
* update: ResourceAction;
|
|
2781
|
+
* discontinue: ResourceAction;
|
|
2782
|
+
* restock: ResourceAction;
|
|
2783
|
+
* }> = {
|
|
2784
|
+
* name: "products",
|
|
2785
|
+
* label: "Products",
|
|
2786
|
+
* title: "Product Catalog Management",
|
|
2787
|
+
* actions: {
|
|
2788
|
+
* read: { label: "View Product", title: "Display product details" },
|
|
2789
|
+
* create: { label: "Add Product", title: "Create new product entry" },
|
|
2790
|
+
* update: { label: "Edit Product", title: "Modify product information" },
|
|
2791
|
+
* discontinue: { label: "Discontinue Product", title: "Mark product as discontinued" },
|
|
2792
|
+
* restock: { label: "Restock Product", title: "Update product inventory" }
|
|
2793
|
+
* }
|
|
2794
|
+
* };
|
|
2795
|
+
* ```
|
|
2796
|
+
*
|
|
2797
|
+
* #### Module Augmentation Usage
|
|
2798
|
+
* ```typescript
|
|
2799
|
+
* // In your application's types file
|
|
2800
|
+
* import "reslib/resources";
|
|
2801
|
+
*
|
|
2802
|
+
* declare module "reslib/resources" {
|
|
2803
|
+
* interface Resources {
|
|
2804
|
+
* // All of these must implement ResourceBase
|
|
2805
|
+
* users: {
|
|
2806
|
+
* name: "users";
|
|
2807
|
+
* label: "Users";
|
|
2808
|
+
* actions: {
|
|
2809
|
+
* read: { label: "Read User" };
|
|
2810
|
+
* create: { label: "Create User" };
|
|
2811
|
+
* };
|
|
2812
|
+
* };
|
|
2813
|
+
*
|
|
2814
|
+
* posts: {
|
|
2815
|
+
* name: "posts";
|
|
2816
|
+
* label: "Posts";
|
|
2817
|
+
* title: "Blog Posts";
|
|
2818
|
+
* actions: {
|
|
2819
|
+
* read: { label: "Read Post" };
|
|
2820
|
+
* publish: { label: "Publish Post" };
|
|
2821
|
+
* };
|
|
2822
|
+
* };
|
|
2823
|
+
* }
|
|
2824
|
+
* }
|
|
2825
|
+
*
|
|
2826
|
+
* // TypeScript will enforce ResourceBase structure
|
|
2827
|
+
* // Invalid resources will cause compilation errors
|
|
2828
|
+
* ```
|
|
2829
|
+
*
|
|
2830
|
+
* #### Type-Safe Resource Operations
|
|
2831
|
+
* ```typescript
|
|
2832
|
+
* // Using ResourceBase for type-safe operations
|
|
2833
|
+
* function createResourceHandler<T extends ResourceBase>(
|
|
2834
|
+
* resource: T
|
|
2835
|
+
* ): ResourceHandler<T> {
|
|
2836
|
+
* return {
|
|
2837
|
+
* name: resource.name,
|
|
2838
|
+
* label: resource.label,
|
|
2839
|
+
* actions: Object.keys(resource.actions),
|
|
2840
|
+
* // TypeScript knows resource has name, label, and actions
|
|
2841
|
+
* };
|
|
2842
|
+
* }
|
|
2843
|
+
*
|
|
2844
|
+
* const userHandler = createResourceHandler(userResource);
|
|
2845
|
+
* // userHandler.name: "users"
|
|
2846
|
+
* // userHandler.label: "Users"
|
|
2847
|
+
* // userHandler.actions: string[]
|
|
2848
|
+
* ```
|
|
2849
|
+
*
|
|
2850
|
+
* #### Resource Validation
|
|
2851
|
+
* ```typescript
|
|
2852
|
+
* // Custom validation using ResourceBase structure
|
|
2853
|
+
* function validateResource(resource: any): resource is ResourceBase {
|
|
2854
|
+
* return (
|
|
2855
|
+
* typeof resource === 'object' &&
|
|
2856
|
+
* typeof resource.name === 'string' &&
|
|
2857
|
+
* typeof resource.actions === 'object' &&
|
|
2858
|
+
* resource.actions !== null
|
|
2859
|
+
* );
|
|
2860
|
+
* }
|
|
2861
|
+
*
|
|
2862
|
+
* // Usage
|
|
2863
|
+
* if (validateResource(someResource)) {
|
|
2864
|
+
* // TypeScript knows this is a ResourceBase
|
|
2865
|
+
* console.log(`Valid resource: ${someResource.name}`);
|
|
2866
|
+
* }
|
|
2867
|
+
* ```
|
|
2868
|
+
*
|
|
2869
|
+
* ### Best Practices
|
|
2870
|
+
* - **Consistent Naming**: Use lowercase resource names (e.g., "users", "products")
|
|
2871
|
+
* - **Descriptive Labels**: Provide clear, user-friendly labels for UI display
|
|
2872
|
+
* - **Comprehensive Actions**: Define all relevant actions for the resource's lifecycle
|
|
2873
|
+
* - **Optional Properties**: Use `label` and `title` for better UX, but they're optional
|
|
2874
|
+
* - **Type Safety**: Leverage the generic parameters for precise action typing
|
|
2875
|
+
* - **Documentation**: Document custom actions and their purposes
|
|
2876
|
+
*
|
|
2877
|
+
* ### Integration with Other Types
|
|
2878
|
+
* - **`Resources`**: Global interface where resources are defined via module augmentation
|
|
2879
|
+
* - **`ResourceName`**: Union type of all valid resource names (requires ResourceBase compliance)
|
|
2880
|
+
* - **`ResourceConfig<T>`**: Provides type-safe access to individual resource configurations
|
|
2881
|
+
* - **`ResourceActions<T>`**: Type-safe access to a resource's actions
|
|
2882
|
+
* - **`ValidateResource<T>`**: Type-level validation that checks ResourceBase implementation
|
|
2883
|
+
* - **`ValidatedResources`**: Maps all resources to ensure they extend ResourceBase
|
|
2884
|
+
*
|
|
2885
|
+
* ### Migration Notes
|
|
2886
|
+
* When adding new resources, ensure they implement all required `ResourceBase` properties.
|
|
2887
|
+
* The `name` property must match the key used in the `Resources` interface. Existing
|
|
2888
|
+
* resources that don't conform to this interface will cause TypeScript compilation errors
|
|
2889
|
+
* after upgrading, helping catch configuration issues early.
|
|
2890
|
+
*
|
|
2891
|
+
* @interface ResourceBase
|
|
2892
|
+
* @template TResourceName - The specific resource name type
|
|
2893
|
+
* @template Actions - The record of actions for this resource
|
|
2894
|
+
* @public
|
|
2895
|
+
*
|
|
2896
|
+
* @see {@link Resources} - Global interface for resource definitions
|
|
2897
|
+
* @see {@link ResourceName} - Union of all valid resource names
|
|
2898
|
+
* @see {@link ResourceConfig} - Type-safe resource configuration access
|
|
2899
|
+
* @see {@link ValidateResource} - Type-level validation of resource structure
|
|
2900
|
+
* @example
|
|
2901
|
+
* ```typescript
|
|
2902
|
+
* // Complete example of ResourceBase usage
|
|
2903
|
+
* import "reslib/resources";
|
|
2904
|
+
*
|
|
2905
|
+
* // Define custom action types for better type safety
|
|
2906
|
+
* interface UserActions {
|
|
2907
|
+
* read: ResourceAction;
|
|
2908
|
+
* create: ResourceAction;
|
|
2909
|
+
* update: ResourceAction;
|
|
2910
|
+
* delete: ResourceAction;
|
|
2911
|
+
* activate: ResourceAction;
|
|
2912
|
+
* deactivate: ResourceAction;
|
|
2913
|
+
* }
|
|
2914
|
+
*
|
|
2915
|
+
* // Create a fully typed resource
|
|
2916
|
+
* const userResource: ResourceBase<"users", UserActions> = {
|
|
2917
|
+
* name: "users",
|
|
2918
|
+
* label: "Users",
|
|
2919
|
+
* title: "User Account Management",
|
|
2920
|
+
* actions: {
|
|
2921
|
+
* read: {
|
|
2922
|
+
* label: "Read User",
|
|
2923
|
+
* title: "View user account details"
|
|
2924
|
+
* },
|
|
2925
|
+
* create: {
|
|
2926
|
+
* label: "Create User",
|
|
2927
|
+
* title: "Add new user account"
|
|
2928
|
+
* },
|
|
2929
|
+
* update: {
|
|
2930
|
+
* label: "Update User",
|
|
2931
|
+
* title: "Modify user account information"
|
|
2932
|
+
* },
|
|
2933
|
+
* delete: {
|
|
2934
|
+
* label: "Delete User",
|
|
2935
|
+
* title: "Remove user account"
|
|
2936
|
+
* },
|
|
2937
|
+
* activate: {
|
|
2938
|
+
* label: "Activate User",
|
|
2939
|
+
* title: "Enable user account access"
|
|
2940
|
+
* },
|
|
2941
|
+
* deactivate: {
|
|
2942
|
+
* label: "Deactivate User",
|
|
2943
|
+
* title: "Disable user account access"
|
|
2944
|
+
* }
|
|
2945
|
+
* }
|
|
2946
|
+
* };
|
|
2947
|
+
*
|
|
2948
|
+
* // Module augmentation for global availability
|
|
2949
|
+
* declare module "reslib/resources" {
|
|
2950
|
+
* interface Resources {
|
|
2951
|
+
* users: typeof userResource;
|
|
2952
|
+
* }
|
|
2953
|
+
* }
|
|
2954
|
+
*
|
|
2955
|
+
* // Type-safe usage throughout the application
|
|
2956
|
+
* type UserName = "users"; // From ResourceName union
|
|
2957
|
+
* type UserConfig = ResourceConfig<"users">; // Full type safety
|
|
2958
|
+
* type UserActionNames = ResourceActionName<"users">; // "read" | "create" | "update" | ...
|
|
2959
|
+
* ```
|
|
2960
|
+
*/
|
|
2961
|
+
export interface ResourceBase<TResourceName extends ResourceName = ResourceName, Actions extends Record<string, ResourceAction> = Record<string, ResourceAction>> {
|
|
2962
|
+
/**
|
|
2963
|
+
* The internal name of the resource.
|
|
2964
|
+
*
|
|
2965
|
+
* This name is used within the system for referencing the resource programmatically.
|
|
2966
|
+
* It is often a short, unique identifier for the resource.
|
|
2967
|
+
*
|
|
2968
|
+
* @example
|
|
2969
|
+
* ```typescript
|
|
2970
|
+
* const userResource: ResourceBase = { name: "user" };
|
|
2971
|
+
* ```
|
|
2972
|
+
*/
|
|
2973
|
+
name: TResourceName;
|
|
2974
|
+
/**
|
|
2975
|
+
* A user-friendly label for the resource.
|
|
2976
|
+
*
|
|
2977
|
+
* This is typically a shorter name intended for display in UI elements, such as dropdowns or buttons.
|
|
2978
|
+
* It helps users identify the resource within the user interface.
|
|
2979
|
+
*
|
|
2980
|
+
* @example
|
|
2981
|
+
* ```typescript
|
|
2982
|
+
* const productResource: ResourceBase = { label: "Product" };
|
|
2983
|
+
* ```
|
|
2984
|
+
*/
|
|
2985
|
+
label?: string;
|
|
2986
|
+
/**
|
|
2987
|
+
* A short text that appears when the user hovers over the resource.
|
|
2988
|
+
* The title provides additional context or information about the resource.
|
|
2989
|
+
*
|
|
2990
|
+
* Typically used in user interfaces to clarify what a particular resource represents or to give instructions.
|
|
2991
|
+
*
|
|
2992
|
+
* @example
|
|
2993
|
+
* ```typescript
|
|
2994
|
+
* const userResource: ResourceBase = { title: "This resource manages user information." };
|
|
2995
|
+
* ```
|
|
2996
|
+
*/
|
|
2997
|
+
title?: string;
|
|
2998
|
+
/**
|
|
2999
|
+
* The actions associated with this resource.
|
|
3000
|
+
* This is a well-typed record that preserves key inference while satisfying Record<string, ResourceAction>.
|
|
3001
|
+
*
|
|
3002
|
+
* @example
|
|
3003
|
+
* ```typescript
|
|
3004
|
+
* const userResource: ResourceBase = {
|
|
3005
|
+
* actions: {
|
|
3006
|
+
* read: { label: "Read User" },
|
|
3007
|
+
* create: { label: "Create User" },
|
|
3008
|
+
* archive: { label: "Archive User" } // Custom action
|
|
3009
|
+
* }
|
|
3010
|
+
* };
|
|
3011
|
+
*
|
|
3012
|
+
* // TypeScript infers: "read" | "create" | "archive"
|
|
3013
|
+
* type UserActionNames = GetResourceActionNames<typeof userResource>;
|
|
3014
|
+
*
|
|
3015
|
+
* // Still compatible with generic Record<string, ResourceAction>
|
|
3016
|
+
* const genericActions: Record<string, ResourceAction> = userResource.actions;
|
|
3017
|
+
* ```
|
|
3018
|
+
*/
|
|
3019
|
+
actions: ResourceActionsRecord<Actions>;
|
|
3020
|
+
}
|
|
3021
|
+
/**
|
|
3022
|
+
* @type ResourcePrimaryKey
|
|
3023
|
+
*
|
|
3024
|
+
* Represents the type of primary keys that can be utilized in a resource.
|
|
3025
|
+
* This type is a union that provides flexibility in defining unique identifiers
|
|
3026
|
+
* for resources, accommodating various data structures and use cases.
|
|
3027
|
+
*
|
|
3028
|
+
* ### Possible Forms:
|
|
3029
|
+
*
|
|
3030
|
+
* - **Record<string, string | number>**: An object where the keys are strings
|
|
3031
|
+
* and the values can be either strings or numbers. This allows for composite keys
|
|
3032
|
+
* that consist of multiple fields.
|
|
3033
|
+
* - **Example**:
|
|
3034
|
+
* ```typescript
|
|
3035
|
+
* const compositeKey: TPrimaryKey = { userId: "user123", orderId: 456 };
|
|
3036
|
+
* // A composite key representing a user and their order
|
|
3037
|
+
* ```
|
|
3038
|
+
*
|
|
3039
|
+
* ### Notes:
|
|
3040
|
+
* - This type is particularly useful in scenarios where resources may have
|
|
3041
|
+
* different types of identifiers, such as in databases or APIs.
|
|
3042
|
+
* - Using a `Record` allows for more complex primary key structures, which can
|
|
3043
|
+
* be beneficial in applications that require composite keys.
|
|
3044
|
+
*
|
|
3045
|
+
* ### Use Cases:
|
|
3046
|
+
* - Defining primary keys in database models.
|
|
3047
|
+
* - Creating unique identifiers for API resources.
|
|
3048
|
+
* - Handling composite keys in data structures.
|
|
3049
|
+
*
|
|
3050
|
+
* ### Related Types:
|
|
3051
|
+
* - Consider using `ResourceBase` for defining the overall structure of a resource
|
|
3052
|
+
* that utilizes this primary key type.
|
|
3053
|
+
*
|
|
3054
|
+
* ### Example Usage:
|
|
3055
|
+
* Here’s how you might use the `ResourcePrimaryKey` type in a function that
|
|
3056
|
+
* retrieves a resource by its primary key:
|
|
3057
|
+
*
|
|
3058
|
+
* ```typescript
|
|
3059
|
+
* function getResourceById(id: TPrimaryKey): ResourceMeta {
|
|
3060
|
+
* // Implementation to list the resource based on the provided primary key
|
|
3061
|
+
* }
|
|
3062
|
+
*
|
|
3063
|
+
* const resource = getResourceById("user123"); // Fetching by string ID
|
|
3064
|
+
* const anotherResource = getResourceById(456); // Fetching by numeric ID
|
|
3065
|
+
* const compositeResource = getResourceById({ userId: "user123", orderId: 456 }); // Fetching by composite key
|
|
3066
|
+
* ```
|
|
3067
|
+
*
|
|
3068
|
+
* ### Summary:
|
|
3069
|
+
* The `ResourcePrimaryKey` type provides a versatile way to define primary keys
|
|
3070
|
+
* for resources, supporting simple and complex identifiers. This flexibility is
|
|
3071
|
+
* essential for applications that manage diverse data structures and require
|
|
3072
|
+
* unique identification of resources.
|
|
3073
|
+
*/
|
|
3074
|
+
export type ResourcePrimaryKey = string | number | object;
|
|
3075
|
+
/**
|
|
3076
|
+
* @interface ResourceDataService
|
|
3077
|
+
*
|
|
3078
|
+
* Represents a data provider interface for managing resources.
|
|
3079
|
+
* This interface defines methods for performing CRUD (Create, Read, Update, Delete)
|
|
3080
|
+
* operations on resources, allowing for flexible data management.
|
|
3081
|
+
*
|
|
3082
|
+
* @template DataType - The type of the resource data being managed. Defaults to `any`,
|
|
3083
|
+
* allowing for flexibility in the type of data handled by the provider.
|
|
3084
|
+
*
|
|
3085
|
+
* @template TPrimaryKey - The type of the primary key used to identify resources.
|
|
3086
|
+
*
|
|
3087
|
+
*
|
|
3088
|
+
* ### Methods:
|
|
3089
|
+
*
|
|
3090
|
+
* - **create(record: Partial<DataType>)**: Creates a new resource record.
|
|
3091
|
+
* - **Parameters**:
|
|
3092
|
+
* - `record`: The data for the new resource to be created.
|
|
3093
|
+
* - **Returns**: A promise that resolves to an `DataType`,
|
|
3094
|
+
* indicating the success or failure of the operation.
|
|
3095
|
+
* - **Example**:
|
|
3096
|
+
* ```typescript
|
|
3097
|
+
* const result = await dataProvider.create({ name: "New ResourceMeta" });
|
|
3098
|
+
* ```
|
|
3099
|
+
*
|
|
3100
|
+
* - **update(primaryKey: TPrimaryKey, updatedData: Partial<DataType>)**: Updates an existing resource record.
|
|
3101
|
+
* - **Parameters**:
|
|
3102
|
+
* - `primaryKey`: The primary key of the resource to update.
|
|
3103
|
+
* - `updatedData`: An object containing the updated data for the resource.
|
|
3104
|
+
* - **Returns**: A promise that resolves to an `DataType`,
|
|
3105
|
+
* indicating the success or failure of the update operation.
|
|
3106
|
+
* - **Example**:
|
|
3107
|
+
* ```typescript
|
|
3108
|
+
* const result = await dataProvider.update("resourceId", { name: "Updated ResourceMeta" });
|
|
3109
|
+
* ```
|
|
3110
|
+
*
|
|
3111
|
+
* - **delete(primaryKey: TPrimaryKey)**: Deletes a resource record by its primary key.
|
|
3112
|
+
* - **Parameters**:
|
|
3113
|
+
* - `primaryKey`: The primary key of the resource to delete.
|
|
3114
|
+
* indicating the success or failure of the delete operation.
|
|
3115
|
+
* - **Example**:
|
|
3116
|
+
* ```typescript
|
|
3117
|
+
* const result = await dataProvider.delete("resourceId");
|
|
3118
|
+
* ```
|
|
3119
|
+
*
|
|
3120
|
+
* - **findOne(primaryKey: TPrimaryKey)**: Retrieves a single resource record by its primary key.
|
|
3121
|
+
* - **Parameters**:
|
|
3122
|
+
* - `primaryKey`: The primary key of the resource to retrieve.
|
|
3123
|
+
* - **Returns**: A promise that resolves to an `DataType | null`,
|
|
3124
|
+
* containing the requested resource record or null if not found.
|
|
3125
|
+
* - **Example**:
|
|
3126
|
+
* ```typescript
|
|
3127
|
+
* const result = await dataProvider.findOne("resourceId");
|
|
3128
|
+
* ```
|
|
3129
|
+
*
|
|
3130
|
+
* - **findOneOrFail(primaryKey: TPrimaryKey)**: Retrieves a single resource record by its primary key or throws an error if not found.
|
|
3131
|
+
* - **Parameters**:
|
|
3132
|
+
* - `primaryKey`: The primary key of the resource to retrieve.
|
|
3133
|
+
* - **Returns**: A promise that resolves to an `DataType`,
|
|
3134
|
+
* containing the requested resource record.
|
|
3135
|
+
* - **Example**:
|
|
3136
|
+
* ```typescript
|
|
3137
|
+
* const result = await dataProvider.findOneOrFail("resourceId");
|
|
3138
|
+
* ```
|
|
3139
|
+
*
|
|
3140
|
+
* - **find(options?: ResourceQueryOptions<DataType>)**: Retrieves multiple resource records based on query options.
|
|
3141
|
+
* - **Parameters**:
|
|
3142
|
+
* - `options`: Optional query options to filter the results.
|
|
3143
|
+
* - **Returns**: A promise that resolves to an `ResourcePaginatedResult<DataType>`,
|
|
3144
|
+
* containing the list of resource records.
|
|
3145
|
+
* - **Example**:
|
|
3146
|
+
* ```typescript
|
|
3147
|
+
* const result = await dataProvider.find({ limit: 10, skip: 0 });
|
|
3148
|
+
* ```
|
|
3149
|
+
*
|
|
3150
|
+
* - **findAndCount(options?: ResourceQueryOptions<DataType>)**: Retrieves multiple resource records and the total count based on query options.
|
|
3151
|
+
* - **Parameters**:
|
|
3152
|
+
* - `options`: Optional query options to filter the results.
|
|
3153
|
+
* - **Returns**: A promise that resolves to an `ResourcePaginatedResult<DataType>`,
|
|
3154
|
+
* containing the list of resource records and the total count.
|
|
3155
|
+
* - **Example**:
|
|
3156
|
+
* ```typescript
|
|
3157
|
+
* const result = await dataProvider.findAndCount({ limit: 10, skip: 0 });
|
|
3158
|
+
* ```
|
|
3159
|
+
*
|
|
3160
|
+
* - **createMany(data: Partial<DataType>[])**: Creates multiple resource records.
|
|
3161
|
+
* - **Parameters**:
|
|
3162
|
+
* - `data`: An array of data for the new resources to be created.
|
|
3163
|
+
* - **Returns**: A promise that resolves to an `DataType[]`,
|
|
3164
|
+
* indicating the success or failure of the operation.
|
|
3165
|
+
* - **Example**:
|
|
3166
|
+
* ```typescript
|
|
3167
|
+
* const result = await dataProvider.createMany([{ name: "ResourceMeta 1" }, { name: "ResourceMeta 2" }]);
|
|
3168
|
+
* ```
|
|
3169
|
+
*
|
|
3170
|
+
* - **updateMany(data: ResourceManyCriteria<TPrimaryKey,DataType>)**: Updates multiple resource records.
|
|
3171
|
+
* - **Parameters**:
|
|
3172
|
+
* - `data`: An object containing the updated data for the resources.
|
|
3173
|
+
* - **Returns**: A promise that resolves to an `DataType[]`,
|
|
3174
|
+
* indicating the success or failure of the update operation.
|
|
3175
|
+
* - **Example**:
|
|
3176
|
+
* ```typescript
|
|
3177
|
+
* const result = await dataProvider.updateMany({ status: "active" });
|
|
3178
|
+
* ```
|
|
3179
|
+
*
|
|
3180
|
+
* - **deleteMany(criteria: ResourceQueryOptions<DataType>)**: Deletes multiple resource records based on criteria.
|
|
3181
|
+
* - **Parameters**:
|
|
3182
|
+
* - `criteria`: The criteria to filter which resources to delete.
|
|
3183
|
+
* - **Returns**: A promise that resolves to an `number`,
|
|
3184
|
+
* indicating the success or failure of the delete operation.
|
|
3185
|
+
* - **Example**:
|
|
3186
|
+
* ```typescript
|
|
3187
|
+
* const result = await dataProvider.deleteMany({ filters: { status: "inactive" } });
|
|
3188
|
+
* ```
|
|
3189
|
+
*
|
|
3190
|
+
* - **count(options?: ResourceQueryOptions<DataType>)**: Counts the total number of resource records based on query options.
|
|
3191
|
+
* - **Parameters**:
|
|
3192
|
+
* - `options`: Optional query options to filter the count.
|
|
3193
|
+
* - **Returns**: A promise that resolves to an `number`,
|
|
3194
|
+
* containing the total count of resource records.
|
|
3195
|
+
* - **Example**:
|
|
3196
|
+
* ```typescript
|
|
3197
|
+
* const result = await dataProvider.count({ filters: { status: "active" } });
|
|
3198
|
+
* ```
|
|
3199
|
+
*
|
|
3200
|
+
* - **exists(primaryKey: TPrimaryKey)**: Checks if a resource record exists by its primary key.
|
|
3201
|
+
* - **Parameters**:
|
|
3202
|
+
* - `primaryKey`: The primary key of the resource to check.
|
|
3203
|
+
* - **Returns**: A promise that resolves to an `boolean`,
|
|
3204
|
+
* indicating whether the resource exists.
|
|
3205
|
+
* - **Example**:
|
|
3206
|
+
* ```typescript
|
|
3207
|
+
* const result = await dataProvider.exists("resourceId");
|
|
3208
|
+
* ```
|
|
3209
|
+
*
|
|
3210
|
+
* - **distinct?(field: keyof DataType, options?: ResourceQueryOptions<DataType>)**: Retrieves distinct values for a specified field.
|
|
3211
|
+
* - **Parameters**:
|
|
3212
|
+
* - `field`: The field for which to retrieve distinct values.
|
|
3213
|
+
* - `options`: Optional query options to filter the results.
|
|
3214
|
+
* - **Returns**: A promise that resolves to an `DataType[]`,
|
|
3215
|
+
* containing the distinct values.
|
|
3216
|
+
* - **Example**:
|
|
3217
|
+
* ```typescript
|
|
3218
|
+
* const result = await dataProvider.distinct("category");
|
|
3219
|
+
* ```
|
|
3220
|
+
*
|
|
3221
|
+
* - **aggregate?(pipeline: any[])**: Performs aggregation operations on the resource data.
|
|
3222
|
+
* - **Parameters**:
|
|
3223
|
+
* - `pipeline`: An array representing the aggregation pipeline.
|
|
3224
|
+
* - **Returns**: A promise that resolves to an `number`,
|
|
3225
|
+
* containing the aggregated results.
|
|
3226
|
+
* - **Example**:
|
|
3227
|
+
* ```typescript
|
|
3228
|
+
* const result = await dataProvider.aggregate([{ $group: { _id: "$category", count: { $sum: 1 } } }]);
|
|
3229
|
+
* ```
|
|
3230
|
+
*
|
|
3231
|
+
* ### Notes:
|
|
3232
|
+
* - This interface provides a standard way to interact with resource data,
|
|
3233
|
+
* ensuring that all operations return consistent results.
|
|
3234
|
+
* - The use of promises allows for asynchronous operations, making it suitable
|
|
3235
|
+
* for use in modern web applications.
|
|
3236
|
+
*
|
|
3237
|
+
* ### Example Usage:
|
|
3238
|
+
* Here’s how you might implement the `ResourceDataService` interface:
|
|
3239
|
+
*
|
|
3240
|
+
* ```typescript
|
|
3241
|
+
* class MyDataProvider implements ResourceDataService<MyResourceType> {
|
|
3242
|
+
* async create(record: MyResourceType) {
|
|
3243
|
+
* // Implementation for creating a resource
|
|
3244
|
+
* }
|
|
3245
|
+
*
|
|
3246
|
+
* async list() {
|
|
3247
|
+
* // Implementation for fetching resources
|
|
3248
|
+
* }
|
|
3249
|
+
*
|
|
3250
|
+
* // Implement other methods...
|
|
3251
|
+
* }
|
|
3252
|
+
* ```
|
|
3253
|
+
*
|
|
3254
|
+
* ### Summary:
|
|
3255
|
+
* The `ResourceDataService` interface defines a comprehensive set of methods
|
|
3256
|
+
* for managing resources, facilitating CRUD operations and ensuring a consistent
|
|
3257
|
+
* approach to data handling in applications.
|
|
3258
|
+
*/
|
|
3259
|
+
export interface ResourceDataService<DataType = unknown, TPrimaryKey extends ResourcePrimaryKey = ResourcePrimaryKey> {
|
|
3260
|
+
/***
|
|
3261
|
+
* Creates a new resource record.
|
|
3262
|
+
* @template T - The type of the resource data being created.
|
|
3263
|
+
* @param record The data for the new resource to be created.
|
|
3264
|
+
* @returns A promise that resolves to an `DataType`,
|
|
3265
|
+
* indicating the success or failure of the operation.
|
|
3266
|
+
* @example
|
|
3267
|
+
* ```typescript
|
|
3268
|
+
* const result = await dataProvider.create({ name: "New ResourceMeta" });
|
|
3269
|
+
* ```
|
|
3270
|
+
*/
|
|
3271
|
+
create<T extends DataType>(record: T): Promise<DataType>;
|
|
3272
|
+
/***
|
|
3273
|
+
* Updates an existing resource record.
|
|
3274
|
+
* @template T - The type of the resource data being updated.
|
|
3275
|
+
* @param primaryKey The primary key of the resource to update.
|
|
3276
|
+
* @param updateData An object containing the updated data for the resource.
|
|
3277
|
+
* @returns A promise that resolves to an `DataType`,
|
|
3278
|
+
* indicating the success or failure of the update operation.
|
|
3279
|
+
* @example
|
|
3280
|
+
* ```typescript
|
|
3281
|
+
* const result = await dataProvider.update("resourceId", { name: "Updated ResourceMeta" });
|
|
3282
|
+
* ```
|
|
3283
|
+
*/
|
|
3284
|
+
update<T extends Partial<DataType>>(primaryKey: TPrimaryKey, updateData: T): Promise<DataType>;
|
|
3285
|
+
/***
|
|
3286
|
+
* Deletes a resource record by its primary key.
|
|
3287
|
+
* @param primaryKey The primary key of the resource to delete.
|
|
3288
|
+
* @returns A promise that resolves to an `Promise<boolean>`,
|
|
3289
|
+
* indicating the success or failure of the delete operation.
|
|
3290
|
+
* @example
|
|
3291
|
+
* ```typescript
|
|
3292
|
+
* const result = await dataProvider.delete("resourceId");
|
|
3293
|
+
* ```
|
|
3294
|
+
*/
|
|
3295
|
+
delete(primaryKey: TPrimaryKey): Promise<boolean>;
|
|
3296
|
+
/***
|
|
3297
|
+
* Retrieves a single resource record by its primary key.
|
|
3298
|
+
* @param options The primary key or query options of the resource to retrieve.
|
|
3299
|
+
* @returns A promise that resolves to an `DataType | null`,
|
|
3300
|
+
* containing the requested resource record or null if not found.
|
|
3301
|
+
* @example
|
|
3302
|
+
* ```typescript
|
|
3303
|
+
* const result = await dataProvider.findOne("resourceId");
|
|
3304
|
+
* ```
|
|
3305
|
+
* @example
|
|
3306
|
+
* ```typescript
|
|
3307
|
+
* const result = await dataProvider.findOne({ firstName: 1 });
|
|
3308
|
+
* ```
|
|
3309
|
+
*/
|
|
3310
|
+
findOne(options: TPrimaryKey | ResourceQueryOptions<DataType>): Promise<DataType | null>;
|
|
3311
|
+
/***
|
|
3312
|
+
* Retrieves a single resource record by its primary key or throws an error if not found.
|
|
3313
|
+
* @param primaryKey The primary key or query options of the resource to retrieve.
|
|
3314
|
+
* @returns A promise that resolves to an `DataType`,
|
|
3315
|
+
* containing the requested resource record.
|
|
3316
|
+
* @example
|
|
3317
|
+
* ```typescript
|
|
3318
|
+
* const result = await dataProvider.findOneOrFail("resourceId");
|
|
3319
|
+
* ```
|
|
3320
|
+
*/
|
|
3321
|
+
findOneOrFail(options: TPrimaryKey | ResourceQueryOptions<DataType>): Promise<DataType>;
|
|
3322
|
+
/***
|
|
3323
|
+
* Retrieves multiple resource records based on query options.
|
|
3324
|
+
* @param options Optional query options to filter the results.
|
|
3325
|
+
* @returns A promise that resolves to an `DataType[]`,
|
|
3326
|
+
* containing the list of resource records.
|
|
3327
|
+
* @example
|
|
3328
|
+
* ```typescript
|
|
3329
|
+
* const result = await dataProvider.find({ limit: 10, skip: 0 });
|
|
3330
|
+
* ```
|
|
3331
|
+
*/
|
|
3332
|
+
find(options?: ResourceQueryOptions<DataType>): Promise<DataType[]>;
|
|
3333
|
+
/***
|
|
3334
|
+
* Retrieves multiple resource records and the total count based on query options.
|
|
3335
|
+
* @param options Optional query options to filter the results.
|
|
3336
|
+
* @returns A promise that resolves to an `DataType[]`,
|
|
3337
|
+
* containing the list of resource records and the total count.
|
|
3338
|
+
* @example
|
|
3339
|
+
* ```typescript
|
|
3340
|
+
* const result = await dataProvider.findAndCount({ limit: 10, skip: 0 });
|
|
3341
|
+
* ```
|
|
3342
|
+
*/
|
|
3343
|
+
findAndCount(options?: ResourceQueryOptions<DataType>): Promise<[DataType[], number]>;
|
|
3344
|
+
/***
|
|
3345
|
+
* Retrieves multiple resource records and paginates the results.
|
|
3346
|
+
* @param options Optional query options to filter the results.
|
|
3347
|
+
* @returns A promise that resolves to an `ResourcePaginatedResult<DataType>`,
|
|
3348
|
+
* containing the list of resource records and the total count.
|
|
3349
|
+
* @example
|
|
3350
|
+
* ```typescript
|
|
3351
|
+
* const result = await dataProvider.findAndPaginate({ limit: 10, skip: 0 });
|
|
3352
|
+
*/
|
|
3353
|
+
findAndPaginate(options?: ResourceQueryOptions<DataType>): Promise<ResourcePaginatedResult<DataType>>;
|
|
3354
|
+
/***
|
|
3355
|
+
* Creates multiple resource records.
|
|
3356
|
+
* @template T - The type of the resource data being created.
|
|
3357
|
+
* @param data An array of data for the new resources to be created.
|
|
3358
|
+
* @returns A promise that resolves to an `DataType[]`,
|
|
3359
|
+
* indicating the success or failure of the operation.
|
|
3360
|
+
* @example
|
|
3361
|
+
* ```typescript
|
|
3362
|
+
* const result = await dataProvider.createMany([{ name: "ResourceMeta 1" }, { name: "ResourceMeta 2" }]);
|
|
3363
|
+
* ```
|
|
3364
|
+
*/
|
|
3365
|
+
createMany<T extends DataType>(data: T[]): Promise<DataType[]>;
|
|
3366
|
+
/***
|
|
3367
|
+
* Updates multiple resource records.
|
|
3368
|
+
* @template T - The type of the resource data being updated.
|
|
3369
|
+
* @param criteria An object containing the filter criteria for the resources.
|
|
3370
|
+
* @param data An object containing the updated data for the resources.
|
|
3371
|
+
* @returns A promise that resolves to an `DataType[]`,
|
|
3372
|
+
* indicating the success or failure of the update operation.
|
|
3373
|
+
* @example
|
|
3374
|
+
* ```typescript
|
|
3375
|
+
* const result = await dataProvider.updateMany({ status: "active" });
|
|
3376
|
+
* ```
|
|
3377
|
+
*/
|
|
3378
|
+
updateMany<T extends Partial<DataType>>(criteria: ResourceManyCriteria<DataType, TPrimaryKey>, data: T): Promise<number>;
|
|
3379
|
+
/**
|
|
3380
|
+
*
|
|
3381
|
+
* @param criteria The criteria to filter which resources to delete.
|
|
3382
|
+
* @returns A promise that resolves to an `number`,
|
|
3383
|
+
* indicating the success or failure of the delete operation.
|
|
3384
|
+
* @example
|
|
3385
|
+
* ```typescript
|
|
3386
|
+
* const result = await dataProvider.deleteMany({ filters: { status: "inactive" } });
|
|
3387
|
+
* ```
|
|
3388
|
+
*/
|
|
3389
|
+
deleteMany(criteria: ResourceManyCriteria<DataType, TPrimaryKey>): Promise<number>;
|
|
3390
|
+
/***
|
|
3391
|
+
* Counts the total number of resource records based on query options.
|
|
3392
|
+
* @param options Optional query options to filter the count.
|
|
3393
|
+
* @returns A promise that resolves to an `number`,
|
|
3394
|
+
* containing the total count of resource records.
|
|
3395
|
+
* @example
|
|
3396
|
+
* ```typescript
|
|
3397
|
+
* const result = await dataProvider.count({ filters: { status: "active" } });
|
|
3398
|
+
* ```
|
|
3399
|
+
*/
|
|
3400
|
+
count(options?: ResourceQueryOptions<DataType>): Promise<number>;
|
|
3401
|
+
/**
|
|
3402
|
+
*
|
|
3403
|
+
* @param primaryKey The primary key of the resource to check.
|
|
3404
|
+
* @returns A promise that resolves to an `boolean`,
|
|
3405
|
+
* indicating whether the resource exists.
|
|
3406
|
+
* @example
|
|
3407
|
+
* ```typescript
|
|
3408
|
+
* const result = await dataProvider.exists("resourceId");
|
|
3409
|
+
* ```
|
|
3410
|
+
*/
|
|
3411
|
+
exists(primaryKey: TPrimaryKey): Promise<boolean>;
|
|
3412
|
+
}
|
|
3413
|
+
/**
|
|
3414
|
+
* @type ResourceManyCriteria
|
|
3415
|
+
*
|
|
3416
|
+
* Represents the criteria used for multiple actions on a resource.
|
|
3417
|
+
* This type allows for flexible definitions of what constitutes an update,
|
|
3418
|
+
* accommodating various scenarios based on the primary key or partial data.
|
|
3419
|
+
*
|
|
3420
|
+
* ### Type Parameters
|
|
3421
|
+
* - **TPrimaryKey**: The type of the primary key used to identify resources.
|
|
3422
|
+
* Defaults to `ResourcePrimaryKey`, which can be a string, number, or object.
|
|
3423
|
+
* - **DataType**: The type of data associated with the resource. Defaults to `Dictionary`,
|
|
3424
|
+
* which is a generic dictionary type allowing for any key-value pairs.
|
|
3425
|
+
*
|
|
3426
|
+
* ### Possible Forms
|
|
3427
|
+
* The `ResourceManyCriteria` can take one of the following forms:
|
|
3428
|
+
*
|
|
3429
|
+
* 1. **Array of Primary Keys**:
|
|
3430
|
+
* - An array of primary keys that uniquely identify the resources to be updated.
|
|
3431
|
+
* - **Example**:
|
|
3432
|
+
* ```typescript
|
|
3433
|
+
* const updateCriteria: ResourceManyCriteria<string> = ["user123", "user456"];
|
|
3434
|
+
* ```
|
|
3435
|
+
*
|
|
3436
|
+
* 2. **Partial Data Object**:
|
|
3437
|
+
* - An object containing partial data that represents the fields to be updated.
|
|
3438
|
+
* - **Example**:
|
|
3439
|
+
* ```typescript
|
|
3440
|
+
* const updateCriteria: ResourceManyCriteria<string, { name: string; age: number }> = {
|
|
3441
|
+
* name: "John Doe",
|
|
3442
|
+
* age: 30
|
|
3443
|
+
* };
|
|
3444
|
+
* ```
|
|
3445
|
+
*
|
|
3446
|
+
* 3. **Record of Data Fields**:
|
|
3447
|
+
* - A record where each key corresponds to a field in the resource, allowing for
|
|
3448
|
+
* updates to specific fields.
|
|
3449
|
+
* - **Example**:
|
|
3450
|
+
* ```typescript
|
|
3451
|
+
* const updateCriteria: ResourceManyCriteria<string, { name: string; age: number }> = {
|
|
3452
|
+
* name: "Jane Doe",
|
|
3453
|
+
* age: 25
|
|
3454
|
+
* };
|
|
3455
|
+
* ```
|
|
3456
|
+
*
|
|
3457
|
+
* ### Notes
|
|
3458
|
+
* - This type is particularly useful in scenarios where resources can be updated
|
|
3459
|
+
* based on different criteria, such as updating multiple records at once or
|
|
3460
|
+
* modifying specific fields of a resource.
|
|
3461
|
+
* - By leveraging TypeScript's generics, this type provides strong typing and
|
|
3462
|
+
* flexibility, ensuring that the criteria used for updates are well-defined and
|
|
3463
|
+
* type-safe.
|
|
3464
|
+
*
|
|
3465
|
+
* ### Example Usage
|
|
3466
|
+
* Here’s how you might use the `ResourceManyCriteria` type in a function that
|
|
3467
|
+
* updates resources:
|
|
3468
|
+
*
|
|
3469
|
+
* ```typescript
|
|
3470
|
+
* function updateResources(criteria: ResourceManyCriteria<string, { name: string; age: number }>) {
|
|
3471
|
+
* // Implementation to update resources based on the provided criteria
|
|
3472
|
+
* }
|
|
3473
|
+
*
|
|
3474
|
+
* // Example of updating resources by primary keys
|
|
3475
|
+
* updateResources(["user123", "user456"]);
|
|
3476
|
+
*
|
|
3477
|
+
* // Example of updating resources with partial data
|
|
3478
|
+
* updateResources({ name: "John Doe", age: 30 });
|
|
3479
|
+
* ```
|
|
3480
|
+
* @typeParam DataType - The type of data associated with the resource.
|
|
3481
|
+
* @default any
|
|
3482
|
+
* @typeParam TPrimaryKey - The type of the primary key used to identify resources.
|
|
3483
|
+
* @default ResourcePrimaryKey
|
|
3484
|
+
* @see {@link ResourcePrimaryKey} for the `ResourcePrimaryKey` type.
|
|
3485
|
+
* @see {@link MongoQuery} for the `MongoQuery` type.
|
|
3486
|
+
* @example
|
|
3487
|
+
* // Example of using ResourceManyCriteria
|
|
3488
|
+
* const criteria: ResourceManyCriteria<string, { name: string; age: number }> = {
|
|
3489
|
+
* name: "John Doe",
|
|
3490
|
+
* age: 30
|
|
3491
|
+
* };
|
|
3492
|
+
* @Example
|
|
3493
|
+
* // Example of using ResourceManyCriteria with an array of primary keys
|
|
3494
|
+
* const criteria: ResourceManyCriteria<string, { name: string; age: number }> = [
|
|
3495
|
+
* "user123",
|
|
3496
|
+
* "user456"
|
|
3497
|
+
* ];
|
|
3498
|
+
*/
|
|
3499
|
+
export type ResourceManyCriteria<DataType = unknown, TPrimaryKey extends ResourcePrimaryKey = ResourcePrimaryKey> = TPrimaryKey[] | MongoQuery<DataType>;
|
|
3500
|
+
/**
|
|
3501
|
+
* Interface representing options for fetching resources.
|
|
3502
|
+
*
|
|
3503
|
+
* This interface allows you to specify various options when retrieving resources,
|
|
3504
|
+
* including filters to narrow down the results based on specific criteria.
|
|
3505
|
+
*
|
|
3506
|
+
* @template DataType - The type of data being fetched. Defaults to 'any'.
|
|
3507
|
+
* @example
|
|
3508
|
+
* // Example of using ResourceQueryOptions
|
|
3509
|
+
* const fetchOptions: ResourceQueryOptions<MyDataType, string> = {
|
|
3510
|
+
* filters: {
|
|
3511
|
+
* status: { $eq: "active" }, // Filter for active resources
|
|
3512
|
+
* category: { $in: ["A", "B"] } // Filter for categories A or B
|
|
3513
|
+
* },
|
|
3514
|
+
* orderBy: { createdAt: 'desc' }, // Sort by creation date descending
|
|
3515
|
+
* limit: 20, // Limit results to 20
|
|
3516
|
+
* skip: 0 // Do not skip any results
|
|
3517
|
+
* };
|
|
3518
|
+
*/
|
|
3519
|
+
export interface ResourceQueryOptions<DataType = unknown> {
|
|
3520
|
+
/** Fields to include in the response. */
|
|
3521
|
+
fields?: Array<keyof DataType extends never ? string : keyof DataType>;
|
|
3522
|
+
relations?: string[];
|
|
3523
|
+
orderBy?: ResourceQueryOrderBy<DataType>;
|
|
3524
|
+
limit?: number;
|
|
3525
|
+
skip?: number;
|
|
3526
|
+
page?: number;
|
|
3527
|
+
/** Include relationships or nested resources. */
|
|
3528
|
+
include?: ResourceName[];
|
|
3529
|
+
/** Include only distinct results or specific fields for distinct values. */
|
|
3530
|
+
distinct?: boolean | Array<keyof DataType>;
|
|
3531
|
+
/** Include soft-deleted resources. */
|
|
3532
|
+
includeDeleted?: boolean;
|
|
3533
|
+
/** Cache the results for performance optimization. */
|
|
3534
|
+
cache?: boolean;
|
|
3535
|
+
/** Time-to-Live for cache, in seconds. */
|
|
3536
|
+
cacheTTL?: number;
|
|
3537
|
+
/**
|
|
3538
|
+
* Where clause to filter the results.
|
|
3539
|
+
* Resources are filtered using a MongoDB-like query syntax.
|
|
3540
|
+
* This allows you to specify conditions for filtering resources based on various criteria.
|
|
3541
|
+
*
|
|
3542
|
+
* @type {MongoQuery}
|
|
3543
|
+
* @see {@link https://www.mongodb.com/docs/manual/reference/operator/query/} for more information on MongoDB query operators.
|
|
3544
|
+
* @example
|
|
3545
|
+
* const queryOptions: ResourceQueryOptions<{ id: number, name: string }> = {
|
|
3546
|
+
* where: {
|
|
3547
|
+
* name : "John",
|
|
3548
|
+
* surname : "Doe"
|
|
3549
|
+
* },
|
|
3550
|
+
* orderBy: { name: 'asc' },
|
|
3551
|
+
* limit: 10,
|
|
3552
|
+
* skip: 0
|
|
3553
|
+
* };
|
|
3554
|
+
* @see {@link MongoQuery} for more information on where clauses.
|
|
3555
|
+
* @example
|
|
3556
|
+
* const queryOptions: ResourceQueryOptions<{ id: number, name: string }> = {
|
|
3557
|
+
* where: {
|
|
3558
|
+
* name : "John",
|
|
3559
|
+
* surname : "Doe"
|
|
3560
|
+
* },
|
|
3561
|
+
* orderBy: { name: 'asc' },
|
|
3562
|
+
* limit: 10,
|
|
3563
|
+
* skip: 0
|
|
3564
|
+
* };
|
|
3565
|
+
*/
|
|
3566
|
+
where?: MongoQuery<DataType>;
|
|
3567
|
+
}
|
|
3568
|
+
/**
|
|
3569
|
+
* @interface ResourcePaginatedResult
|
|
3570
|
+
*
|
|
3571
|
+
* Represents the result of a paginated resource list operation.
|
|
3572
|
+
* This interface encapsulates the data retrieved from a paginated API response,
|
|
3573
|
+
* along with metadata about the pagination state and navigation links.
|
|
3574
|
+
*
|
|
3575
|
+
* @template DataType - The type of the resources being fetched. Defaults to `any`.
|
|
3576
|
+
*
|
|
3577
|
+
* ### Properties:
|
|
3578
|
+
*
|
|
3579
|
+
* - **data**: An array of fetched resources.
|
|
3580
|
+
* - **Type**: `DataType[]`
|
|
3581
|
+
* - **Description**: This property contains the list of resources retrieved from the API.
|
|
3582
|
+
* - **Example**:
|
|
3583
|
+
* ```typescript
|
|
3584
|
+
* const result: ResourcePaginatedResult<User> = {
|
|
3585
|
+
* data: [
|
|
3586
|
+
* { id: 1, name: "John Doe" },
|
|
3587
|
+
* { id: 2, name: "Jane Smith" }
|
|
3588
|
+
* ],
|
|
3589
|
+
* meta: { totalItems: 100, currentPage: 1, pageSize: 10, totalPages: 10 },
|
|
3590
|
+
* links: { first: null, previous: null, next: "http://api.example.com/users?page=2", last: "http://api.example.com/users?page=10" }
|
|
3591
|
+
* };
|
|
3592
|
+
* ```
|
|
3593
|
+
*
|
|
3594
|
+
* - **meta**: Metadata about the pagination state.
|
|
3595
|
+
* - **Type**: `Object`
|
|
3596
|
+
* - **Description**: This property provides information about the total number of items, the current page, the page size, and the total number of pages.
|
|
3597
|
+
* - **Properties**:
|
|
3598
|
+
* - **totalItems**: The total number of items available across all pages.
|
|
3599
|
+
* - **Type**: `number`
|
|
3600
|
+
* - **Example**: `100` indicates there are 100 items in total.
|
|
3601
|
+
* - **currentPage**: The current page number being viewed.
|
|
3602
|
+
* - **Type**: `number`
|
|
3603
|
+
* - **Example**: `1` indicates the first page.
|
|
3604
|
+
* - **pageSize**: The number of items displayed per page.
|
|
3605
|
+
* - **Type**: `number`
|
|
3606
|
+
* - **Example**: `10` indicates that 10 items are shown per page.
|
|
3607
|
+
* - **totalPages**: The total number of pages available.
|
|
3608
|
+
* - **Type**: `number`
|
|
3609
|
+
* - **Example**: `10` indicates there are 10 pages in total.
|
|
3610
|
+
*
|
|
3611
|
+
* - **links**: Navigation links for paginated results.
|
|
3612
|
+
* - **Type**: `Object`
|
|
3613
|
+
* - **Description**: This property contains URLs for navigating through the paginated results.
|
|
3614
|
+
* - **Properties**:
|
|
3615
|
+
* - **first**: URL to the first page of results.
|
|
3616
|
+
* - **Type**: `string | null`
|
|
3617
|
+
* - **Example**: `"http://api.example.com/users?page=1"` or `null` if there is no first page.
|
|
3618
|
+
* - **previous**: URL to the previous page of results.
|
|
3619
|
+
* - **Type**: `string | null`
|
|
3620
|
+
* - **Example**: `"http://api.example.com/users?page=1"` or `null` if there is no previous page.
|
|
3621
|
+
* - **next**: URL to the next page of results.
|
|
3622
|
+
* - **Type**: `string | null`
|
|
3623
|
+
* - **Example**: `"http://api.example.com/users?page=2"` or `null` if there is no next page.
|
|
3624
|
+
* - **last**: URL to the last page of results.
|
|
3625
|
+
* - **Type**: `string | null`
|
|
3626
|
+
* - **Example**: `"http://api.example.com/users?page=10"` or `null` if there is no last page.
|
|
3627
|
+
*
|
|
3628
|
+
* ### Example Usage:
|
|
3629
|
+
* Here’s how you might use the `ResourcePaginatedResult` interface in a function that fetches paginated user data:
|
|
3630
|
+
*
|
|
3631
|
+
* ```typescript
|
|
3632
|
+
* async function fetchUsers(page: number): Promise<ResourcePaginatedResult<User>> {
|
|
3633
|
+
* const response = await list(`http://api.example.com/users?page=${page}`);
|
|
3634
|
+
* const result: ResourcePaginatedResult<User> = await response.json();
|
|
3635
|
+
* return result;
|
|
3636
|
+
* }
|
|
3637
|
+
*
|
|
3638
|
+
* fetchUsers(1).then(result => {
|
|
3639
|
+
* console.log(`Total Users: ${result.meta.totalItems}`);
|
|
3640
|
+
* console.log(`Current Page: ${result.meta.currentPage}`);
|
|
3641
|
+
* console.log(`Users on this page:`, result.data);
|
|
3642
|
+
* });
|
|
3643
|
+
* ```
|
|
3644
|
+
*
|
|
3645
|
+
* ### Notes:
|
|
3646
|
+
* - This interface is particularly useful for APIs that return large datasets,
|
|
3647
|
+
* allowing clients to retrieve data in manageable chunks.
|
|
3648
|
+
* - The `links` property facilitates easy navigation between pages, enhancing user experience.
|
|
3649
|
+
*/
|
|
3650
|
+
export interface ResourcePaginatedResult<DataType = unknown> {
|
|
3651
|
+
/** List of fetched resources. */
|
|
3652
|
+
data: DataType[];
|
|
3653
|
+
statusCode?: number;
|
|
3654
|
+
success?: boolean;
|
|
3655
|
+
error?: any;
|
|
3656
|
+
message?: string;
|
|
3657
|
+
status?: string;
|
|
3658
|
+
errors?: string | Error[];
|
|
3659
|
+
/** Pagination metadata. */
|
|
3660
|
+
meta?: ResourcePaginationMeta;
|
|
3661
|
+
/** Links for navigation in paginated results. */
|
|
3662
|
+
links?: {
|
|
3663
|
+
/** URL or index to the first page. */
|
|
3664
|
+
first?: string | number;
|
|
3665
|
+
/** URL or index to the previous page. */
|
|
3666
|
+
previous?: string | number;
|
|
3667
|
+
/** URL or index to the next page. */
|
|
3668
|
+
next?: string | number;
|
|
3669
|
+
/** URL or index to the last page. */
|
|
3670
|
+
last?: string | number;
|
|
3671
|
+
};
|
|
3672
|
+
}
|
|
3673
|
+
/**
|
|
3674
|
+
* @typedef ResourcePaginationMeta
|
|
3675
|
+
* Represents the pagination metadata for a resource.
|
|
3676
|
+
*
|
|
3677
|
+
* This type defines the structure of the pagination metadata returned by a resource query operation.
|
|
3678
|
+
* It includes information about the total number of items, the current page, the page size, and other
|
|
3679
|
+
* pagination-related properties.
|
|
3680
|
+
*
|
|
3681
|
+
* @property {number} total - The total number of items available.
|
|
3682
|
+
* @property {number} [currentPage] - The current page number.
|
|
3683
|
+
* @property {number} [pageSize] - The number of items per page.
|
|
3684
|
+
* @property {number} [totalPages] - The total number of pages.
|
|
3685
|
+
* @property {number} [nextPage] - The next page number.
|
|
3686
|
+
* @property {number} [previousPage] - The previous page number.
|
|
3687
|
+
* @property {number} [lastPage] - The last page number.
|
|
3688
|
+
* @property {boolean} [hasNextPage] - Whether there is a next page.
|
|
3689
|
+
* @property {boolean} [hasPreviousPage] - Whether there is a previous page.
|
|
3690
|
+
*/
|
|
3691
|
+
export interface ResourcePaginationMeta {
|
|
3692
|
+
/** The total number of items available. */
|
|
3693
|
+
total: number;
|
|
3694
|
+
/** The current page number. */
|
|
3695
|
+
currentPage?: number;
|
|
3696
|
+
/** The number of items per page. */
|
|
3697
|
+
pageSize?: number;
|
|
3698
|
+
/** The total number of pages. */
|
|
3699
|
+
totalPages?: number;
|
|
3700
|
+
nextPage?: number;
|
|
3701
|
+
previousPage?: number;
|
|
3702
|
+
lastPage?: number;
|
|
3703
|
+
/***
|
|
3704
|
+
* Whether there is a next page.
|
|
3705
|
+
*/
|
|
3706
|
+
hasNextPage?: boolean;
|
|
3707
|
+
/***
|
|
3708
|
+
* Whether there is a previous page.
|
|
3709
|
+
*/
|
|
3710
|
+
hasPreviousPage?: boolean;
|
|
3711
|
+
}
|
|
3712
|
+
/**
|
|
3713
|
+
* Type representing default events that can occur on a resource.
|
|
3714
|
+
* This includes both action names and data service method names.
|
|
3715
|
+
*
|
|
3716
|
+
* This type combines resource-specific action names with standard data service
|
|
3717
|
+
* operations to provide a comprehensive set of events that can be tracked or
|
|
3718
|
+
* handled for a given resource. It's useful for event-driven architectures,
|
|
3719
|
+
* logging, auditing, and reactive systems.
|
|
3720
|
+
*
|
|
3721
|
+
* @type ResourceDefaultEvent
|
|
3722
|
+
* @template TResourceName - The name of the resource
|
|
3723
|
+
*
|
|
3724
|
+
* @example
|
|
3725
|
+
* ```typescript
|
|
3726
|
+
* // Basic usage - event type for a specific resource
|
|
3727
|
+
* import "reslib/resources";
|
|
3728
|
+
*
|
|
3729
|
+
* declare module "reslib/resources" {
|
|
3730
|
+
* interface Resources {
|
|
3731
|
+
* users: {
|
|
3732
|
+
* actions: {
|
|
3733
|
+
* read: { label: "Read User" };
|
|
3734
|
+
* create: { label: "Create User" };
|
|
3735
|
+
* update: { label: "Update User" };
|
|
3736
|
+
* archive: { label: "Archive User" };
|
|
3737
|
+
* }
|
|
3738
|
+
* };
|
|
3739
|
+
* }
|
|
3740
|
+
* }
|
|
3741
|
+
*
|
|
3742
|
+
* // All possible events for the users resource
|
|
3743
|
+
* type UserEvent = ResourceDefaultEvent<"users">;
|
|
3744
|
+
* // Result: "read" | "create" | "update" | "archive" | "create" | "update" | "delete" | "findOne" | "find" | ...
|
|
3745
|
+
*
|
|
3746
|
+
* // Note: Includes both resource actions and data service methods
|
|
3747
|
+
* ```
|
|
3748
|
+
*
|
|
3749
|
+
* @example
|
|
3750
|
+
* ```typescript
|
|
3751
|
+
* // Event-driven resource management
|
|
3752
|
+
* class ResourceEventEmitter<TResourceName extends ResourceName> {
|
|
3753
|
+
* private listeners = new Map<ResourceDefaultEvent<TResourceName>, Function[]>();
|
|
3754
|
+
*
|
|
3755
|
+
* on(event: ResourceDefaultEvent<TResourceName>, listener: Function) {
|
|
3756
|
+
* const current = this.listeners.get(event) || [];
|
|
3757
|
+
* current.push(listener);
|
|
3758
|
+
* this.listeners.set(event, current);
|
|
3759
|
+
* }
|
|
3760
|
+
*
|
|
3761
|
+
* emit(event: ResourceDefaultEvent<TResourceName>, data?: any) {
|
|
3762
|
+
* const listeners = this.listeners.get(event) || [];
|
|
3763
|
+
* listeners.forEach(listener => listener(data));
|
|
3764
|
+
* }
|
|
3765
|
+
* }
|
|
3766
|
+
*
|
|
3767
|
+
* // Usage
|
|
3768
|
+
* const userEmitter = new ResourceEventEmitter<"users">();
|
|
3769
|
+
*
|
|
3770
|
+
* userEmitter.on("create", (userData) => {
|
|
3771
|
+
* console.log("User created:", userData);
|
|
3772
|
+
* // Send welcome email, update analytics, etc.
|
|
3773
|
+
* });
|
|
3774
|
+
*
|
|
3775
|
+
* userEmitter.on("findOne", (userId) => {
|
|
3776
|
+
* console.log("User accessed:", userId);
|
|
3777
|
+
* // Log access, update last accessed time, etc.
|
|
3778
|
+
* });
|
|
3779
|
+
*
|
|
3780
|
+
* // Trigger events
|
|
3781
|
+
* userEmitter.emit("create", { id: 1, name: "John" });
|
|
3782
|
+
* userEmitter.emit("findOne", 1);
|
|
3783
|
+
* ```
|
|
3784
|
+
*
|
|
3785
|
+
* @example
|
|
3786
|
+
* ```typescript
|
|
3787
|
+
* // Audit logging with typed events
|
|
3788
|
+
* interface AuditLog<TResourceName extends ResourceName> {
|
|
3789
|
+
* resource: TResourceName;
|
|
3790
|
+
* event: ResourceDefaultEvent<TResourceName>;
|
|
3791
|
+
* userId: string;
|
|
3792
|
+
* timestamp: Date;
|
|
3793
|
+
* data?: any;
|
|
3794
|
+
* }
|
|
3795
|
+
*
|
|
3796
|
+
* class ResourceAuditor {
|
|
3797
|
+
* private logs: AuditLog<ResourceName>[] = [];
|
|
3798
|
+
*
|
|
3799
|
+
* log<TResourceName extends ResourceName>(
|
|
3800
|
+
* resource: TResourceName,
|
|
3801
|
+
* event: ResourceDefaultEvent<TResourceName>,
|
|
3802
|
+
* userId: string,
|
|
3803
|
+
* data?: any
|
|
3804
|
+
* ) {
|
|
3805
|
+
* this.logs.push({
|
|
3806
|
+
* resource,
|
|
3807
|
+
* event,
|
|
3808
|
+
* userId,
|
|
3809
|
+
* timestamp: new Date(),
|
|
3810
|
+
* data
|
|
3811
|
+
* });
|
|
3812
|
+
* }
|
|
3813
|
+
*
|
|
3814
|
+
* getLogsForResource<TResourceName extends ResourceName>(
|
|
3815
|
+
* resource: TResourceName
|
|
3816
|
+
* ): AuditLog<TResourceName>[] {
|
|
3817
|
+
* return this.logs.filter(log => log.resource === resource) as AuditLog<TResourceName>[];
|
|
3818
|
+
* }
|
|
3819
|
+
* }
|
|
3820
|
+
*
|
|
3821
|
+
* // Usage
|
|
3822
|
+
* const auditor = new ResourceAuditor();
|
|
3823
|
+
*
|
|
3824
|
+
* auditor.log("users", "create", "user123", { name: "John Doe" });
|
|
3825
|
+
* auditor.log("users", "update", "user456", { id: 1, name: "Jane Doe" });
|
|
3826
|
+
* auditor.log("users", "find", "user789"); // Data service method
|
|
3827
|
+
*
|
|
3828
|
+
* const userLogs = auditor.getLogsForResource("users");
|
|
3829
|
+
* // TypeScript knows these are user-related events
|
|
3830
|
+
* ```
|
|
3831
|
+
*
|
|
3832
|
+
* @example
|
|
3833
|
+
* ```typescript
|
|
3834
|
+
* // Reactive resource hooks
|
|
3835
|
+
* function useResourceEvent<TResourceName extends ResourceName>(
|
|
3836
|
+
* resource: TResourceName,
|
|
3837
|
+
* event: ResourceDefaultEvent<TResourceName>,
|
|
3838
|
+
* callback: (data?: any) => void
|
|
3839
|
+
* ) {
|
|
3840
|
+
* // Implementation would set up event listeners
|
|
3841
|
+
* console.log(`Setting up ${event} listener for ${resource}`);
|
|
3842
|
+
* // Return cleanup function, etc.
|
|
3843
|
+
* }
|
|
3844
|
+
*
|
|
3845
|
+
* // Usage in a React-like component
|
|
3846
|
+
* function UserComponent() {
|
|
3847
|
+
* // Type-safe event handling
|
|
3848
|
+
* useResourceEvent("users", "create", (userData) => {
|
|
3849
|
+
* console.log("New user created:", userData);
|
|
3850
|
+
* // Update UI, refresh data, etc.
|
|
3851
|
+
* });
|
|
3852
|
+
*
|
|
3853
|
+
* useResourceEvent("users", "update", (updateData) => {
|
|
3854
|
+
* console.log("User updated:", updateData);
|
|
3855
|
+
* // Refresh user data, show notification, etc.
|
|
3856
|
+
* });
|
|
3857
|
+
*
|
|
3858
|
+
* useResourceEvent("users", "findOne", (userId) => {
|
|
3859
|
+
* console.log("User profile viewed:", userId);
|
|
3860
|
+
* // Track analytics, etc.
|
|
3861
|
+
* });
|
|
3862
|
+
*
|
|
3863
|
+
* return <div>User Management Component</div>;
|
|
3864
|
+
* }
|
|
3865
|
+
* ```
|
|
3866
|
+
*
|
|
3867
|
+
* @example
|
|
3868
|
+
* ```typescript
|
|
3869
|
+
* // Middleware system with typed events
|
|
3870
|
+
* type MiddlewareFn<TResourceName extends ResourceName> = (
|
|
3871
|
+
* event: ResourceDefaultEvent<TResourceName>,
|
|
3872
|
+
* data: any,
|
|
3873
|
+
* next: () => void
|
|
3874
|
+
* ) => void;
|
|
3875
|
+
*
|
|
3876
|
+
* class ResourceMiddleware<TResourceName extends ResourceName> {
|
|
3877
|
+
* private middlewares: MiddlewareFn<TResourceName>[] = [];
|
|
3878
|
+
*
|
|
3879
|
+
* use(middleware: MiddlewareFn<TResourceName>) {
|
|
3880
|
+
* this.middlewares.push(middleware);
|
|
3881
|
+
* }
|
|
3882
|
+
*
|
|
3883
|
+
* async execute(event: ResourceDefaultEvent<TResourceName>, data: any) {
|
|
3884
|
+
* let index = 0;
|
|
3885
|
+
*
|
|
3886
|
+
* const next = () => {
|
|
3887
|
+
* if (index < this.middlewares.length) {
|
|
3888
|
+
* this.middlewares[index++](event, data, next);
|
|
3889
|
+
* }
|
|
3890
|
+
* };
|
|
3891
|
+
*
|
|
3892
|
+
* next();
|
|
3893
|
+
* }
|
|
3894
|
+
* }
|
|
3895
|
+
*
|
|
3896
|
+
* // Usage
|
|
3897
|
+
* const userMiddleware = new ResourceMiddleware<"users">();
|
|
3898
|
+
*
|
|
3899
|
+
* userMiddleware.use((event, data, next) => {
|
|
3900
|
+
* console.log(`Pre-${event} validation`);
|
|
3901
|
+
* // Validate permissions, data, etc.
|
|
3902
|
+
* next();
|
|
3903
|
+
* });
|
|
3904
|
+
*
|
|
3905
|
+
* userMiddleware.use((event, data, next) => {
|
|
3906
|
+
* console.log(`Post-${event} logging`);
|
|
3907
|
+
* // Log the event, send notifications, etc.
|
|
3908
|
+
* next();
|
|
3909
|
+
* });
|
|
3910
|
+
*
|
|
3911
|
+
* // Execute middleware for events
|
|
3912
|
+
* userMiddleware.execute("create", { name: "John" });
|
|
3913
|
+
* userMiddleware.execute("findOne", 123);
|
|
3914
|
+
* ```
|
|
3915
|
+
*/
|
|
3916
|
+
export type ResourceDefaultEvent<TResourceName extends ResourceName> = ResourceActionName<TResourceName> | keyof ResourceDataService;
|
|
3917
|
+
/**
|
|
3918
|
+
* Represents contextual information about a resource for operations like translations, logging, and error handling.
|
|
3919
|
+
*
|
|
3920
|
+
* This interface provides a standardized way to pass resource identification and contextual data
|
|
3921
|
+
* throughout the application. It's primarily used for internationalization, error messages, and
|
|
3922
|
+
* logging where resource-specific information is needed.
|
|
3923
|
+
*
|
|
3924
|
+
* @interface ResourceContext
|
|
3925
|
+
*
|
|
3926
|
+
* @example
|
|
3927
|
+
* ```typescript
|
|
3928
|
+
* // Basic usage in translations
|
|
3929
|
+
* const context: ResourceContext = {
|
|
3930
|
+
* resourceName: "user",
|
|
3931
|
+
* resourceLabel: "User",
|
|
3932
|
+
* operation: "create",
|
|
3933
|
+
* count: 5
|
|
3934
|
+
* };
|
|
3935
|
+
*
|
|
3936
|
+
* // Used in error messages
|
|
3937
|
+
* i18n.t("resources.notFound", context);
|
|
3938
|
+
* // Results in: "User with ID 123 not found"
|
|
3939
|
+
* ```
|
|
3940
|
+
*
|
|
3941
|
+
* @example
|
|
3942
|
+
* ```typescript
|
|
3943
|
+
* // Extended with custom properties
|
|
3944
|
+
* const extendedContext: ResourceContext = {
|
|
3945
|
+
* resourceName: "product",
|
|
3946
|
+
* resourceLabel: "Product",
|
|
3947
|
+
* category: "electronics",
|
|
3948
|
+
* price: 99.99
|
|
3949
|
+
* };
|
|
3950
|
+
* ```
|
|
3951
|
+
*/
|
|
3952
|
+
export interface ResourceContext extends Record<string, any> {
|
|
3953
|
+
/** The unique programmatic name of the resource */
|
|
3954
|
+
resourceName: ResourceName;
|
|
3955
|
+
/** The human-readable label of the resource for display purposes */
|
|
3956
|
+
resourceLabel: string;
|
|
3957
|
+
}
|
|
3958
|
+
/**
|
|
3959
|
+
* @interface ResourceTranslations
|
|
3960
|
+
*
|
|
3961
|
+
* Represents the translation structure for resources in the application.
|
|
3962
|
+
* This type defines the expected structure of translations for each resource,
|
|
3963
|
+
* including labels, titles, and action-specific translations.
|
|
3964
|
+
*
|
|
3965
|
+
* @example
|
|
3966
|
+
* ```typescript
|
|
3967
|
+
* // resources actions translations structure :
|
|
3968
|
+
* // Here is an example of the structure of the translations for the "user" resource:
|
|
3969
|
+
* const userTranslations: ResourceTranslations = {
|
|
3970
|
+
* user: {
|
|
3971
|
+
* label: "User",
|
|
3972
|
+
* title: "Manage user data",
|
|
3973
|
+
* create: {
|
|
3974
|
+
* label: "Create User",
|
|
3975
|
+
* title: "Click to add a new user.",
|
|
3976
|
+
* },
|
|
3977
|
+
* read: {
|
|
3978
|
+
* label: "View User",
|
|
3979
|
+
* title: "Click to view a specific user.",
|
|
3980
|
+
* },
|
|
3981
|
+
* update: {
|
|
3982
|
+
* label: "Update User",
|
|
3983
|
+
* title: "Click to update a specific user.",
|
|
3984
|
+
* zero: "No users to update.",
|
|
3985
|
+
* one: "Updated one user.",
|
|
3986
|
+
* other: "Updated %{count} users.",
|
|
3987
|
+
* },
|
|
3988
|
+
* delete: {
|
|
3989
|
+
* label: "Delete User",
|
|
3990
|
+
* title: "Click to delete a specific user.",
|
|
3991
|
+
* zero: "No users to delete.",
|
|
3992
|
+
* one: "Deleted one user.",
|
|
3993
|
+
* other: "Deleted %{count} users.",
|
|
3994
|
+
* },
|
|
3995
|
+
* }
|
|
3996
|
+
* };
|
|
3997
|
+
* ```
|
|
3998
|
+
*/
|
|
3999
|
+
export type ResourceTranslations = {
|
|
4000
|
+
[Name in ResourceName]: ResourceTranslation<Name>;
|
|
4001
|
+
}[ResourceName];
|
|
4002
|
+
/**
|
|
4003
|
+
* @interface ResourceTranslation
|
|
4004
|
+
*
|
|
4005
|
+
* Represents the translation structure for a specific resource in the application.
|
|
4006
|
+
* This generic type defines the expected structure of translations for a given resource,
|
|
4007
|
+
* dynamically generating the translation keys based on the resource's defined actions.
|
|
4008
|
+
*
|
|
4009
|
+
* @template Name - The name of the resource for which translations are defined.
|
|
4010
|
+
* Must be a valid `ResourceName`.
|
|
4011
|
+
*
|
|
4012
|
+
* ### Structure:
|
|
4013
|
+
*
|
|
4014
|
+
* - **Core Properties**:
|
|
4015
|
+
* - `label`: The display name of the resource (required).
|
|
4016
|
+
* - `title`: The title or heading for the resource (optional).
|
|
4017
|
+
* - `description`: A detailed description of the resource (optional).
|
|
4018
|
+
* - `forbiddenError`: Error message when access to the resource is forbidden (required).
|
|
4019
|
+
* - `notFoundError`: Error message when the resource is not found (required).
|
|
4020
|
+
*
|
|
4021
|
+
* - **Action Translations**: Dynamically generated based on the resource's actions.
|
|
4022
|
+
* Each action defined in the resource's `actions` property will have:
|
|
4023
|
+
* - `label`: The display label for the action.
|
|
4024
|
+
* - `title`: The tooltip or help text for the action.
|
|
4025
|
+
* - `zero`: Message when no items are affected (for pluralization).
|
|
4026
|
+
* - `one`: Message when one item is affected (for pluralization).
|
|
4027
|
+
* - `other`: Message when multiple items are affected (for pluralization).
|
|
4028
|
+
*
|
|
4029
|
+
* - **Additional Properties**: Any additional translation keys can be added via `Record<string, any>`.
|
|
4030
|
+
*
|
|
4031
|
+
* ### Type Generation:
|
|
4032
|
+
*
|
|
4033
|
+
* The type uses conditional types and mapped types to dynamically generate the structure:
|
|
4034
|
+
* - It checks if the resource has an `actions` property.
|
|
4035
|
+
* - For each action, it creates a translation object with the required fields.
|
|
4036
|
+
* - It combines this with the core properties and allows for additional custom properties.
|
|
4037
|
+
*
|
|
4038
|
+
* @example
|
|
4039
|
+
* ```typescript
|
|
4040
|
+
* // For a resource with actions: { read: {...}, create: {...}, update: {...}, delete: {...} }
|
|
4041
|
+
* const userTranslations: ResourceTranslation<"user"> = {
|
|
4042
|
+
* label: "User",
|
|
4043
|
+
* title: "Manage user data",
|
|
4044
|
+
* description: "User management and administration",
|
|
4045
|
+
* forbiddenError: "You do not have permission to access this resource.",
|
|
4046
|
+
* notFoundError: "The requested user was not found.",
|
|
4047
|
+
*
|
|
4048
|
+
* // Action-specific translations (auto-generated based on resource actions)
|
|
4049
|
+
* read: {
|
|
4050
|
+
* label: "View User",
|
|
4051
|
+
* title: "Click to view a specific user.",
|
|
4052
|
+
* zero: "No users found.",
|
|
4053
|
+
* one: "Viewing one user.",
|
|
4054
|
+
* other: "Viewing %{count} users."
|
|
4055
|
+
* },
|
|
4056
|
+
* create: {
|
|
4057
|
+
* label: "Create User",
|
|
4058
|
+
* title: "Click to add a new user.",
|
|
4059
|
+
* zero: "No users created.",
|
|
4060
|
+
* one: "Created one user.",
|
|
4061
|
+
* other: "Created %{count} users."
|
|
4062
|
+
* },
|
|
4063
|
+
* update: {
|
|
4064
|
+
* label: "Update User",
|
|
4065
|
+
* title: "Click to update a specific user.",
|
|
4066
|
+
* zero: "No users updated.",
|
|
4067
|
+
* one: "Updated one user.",
|
|
4068
|
+
* other: "Updated %{count} users."
|
|
4069
|
+
* },
|
|
4070
|
+
* delete: {
|
|
4071
|
+
* label: "Delete User",
|
|
4072
|
+
* title: "Click to delete a specific user.",
|
|
4073
|
+
* zero: "No users deleted.",
|
|
4074
|
+
* one: "Deleted one user.",
|
|
4075
|
+
* other: "Deleted %{count} users."
|
|
4076
|
+
* },
|
|
4077
|
+
*
|
|
4078
|
+
* // Additional custom translations
|
|
4079
|
+
* customAction: "Custom action performed",
|
|
4080
|
+
* validationError: "Please check your input"
|
|
4081
|
+
* };
|
|
4082
|
+
* ```
|
|
4083
|
+
*
|
|
4084
|
+
* ### Notes:
|
|
4085
|
+
*
|
|
4086
|
+
* - The action translations are automatically inferred from the resource's action definitions.
|
|
4087
|
+
* - The `zero`, `one`, and `other` fields support pluralization in internationalization.
|
|
4088
|
+
* - The `%{count}` placeholder can be used in pluralization messages for dynamic counts.
|
|
4089
|
+
* - Additional properties allow for custom translations specific to the resource's needs.
|
|
4090
|
+
* - This type ensures type safety by tying translations directly to the resource structure.
|
|
4091
|
+
*/
|
|
4092
|
+
export type ResourceTranslation<Name extends ResourceName> = {
|
|
4093
|
+
/**
|
|
4094
|
+
* The display name of the resource (required).
|
|
4095
|
+
*/
|
|
4096
|
+
label: string;
|
|
4097
|
+
/**
|
|
4098
|
+
* The title or heading for the resource (optional).
|
|
4099
|
+
*/
|
|
4100
|
+
title?: string;
|
|
4101
|
+
/**
|
|
4102
|
+
* A detailed description of the resource (optional).
|
|
4103
|
+
*/
|
|
4104
|
+
description?: string;
|
|
4105
|
+
/**
|
|
4106
|
+
* Error message when access to the resource is forbidden (required).
|
|
4107
|
+
*/
|
|
4108
|
+
forbiddenError: string;
|
|
4109
|
+
/**
|
|
4110
|
+
* Error message when the resource is not found (required).
|
|
4111
|
+
*/
|
|
4112
|
+
notFoundError: string;
|
|
4113
|
+
} & (Resources[Name] extends {
|
|
4114
|
+
actions: infer Actions;
|
|
4115
|
+
} ? Actions extends Record<string, ResourceAction> ? {
|
|
4116
|
+
[Key in keyof Actions]: {
|
|
4117
|
+
/**
|
|
4118
|
+
* The display label for the action.
|
|
4119
|
+
*/
|
|
4120
|
+
label?: string;
|
|
4121
|
+
/**
|
|
4122
|
+
* The tooltip or help text for the action.
|
|
4123
|
+
*/
|
|
4124
|
+
title?: string;
|
|
4125
|
+
/**
|
|
4126
|
+
* Message when no items are affected (for pluralization).
|
|
4127
|
+
*/
|
|
4128
|
+
zero?: string;
|
|
4129
|
+
/**
|
|
4130
|
+
* Message when one item is affected (for pluralization).
|
|
4131
|
+
*/
|
|
4132
|
+
one?: string;
|
|
4133
|
+
/**
|
|
4134
|
+
* Message when multiple items are affected (for pluralization).
|
|
4135
|
+
*/
|
|
4136
|
+
other?: string;
|
|
4137
|
+
};
|
|
4138
|
+
}[keyof Actions] : {} : {}) & Dictionary;
|