@grest-ts/locator 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +214 -209
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/GGLocator.ts +45 -45
- package/src/GGLocatorKey.ts +29 -29
- package/src/GGLocatorScope.ts +205 -205
- package/src/GGLocatorScopeDebug.ts +153 -153
- package/src/GGLocatorStorage.ts +9 -9
- package/src/index-browser.ts +4 -4
- package/src/index-node.ts +8 -8
package/src/GGLocatorScope.ts
CHANGED
|
@@ -1,206 +1,206 @@
|
|
|
1
|
-
import type {GGLocatorKey} from "./GGLocatorKey";
|
|
2
|
-
import {GG_ASYNC_STORAGE} from "./GGLocatorStorage";
|
|
3
|
-
import {GGLocatorScopeDebug, GGLocatorScopeDebugList} from "./GGLocatorScopeDebug";
|
|
4
|
-
|
|
5
|
-
type ConstructorOf<T> = new (...args: any[]) => T;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* service-locator pattern for async context cases.
|
|
9
|
-
*/
|
|
10
|
-
export class GGLocatorScope {
|
|
11
|
-
|
|
12
|
-
public readonly name: string
|
|
13
|
-
public readonly serviceName?: string
|
|
14
|
-
private readonly parent: GGLocatorScope | undefined
|
|
15
|
-
private readonly values: Map<string, any>
|
|
16
|
-
private readonly registrationStacks: Map<string, string[]>
|
|
17
|
-
private readonly creationStack: string[]
|
|
18
|
-
|
|
19
|
-
constructor(name: string, parent?: GGLocatorScope, serviceName?: string) {
|
|
20
|
-
this.name = name
|
|
21
|
-
this.serviceName = serviceName ?? this.serviceName ?? "Unknown";
|
|
22
|
-
this.parent = parent
|
|
23
|
-
this.values = new Map(this.parent?.values ?? []);
|
|
24
|
-
this.registrationStacks = new Map(this.parent?.registrationStacks ?? []);
|
|
25
|
-
this.creationStack = new Error().stack?.split("\n").splice(2) ?? [];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
private _get<T>(name: string): T {
|
|
29
|
-
const value = this.values.get(name);
|
|
30
|
-
if (value === undefined) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`Service "${name}" not found in async context!\n` +
|
|
33
|
-
`Scope tree: ${this.getScopeDebug().toString()}`
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
return value;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
public get<T>(token: GGLocatorKey<T>): T {
|
|
40
|
-
return this._get(token.name)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
public tryGet<T>(token: GGLocatorKey<T>): T | undefined {
|
|
44
|
-
return this.values.get(token.name);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
public getByTokenName<T>(name: string, type: ConstructorOf<T>): T {
|
|
48
|
-
const value = this._get(name)
|
|
49
|
-
if (!(value instanceof type)) {
|
|
50
|
-
throw new Error("Service is not extending expected type! Expected '" + type?.name + "', but got '" + value?.constructor?.name + "' instead.")
|
|
51
|
-
}
|
|
52
|
-
return value as T;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
public set<T>(token: GGLocatorKey<T>, value: T): void {
|
|
56
|
-
if (this.values.has(token.name)) {
|
|
57
|
-
throw new Error(`Scope already has service "${token.name}"!`)
|
|
58
|
-
}
|
|
59
|
-
this.overwrite(token, value)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
public overwrite<T>(token: GGLocatorKey<T>, value: T): void {
|
|
63
|
-
this.values.set(token.name, value);
|
|
64
|
-
this.registrationStacks.set(token.name, new Error().stack.split("\n").splice(2));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
public has<T>(token: GGLocatorKey<T>): boolean {
|
|
68
|
-
return this.values.has(token.name);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Returns all registered key names in this scope.
|
|
73
|
-
* Used by testkit to report available services to test runner.
|
|
74
|
-
*/
|
|
75
|
-
public getKeys(): string[] {
|
|
76
|
-
return Array.from(this.values.keys());
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// -----------
|
|
80
|
-
|
|
81
|
-
private _lifecycleHandler?: (service: GGLocatorLifecycleCallbacks) => void;
|
|
82
|
-
|
|
83
|
-
public setLifecycleOwner(handler: (service: GGLocatorLifecycleCallbacks) => void | undefined): void {
|
|
84
|
-
this._lifecycleHandler = handler;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
public setWithLifecycle<T>(token: GGLocatorKey<T>, value: T, lifecycle: GGLocatorLifecycleCallbacks): void {
|
|
88
|
-
if (!this._lifecycleHandler) {
|
|
89
|
-
throw new Error("No lifecycle handler - are you registering outside compose phase?");
|
|
90
|
-
}
|
|
91
|
-
this.set(token, value);
|
|
92
|
-
this._lifecycleHandler(lifecycle);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// -----------
|
|
96
|
-
|
|
97
|
-
public setImmediate<R>(fn: () => R): NodeJS.Immediate {
|
|
98
|
-
return setImmediate(() => this.run(fn))
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
public setTimeout<R>(fn: () => R, timeout: number): NodeJS.Timeout {
|
|
102
|
-
return setTimeout(() => this.run(fn), timeout)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
public setInterval<R>(fn: () => R, timeout: number): NodeJS.Timeout {
|
|
106
|
-
return setInterval(() => this.run(fn), timeout)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Wraps a function with a async context run() call. Safe for most cases.
|
|
111
|
-
* Consider wrapWithEnter in case you want same context for that function forever.
|
|
112
|
-
*/
|
|
113
|
-
public wrapWithRun<T extends (...args: any[]) => any>(fn: T): T {
|
|
114
|
-
return ((...args: any[]) => {
|
|
115
|
-
return this.run(() => {
|
|
116
|
-
return fn(...args)
|
|
117
|
-
})
|
|
118
|
-
}) as T;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Wraps function to ensure this scope is active before each call.
|
|
123
|
-
* Unlike wrapWithRun, doesn't create a new async boundary - just ensures scope is entered.
|
|
124
|
-
* More performant for high-frequency event handlers where scope doesn't change.
|
|
125
|
-
*/
|
|
126
|
-
public wrapWithEnter<T extends (...args: any[]) => any>(fn: T): T {
|
|
127
|
-
return ((...args: any[]) => {
|
|
128
|
-
this.ensureEntered()
|
|
129
|
-
return fn(...args)
|
|
130
|
-
}) as T;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
public getScopeDebug(): GGLocatorScopeDebug {
|
|
134
|
-
const debug = new GGLocatorScopeDebug(this.name, this.creationStack);
|
|
135
|
-
this.values.forEach((value, name) => {
|
|
136
|
-
debug.add(name, value, this.registrationStacks.get(name));
|
|
137
|
-
})
|
|
138
|
-
return debug;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
public getScopeDebugFull(list: GGLocatorScopeDebugList = new GGLocatorScopeDebugList()): GGLocatorScopeDebugList {
|
|
142
|
-
if (this.parent) {
|
|
143
|
-
this.parent.getScopeDebugFull(list);
|
|
144
|
-
}
|
|
145
|
-
list.add(this.getScopeDebug())
|
|
146
|
-
return list;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// -----------
|
|
150
|
-
|
|
151
|
-
public branch(name: string, serviceName?: string): GGLocatorScope {
|
|
152
|
-
return new GGLocatorScope(name, this, serviceName);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
public run<R>(fn: () => R): R {
|
|
156
|
-
return GG_ASYNC_STORAGE.run(this, fn);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
public enter() {
|
|
160
|
-
GG_ASYNC_STORAGE.enterWith(this);
|
|
161
|
-
return this;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
public ensureEntered(): void {
|
|
165
|
-
const store = GG_ASYNC_STORAGE.getStore();
|
|
166
|
-
if (store !== this) {
|
|
167
|
-
GG_ASYNC_STORAGE.enterWith(this);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
public reset(): void {
|
|
172
|
-
this.values.clear();
|
|
173
|
-
this.parent.values.forEach((value, name) => this.values.set(name, value))
|
|
174
|
-
this.registrationStacks.clear();
|
|
175
|
-
this.parent.registrationStacks.forEach((value, name) => this.registrationStacks.set(name, value))
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Service initialization order - determines the sequence in which services start and stop.
|
|
182
|
-
* Services with lower values start first and stop last (reverse order for teardown).
|
|
183
|
-
*
|
|
184
|
-
* Example startup order: CONFIG → DATABASE → HTTP → INTL → BUSINESS → PERIODIC_JOBS → SERVICE_DISCOVERY
|
|
185
|
-
*/
|
|
186
|
-
export enum GGLocatorServiceType {
|
|
187
|
-
CONFIG = 0,
|
|
188
|
-
DATABASE = 10,
|
|
189
|
-
HTTP = 20,
|
|
190
|
-
INTL = 25,
|
|
191
|
-
BUSINESS = 30,
|
|
192
|
-
PERIODIC_JOBS = 40,
|
|
193
|
-
SERVICE_DISCOVERY = 999
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Service lifecycle callbacks registered with GGServiceRegistry.
|
|
198
|
-
*
|
|
199
|
-
* Services register these during construction (in GGRuntime.compose) to participate
|
|
200
|
-
* in the runtime lifecycle - startup, teardown, and per-request context hooks.
|
|
201
|
-
*/
|
|
202
|
-
export interface GGLocatorLifecycleCallbacks {
|
|
203
|
-
type: GGLocatorServiceType
|
|
204
|
-
start: () => any | Promise<any>
|
|
205
|
-
teardown?: () => any | Promise<any>
|
|
1
|
+
import type {GGLocatorKey} from "./GGLocatorKey";
|
|
2
|
+
import {GG_ASYNC_STORAGE} from "./GGLocatorStorage";
|
|
3
|
+
import {GGLocatorScopeDebug, GGLocatorScopeDebugList} from "./GGLocatorScopeDebug";
|
|
4
|
+
|
|
5
|
+
type ConstructorOf<T> = new (...args: any[]) => T;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* service-locator pattern for async context cases.
|
|
9
|
+
*/
|
|
10
|
+
export class GGLocatorScope {
|
|
11
|
+
|
|
12
|
+
public readonly name: string
|
|
13
|
+
public readonly serviceName?: string
|
|
14
|
+
private readonly parent: GGLocatorScope | undefined
|
|
15
|
+
private readonly values: Map<string, any>
|
|
16
|
+
private readonly registrationStacks: Map<string, string[]>
|
|
17
|
+
private readonly creationStack: string[]
|
|
18
|
+
|
|
19
|
+
constructor(name: string, parent?: GGLocatorScope, serviceName?: string) {
|
|
20
|
+
this.name = name
|
|
21
|
+
this.serviceName = serviceName ?? this.serviceName ?? "Unknown";
|
|
22
|
+
this.parent = parent
|
|
23
|
+
this.values = new Map(this.parent?.values ?? []);
|
|
24
|
+
this.registrationStacks = new Map(this.parent?.registrationStacks ?? []);
|
|
25
|
+
this.creationStack = new Error().stack?.split("\n").splice(2) ?? [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private _get<T>(name: string): T {
|
|
29
|
+
const value = this.values.get(name);
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Service "${name}" not found in async context!\n` +
|
|
33
|
+
`Scope tree: ${this.getScopeDebug().toString()}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public get<T>(token: GGLocatorKey<T>): T {
|
|
40
|
+
return this._get(token.name)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public tryGet<T>(token: GGLocatorKey<T>): T | undefined {
|
|
44
|
+
return this.values.get(token.name);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public getByTokenName<T>(name: string, type: ConstructorOf<T>): T {
|
|
48
|
+
const value = this._get(name)
|
|
49
|
+
if (!(value instanceof type)) {
|
|
50
|
+
throw new Error("Service is not extending expected type! Expected '" + type?.name + "', but got '" + value?.constructor?.name + "' instead.")
|
|
51
|
+
}
|
|
52
|
+
return value as T;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public set<T>(token: GGLocatorKey<T>, value: T): void {
|
|
56
|
+
if (this.values.has(token.name)) {
|
|
57
|
+
throw new Error(`Scope already has service "${token.name}"!`)
|
|
58
|
+
}
|
|
59
|
+
this.overwrite(token, value)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public overwrite<T>(token: GGLocatorKey<T>, value: T): void {
|
|
63
|
+
this.values.set(token.name, value);
|
|
64
|
+
this.registrationStacks.set(token.name, new Error().stack.split("\n").splice(2));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public has<T>(token: GGLocatorKey<T>): boolean {
|
|
68
|
+
return this.values.has(token.name);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns all registered key names in this scope.
|
|
73
|
+
* Used by testkit to report available services to test runner.
|
|
74
|
+
*/
|
|
75
|
+
public getKeys(): string[] {
|
|
76
|
+
return Array.from(this.values.keys());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// -----------
|
|
80
|
+
|
|
81
|
+
private _lifecycleHandler?: (service: GGLocatorLifecycleCallbacks) => void;
|
|
82
|
+
|
|
83
|
+
public setLifecycleOwner(handler: (service: GGLocatorLifecycleCallbacks) => void | undefined): void {
|
|
84
|
+
this._lifecycleHandler = handler;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public setWithLifecycle<T>(token: GGLocatorKey<T>, value: T, lifecycle: GGLocatorLifecycleCallbacks): void {
|
|
88
|
+
if (!this._lifecycleHandler) {
|
|
89
|
+
throw new Error("No lifecycle handler - are you registering outside compose phase?");
|
|
90
|
+
}
|
|
91
|
+
this.set(token, value);
|
|
92
|
+
this._lifecycleHandler(lifecycle);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// -----------
|
|
96
|
+
|
|
97
|
+
public setImmediate<R>(fn: () => R): NodeJS.Immediate {
|
|
98
|
+
return setImmediate(() => this.run(fn))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public setTimeout<R>(fn: () => R, timeout: number): NodeJS.Timeout {
|
|
102
|
+
return setTimeout(() => this.run(fn), timeout)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public setInterval<R>(fn: () => R, timeout: number): NodeJS.Timeout {
|
|
106
|
+
return setInterval(() => this.run(fn), timeout)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Wraps a function with a async context run() call. Safe for most cases.
|
|
111
|
+
* Consider wrapWithEnter in case you want same context for that function forever.
|
|
112
|
+
*/
|
|
113
|
+
public wrapWithRun<T extends (...args: any[]) => any>(fn: T): T {
|
|
114
|
+
return ((...args: any[]) => {
|
|
115
|
+
return this.run(() => {
|
|
116
|
+
return fn(...args)
|
|
117
|
+
})
|
|
118
|
+
}) as T;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Wraps function to ensure this scope is active before each call.
|
|
123
|
+
* Unlike wrapWithRun, doesn't create a new async boundary - just ensures scope is entered.
|
|
124
|
+
* More performant for high-frequency event handlers where scope doesn't change.
|
|
125
|
+
*/
|
|
126
|
+
public wrapWithEnter<T extends (...args: any[]) => any>(fn: T): T {
|
|
127
|
+
return ((...args: any[]) => {
|
|
128
|
+
this.ensureEntered()
|
|
129
|
+
return fn(...args)
|
|
130
|
+
}) as T;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public getScopeDebug(): GGLocatorScopeDebug {
|
|
134
|
+
const debug = new GGLocatorScopeDebug(this.name, this.creationStack);
|
|
135
|
+
this.values.forEach((value, name) => {
|
|
136
|
+
debug.add(name, value, this.registrationStacks.get(name));
|
|
137
|
+
})
|
|
138
|
+
return debug;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public getScopeDebugFull(list: GGLocatorScopeDebugList = new GGLocatorScopeDebugList()): GGLocatorScopeDebugList {
|
|
142
|
+
if (this.parent) {
|
|
143
|
+
this.parent.getScopeDebugFull(list);
|
|
144
|
+
}
|
|
145
|
+
list.add(this.getScopeDebug())
|
|
146
|
+
return list;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// -----------
|
|
150
|
+
|
|
151
|
+
public branch(name: string, serviceName?: string): GGLocatorScope {
|
|
152
|
+
return new GGLocatorScope(name, this, serviceName);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public run<R>(fn: () => R): R {
|
|
156
|
+
return GG_ASYNC_STORAGE.run(this, fn);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public enter() {
|
|
160
|
+
GG_ASYNC_STORAGE.enterWith(this);
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public ensureEntered(): void {
|
|
165
|
+
const store = GG_ASYNC_STORAGE.getStore();
|
|
166
|
+
if (store !== this) {
|
|
167
|
+
GG_ASYNC_STORAGE.enterWith(this);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public reset(): void {
|
|
172
|
+
this.values.clear();
|
|
173
|
+
this.parent.values.forEach((value, name) => this.values.set(name, value))
|
|
174
|
+
this.registrationStacks.clear();
|
|
175
|
+
this.parent.registrationStacks.forEach((value, name) => this.registrationStacks.set(name, value))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Service initialization order - determines the sequence in which services start and stop.
|
|
182
|
+
* Services with lower values start first and stop last (reverse order for teardown).
|
|
183
|
+
*
|
|
184
|
+
* Example startup order: CONFIG → DATABASE → HTTP → INTL → BUSINESS → PERIODIC_JOBS → SERVICE_DISCOVERY
|
|
185
|
+
*/
|
|
186
|
+
export enum GGLocatorServiceType {
|
|
187
|
+
CONFIG = 0,
|
|
188
|
+
DATABASE = 10,
|
|
189
|
+
HTTP = 20,
|
|
190
|
+
INTL = 25,
|
|
191
|
+
BUSINESS = 30,
|
|
192
|
+
PERIODIC_JOBS = 40,
|
|
193
|
+
SERVICE_DISCOVERY = 999
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Service lifecycle callbacks registered with GGServiceRegistry.
|
|
198
|
+
*
|
|
199
|
+
* Services register these during construction (in GGRuntime.compose) to participate
|
|
200
|
+
* in the runtime lifecycle - startup, teardown, and per-request context hooks.
|
|
201
|
+
*/
|
|
202
|
+
export interface GGLocatorLifecycleCallbacks {
|
|
203
|
+
type: GGLocatorServiceType
|
|
204
|
+
start: () => any | Promise<any>
|
|
205
|
+
teardown?: () => any | Promise<any>
|
|
206
206
|
}
|