@declaro/core 2.0.0-beta.8 → 2.0.0-y.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/dist/app/app-context.d.ts +8 -0
- package/dist/app/app-lifecycle.d.ts +4 -0
- package/dist/app/app.d.ts +22 -0
- package/dist/app/index.d.ts +3 -20
- package/dist/auth/permission-validator.d.ts +34 -0
- package/dist/auth/permission-validator.test.d.ts +1 -0
- package/dist/context/context.d.ts +88 -13
- package/dist/context/legacy-context.test.d.ts +1 -0
- package/dist/errors/errors.d.ts +36 -0
- package/dist/events/event-manager.d.ts +11 -6
- package/dist/http/headers.d.ts +4 -0
- package/dist/http/headers.spec.d.ts +1 -0
- package/dist/http/request-context.d.ts +12 -0
- package/dist/http/request-context.spec.d.ts +1 -0
- package/dist/http/request.d.ts +8 -0
- package/dist/http/request.spec.d.ts +1 -0
- package/dist/http/url.d.ts +8 -0
- package/dist/http/url.spec.d.ts +1 -0
- package/dist/index.d.ts +9 -3
- package/dist/pkg.cjs +30 -2
- package/dist/pkg.mjs +56461 -207
- package/dist/schema/application.d.ts +83 -0
- package/dist/schema/application.test.d.ts +1 -0
- package/dist/schema/define-model.d.ts +7 -4
- package/dist/schema/index.d.ts +7 -0
- package/dist/schema/labels.d.ts +13 -0
- package/dist/schema/labels.test.d.ts +1 -0
- package/dist/schema/module.d.ts +7 -0
- package/dist/schema/module.test.d.ts +1 -0
- package/dist/schema/properties.d.ts +19 -0
- package/dist/schema/response.d.ts +31 -0
- package/dist/schema/response.test.d.ts +1 -0
- package/dist/schema/transform-model.d.ts +1 -1
- package/dist/schema/types.d.ts +81 -15
- package/dist/schema/types.test.d.ts +1 -0
- package/dist/typescript/constant-manipulation/snake-case.d.ts +22 -0
- package/dist/typescript/index.d.ts +1 -0
- package/dist/typescript/objects.d.ts +6 -0
- package/package.json +8 -3
- package/src/app/app-context.ts +14 -0
- package/src/app/app-lifecycle.ts +14 -0
- package/src/app/app.ts +45 -0
- package/src/app/index.ts +3 -34
- package/src/auth/permission-validator.test.ts +209 -0
- package/src/auth/permission-validator.ts +135 -0
- package/src/context/context.test.ts +585 -94
- package/src/context/context.ts +348 -32
- package/src/context/legacy-context.test.ts +141 -0
- package/src/errors/errors.ts +73 -0
- package/src/events/event-manager.spec.ts +54 -8
- package/src/events/event-manager.ts +40 -24
- package/src/http/headers.spec.ts +48 -0
- package/src/http/headers.ts +16 -0
- package/src/http/request-context.spec.ts +39 -0
- package/src/http/request-context.ts +43 -0
- package/src/http/request.spec.ts +52 -0
- package/src/http/request.ts +22 -0
- package/src/http/url.spec.ts +87 -0
- package/src/http/url.ts +48 -0
- package/src/index.ts +9 -3
- package/src/schema/application.test.ts +286 -0
- package/src/schema/application.ts +150 -0
- package/src/schema/define-model.test.ts +48 -2
- package/src/schema/define-model.ts +40 -9
- package/src/schema/index.ts +7 -0
- package/src/schema/labels.test.ts +60 -0
- package/src/schema/labels.ts +30 -0
- package/src/schema/module.test.ts +39 -0
- package/src/schema/module.ts +6 -0
- package/src/schema/properties.ts +40 -0
- package/src/schema/response.test.ts +101 -0
- package/src/schema/response.ts +93 -0
- package/src/schema/transform-model.ts +1 -1
- package/src/schema/types.test.ts +28 -0
- package/src/schema/types.ts +135 -15
- package/src/typescript/constant-manipulation/snake-case.md +496 -0
- package/src/typescript/constant-manipulation/snake-case.ts +76 -0
- package/src/typescript/index.ts +1 -0
- package/src/typescript/objects.ts +8 -5
- package/tsconfig.json +4 -1
- package/dist/context/index.d.ts +0 -3
- package/dist/interfaces/IDatastoreProvider.d.ts +0 -16
- package/dist/interfaces/IStore.d.ts +0 -4
- package/dist/interfaces/index.d.ts +0 -2
- package/dist/server/index.d.ts +0 -2
- package/src/context/index.ts +0 -3
- package/src/interfaces/IDatastoreProvider.ts +0 -23
- package/src/interfaces/IStore.ts +0 -4
- package/src/interfaces/index.ts +0 -2
- package/src/server/index.ts +0 -3
|
@@ -1,144 +1,635 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ContextConsumer } from './context-consumer'
|
|
4
|
-
import { describe, it, vi } from 'vitest'
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { Context } from './context'
|
|
5
3
|
|
|
6
4
|
describe('Context', () => {
|
|
7
|
-
it('Should allow
|
|
8
|
-
|
|
5
|
+
it('Should allow value dependency injection', async () => {
|
|
6
|
+
type Scope = {
|
|
7
|
+
foo: string
|
|
8
|
+
bar: number
|
|
9
|
+
}
|
|
10
|
+
const context = new Context<Scope>()
|
|
11
|
+
|
|
12
|
+
context.registerValue('foo', 'Hello')
|
|
13
|
+
context.registerValue('bar', 42)
|
|
14
|
+
|
|
15
|
+
const foo = context.resolve('foo')
|
|
16
|
+
const bar = context.resolve('bar')
|
|
17
|
+
|
|
18
|
+
expect(foo).toBe('Hello')
|
|
19
|
+
expect(bar).toBe(42)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('Should allow factory dependency injection', async () => {
|
|
23
|
+
type Scope = {
|
|
24
|
+
foo: string
|
|
25
|
+
bar: number
|
|
26
|
+
name: string
|
|
27
|
+
}
|
|
28
|
+
const context = new Context<Scope>()
|
|
29
|
+
|
|
30
|
+
context.registerValue('bar', 42)
|
|
31
|
+
context.registerValue('name', 'Person')
|
|
32
|
+
|
|
33
|
+
context.registerFactory('foo', (name: string, bar: number) => `Hello ${name}, the answer is ${bar}`, [
|
|
34
|
+
'name',
|
|
35
|
+
'bar',
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
const foo = context.resolve('foo')
|
|
39
|
+
|
|
40
|
+
expect(foo).toBe('Hello Person, the answer is 42')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('Should allow async factory dependency injection', async () => {
|
|
44
|
+
type Scope = {
|
|
45
|
+
foo: Promise<string>
|
|
46
|
+
bar: number
|
|
47
|
+
name: Promise<string>
|
|
48
|
+
baz: string
|
|
49
|
+
}
|
|
50
|
+
const context = new Context<Scope>()
|
|
51
|
+
|
|
52
|
+
context.registerValue('bar', 42)
|
|
53
|
+
context.registerAsyncFactory('name', async () => 'Person')
|
|
54
|
+
|
|
55
|
+
context.registerAsyncFactory(
|
|
56
|
+
'foo',
|
|
57
|
+
async (name: string, bar: number) => `Hello ${name}, the answer is ${bar}`,
|
|
58
|
+
['name', 'bar'],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const foo = await context.resolve('foo')
|
|
62
|
+
|
|
63
|
+
expect(foo).toBe('Hello Person, the answer is 42')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should allow class dependency injection', async () => {
|
|
67
|
+
class Thing1 {
|
|
68
|
+
constructor(public name: string) {}
|
|
69
|
+
|
|
70
|
+
testThing1() {
|
|
71
|
+
return `Hello, ${this.name}, this is Thing 1`
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class Thing2 {
|
|
76
|
+
constructor(public thing1: Thing1, public bar: number) {}
|
|
77
|
+
|
|
78
|
+
testThing2() {
|
|
79
|
+
return this.thing1.testThing1() + ' and Thing 2. The answer is ' + this.bar.toString()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type Scope = {
|
|
84
|
+
foo: string
|
|
85
|
+
bar: number
|
|
86
|
+
name: string
|
|
87
|
+
thing1: Thing1
|
|
88
|
+
thing2: Thing2
|
|
89
|
+
}
|
|
90
|
+
const context = new Context<Scope>()
|
|
91
|
+
|
|
92
|
+
context.registerValue('bar', 42)
|
|
93
|
+
context.registerValue('name', 'Person')
|
|
94
|
+
|
|
95
|
+
context.registerClass('thing1', Thing1, ['name'])
|
|
96
|
+
context.registerClass('thing2', Thing2, ['thing1', 'bar'])
|
|
97
|
+
|
|
98
|
+
const thing1 = context.resolve('thing1')
|
|
99
|
+
const thing2 = context.resolve('thing2')
|
|
100
|
+
|
|
101
|
+
expect(thing1.testThing1()).toBe('Hello, Person, this is Thing 1')
|
|
102
|
+
expect(thing2.testThing2()).toBe('Hello, Person, this is Thing 1 and Thing 2. The answer is 42')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should allow class dependency injection with async dependencies', async () => {
|
|
106
|
+
class Greeting {
|
|
107
|
+
constructor(public name: string) {}
|
|
108
|
+
|
|
109
|
+
greet() {
|
|
110
|
+
return `Hello, ${this.name}.`
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
class AgeCalculator {
|
|
115
|
+
constructor(public name: string, public age: number) {}
|
|
116
|
+
|
|
117
|
+
printAge() {
|
|
118
|
+
return `${this.name} is ${this.age} years old.`
|
|
119
|
+
}
|
|
120
|
+
}
|
|
9
121
|
|
|
10
|
-
|
|
122
|
+
type Scope = {
|
|
123
|
+
name: Promise<string>
|
|
124
|
+
age: number
|
|
125
|
+
greeting: Promise<Greeting>
|
|
126
|
+
ageCalculator: Promise<AgeCalculator>
|
|
127
|
+
}
|
|
11
128
|
|
|
12
|
-
const
|
|
129
|
+
const context = new Context<Scope>()
|
|
13
130
|
|
|
14
|
-
|
|
131
|
+
context.registerValue('age', 42)
|
|
132
|
+
context.registerAsyncFactory('name', async () => 'Person')
|
|
133
|
+
context.registerAsyncClass('greeting', Greeting, ['name'])
|
|
134
|
+
context.registerAsyncClass('ageCalculator', AgeCalculator, ['name', 'age'])
|
|
15
135
|
|
|
16
|
-
const
|
|
17
|
-
context.
|
|
136
|
+
const greeting = await context.resolve('greeting')
|
|
137
|
+
const ageCalculator = await context.resolve('ageCalculator')
|
|
18
138
|
|
|
19
|
-
|
|
20
|
-
expect(
|
|
139
|
+
expect(greeting.greet()).toBe('Hello, Person.')
|
|
140
|
+
expect(ageCalculator.printAge()).toBe('Person is 42 years old.')
|
|
21
141
|
})
|
|
22
142
|
|
|
23
|
-
it('
|
|
24
|
-
|
|
143
|
+
it('should support singletons', async () => {
|
|
144
|
+
let factoryInstances = 0
|
|
145
|
+
let asyncFactoryInstances = 0
|
|
146
|
+
let classInstances = 0
|
|
25
147
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
148
|
+
class Singleton {
|
|
149
|
+
constructor(public value: string) {
|
|
150
|
+
classInstances = classInstances + 1
|
|
151
|
+
}
|
|
29
152
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
153
|
+
test() {
|
|
154
|
+
return 'From a class: ' + this.value
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
type Scope = {
|
|
159
|
+
singletonValue: number
|
|
160
|
+
singletonFactory: string
|
|
161
|
+
singletonAsyncFactory: Promise<string>
|
|
162
|
+
singletonClass: Singleton
|
|
163
|
+
singletonAsyncClass: Promise<Singleton>
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const context = new Context<Scope>()
|
|
167
|
+
|
|
168
|
+
context.registerValue('singletonValue', 42) // Note: Values are inherently singletons
|
|
169
|
+
context.registerFactory(
|
|
170
|
+
'singletonFactory',
|
|
171
|
+
(value: number) => {
|
|
172
|
+
factoryInstances = factoryInstances + 1
|
|
173
|
+
return `The answer is ${value}`
|
|
174
|
+
},
|
|
175
|
+
['singletonValue'],
|
|
176
|
+
{
|
|
177
|
+
singleton: true,
|
|
178
|
+
},
|
|
179
|
+
)
|
|
180
|
+
context.registerAsyncFactory(
|
|
181
|
+
'singletonAsyncFactory',
|
|
182
|
+
async (answer: string) => {
|
|
183
|
+
asyncFactoryInstances = asyncFactoryInstances + 1
|
|
184
|
+
return `Since you've waited a long time, ${answer}`
|
|
185
|
+
},
|
|
186
|
+
['singletonFactory'],
|
|
187
|
+
{
|
|
188
|
+
singleton: true,
|
|
189
|
+
},
|
|
190
|
+
)
|
|
191
|
+
context.registerClass('singletonClass', Singleton, ['singletonFactory'], {
|
|
192
|
+
singleton: true,
|
|
37
193
|
})
|
|
194
|
+
context.registerAsyncClass('singletonAsyncClass', Singleton, ['singletonAsyncFactory'], {
|
|
195
|
+
singleton: true,
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
const singletonValue = context.resolve('singletonValue')
|
|
199
|
+
const singletonFactory = context.resolve('singletonFactory')
|
|
200
|
+
const singletonAsyncFactory = await context.resolve('singletonAsyncFactory')
|
|
201
|
+
const singletonClass = context.resolve('singletonClass')
|
|
202
|
+
const singletonAsyncClass = await context.resolve('singletonAsyncClass')
|
|
38
203
|
|
|
39
|
-
|
|
204
|
+
expect(singletonValue).toBe(42)
|
|
205
|
+
expect(singletonFactory).toBe('The answer is 42')
|
|
206
|
+
expect(singletonAsyncFactory).toBe("Since you've waited a long time, The answer is 42")
|
|
207
|
+
expect(singletonClass.test()).toBe('From a class: The answer is 42')
|
|
208
|
+
expect(singletonAsyncClass.test()).toBe("From a class: Since you've waited a long time, The answer is 42")
|
|
40
209
|
|
|
41
|
-
expect(
|
|
210
|
+
expect(factoryInstances).toBe(1)
|
|
211
|
+
expect(classInstances).toBe(2)
|
|
42
212
|
})
|
|
43
213
|
|
|
44
|
-
it('
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
214
|
+
it('should support eager dependencies', async () => {
|
|
215
|
+
type Scope = {
|
|
216
|
+
foo: string
|
|
217
|
+
bar: number
|
|
218
|
+
name: string
|
|
219
|
+
}
|
|
220
|
+
const context = new Context<Scope>()
|
|
221
|
+
|
|
222
|
+
context.registerValue('bar', 42)
|
|
223
|
+
context.registerValue('name', 'Person')
|
|
224
|
+
|
|
225
|
+
let factoryInstances = 0
|
|
226
|
+
|
|
227
|
+
context.registerFactory(
|
|
228
|
+
'foo',
|
|
229
|
+
(name: string, bar: number) => {
|
|
230
|
+
factoryInstances = factoryInstances + 1
|
|
231
|
+
return `Hello ${name}, the answer is ${bar}`
|
|
232
|
+
},
|
|
233
|
+
['name', 'bar'],
|
|
234
|
+
{
|
|
235
|
+
eager: true,
|
|
236
|
+
},
|
|
237
|
+
)
|
|
48
238
|
|
|
49
|
-
|
|
239
|
+
expect(factoryInstances).toBe(0)
|
|
50
240
|
|
|
51
|
-
|
|
52
|
-
context1.provide('c', 1)
|
|
53
|
-
context2.provide('b', 2)
|
|
54
|
-
context2.provide('c', 2)
|
|
55
|
-
context2.provide(SYMBOL_KEY, 42)
|
|
56
|
-
context3.provide('c', 3)
|
|
241
|
+
await context.emit('declaro:init')
|
|
57
242
|
|
|
58
|
-
|
|
243
|
+
expect(factoryInstances).toBe(1)
|
|
59
244
|
|
|
60
|
-
const
|
|
61
|
-
const b = context1.inject('b')
|
|
62
|
-
const c = context1.inject('c')
|
|
63
|
-
const symbolValue = context1.inject(SYMBOL_KEY)
|
|
245
|
+
const foo = context.resolve('foo')
|
|
64
246
|
|
|
65
|
-
expect(
|
|
66
|
-
|
|
67
|
-
expect(
|
|
68
|
-
expect(symbolValue).toBe(42)
|
|
247
|
+
expect(factoryInstances).toBe(1)
|
|
248
|
+
|
|
249
|
+
expect(foo).toBe('Hello Person, the answer is 42')
|
|
69
250
|
})
|
|
70
251
|
|
|
71
|
-
it('
|
|
72
|
-
|
|
73
|
-
|
|
252
|
+
it('should support scope resolution', async () => {
|
|
253
|
+
type Scope = {
|
|
254
|
+
foo: string
|
|
255
|
+
bar: number
|
|
256
|
+
name: string
|
|
257
|
+
}
|
|
258
|
+
const context = new Context<Scope>()
|
|
259
|
+
|
|
260
|
+
context.registerValue('bar', 42)
|
|
261
|
+
context.registerValue('name', 'Person')
|
|
262
|
+
|
|
263
|
+
let factoryInstances = 0
|
|
264
|
+
|
|
265
|
+
context.registerFactory(
|
|
266
|
+
'foo',
|
|
267
|
+
(name: string, bar: number) => {
|
|
268
|
+
factoryInstances = factoryInstances + 1
|
|
269
|
+
return `Hello ${name}, the answer is ${bar}`
|
|
270
|
+
},
|
|
271
|
+
['name', 'bar'],
|
|
272
|
+
)
|
|
74
273
|
|
|
75
|
-
|
|
76
|
-
context2.provide('a', 2)
|
|
274
|
+
expect(factoryInstances).toBe(0)
|
|
77
275
|
|
|
78
|
-
|
|
276
|
+
const foo = context.scope.foo
|
|
79
277
|
|
|
80
|
-
|
|
81
|
-
|
|
278
|
+
expect(factoryInstances).toBe(1)
|
|
279
|
+
expect(foo).toBe('Hello Person, the answer is 42')
|
|
82
280
|
|
|
83
|
-
|
|
84
|
-
|
|
281
|
+
const foo2 = context.scope.foo
|
|
282
|
+
|
|
283
|
+
expect(factoryInstances).toBe(2)
|
|
284
|
+
expect(foo2).toBe('Hello Person, the answer is 42')
|
|
285
|
+
|
|
286
|
+
expect(context.scope.bar).toBe(42)
|
|
287
|
+
expect(context.scope.name).toBe('Person')
|
|
85
288
|
})
|
|
86
289
|
|
|
87
|
-
it('
|
|
88
|
-
|
|
290
|
+
it('should support merging scopes', async () => {
|
|
291
|
+
type ScopeA = {
|
|
292
|
+
bar: number
|
|
293
|
+
name: string
|
|
294
|
+
foo: string
|
|
295
|
+
}
|
|
296
|
+
const context = new Context<ScopeA>()
|
|
297
|
+
|
|
298
|
+
let factoryInstances = 0
|
|
89
299
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
300
|
+
context.registerValue('bar', 42)
|
|
301
|
+
context.registerValue('name', 'Person')
|
|
302
|
+
context.registerFactory('foo', () => {
|
|
303
|
+
factoryInstances = factoryInstances + 1
|
|
304
|
+
return 'This should never run'
|
|
94
305
|
})
|
|
95
306
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
307
|
+
type ScopeB = ScopeA & {
|
|
308
|
+
foo: string
|
|
309
|
+
baz: number
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const context2 = new Context<ScopeB>()
|
|
313
|
+
context2.extend(context)
|
|
314
|
+
|
|
315
|
+
context2.registerFactory(
|
|
316
|
+
'foo',
|
|
317
|
+
(name: string, bar: number) => {
|
|
318
|
+
factoryInstances = factoryInstances + 1
|
|
319
|
+
return `Hello ${name}, the answer is ${bar}`
|
|
320
|
+
},
|
|
321
|
+
['name', 'bar'],
|
|
322
|
+
)
|
|
323
|
+
context2.registerValue('baz', 100)
|
|
324
|
+
|
|
325
|
+
// Ensure that merging doesn't actually run any factories
|
|
326
|
+
expect(factoryInstances).toBe(0)
|
|
327
|
+
|
|
328
|
+
const foo = context2.scope.foo
|
|
329
|
+
|
|
330
|
+
expect(factoryInstances).toBe(1)
|
|
331
|
+
expect(foo).toBe('Hello Person, the answer is 42')
|
|
332
|
+
|
|
333
|
+
const foo2 = context2.scope.foo
|
|
334
|
+
|
|
335
|
+
expect(factoryInstances).toBe(2)
|
|
336
|
+
expect(foo2).toBe('Hello Person, the answer is 42')
|
|
337
|
+
|
|
338
|
+
expect(context2.scope.bar).toBe(42)
|
|
339
|
+
expect(context2.scope.name).toBe('Person')
|
|
100
340
|
})
|
|
101
341
|
|
|
102
|
-
it(
|
|
103
|
-
|
|
342
|
+
it(`Should allow factories without args`, () => {
|
|
343
|
+
type Scope = {
|
|
344
|
+
foo: string
|
|
345
|
+
}
|
|
346
|
+
const context = new Context<Scope>()
|
|
347
|
+
|
|
348
|
+
context.registerFactory('foo', () => 'Hello')
|
|
104
349
|
|
|
105
|
-
const
|
|
106
|
-
let instance = context.singleton(KEY, 1)
|
|
107
|
-
instance = context.singleton(KEY, 2)
|
|
108
|
-
instance = context.singleton(KEY, 3)
|
|
350
|
+
const foo = context.resolve('foo')
|
|
109
351
|
|
|
110
|
-
expect(
|
|
352
|
+
expect(foo).toBe('Hello')
|
|
111
353
|
})
|
|
112
354
|
|
|
113
|
-
it('
|
|
114
|
-
|
|
355
|
+
it('should get nested dependencies', async () => {
|
|
356
|
+
type Scope = {
|
|
357
|
+
foo: string
|
|
358
|
+
}
|
|
115
359
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
360
|
+
type ScopeA = Scope & {
|
|
361
|
+
bar: string
|
|
362
|
+
}
|
|
120
363
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
364
|
+
type ScopeB = ScopeA & {
|
|
365
|
+
baz: string
|
|
124
366
|
}
|
|
125
367
|
|
|
126
|
-
|
|
368
|
+
type ScopeC = ScopeB & {
|
|
369
|
+
qux: string
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const context = new Context<Scope>()
|
|
373
|
+
context.registerFactory('foo', () => 'A partridge in a pear tree')
|
|
374
|
+
|
|
375
|
+
const contextA = new Context<ScopeA>()
|
|
376
|
+
contextA.extend(context)
|
|
377
|
+
contextA.registerFactory('bar', (foo: string) => `${foo}, two turtle doves`, ['foo'])
|
|
378
|
+
|
|
379
|
+
const contextB = new Context<ScopeB>()
|
|
380
|
+
contextB.extend(contextA)
|
|
381
|
+
contextB.registerFactory('baz', (bar: string) => `${bar}, three French hens`, ['bar'])
|
|
382
|
+
|
|
383
|
+
const contextC = new Context<ScopeC>()
|
|
384
|
+
contextC.extend(contextB)
|
|
385
|
+
contextC.registerFactory('qux', (baz: string) => `${baz}, four calling birds`, ['baz'])
|
|
386
|
+
|
|
387
|
+
const qux = contextC.resolve('qux')
|
|
388
|
+
|
|
389
|
+
const allDependencies = contextC.getAllDependencies('qux')
|
|
390
|
+
|
|
391
|
+
expect(allDependencies[0].key).toBe('baz')
|
|
392
|
+
expect(allDependencies[1].key).toBe('bar')
|
|
393
|
+
expect(allDependencies[2].key).toBe('foo')
|
|
394
|
+
|
|
395
|
+
expect(allDependencies[0].inject).toEqual(['bar'])
|
|
396
|
+
expect(allDependencies[1].inject).toEqual(['foo'])
|
|
397
|
+
expect(allDependencies[2].inject).toEqual([])
|
|
398
|
+
|
|
399
|
+
expect(qux).toBe('A partridge in a pear tree, two turtle doves, three French hens, four calling birds')
|
|
127
400
|
})
|
|
128
401
|
|
|
129
|
-
it('
|
|
130
|
-
|
|
402
|
+
it('should get nested dependent values', async () => {
|
|
403
|
+
type Scope = {
|
|
404
|
+
foo: string
|
|
405
|
+
}
|
|
131
406
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
407
|
+
type ScopeA = Scope & {
|
|
408
|
+
bar: string
|
|
409
|
+
}
|
|
136
410
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
411
|
+
type ScopeB = ScopeA & {
|
|
412
|
+
baz: string
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
type ScopeC = ScopeB & {
|
|
416
|
+
qux: string
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const context = new Context<Scope>()
|
|
420
|
+
context.registerFactory('foo', () => 'A partridge in a pear tree')
|
|
421
|
+
|
|
422
|
+
const contextA = new Context<ScopeA>()
|
|
423
|
+
contextA.extend(context)
|
|
424
|
+
contextA.registerFactory('bar', (foo: string) => `${foo}, two turtle doves`, ['foo'])
|
|
425
|
+
|
|
426
|
+
const contextB = new Context<ScopeB>()
|
|
427
|
+
contextB.extend(contextA)
|
|
428
|
+
contextB.registerFactory('baz', (bar: string) => `${bar}, three French hens`, ['bar'])
|
|
429
|
+
|
|
430
|
+
const contextC = new Context<ScopeC>()
|
|
431
|
+
contextC.extend(contextB)
|
|
432
|
+
contextC.registerFactory('qux', (baz: string) => `${baz}, four calling birds`, ['baz'])
|
|
433
|
+
|
|
434
|
+
const dependents = contextC.getAllDependents('foo')
|
|
435
|
+
|
|
436
|
+
expect(dependents[0].key).toBe('bar')
|
|
437
|
+
expect(dependents[1].key).toBe('baz')
|
|
438
|
+
expect(dependents[2].key).toBe('qux')
|
|
439
|
+
|
|
440
|
+
expect(dependents[0].inject).toEqual(['foo'])
|
|
441
|
+
expect(dependents[1].inject).toEqual(['bar'])
|
|
442
|
+
expect(dependents[2].inject).toEqual(['baz'])
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
it('should protect against scope leakage', async () => {
|
|
446
|
+
type BaseScope = {
|
|
447
|
+
foo: string
|
|
448
|
+
bar: string
|
|
449
|
+
base: string
|
|
450
|
+
validSingleton: string
|
|
451
|
+
validCachedSingleton: string
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
type ScopeA = BaseScope & {
|
|
455
|
+
baz: string
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
type ScopeB = BaseScope & {
|
|
459
|
+
qux: string
|
|
140
460
|
}
|
|
141
461
|
|
|
142
|
-
|
|
462
|
+
const baseContext = new Context<BaseScope>()
|
|
463
|
+
|
|
464
|
+
baseContext.registerFactory('foo', () => 'Hello', [], {
|
|
465
|
+
singleton: true,
|
|
466
|
+
})
|
|
467
|
+
baseContext.registerFactory('bar', () => 'World', [])
|
|
468
|
+
baseContext.registerFactory('base', (foo: string, bar: string) => `Base: ${foo} ${bar}`, ['foo', 'bar'], {
|
|
469
|
+
singleton: true,
|
|
470
|
+
})
|
|
471
|
+
let validSingletonInstances = 0
|
|
472
|
+
baseContext.registerFactory(
|
|
473
|
+
'validSingleton',
|
|
474
|
+
() => {
|
|
475
|
+
validSingletonInstances = validSingletonInstances + 1
|
|
476
|
+
return 'Singleton'
|
|
477
|
+
},
|
|
478
|
+
[],
|
|
479
|
+
{
|
|
480
|
+
singleton: true,
|
|
481
|
+
},
|
|
482
|
+
)
|
|
483
|
+
let validCachedSingletonInstances = 0
|
|
484
|
+
baseContext.registerFactory(
|
|
485
|
+
'validCachedSingleton',
|
|
486
|
+
() => {
|
|
487
|
+
validCachedSingletonInstances = validCachedSingletonInstances + 1
|
|
488
|
+
return 'Cached Singleton'
|
|
489
|
+
},
|
|
490
|
+
[],
|
|
491
|
+
{
|
|
492
|
+
singleton: true,
|
|
493
|
+
},
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
// Resolve this before forking context to ensure that the cached value gets passed on.
|
|
497
|
+
const base = baseContext.resolve('base')
|
|
498
|
+
const validCachedSingleton = baseContext.resolve('validCachedSingleton')
|
|
499
|
+
|
|
500
|
+
const contextA = new Context<ScopeA>()
|
|
501
|
+
contextA.extend(baseContext)
|
|
502
|
+
// Override a value that the singleton is going to depend on, but is not a singleton
|
|
503
|
+
contextA.registerFactory('bar', () => 'Robby', [])
|
|
504
|
+
// Add another property that dependes on the overridden value
|
|
505
|
+
contextA.registerFactory('baz', (foo: string, bar: string) => `${foo} ${bar}`, ['foo', 'bar'], {
|
|
506
|
+
singleton: true,
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
const contextB = new Context<ScopeB>()
|
|
510
|
+
contextB.extend(baseContext)
|
|
511
|
+
// Override a value that the singleton is going to depend on, and is also a singleton
|
|
512
|
+
contextB.registerFactory('foo', () => 'Goodbye', [], {
|
|
513
|
+
singleton: true,
|
|
514
|
+
})
|
|
515
|
+
// Add another property that dependes on the overridden value
|
|
516
|
+
contextB.registerFactory('qux', (foo: string, bar: string) => `${foo} ${bar}`, ['foo', 'bar'], {
|
|
517
|
+
singleton: true,
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
const baseA = contextA.resolve('base')
|
|
521
|
+
const baseB = contextB.resolve('base')
|
|
522
|
+
|
|
523
|
+
const baz = contextA.resolve('baz')
|
|
524
|
+
const qux = contextB.resolve('qux')
|
|
525
|
+
const foo = baseContext.resolve('foo')
|
|
526
|
+
const bar = baseContext.resolve('bar')
|
|
527
|
+
|
|
528
|
+
const validSingleton = baseContext.resolve('validSingleton')
|
|
529
|
+
const validSingletonA = contextA.resolve('validSingleton')
|
|
530
|
+
const validSingletonB = contextB.resolve('validSingleton')
|
|
531
|
+
|
|
532
|
+
const validCachedSingletonA = contextA.resolve('validCachedSingleton')
|
|
533
|
+
const validCachedSingletonB = contextB.resolve('validCachedSingleton')
|
|
534
|
+
|
|
535
|
+
// resolve them all again
|
|
536
|
+
baseContext.resolve('validSingleton')
|
|
537
|
+
contextA.resolve('validSingleton')
|
|
538
|
+
contextB.resolve('validSingleton')
|
|
539
|
+
baseContext.resolve('validCachedSingleton')
|
|
540
|
+
contextA.resolve('validCachedSingleton')
|
|
541
|
+
contextB.resolve('validCachedSingleton')
|
|
542
|
+
|
|
543
|
+
expect(baz).toBe('Hello Robby')
|
|
544
|
+
expect(qux).toBe('Goodbye World')
|
|
545
|
+
expect(foo).toBe('Hello')
|
|
546
|
+
expect(bar).toBe('World')
|
|
547
|
+
|
|
548
|
+
expect(baseA).toBe('Base: Hello Robby')
|
|
549
|
+
expect(baseB).toBe('Base: Goodbye World')
|
|
550
|
+
expect(base).toBe('Base: Hello World')
|
|
551
|
+
|
|
552
|
+
expect(validSingleton).toBe('Singleton')
|
|
553
|
+
expect(validSingletonA).toBe('Singleton')
|
|
554
|
+
expect(validSingletonB).toBe('Singleton')
|
|
555
|
+
|
|
556
|
+
expect(validCachedSingleton).toBe('Cached Singleton')
|
|
557
|
+
expect(validCachedSingletonA).toBe('Cached Singleton')
|
|
558
|
+
expect(validCachedSingletonB).toBe('Cached Singleton')
|
|
559
|
+
|
|
560
|
+
expect(validSingletonInstances).toBe(3)
|
|
561
|
+
expect(validCachedSingletonInstances).toBe(1)
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
it('should invalid nested dependency caches when overriding a value', async () => {
|
|
565
|
+
type Scope = {
|
|
566
|
+
foo: string
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
type ScopeA = Scope & {
|
|
570
|
+
bar: string
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
type ScopeB = ScopeA & {
|
|
574
|
+
baz: string
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
type ScopeC = ScopeB & {
|
|
578
|
+
qux: string
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const context = new Context<Scope>()
|
|
582
|
+
context.registerFactory('foo', () => 'A partridge in a pear tree', [], {
|
|
583
|
+
singleton: true,
|
|
584
|
+
})
|
|
585
|
+
context.resolve('foo')
|
|
586
|
+
|
|
587
|
+
const contextA = new Context<ScopeA>()
|
|
588
|
+
contextA.extend(context)
|
|
589
|
+
contextA.registerFactory('bar', (foo: string) => `two turtle doves, ${foo}`, ['foo'], {
|
|
590
|
+
singleton: true,
|
|
591
|
+
})
|
|
592
|
+
contextA.resolve('bar')
|
|
593
|
+
|
|
594
|
+
const contextB = new Context<ScopeB>()
|
|
595
|
+
contextB.extend(contextA)
|
|
596
|
+
contextB.registerFactory('baz', (bar: string) => `three French hens, ${bar}`, ['bar'], {
|
|
597
|
+
singleton: true,
|
|
598
|
+
})
|
|
599
|
+
contextB.resolve('baz')
|
|
600
|
+
|
|
601
|
+
const contextC = new Context<ScopeC>()
|
|
602
|
+
contextC.extend(contextB)
|
|
603
|
+
contextC.registerFactory('qux', (baz: string) => `four calling birds, ${baz}`, ['baz'], {
|
|
604
|
+
singleton: true,
|
|
605
|
+
})
|
|
606
|
+
contextC.resolve('qux')
|
|
607
|
+
contextC.registerFactory('foo', () => 'and a partridge in a pear tree', [], {
|
|
608
|
+
singleton: true,
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
const fooCache = contextC.introspect('foo').cachedValue
|
|
612
|
+
const barCache = contextC.introspect('bar').cachedValue
|
|
613
|
+
const bazCache = contextC.introspect('baz').cachedValue
|
|
614
|
+
const quxCache = contextC.introspect('qux').cachedValue
|
|
615
|
+
|
|
616
|
+
expect(fooCache).toBeUndefined()
|
|
617
|
+
expect(barCache).toBeUndefined()
|
|
618
|
+
expect(bazCache).toBeUndefined()
|
|
619
|
+
expect(quxCache).toBeUndefined()
|
|
620
|
+
|
|
621
|
+
contextC.resolve('qux')
|
|
622
|
+
|
|
623
|
+
const fooCache2 = contextC.introspect('foo').cachedValue
|
|
624
|
+
const barCache2 = contextC.introspect('bar').cachedValue
|
|
625
|
+
const bazCache2 = contextC.introspect('baz').cachedValue
|
|
626
|
+
const quxCache2 = contextC.introspect('qux').cachedValue
|
|
627
|
+
|
|
628
|
+
expect(fooCache2).toBe('and a partridge in a pear tree')
|
|
629
|
+
expect(barCache2).toBe('two turtle doves, and a partridge in a pear tree')
|
|
630
|
+
expect(bazCache2).toBe('three French hens, two turtle doves, and a partridge in a pear tree')
|
|
631
|
+
expect(quxCache2).toBe(
|
|
632
|
+
'four calling birds, three French hens, two turtle doves, and a partridge in a pear tree',
|
|
633
|
+
)
|
|
143
634
|
})
|
|
144
635
|
})
|