@grest-ts/locator 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Grest Games OÜ
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Grest Games OÜ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -3,212 +3,212 @@
3
3
  > [Documentation](https://github.com/grest-ts/grest-ts#readme) | [All packages](https://github.com/grest-ts/grest-ts#package-reference)
4
4
  <!-- GREST-TS-BANNER-END -->
5
5
 
6
- # Locator Package (@grest-ts/locator)
7
-
8
- Service locator pattern implementation using AsyncLocalStorage for managing dependency injection in async contexts. Provides type-safe access to services throughout the request lifecycle without
9
- explicit passing.
10
-
11
- ## When do you need it?
12
-
13
- - **Framework level**: The framework uses GGLocator internally to provide access to services like logging, config, databases, etc.
14
- - **Your services**: You can use it to avoid passing service dependencies through deep call stacks. Register once, access anywhere.
15
-
16
- Note: GGLocator is for **services** (long-lived dependencies). For request-scoped data (requestId, auth, etc.), use `GGContext` instead.
17
-
18
- ## Basic Usage
19
-
20
- ### Defining a Service Key
21
-
22
- ```typescript
23
- import {GGLocatorKey} from "@grest-ts/locator"
24
-
25
- interface UserService {
26
- getUser(id: string): Promise<User>
27
- }
28
-
29
- const UserServiceKey = new GGLocatorKey<UserService>("UserService")
30
- ```
31
-
32
- ### Case study: Dependency injection (DI) in Services
33
-
34
- Explicit, manually passing dependencies. This is what you probably would do for smaller services
35
-
36
- ```typescript
37
- class OrderService {
38
-
39
- private readonly userService: UserService
40
- private readonly paymentService: PaymentService
41
-
42
- constructor(userService: UserService, paymentService: PaymentService) {
43
- this.userService = userService
44
- this.paymentService = paymentService
45
- }
46
-
47
- async createOrder(userId: string, items: Item[]) {
48
- const user = await this.userService.getUser(userId)
49
- return this.paymentService.charge(user, items)
50
- }
51
- }
52
- ```
53
-
54
- Using dependency injection via GGLocator keys. Use this when things start getting bigger and you really see value in this.
55
-
56
- Pros: Less setup
57
-
58
- Cons: Less visibility for dependencies.
59
-
60
- ```typescript
61
- class OrderService {
62
-
63
- private readonly userService = UserServiceKey.get()
64
- private readonly paymentService = PaymentServiceKey.get()
65
-
66
- async createOrder(userId: string, items: Item[]) {
67
- const user = await this.userService.getUser(userId)
68
- return this.paymentService.charge(user, items)
69
- }
70
- }
71
- ```
72
-
73
- What NOT to do:
74
-
75
- ```typescript
76
- class OrderService {
77
-
78
- async createOrder(userId: string, items: Item[]) {
79
- // VERY BAD: Keeps the dependency "hidden" and causes issues for your project in the future.
80
- const user = await UserServiceKey.get().getUser(userId)
81
- return PaymentServiceKey.get().charge(user, items)
82
- }
83
- }
84
- ```
85
-
86
- But when is direct access fine?
87
-
88
- You can always define that some "service" is generic and accessible everywhere. This is for you to decide where you draw the line.
89
- Passing Logging, metrics, tracing etc around everywhere can become very tedious, so it is easier to just use inline.
90
-
91
- ```typescript
92
- class OrderService {
93
-
94
- async createOrder(userId: string, items: Item[]) {
95
- // Important - all these next examples are still async context scoped.
96
- GGLog.info(this, "Some log message") // Library itself provides quick access is usually a hint it is meant to be used like this.
97
- MyMetrics.orders.increment("orders.created") // Metrics are usually defined globally and accessible everywhere.
98
- const isEnabled = MyConfig.something.somewhere.get() // Config is usually defined globally and accessible everywhere.
99
- }
100
- }
101
- ```
102
-
103
- ### Registering and Accessing Services
104
-
105
- ```typescript
106
- import {GGLocatorScope} from "@grest-ts/locator"
107
-
108
- // You usually don't need to create the scope, but for the completeness of this example, we create it here.
109
- new GGLocatorScope("something").run(async () => {
110
-
111
- // Set the service
112
- UserServiceKey.set(new UserServiceImpl())
113
-
114
- // Set the service
115
- UserServiceKey.overwrite(new UserServiceImpl())
116
-
117
- // Access service anywhere in the call stack
118
- const userService = UserServiceKey.get() // Throws if service is not set.
119
- const user = await userService.getUser("123")
120
-
121
- // Optionally get the service
122
- const userServiceOpt = UserServiceKey.tryGet()
123
- if (userServiceOpt) {
124
- const user = await userServiceOpt.getUser("123")
125
- }
126
-
127
- })
128
- ```
129
-
130
- ### Using GGLocator Static Helpers
131
-
132
- ```typescript
133
- import {GGLocator} from "@grest-ts/locator"
134
-
135
- // Check if scope exists
136
- if (GGLocator.hasScope()) {
137
- const scope = GGLocator.getScope()
138
- }
139
-
140
- // Safe access (returns undefined if no scope)
141
- const scope = GGLocator.tryGetScope()
142
- ```
143
-
144
- ## Debugging
145
-
146
- To see what services are registered in the current scope:
147
-
148
- ```typescript
149
- const debugData = GGLocator.getScope().getScopeDebugFull();
150
- console.log(debugData.toString()) // Nicer for console log for a quick peek.
151
- console.log(debugData.toJSON()) // JSON format.
152
- ```
153
-
154
- This prints the full scope tree with all registered services and their registration stacks - useful for understanding what's available in your current context.
155
-
156
- ## Scope Branching
157
-
158
- Create child scopes that inherit parent services:
159
-
160
- ```typescript
161
- const rootScope = new GGLocatorScope("root")
162
- rootScope.set(ConfigKey, config)
163
-
164
- // Child scope inherits parent services
165
- const requestScope = rootScope.branch("request")
166
- requestScope.set(RequestContextKey, requestContext)
167
-
168
- requestScope.run(() => {
169
- ConfigKey.get() // Works - inherited from parent
170
- RequestContextKey.get() // Works - set in this scope
171
- })
172
- ```
173
-
174
- ## GGLocatorKey Methods
175
-
176
- ```typescript
177
- const ServiceKey = new GGLocatorKey<MyService>("MyService")
178
-
179
- // Get service (throws if not found)
180
- const service = ServiceKey.get()
181
-
182
- // Try get service (returns undefined if not found)
183
- const service = ServiceKey.tryGet()
184
-
185
- // Check if service exists
186
- if (ServiceKey.has()) { ...
187
- }
188
-
189
- // Set service (throws if already set)
190
- ServiceKey.set(new MyService())
191
-
192
- // Overwrite service (allows replacing)
193
- ServiceKey.overwrite(new MyService())
194
- ```
195
-
196
- ## Async Context Helpers
197
-
198
- Wrap callbacks to preserve scope across async boundaries:
199
-
200
- ```typescript
201
- // Wrap function to run within current scope
202
- const wrappedFn = GGLocator.wrapWithRun(myCallback)
203
-
204
- // Schedule with scope preservation
205
- GGLocator.setTimeout(() => {
206
- // Scope is preserved here
207
- ServiceKey.get()
208
- }, 1000)
209
-
210
- GGLocator.setInterval(() => { ...
211
- }, 5000)
212
- GGLocator.setImmediate(() => { ...
213
- })
214
- ```
6
+ # Locator Package (@grest-ts/locator)
7
+
8
+ Service locator pattern implementation using AsyncLocalStorage for managing dependency injection in async contexts. Provides type-safe access to services throughout the request lifecycle without
9
+ explicit passing.
10
+
11
+ ## When do you need it?
12
+
13
+ - **Framework level**: The framework uses GGLocator internally to provide access to services like logging, config, databases, etc.
14
+ - **Your services**: You can use it to avoid passing service dependencies through deep call stacks. Register once, access anywhere.
15
+
16
+ Note: GGLocator is for **services** (long-lived dependencies). For request-scoped data (requestId, auth, etc.), use `GGContext` instead.
17
+
18
+ ## Basic Usage
19
+
20
+ ### Defining a Service Key
21
+
22
+ ```typescript
23
+ import {GGLocatorKey} from "@grest-ts/locator"
24
+
25
+ interface UserService {
26
+ getUser(id: string): Promise<User>
27
+ }
28
+
29
+ const UserServiceKey = new GGLocatorKey<UserService>("UserService")
30
+ ```
31
+
32
+ ### Case study: Dependency injection (DI) in Services
33
+
34
+ Explicit, manually passing dependencies. This is what you probably would do for smaller services
35
+
36
+ ```typescript
37
+ class OrderService {
38
+
39
+ private readonly userService: UserService
40
+ private readonly paymentService: PaymentService
41
+
42
+ constructor(userService: UserService, paymentService: PaymentService) {
43
+ this.userService = userService
44
+ this.paymentService = paymentService
45
+ }
46
+
47
+ async createOrder(userId: string, items: Item[]) {
48
+ const user = await this.userService.getUser(userId)
49
+ return this.paymentService.charge(user, items)
50
+ }
51
+ }
52
+ ```
53
+
54
+ Using dependency injection via GGLocator keys. Use this when things start getting bigger and you really see value in this.
55
+
56
+ Pros: Less setup
57
+
58
+ Cons: Less visibility for dependencies.
59
+
60
+ ```typescript
61
+ class OrderService {
62
+
63
+ private readonly userService = UserServiceKey.get()
64
+ private readonly paymentService = PaymentServiceKey.get()
65
+
66
+ async createOrder(userId: string, items: Item[]) {
67
+ const user = await this.userService.getUser(userId)
68
+ return this.paymentService.charge(user, items)
69
+ }
70
+ }
71
+ ```
72
+
73
+ What NOT to do:
74
+
75
+ ```typescript
76
+ class OrderService {
77
+
78
+ async createOrder(userId: string, items: Item[]) {
79
+ // VERY BAD: Keeps the dependency "hidden" and causes issues for your project in the future.
80
+ const user = await UserServiceKey.get().getUser(userId)
81
+ return PaymentServiceKey.get().charge(user, items)
82
+ }
83
+ }
84
+ ```
85
+
86
+ But when is direct access fine?
87
+
88
+ You can always define that some "service" is generic and accessible everywhere. This is for you to decide where you draw the line.
89
+ Passing Logging, metrics, tracing etc around everywhere can become very tedious, so it is easier to just use inline.
90
+
91
+ ```typescript
92
+ class OrderService {
93
+
94
+ async createOrder(userId: string, items: Item[]) {
95
+ // Important - all these next examples are still async context scoped.
96
+ GGLog.info(this, "Some log message") // Library itself provides quick access is usually a hint it is meant to be used like this.
97
+ MyMetrics.orders.increment("orders.created") // Metrics are usually defined globally and accessible everywhere.
98
+ const isEnabled = MyConfig.something.somewhere.get() // Config is usually defined globally and accessible everywhere.
99
+ }
100
+ }
101
+ ```
102
+
103
+ ### Registering and Accessing Services
104
+
105
+ ```typescript
106
+ import {GGLocatorScope} from "@grest-ts/locator"
107
+
108
+ // You usually don't need to create the scope, but for the completeness of this example, we create it here.
109
+ new GGLocatorScope("something").run(async () => {
110
+
111
+ // Set the service
112
+ UserServiceKey.set(new UserServiceImpl())
113
+
114
+ // Set the service
115
+ UserServiceKey.overwrite(new UserServiceImpl())
116
+
117
+ // Access service anywhere in the call stack
118
+ const userService = UserServiceKey.get() // Throws if service is not set.
119
+ const user = await userService.getUser("123")
120
+
121
+ // Optionally get the service
122
+ const userServiceOpt = UserServiceKey.tryGet()
123
+ if (userServiceOpt) {
124
+ const user = await userServiceOpt.getUser("123")
125
+ }
126
+
127
+ })
128
+ ```
129
+
130
+ ### Using GGLocator Static Helpers
131
+
132
+ ```typescript
133
+ import {GGLocator} from "@grest-ts/locator"
134
+
135
+ // Check if scope exists
136
+ if (GGLocator.hasScope()) {
137
+ const scope = GGLocator.getScope()
138
+ }
139
+
140
+ // Safe access (returns undefined if no scope)
141
+ const scope = GGLocator.tryGetScope()
142
+ ```
143
+
144
+ ## Debugging
145
+
146
+ To see what services are registered in the current scope:
147
+
148
+ ```typescript
149
+ const debugData = GGLocator.getScope().getScopeDebugFull();
150
+ console.log(debugData.toString()) // Nicer for console log for a quick peek.
151
+ console.log(debugData.toJSON()) // JSON format.
152
+ ```
153
+
154
+ This prints the full scope tree with all registered services and their registration stacks - useful for understanding what's available in your current context.
155
+
156
+ ## Scope Branching
157
+
158
+ Create child scopes that inherit parent services:
159
+
160
+ ```typescript
161
+ const rootScope = new GGLocatorScope("root")
162
+ rootScope.set(ConfigKey, config)
163
+
164
+ // Child scope inherits parent services
165
+ const requestScope = rootScope.branch("request")
166
+ requestScope.set(RequestContextKey, requestContext)
167
+
168
+ requestScope.run(() => {
169
+ ConfigKey.get() // Works - inherited from parent
170
+ RequestContextKey.get() // Works - set in this scope
171
+ })
172
+ ```
173
+
174
+ ## GGLocatorKey Methods
175
+
176
+ ```typescript
177
+ const ServiceKey = new GGLocatorKey<MyService>("MyService")
178
+
179
+ // Get service (throws if not found)
180
+ const service = ServiceKey.get()
181
+
182
+ // Try get service (returns undefined if not found)
183
+ const service = ServiceKey.tryGet()
184
+
185
+ // Check if service exists
186
+ if (ServiceKey.has()) { ...
187
+ }
188
+
189
+ // Set service (throws if already set)
190
+ ServiceKey.set(new MyService())
191
+
192
+ // Overwrite service (allows replacing)
193
+ ServiceKey.overwrite(new MyService())
194
+ ```
195
+
196
+ ## Async Context Helpers
197
+
198
+ Wrap callbacks to preserve scope across async boundaries:
199
+
200
+ ```typescript
201
+ // Wrap function to run within current scope
202
+ const wrappedFn = GGLocator.wrapWithRun(myCallback)
203
+
204
+ // Schedule with scope preservation
205
+ GGLocator.setTimeout(() => {
206
+ // Scope is preserved here
207
+ ServiceKey.get()
208
+ }, 1000)
209
+
210
+ GGLocator.setInterval(() => { ...
211
+ }, 5000)
212
+ GGLocator.setImmediate(() => { ...
213
+ })
214
+ ```