@rws-framework/db 3.9.0 → 3.9.3

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.
@@ -6,50 +6,32 @@ import { RelationUtils } from "./RelationUtils";
6
6
  import { OpModelType } from "..";
7
7
  import { ModelUtils } from "./ModelUtils";
8
8
  import { FindByType, IPaginationParams } from "../../types/FindParams";
9
- import { LoadingContext } from "./LoadingContext";
10
- import chalk from 'chalk';
11
-
12
- function circularReferenceWarning(modelType: string, id: string | number): void {
13
- console.warn(chalk.yellow(`Circular reference detected: ${modelType}:${id} is already being loaded. Breaking cycle.`));
14
- }
15
9
 
16
10
  export class FindUtils {
17
11
  public static async findOneBy<T extends RWSModel<T>>(
18
12
  opModel: OpModelType<T>,
19
13
  findParams?: FindByType
20
14
  ): Promise<T | null> {
21
- // Wrap in new execution context to ensure clean loading stack per operation
22
- return LoadingContext.withNewExecutionContext(async () => {
23
- const conditions = findParams?.conditions ?? {};
24
- const ordering = findParams?.ordering ?? null;
25
- const fields = findParams?.fields ?? null;
26
- const allowRelations = findParams?.allowRelations ?? true;
27
- const fullData = findParams?.fullData ?? false;
28
-
29
- opModel.checkForInclusionWithThrow('');
30
-
31
- const collection = Reflect.get(opModel, '_collection');
32
- const dbData = await opModel.services.dbService.findOneBy(collection, conditions, fields, ordering);
33
-
34
- if (dbData) {
35
- const modelType = opModel.name;
36
- const id = dbData.id;
37
-
38
- // Check if this model is already being loaded to prevent circular references
39
- if (LoadingContext.isLoading(modelType, id)) {
40
- circularReferenceWarning(modelType, id);
41
- return null;
42
- }
15
+ const conditions = findParams?.conditions ?? {};
16
+ const ordering = findParams?.ordering ?? null;
17
+ const fields = findParams?.fields ?? null;
18
+ const allowRelations = findParams?.allowRelations ?? true;
19
+ const fullData = findParams?.fullData ?? false;
20
+
21
+ opModel.checkForInclusionWithThrow('');
43
22
 
44
- return await LoadingContext.withLoadingContext(modelType, id, async () => {
45
- const inst: T = new (opModel as { new(): T })();
46
- const loaded = await inst._asyncFill(dbData, fullData, allowRelations, findParams?.cancelPostLoad ? false : true);
47
- return loaded as T;
48
- });
49
- }
50
23
 
51
- return null;
52
- });
24
+ const collection = Reflect.get(opModel, '_collection');
25
+ const dbData = await opModel.services.dbService.findOneBy(collection, conditions, fields, ordering);
26
+
27
+
28
+ if (dbData) {
29
+ const inst: T = new (opModel as { new(): T })();
30
+ const loaded = await inst._asyncFill(dbData, fullData, allowRelations, findParams?.cancelPostLoad ? false : true);
31
+ return loaded as T;
32
+ }
33
+
34
+ return null;
53
35
  }
54
36
 
55
37
  public static async find<T extends RWSModel<T>>(
@@ -57,89 +39,59 @@ export class FindUtils {
57
39
  id: string | number,
58
40
  findParams: Omit<FindByType, 'conditions'> = null
59
41
  ): Promise<T | null> {
60
- // Wrap in new execution context to ensure clean loading stack per operation
61
- return LoadingContext.withNewExecutionContext(async () => {
62
- const ordering = findParams?.ordering ?? null;
63
- const fields = findParams?.fields ?? null;
64
- const allowRelations = findParams?.allowRelations ?? true;
65
- const fullData = findParams?.fullData ?? false;
66
-
67
- const collection = Reflect.get(opModel, '_collection');
68
- opModel.checkForInclusionWithThrow(opModel.name);
69
-
70
- const dbData = await opModel.services.dbService.findOneBy(collection, { id }, fields, ordering);
71
-
72
- if (dbData) {
73
- const modelType = opModel.name;
74
-
75
- // Check if this model is already being loaded to prevent circular references
76
- if (LoadingContext.isLoading(modelType, id)) {
77
- circularReferenceWarning(modelType, id);
78
- return null;
79
- }
42
+ const ordering = findParams?.ordering ?? null;
43
+ const fields = findParams?.fields ?? null;
44
+ const allowRelations = findParams?.allowRelations ?? true;
45
+ const fullData = findParams?.fullData ?? false;
80
46
 
81
- return await LoadingContext.withLoadingContext(modelType, id, async () => {
82
- const inst: T = new (opModel as { new(): T })();
83
- const loaded = await inst._asyncFill(dbData, fullData, allowRelations, findParams?.cancelPostLoad ? false : true);
84
- return loaded as T;
85
- });
86
- }
47
+ const collection = Reflect.get(opModel, '_collection');
48
+ opModel.checkForInclusionWithThrow(opModel.name);
49
+
50
+ const dbData = await opModel.services.dbService.findOneBy(collection, { id }, fields, ordering);
51
+
52
+ if (dbData) {
53
+ const inst: T = new (opModel as { new(): T })();
54
+ const loaded = await inst._asyncFill(dbData, fullData, allowRelations, findParams?.cancelPostLoad ? false : true);
55
+ return loaded as T;
56
+ }
87
57
 
88
- return null;
89
- });
58
+ return null;
90
59
  }
91
60
 
92
61
  public static async findBy<T extends RWSModel<T>>(
93
62
  opModel: OpModelType<T>,
94
63
  findParams?: FindByType
95
64
  ): Promise<T[]> {
96
- // Wrap in new execution context to ensure clean loading stack per operation
97
- return LoadingContext.withNewExecutionContext(async () => {
98
- const conditions = findParams?.conditions ?? {};
99
- const ordering = findParams?.ordering ?? null;
100
- const fields = findParams?.fields ?? null;
101
- const allowRelations = findParams?.allowRelations ?? true;
102
- const fullData = findParams?.fullData ?? false;
103
-
104
- const collection = Reflect.get(opModel, '_collection');
105
- opModel.checkForInclusionWithThrow(opModel.name);
106
- try {
107
- const paginateParams = findParams?.pagination ? findParams?.pagination : undefined;
108
- const dbData = await opModel.services.dbService.findBy(collection, conditions, fields, ordering, paginateParams);
109
-
110
- if (dbData.length) {
111
- const instanced: T[] = [];
112
-
113
- for (const data of dbData) {
114
- const modelType = opModel.name;
115
- const id = data.id;
116
-
117
- // Check if this model is already being loaded to prevent circular references
118
- if (LoadingContext.isLoading(modelType, id)) {
119
- circularReferenceWarning(modelType, id);
120
- continue;
121
- }
122
-
123
- const loaded = await LoadingContext.withLoadingContext(modelType, id, async () => {
124
- const inst: T = new (opModel as { new(): T })();
125
- return await inst._asyncFill(data, fullData, allowRelations, findParams?.cancelPostLoad ? false : true) as T;
126
- });
127
-
128
- if (loaded) {
129
- instanced.push(loaded);
130
- }
131
- }
132
-
133
- return instanced;
134
- }
65
+ const conditions = findParams?.conditions ?? {};
66
+ const ordering = findParams?.ordering ?? null;
67
+ const fields = findParams?.fields ?? null;
68
+ const allowRelations = findParams?.allowRelations ?? true;
69
+ const fullData = findParams?.fullData ?? false;
70
+
71
+ const collection = Reflect.get(opModel, '_collection');
72
+ opModel.checkForInclusionWithThrow(opModel.name);
73
+ try {
74
+ const paginateParams = findParams?.pagination ? findParams?.pagination : undefined;
75
+ const dbData = await opModel.services.dbService.findBy(collection, conditions, fields, ordering, paginateParams);
76
+
77
+ if (dbData.length) {
78
+ const instanced: T[] = [];
79
+
80
+ for (const data of dbData) {
81
+ const inst: T = new (opModel as { new(): T })();
135
82
 
136
- return [];
137
- } catch (rwsError: Error | any) {
138
- console.error(rwsError);
83
+ instanced.push((await inst._asyncFill(data, fullData, allowRelations, findParams?.cancelPostLoad ? false : true)) as T);
84
+ }
139
85
 
140
- throw rwsError;
86
+ return instanced;
141
87
  }
142
- });
88
+
89
+ return [];
90
+ } catch (rwsError: Error | any) {
91
+ console.error(rwsError);
92
+
93
+ throw rwsError;
94
+ }
143
95
  }
144
96
 
145
97
  public static async paginate<T extends RWSModel<T>>(
@@ -147,50 +99,32 @@ export class FindUtils {
147
99
  paginateParams: IPaginationParams,
148
100
  findParams?: FindByType
149
101
  ): Promise<T[]> {
150
- // Wrap in new execution context to ensure clean loading stack per operation
151
- return LoadingContext.withNewExecutionContext(async () => {
152
- const conditions = findParams?.conditions ?? {};
153
- const ordering = findParams?.ordering ?? null;
154
- const fields = findParams?.fields ?? null;
155
- const allowRelations = findParams?.allowRelations ?? true;
156
- const fullData = findParams?.fullData ?? false;
157
-
158
- const collection = Reflect.get(opModel, '_collection');
159
- opModel.checkForInclusionWithThrow(opModel.name);
160
- try {
161
- const dbData = await opModel.services.dbService.findBy(collection, conditions, fields, ordering, paginateParams);
162
- if (dbData.length) {
163
- const instanced: T[] = [];
164
-
165
- for (const data of dbData) {
166
- const modelType = opModel.name;
167
- const id = data.id;
168
-
169
- // Check if this model is already being loaded to prevent circular references
170
- if (LoadingContext.isLoading(modelType, id)) {
171
- circularReferenceWarning(modelType, id);
172
- continue;
173
- }
174
-
175
- const loaded = await LoadingContext.withLoadingContext(modelType, id, async () => {
176
- const inst: T = new (opModel as { new(): T })();
177
- return await inst._asyncFill(data, fullData, allowRelations, findParams?.cancelPostLoad ? false : true) as T;
178
- });
179
-
180
- if (loaded) {
181
- instanced.push(loaded);
182
- }
183
- }
184
-
185
- return instanced;
102
+ const conditions = findParams?.conditions ?? {};
103
+ const ordering = findParams?.ordering ?? null;
104
+ const fields = findParams?.fields ?? null;
105
+ const allowRelations = findParams?.allowRelations ?? true;
106
+ const fullData = findParams?.fullData ?? false;
107
+
108
+ const collection = Reflect.get(opModel, '_collection');
109
+ opModel.checkForInclusionWithThrow(opModel.name);
110
+ try {
111
+ const dbData = await opModel.services.dbService.findBy(collection, conditions, fields, ordering, paginateParams);
112
+ if (dbData.length) {
113
+ const instanced: T[] = [];
114
+
115
+ for (const data of dbData) {
116
+ const inst: T = new (opModel as { new(): T })();
117
+ instanced.push((await inst._asyncFill(data, fullData, allowRelations, findParams?.cancelPostLoad ? false : true)) as T);
186
118
  }
187
119
 
188
- return [];
189
- } catch (rwsError: Error | any) {
190
- console.error(rwsError);
191
-
192
- throw rwsError;
120
+ return instanced;
193
121
  }
194
- });
122
+
123
+ return [];
124
+ } catch (rwsError: Error | any) {
125
+ console.error(rwsError);
126
+
127
+ throw rwsError;
128
+ }
195
129
  }
196
130
  }
@@ -1,139 +0,0 @@
1
- # LoadingContext Implementation - Solution 1 Documentation (Updated)
2
-
3
- ## Problem Statement
4
-
5
- The RWS framework had two related issues in the postLoad mechanism:
6
-
7
- ### 1. Original Circular Reference Issue
8
- - **User model postLoad** calls `userGroup.reload(true)`
9
- - **UserGroup model postLoad** calls `User.find(owner, { cancelPostLoad: true })`
10
- - If that User gets loaded elsewhere without `cancelPostLoad: true`, it could trigger an infinite cycle
11
-
12
- ### 2. Cross-Request Stack Persistence Issue (Discovered)
13
- - The LoadingContext had a **global loading stack** that persisted across HTTP requests
14
- - Stack built up and never got cleared between requests
15
- - Caused false positive "already being loaded" errors for subsequent requests
16
- - Led to models not loading properly and authentication failures
17
-
18
- ## Solution: Request-Scoped Loading Context
19
-
20
- ### Key Fix: Execution Context Isolation
21
-
22
- The critical fix was changing from a **global static loading stack** to **per-execution-context stacks**:
23
-
24
- **Before (Problematic):**
25
- ```typescript
26
- class LoadingContext {
27
- private static loadingStack: Set<string> = new Set(); // GLOBAL - persisted across requests
28
- }
29
- ```
30
-
31
- **After (Fixed):**
32
- ```typescript
33
- class LoadingContext {
34
- private static executionContexts = new WeakMap<object, Set<string>>(); // Per-context isolation
35
-
36
- static withNewExecutionContext<T>(fn: () => Promise<T>): Promise<T> {
37
- // Creates fresh context for each operation
38
- }
39
- }
40
- ```
41
-
42
- ### Implementation Components
43
-
44
- #### 1. LoadingContext Class (`/models/utils/LoadingContext.ts`) - Updated
45
-
46
- **Key Changes:**
47
- - **Execution Context Isolation**: Each operation gets its own loading stack
48
- - **WeakMap for Context Storage**: Associates loading stacks with execution contexts
49
- - **Automatic Context Creation**: `withNewExecutionContext()` creates fresh contexts
50
- - **Per-Request Isolation**: Each HTTP request gets its own clean loading context
51
-
52
- #### 2. FindUtils Integration (`/models/utils/LoadingUtils.ts`) - Critical Fix
53
-
54
- **All top-level methods now wrap operations in new execution contexts:**
55
-
56
- ```typescript
57
- public static async findOneBy<T extends RWSModel<T>>(
58
- opModel: OpModelType<T>,
59
- findParams?: FindByType
60
- ): Promise<T | null> {
61
- // CRITICAL: Wrap in new execution context to ensure clean loading stack
62
- return LoadingContext.withNewExecutionContext(async () => {
63
- // ... existing logic with circular reference protection
64
- });
65
- }
66
- ```
67
-
68
- ### How The Fix Works
69
-
70
- #### Before Fix (Problematic):
71
- ```
72
- Request 1: User.find(11) -> adds "User:11" to global stack
73
- Request 1: UserGroup.reload() -> adds "UserGroup:10" to global stack
74
- Request 1: Completes, but stack NOT cleared
75
- Request 2: User.find(11) -> "User:11" already in global stack -> FALSE POSITIVE error
76
- ```
77
-
78
- #### After Fix (Working):
79
- ```
80
- Request 1: LoadingContext.withNewExecutionContext() -> creates fresh context
81
- Request 1: User.find(11) -> adds "User:11" to context-specific stack
82
- Request 1: UserGroup.reload() -> adds "UserGroup:10" to same context stack
83
- Request 1: Completes -> context automatically cleaned up
84
-
85
- Request 2: LoadingContext.withNewExecutionContext() -> creates NEW fresh context
86
- Request 2: User.find(11) -> adds "User:11" to NEW context stack -> works normally
87
- ```
88
-
89
- ### Request Isolation Benefits
90
-
91
- 1. **No Cross-Request Interference**: Each HTTP request gets its own loading context
92
- 2. **Automatic Cleanup**: No manual stack management needed
93
- 3. **Proper Circular Detection**: Still prevents infinite loops within single operations
94
- 4. **No False Positives**: Eliminates "already loading" errors between requests
95
-
96
- ### Configuration
97
-
98
- ```typescript
99
- // Set maximum loading depth for new contexts (default: 10)
100
- LoadingContext.setDefaultMaxDepth(15);
101
-
102
- // Get current loading stack for debugging current context
103
- console.log(LoadingContext.getLoadingStack());
104
-
105
- // Manual context creation (usually not needed)
106
- await LoadingContext.withNewExecutionContext(async () => {
107
- // Database operations here get fresh context
108
- });
109
- ```
110
-
111
- ### Testing Results
112
-
113
- **Before Fix - Error Logs:**
114
- ```
115
- Circular reference detected: User:11 is already being loaded. Breaking cycle.
116
- UnauthorizedException: Unauthorized
117
- Circular reference detected: User:11 is already being loaded. Breaking cycle.
118
- Circular reference detected: UserGroup:10 is already being loaded. Breaking cycle.
119
- Circular reference detected: UserGroup:10 is already being loaded. Breaking cycle.
120
- // Repeated across multiple requests - FALSE POSITIVES
121
- ```
122
-
123
- **After Fix:**
124
- - Clean loading per HTTP request
125
- - No false positive circular reference detections
126
- - Proper circular reference protection within individual operations
127
- - No authentication failures due to loading issues
128
- - No cross-request interference
129
-
130
- ### Backward Compatibility
131
-
132
- - **100% backward compatible**: No changes needed to existing model code
133
- - **Existing `cancelPostLoad` still works**: Provides additional layer of protection
134
- - **All existing APIs preserved**: No breaking changes to any interfaces
135
- - **Automatic context management**: Developers don't need to manage contexts manually
136
-
137
- ### Summary
138
-
139
- This fix resolves both the original circular reference issue AND the newly discovered cross-request stack persistence problem. The key insight was that the loading context needs to be **request-scoped** rather than **application-global**. Each database operation chain now gets its own clean loading context, preventing both infinite recursion within operations and false positive detection across operations.
@@ -1,122 +0,0 @@
1
- # LoadingContext Implementation - Solution 1 Documentation
2
-
3
- ## Problem Statement
4
-
5
- The RWS framework had a potential infinite recursion issue in the postLoad mechanism where:
6
-
7
- 1. **User model postLoad** calls `userGroup.reload(true)`
8
- 2. **UserGroup model postLoad** calls `User.find(owner, { cancelPostLoad: true })`
9
- 3. If that User gets loaded elsewhere without `cancelPostLoad: true`, it could trigger an infinite cycle
10
-
11
- The existing anti-recursion mechanism was **per-instance** rather than **per-loading-context**, meaning it only prevented the same object from running postLoad multiple times, but didn't prevent circular loading between different instances of related models.
12
-
13
- ## Solution: Context-Based Loading Stack
14
-
15
- ### Implementation Components
16
-
17
- #### 1. LoadingContext Class (`/models/utils/LoadingContext.ts`)
18
-
19
- A utility class that tracks the chain of models currently being loaded to prevent circular references:
20
-
21
- **Key Features:**
22
- - **Static loading stack**: Tracks `ModelType:ID` combinations currently being loaded
23
- - **Circular reference detection**: Prevents loading a model that's already in the loading chain
24
- - **Maximum depth protection**: Prevents infinite chains with configurable depth limit
25
- - **Automatic cleanup**: Uses try/finally blocks to ensure proper cleanup
26
- - **Debugging support**: Provides stack inspection and clear error messages
27
-
28
- **Key Methods:**
29
- - `isLoading(modelType, id)`: Check if a model is currently being loaded
30
- - `startLoading(modelType, id)`: Mark a model as being loaded
31
- - `finishLoading(modelType, id)`: Mark a model as finished loading
32
- - `withLoadingContext(modelType, id, fn)`: Execute function with automatic context management
33
-
34
- #### 2. FindUtils Integration (`/models/utils/FindUtils.ts`)
35
-
36
- Modified all find methods to use LoadingContext:
37
-
38
- **Changes in `find()`, `findOneBy()`, `findBy()`, and `paginate()`:**
39
- - Check if model is already being loaded before creating instance
40
- - Use `LoadingContext.withLoadingContext()` to wrap model loading
41
- - Return `null` or skip items that are already being loaded
42
- - Provide warning messages for detected circular references
43
-
44
- #### 3. RWSModel Integration (`/models/core/RWSModel.ts`)
45
-
46
- - Added LoadingContext import
47
- - The reload method now uses FindUtils which automatically gets the protection
48
-
49
- ### How It Works
50
-
51
- #### Normal Loading Flow
52
- ```
53
- 1. User.find(1) -> LoadingContext.startLoading('User', 1)
54
- 2. User._asyncFill() -> loads userGroup relation
55
- 3. UserGroup.reload() -> LoadingContext.startLoading('UserGroup', 2)
56
- 4. UserGroup._asyncFill() -> loads owner relation
57
- 5. User.find(owner) -> LoadingContext.startLoading('User', 3)
58
- 6. Complete normally -> LoadingContext.finishLoading() for each
59
- ```
60
-
61
- #### Circular Reference Prevention
62
- ```
63
- 1. User.find(1) -> LoadingContext.startLoading('User', 1)
64
- 2. User._asyncFill() -> loads userGroup relation
65
- 3. UserGroup.reload() -> LoadingContext.startLoading('UserGroup', 2)
66
- 4. UserGroup._asyncFill() -> tries to load User.find(1) again
67
- 5. LoadingContext.isLoading('User', 1) returns true
68
- 6. Returns null instead of creating infinite loop
69
- 7. Warning logged: "Circular reference detected"
70
- ```
71
-
72
- #### Maximum Depth Protection
73
- ```
74
- If loading chain exceeds configurable limit (default 10):
75
- - Throws descriptive error with full loading stack
76
- - Shows exact chain: "Model1:1 -> Model2:2 -> Model3:3 -> ..."
77
- - Helps identify complex circular dependencies
78
- ```
79
-
80
- ### Benefits
81
-
82
- 1. **Complete Circular Reference Prevention**: Unlike the previous per-instance flag, this prevents all types of circular loading
83
- 2. **Transparent Integration**: Existing code doesn't need changes - works automatically through FindUtils
84
- 3. **Performance Optimized**: Minimal overhead - just Set operations for tracking
85
- 4. **Debugging Friendly**: Clear error messages and stack inspection capabilities
86
- 5. **Configurable**: Adjustable maximum depth and behavior
87
- 6. **Fail-Safe**: Always cleans up loading stack even if errors occur
88
-
89
- ### Configuration Options
90
-
91
- ```typescript
92
- // Set maximum loading depth (default: 10)
93
- LoadingContext.setMaxDepth(15);
94
-
95
- // Get current loading stack for debugging
96
- console.log(LoadingContext.getLoadingStack());
97
-
98
- // Clear stack in emergency situations
99
- LoadingContext.clearStack();
100
- ```
101
-
102
- ### Backward Compatibility
103
-
104
- - **100% backward compatible**: No changes needed to existing model code
105
- - **Existing `cancelPostLoad` still works**: Provides additional layer of protection
106
- - **All existing APIs preserved**: No breaking changes to FindUtils or RWSModel
107
-
108
- ### Testing
109
-
110
- A test file (`LoadingContext.test.ts`) demonstrates:
111
- - Basic loading detection
112
- - Circular reference prevention
113
- - Maximum depth protection
114
- - Automatic cleanup
115
-
116
- ### Error Handling
117
-
118
- The implementation provides clear error messages:
119
- - **Circular Reference**: "Circular reference detected: User:1 is already being loaded. Breaking cycle."
120
- - **Maximum Depth**: "Maximum loading depth (10) exceeded. Possible circular reference detected. Loading stack: User:1 -> UserGroup:2 -> ..."
121
-
122
- This solution provides robust protection against infinite recursion while maintaining full backward compatibility and excellent debugging capabilities.
@@ -1,56 +0,0 @@
1
- /**
2
- * LoadingContext - Prevents circular references during model loading by tracking
3
- * the chain of models currently being loaded per execution context
4
- */
5
- declare global {
6
- var __currentExecutionContext: object | undefined;
7
- }
8
- export declare class LoadingContext {
9
- private static executionContexts;
10
- private static defaultMaxDepth;
11
- /**
12
- * Get or create execution context identifier
13
- */
14
- private static getExecutionContext;
15
- /**
16
- * Create a new execution context for an operation
17
- */
18
- static withNewExecutionContext<T>(fn: () => Promise<T>): Promise<T>;
19
- /**
20
- * Get the loading stack for current execution context
21
- */
22
- private static getLoadingStackInternal;
23
- /**
24
- * Check if a model with specific ID is currently being loaded
25
- */
26
- static isLoading(modelType: string, id: string | number): boolean;
27
- /**
28
- * Mark a model as currently being loaded
29
- */
30
- static startLoading(modelType: string, id: string | number): void;
31
- /**
32
- * Mark a model as finished loading
33
- */
34
- static finishLoading(modelType: string, id: string | number): void;
35
- /**
36
- * Get current loading stack for debugging
37
- */
38
- static getLoadingStack(): string[];
39
- /**
40
- * Clear the entire loading stack for current context
41
- */
42
- static clearStack(): void;
43
- /**
44
- * Set default maximum loading depth
45
- */
46
- static setDefaultMaxDepth(depth: number): void;
47
- /**
48
- * Create a unique key for model type and ID
49
- */
50
- private static createKey;
51
- /**
52
- * Execute a function with loading context protection
53
- * Automatically manages start/finish loading calls
54
- */
55
- static withLoadingContext<T>(modelType: string, id: string | number, fn: () => Promise<T>): Promise<T>;
56
- }