@declaro/core 2.0.0-y.0 → 2.1.1

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.
Files changed (266) hide show
  1. package/{LICENSE → LICENSE.md} +1 -1
  2. package/README.md +203 -0
  3. package/dist/browser/index.js +28 -0
  4. package/dist/browser/index.js.map +133 -0
  5. package/dist/browser/scope/index.js +3 -0
  6. package/dist/browser/scope/index.js.map +9 -0
  7. package/dist/bun/index.js +19011 -0
  8. package/dist/bun/index.js.map +132 -0
  9. package/dist/bun/scope/index.js +4 -0
  10. package/dist/bun/scope/index.js.map +9 -0
  11. package/dist/node/index.cjs +19039 -0
  12. package/dist/node/index.cjs.map +132 -0
  13. package/dist/node/index.js +19010 -0
  14. package/dist/node/index.js.map +132 -0
  15. package/dist/node/scope/index.cjs +69 -0
  16. package/dist/node/scope/index.cjs.map +9 -0
  17. package/dist/node/scope/index.js +3 -0
  18. package/dist/node/scope/index.js.map +9 -0
  19. package/dist/ts/app/app-context.d.ts +9 -0
  20. package/dist/ts/app/app-context.d.ts.map +1 -0
  21. package/dist/ts/app/app-lifecycle.d.ts +6 -0
  22. package/dist/ts/app/app-lifecycle.d.ts.map +1 -0
  23. package/dist/ts/app/app.d.ts +24 -0
  24. package/dist/ts/app/app.d.ts.map +1 -0
  25. package/dist/{app → ts/app}/index.d.ts +1 -0
  26. package/dist/ts/app/index.d.ts.map +1 -0
  27. package/dist/ts/application/create-request-context.d.ts +4 -0
  28. package/dist/ts/application/create-request-context.d.ts.map +1 -0
  29. package/dist/ts/application/create-request-context.test.d.ts +2 -0
  30. package/dist/ts/application/create-request-context.test.d.ts.map +1 -0
  31. package/dist/ts/application/use-declaro.d.ts +3 -0
  32. package/dist/ts/application/use-declaro.d.ts.map +1 -0
  33. package/dist/{auth → ts/auth}/permission-validator.d.ts +1 -0
  34. package/dist/ts/auth/permission-validator.d.ts.map +1 -0
  35. package/dist/ts/auth/permission-validator.test.d.ts +2 -0
  36. package/dist/ts/auth/permission-validator.test.d.ts.map +1 -0
  37. package/dist/ts/context/async-context.d.ts +54 -0
  38. package/dist/ts/context/async-context.d.ts.map +1 -0
  39. package/dist/ts/context/async-context.test.d.ts +2 -0
  40. package/dist/ts/context/async-context.test.d.ts.map +1 -0
  41. package/dist/{context → ts/context}/context-consumer.d.ts +4 -0
  42. package/dist/ts/context/context-consumer.d.ts.map +1 -0
  43. package/dist/ts/context/context.circular-deps.test.d.ts +2 -0
  44. package/dist/ts/context/context.circular-deps.test.d.ts.map +1 -0
  45. package/dist/ts/context/context.d.ts +452 -0
  46. package/dist/ts/context/context.d.ts.map +1 -0
  47. package/dist/ts/context/context.test.d.ts +2 -0
  48. package/dist/ts/context/context.test.d.ts.map +1 -0
  49. package/dist/ts/context/legacy-context.test.d.ts +2 -0
  50. package/dist/ts/context/legacy-context.test.d.ts.map +1 -0
  51. package/dist/{context → ts/context}/validators.d.ts +2 -1
  52. package/dist/ts/context/validators.d.ts.map +1 -0
  53. package/dist/ts/dataflow/index.d.ts +2 -0
  54. package/dist/ts/dataflow/index.d.ts.map +1 -0
  55. package/dist/ts/dataflow/objects.d.ts +7 -0
  56. package/dist/ts/dataflow/objects.d.ts.map +1 -0
  57. package/dist/ts/dataflow/objects.test.d.ts +2 -0
  58. package/dist/ts/dataflow/objects.test.d.ts.map +1 -0
  59. package/dist/{errors → ts/errors}/errors.d.ts +16 -3
  60. package/dist/ts/errors/errors.d.ts.map +1 -0
  61. package/dist/ts/events/event-manager.d.ts +19 -0
  62. package/dist/ts/events/event-manager.d.ts.map +1 -0
  63. package/dist/ts/events/event-manager.spec.d.ts +2 -0
  64. package/dist/ts/events/event-manager.spec.d.ts.map +1 -0
  65. package/dist/ts/events/index.d.ts +2 -0
  66. package/dist/ts/events/index.d.ts.map +1 -0
  67. package/dist/ts/http/headers.d.ts +21 -0
  68. package/dist/ts/http/headers.d.ts.map +1 -0
  69. package/dist/ts/http/headers.spec.d.ts +2 -0
  70. package/dist/ts/http/headers.spec.d.ts.map +1 -0
  71. package/dist/ts/http/request-context.d.ts +17 -0
  72. package/dist/ts/http/request-context.d.ts.map +1 -0
  73. package/dist/ts/http/request-context.spec.d.ts +2 -0
  74. package/dist/ts/http/request-context.spec.d.ts.map +1 -0
  75. package/dist/ts/http/request.d.ts +31 -0
  76. package/dist/ts/http/request.d.ts.map +1 -0
  77. package/dist/ts/http/request.spec.d.ts +2 -0
  78. package/dist/ts/http/request.spec.d.ts.map +1 -0
  79. package/dist/{http → ts/http}/url.d.ts +5 -4
  80. package/dist/ts/http/url.d.ts.map +1 -0
  81. package/dist/ts/http/url.spec.d.ts +2 -0
  82. package/dist/ts/http/url.spec.d.ts.map +1 -0
  83. package/dist/ts/index.d.ts +47 -0
  84. package/dist/ts/index.d.ts.map +1 -0
  85. package/dist/{pipelines → ts/pipelines}/index.d.ts +1 -0
  86. package/dist/ts/pipelines/index.d.ts.map +1 -0
  87. package/dist/{pipelines → ts/pipelines}/pipeline-action.d.ts +1 -0
  88. package/dist/ts/pipelines/pipeline-action.d.ts.map +1 -0
  89. package/dist/ts/pipelines/pipeline-action.test.d.ts +2 -0
  90. package/dist/ts/pipelines/pipeline-action.test.d.ts.map +1 -0
  91. package/dist/{pipelines → ts/pipelines}/pipeline.d.ts +3 -2
  92. package/dist/ts/pipelines/pipeline.d.ts.map +1 -0
  93. package/dist/ts/pipelines/pipeline.test.d.ts +2 -0
  94. package/dist/ts/pipelines/pipeline.test.d.ts.map +1 -0
  95. package/dist/ts/schema/json-schema.d.ts +12 -0
  96. package/dist/ts/schema/json-schema.d.ts.map +1 -0
  97. package/dist/ts/schema/labels.d.ts +14 -0
  98. package/dist/ts/schema/labels.d.ts.map +1 -0
  99. package/dist/ts/schema/model-schema.d.ts +75 -0
  100. package/dist/ts/schema/model-schema.d.ts.map +1 -0
  101. package/dist/ts/schema/model-schema.test.d.ts +2 -0
  102. package/dist/ts/schema/model-schema.test.d.ts.map +1 -0
  103. package/dist/ts/schema/model.d.ts +35 -0
  104. package/dist/ts/schema/model.d.ts.map +1 -0
  105. package/dist/ts/schema/schema-mixin.d.ts +24 -0
  106. package/dist/ts/schema/schema-mixin.d.ts.map +1 -0
  107. package/dist/ts/schema/test/mock-model.d.ts +8 -0
  108. package/dist/ts/schema/test/mock-model.d.ts.map +1 -0
  109. package/dist/ts/scope/index.d.ts +34 -0
  110. package/dist/ts/scope/index.d.ts.map +1 -0
  111. package/dist/ts/shared/utils/action-descriptor.d.ts +28 -0
  112. package/dist/ts/shared/utils/action-descriptor.d.ts.map +1 -0
  113. package/dist/ts/shared/utils/action-descriptor.test.d.ts +2 -0
  114. package/dist/ts/shared/utils/action-descriptor.test.d.ts.map +1 -0
  115. package/dist/ts/shared/utils/schema-utils.d.ts +3 -0
  116. package/dist/ts/shared/utils/schema-utils.d.ts.map +1 -0
  117. package/dist/ts/shared/utils/schema-utils.test.d.ts +2 -0
  118. package/dist/ts/shared/utils/schema-utils.test.d.ts.map +1 -0
  119. package/dist/ts/shims/async-local-storage.d.ts +36 -0
  120. package/dist/ts/shims/async-local-storage.d.ts.map +1 -0
  121. package/dist/ts/shims/async-local-storage.test.d.ts +2 -0
  122. package/dist/ts/shims/async-local-storage.test.d.ts.map +1 -0
  123. package/dist/{timing.d.ts → ts/timing.d.ts} +1 -0
  124. package/dist/ts/timing.d.ts.map +1 -0
  125. package/dist/{typescript → ts/typescript}/arrays.d.ts +1 -0
  126. package/dist/ts/typescript/arrays.d.ts.map +1 -0
  127. package/dist/{typescript → ts/typescript}/baseModel.d.ts +1 -0
  128. package/dist/ts/typescript/baseModel.d.ts.map +1 -0
  129. package/dist/{typescript → ts/typescript}/classes.d.ts +1 -0
  130. package/dist/ts/typescript/classes.d.ts.map +1 -0
  131. package/dist/{typescript → ts/typescript}/constant-manipulation/snake-case.d.ts +1 -0
  132. package/dist/ts/typescript/constant-manipulation/snake-case.d.ts.map +1 -0
  133. package/dist/{typescript → ts/typescript}/errors.d.ts +1 -0
  134. package/dist/ts/typescript/errors.d.ts.map +1 -0
  135. package/dist/ts/typescript/fetch.d.ts +3 -0
  136. package/dist/ts/typescript/fetch.d.ts.map +1 -0
  137. package/dist/{typescript → ts/typescript}/generics.d.ts +1 -0
  138. package/dist/ts/typescript/generics.d.ts.map +1 -0
  139. package/dist/{typescript → ts/typescript}/index.d.ts +1 -0
  140. package/dist/ts/typescript/index.d.ts.map +1 -0
  141. package/dist/ts/typescript/objects.d.ts +26 -0
  142. package/dist/ts/typescript/objects.d.ts.map +1 -0
  143. package/dist/{typescript → ts/typescript}/promises.d.ts +1 -0
  144. package/dist/ts/typescript/promises.d.ts.map +1 -0
  145. package/dist/{validation → ts/validation}/index.d.ts +1 -0
  146. package/dist/ts/validation/index.d.ts.map +1 -0
  147. package/dist/{validation → ts/validation}/validation.d.ts +1 -0
  148. package/dist/ts/validation/validation.d.ts.map +1 -0
  149. package/dist/{validation → ts/validation}/validator.d.ts +1 -0
  150. package/dist/ts/validation/validator.d.ts.map +1 -0
  151. package/dist/ts/validation/validator.test.d.ts +2 -0
  152. package/dist/ts/validation/validator.test.d.ts.map +1 -0
  153. package/package.json +46 -13
  154. package/src/app/app-context.ts +4 -5
  155. package/src/app/app-lifecycle.ts +4 -3
  156. package/src/app/app.ts +7 -5
  157. package/src/application/create-request-context.test.ts +345 -0
  158. package/src/application/create-request-context.ts +19 -0
  159. package/src/application/use-declaro.ts +27 -0
  160. package/src/auth/permission-validator.test.ts +238 -2
  161. package/src/auth/permission-validator.ts +3 -3
  162. package/src/context/async-context.test.ts +348 -0
  163. package/src/context/async-context.ts +129 -0
  164. package/src/context/context-consumer.ts +4 -4
  165. package/src/context/context.circular-deps.test.ts +1047 -0
  166. package/src/context/context.test.ts +420 -3
  167. package/src/context/context.ts +590 -87
  168. package/src/context/legacy-context.test.ts +9 -9
  169. package/src/dataflow/objects.test.ts +7 -7
  170. package/src/dataflow/objects.ts +10 -9
  171. package/src/errors/errors.ts +19 -3
  172. package/src/events/event-manager.spec.ts +129 -0
  173. package/src/events/event-manager.ts +25 -14
  174. package/src/http/headers.ts +17 -2
  175. package/src/http/request-context.ts +24 -15
  176. package/src/http/request.ts +27 -6
  177. package/src/http/url.ts +3 -3
  178. package/src/index.ts +34 -3
  179. package/src/pipelines/pipeline.test.ts +11 -9
  180. package/src/schema/json-schema.ts +16 -0
  181. package/src/schema/labels.ts +23 -23
  182. package/src/schema/model-schema.test.ts +282 -0
  183. package/src/schema/model-schema.ts +197 -0
  184. package/src/schema/model.ts +143 -0
  185. package/src/schema/schema-mixin.ts +51 -0
  186. package/src/schema/test/mock-model.ts +19 -0
  187. package/src/scope/index.ts +33 -0
  188. package/src/shared/utils/action-descriptor.test.ts +182 -0
  189. package/src/shared/utils/action-descriptor.ts +102 -0
  190. package/src/shared/utils/schema-utils.test.ts +33 -0
  191. package/src/shared/utils/schema-utils.ts +17 -0
  192. package/src/shims/async-local-storage.test.ts +258 -0
  193. package/src/shims/async-local-storage.ts +82 -0
  194. package/src/typescript/objects.ts +32 -1
  195. package/src/validation/validator.test.ts +12 -20
  196. package/dist/app/app-context.d.ts +0 -8
  197. package/dist/app/app-lifecycle.d.ts +0 -4
  198. package/dist/app/app.d.ts +0 -22
  199. package/dist/auth/permission-validator.test.d.ts +0 -1
  200. package/dist/context/context.d.ts +0 -161
  201. package/dist/context/context.test.d.ts +0 -1
  202. package/dist/context/legacy-context.test.d.ts +0 -1
  203. package/dist/dataflow/index.d.ts +0 -1
  204. package/dist/dataflow/objects.d.ts +0 -5
  205. package/dist/dataflow/objects.test.d.ts +0 -1
  206. package/dist/events/event-manager.d.ts +0 -16
  207. package/dist/events/event-manager.spec.d.ts +0 -1
  208. package/dist/events/index.d.ts +0 -1
  209. package/dist/helpers/index.d.ts +0 -1
  210. package/dist/helpers/ucfirst.d.ts +0 -1
  211. package/dist/http/headers.d.ts +0 -4
  212. package/dist/http/headers.spec.d.ts +0 -1
  213. package/dist/http/request-context.d.ts +0 -12
  214. package/dist/http/request-context.spec.d.ts +0 -1
  215. package/dist/http/request.d.ts +0 -8
  216. package/dist/http/request.spec.d.ts +0 -1
  217. package/dist/http/url.spec.d.ts +0 -1
  218. package/dist/index.d.ts +0 -19
  219. package/dist/pipelines/pipeline-action.test.d.ts +0 -1
  220. package/dist/pipelines/pipeline.test.d.ts +0 -1
  221. package/dist/pkg.cjs +0 -30
  222. package/dist/pkg.mjs +0 -56612
  223. package/dist/schema/application.d.ts +0 -83
  224. package/dist/schema/application.test.d.ts +0 -1
  225. package/dist/schema/define-model.d.ts +0 -10
  226. package/dist/schema/define-model.test.d.ts +0 -1
  227. package/dist/schema/formats.d.ts +0 -10
  228. package/dist/schema/index.d.ts +0 -10
  229. package/dist/schema/labels.d.ts +0 -13
  230. package/dist/schema/labels.test.d.ts +0 -1
  231. package/dist/schema/module.d.ts +0 -7
  232. package/dist/schema/module.test.d.ts +0 -1
  233. package/dist/schema/properties.d.ts +0 -19
  234. package/dist/schema/response.d.ts +0 -31
  235. package/dist/schema/response.test.d.ts +0 -1
  236. package/dist/schema/supported-types.d.ts +0 -12
  237. package/dist/schema/supported-types.test.d.ts +0 -1
  238. package/dist/schema/transform-model.d.ts +0 -4
  239. package/dist/schema/transform-model.test.d.ts +0 -1
  240. package/dist/schema/types.d.ts +0 -95
  241. package/dist/schema/types.test.d.ts +0 -1
  242. package/dist/typescript/fetch.d.ts +0 -2
  243. package/dist/typescript/objects.d.ts +0 -12
  244. package/dist/validation/validator.test.d.ts +0 -1
  245. package/src/helpers/index.ts +0 -1
  246. package/src/helpers/ucfirst.ts +0 -3
  247. package/src/schema/application.test.ts +0 -286
  248. package/src/schema/application.ts +0 -150
  249. package/src/schema/define-model.test.ts +0 -81
  250. package/src/schema/define-model.ts +0 -50
  251. package/src/schema/formats.ts +0 -23
  252. package/src/schema/index.ts +0 -10
  253. package/src/schema/labels.test.ts +0 -60
  254. package/src/schema/module.test.ts +0 -39
  255. package/src/schema/module.ts +0 -6
  256. package/src/schema/properties.ts +0 -40
  257. package/src/schema/response.test.ts +0 -101
  258. package/src/schema/response.ts +0 -93
  259. package/src/schema/supported-types.test.ts +0 -20
  260. package/src/schema/supported-types.ts +0 -15
  261. package/src/schema/transform-model.test.ts +0 -31
  262. package/src/schema/transform-model.ts +0 -24
  263. package/src/schema/types.test.ts +0 -28
  264. package/src/schema/types.ts +0 -163
  265. package/tsconfig.json +0 -11
  266. package/vite.config.ts +0 -24
@@ -1,5 +1,6 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { Context } from './context'
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { Context, type DeclaroScope, type ExtractScope } from './context'
3
+ import type { IEvent } from '../events'
3
4
 
4
5
  describe('Context', () => {
5
6
  it('Should allow value dependency injection', async () => {
@@ -238,7 +239,7 @@ describe('Context', () => {
238
239
 
239
240
  expect(factoryInstances).toBe(0)
240
241
 
241
- await context.emit('declaro:init')
242
+ await context.initializeEagerDependencies()
242
243
 
243
244
  expect(factoryInstances).toBe(1)
244
245
 
@@ -632,4 +633,420 @@ describe('Context', () => {
632
633
  'four calling birds, three French hens, two turtle doves, and a partridge in a pear tree',
633
634
  )
634
635
  })
636
+
637
+ it('should override an async factory with a new value', async () => {
638
+ type ScopeA = {
639
+ foo: Promise<string>
640
+ bar: Promise<number>
641
+ }
642
+
643
+ const contextA = new Context<ScopeA>()
644
+
645
+ contextA.registerAsyncFactory('foo', async () => 'Hello', [], {
646
+ singleton: true,
647
+ })
648
+ contextA.registerAsyncFactory('bar', async () => 42, [], {
649
+ singleton: true,
650
+ })
651
+
652
+ const contextB = new Context<ScopeA>()
653
+
654
+ contextB.extend(contextA)
655
+ contextB.registerAsyncFactory('foo', async () => 'Goodbye', [], {
656
+ singleton: true,
657
+ })
658
+ contextB.registerAsyncFactory('bar', async () => 100, [], {
659
+ singleton: true,
660
+ })
661
+
662
+ const foo = await contextB.resolve('foo')
663
+ const bar = await contextB.resolve('bar')
664
+
665
+ expect(foo).toBe('Goodbye')
666
+ expect(bar).toBe(100)
667
+ })
668
+
669
+ it('should inherit emitter listeners, passing in the new context, when extending', async () => {
670
+ const contextACallback = vi.fn()
671
+ const contextBCallback = vi.fn()
672
+
673
+ type ScopeA = {
674
+ foo: string
675
+ bar: number
676
+ }
677
+
678
+ type ScopeB = ScopeA & {
679
+ baz: string
680
+ }
681
+
682
+ type TestEvent = IEvent & {
683
+ message: string
684
+ }
685
+
686
+ const contextA = new Context<ScopeA>()
687
+ contextA.registerValue('foo', 'Hello')
688
+ contextA.registerValue('bar', 42)
689
+ contextA.on<TestEvent>('test', contextACallback)
690
+
691
+ const contextB = new Context<ScopeB>()
692
+ contextB.extend(contextA)
693
+ contextB.registerValue('baz', 'World')
694
+ contextB.on<TestEvent>('test', contextBCallback)
695
+
696
+ const eventA: TestEvent = {
697
+ type: 'test',
698
+ message: 'Hello World',
699
+ }
700
+
701
+ await contextB.emit('test')
702
+
703
+ expect(contextACallback).toHaveBeenCalledTimes(1)
704
+ expect(contextBCallback).toHaveBeenCalledTimes(1)
705
+ })
706
+
707
+ it('should cancel eager initialization when a depedency is overridden', async () => {
708
+ type Scope = {
709
+ foo: string
710
+ bar: Promise<number>
711
+ }
712
+
713
+ const context = new Context<Scope>()
714
+
715
+ const context1Factory = vi.fn(() => 'Hello')
716
+ const context2Factory = vi.fn(() => 'Goodbye')
717
+ const context1AsyncFactory = vi.fn(async () => 42)
718
+ const context2AsyncFactory = vi.fn(async () => 100)
719
+
720
+ context.registerFactory('foo', context1Factory, [], {
721
+ eager: true,
722
+ })
723
+ context.registerAsyncFactory('bar', context1AsyncFactory, [], {
724
+ eager: true,
725
+ })
726
+
727
+ const context2 = new Context<Scope>()
728
+ context2.extend(context)
729
+ context2.registerFactory('foo', context2Factory, [], {
730
+ eager: true,
731
+ })
732
+ context2.registerAsyncFactory('bar', context2AsyncFactory, [], {
733
+ eager: true,
734
+ })
735
+
736
+ context.registerValue('foo', 'Goodbye')
737
+
738
+ await context2.initializeEagerDependencies()
739
+
740
+ expect(context1Factory).toHaveBeenCalledTimes(0)
741
+ expect(context2Factory).toHaveBeenCalledTimes(1)
742
+ expect(context1AsyncFactory).toHaveBeenCalledTimes(0)
743
+ expect(context2AsyncFactory).toHaveBeenCalledTimes(1)
744
+ })
745
+
746
+ it('should not override singletons when extending, without an explicit override', async () => {
747
+ type Scope = {
748
+ foo: {
749
+ message: string
750
+ _id?: number
751
+ }
752
+ bar: {
753
+ message: string
754
+ _id?: number
755
+ }
756
+ }
757
+
758
+ const context1 = new Context<Scope>()
759
+ context1.registerFactory('foo', () => ({ message: 'Hello' }), [], {
760
+ singleton: true,
761
+ })
762
+
763
+ const foo1 = context1.resolve('foo')
764
+ foo1._id = 1
765
+
766
+ const context2 = new Context<Scope>()
767
+ context2.extend(context1)
768
+
769
+ const context3 = new Context<Scope>()
770
+ context3.extend(context1)
771
+
772
+ const foo2 = context2.resolve('foo')
773
+
774
+ const foo3 = context3.resolve('foo')
775
+
776
+ expect(foo2).toBe(foo3)
777
+ expect(foo2._id).toBe(1)
778
+ })
779
+
780
+ it('should dispatch event objects and event strings', async () => {
781
+ interface CustomEvent extends IEvent {
782
+ type: 'test'
783
+ message: string
784
+ }
785
+ const event: CustomEvent = {
786
+ type: 'test',
787
+ message: 'Hello World',
788
+ }
789
+
790
+ const contextACallback = vi.fn()
791
+
792
+ const contextA = new Context<DeclaroScope>()
793
+
794
+ contextA.on<CustomEvent>('test', contextACallback)
795
+
796
+ await contextA.emit(event)
797
+
798
+ expect(contextACallback).toHaveBeenCalledTimes(1)
799
+ expect(contextACallback.mock.calls[0][1]).toEqual(event)
800
+ expect(contextACallback.mock.calls[0][0]).toBe(contextA)
801
+ })
802
+
803
+ it('should be able to emit events directly through the emitter', async () => {
804
+ interface CustomEvent extends IEvent {
805
+ type: 'test'
806
+ message: string
807
+ }
808
+ const event: CustomEvent = {
809
+ type: 'test',
810
+ message: 'Hello World',
811
+ }
812
+
813
+ const contextACallback = vi.fn()
814
+
815
+ const contextA = new Context<DeclaroScope>()
816
+
817
+ contextA.events.on('test', contextACallback)
818
+
819
+ await contextA.events.emitAsync(event)
820
+
821
+ expect(contextACallback).toHaveBeenCalledTimes(1)
822
+ expect(contextACallback.mock.calls[0][0]).toEqual(event)
823
+ })
824
+
825
+ it('should not squash errors in event handlers', async () => {
826
+ const context = new Context<DeclaroScope>()
827
+ const callback = vi.fn()
828
+
829
+ context.on('test', () => {
830
+ callback()
831
+ throw new Error('Test error')
832
+ })
833
+
834
+ expect(callback).not.toHaveBeenCalled()
835
+ await expect(context.emit('test')).rejects.toThrow('Test error')
836
+ })
837
+
838
+ it('should be able to define an extracted scope type', () => {
839
+ type Scope = {
840
+ foo: string
841
+ bar: number
842
+ }
843
+
844
+ const context = new Context<Scope>()
845
+
846
+ type ExtractedScope = ExtractScope<typeof context>
847
+
848
+ const extractedScope: ExtractedScope = {
849
+ foo: 'Hello',
850
+ bar: 42,
851
+ }
852
+
853
+ const _: Context<Scope> = new Context<ExtractedScope>()
854
+
855
+ expect(extractedScope.foo).toBe('Hello')
856
+ expect(extractedScope.bar).toBe(42)
857
+ })
858
+
859
+ it('should be able to use middleware that requires a subset of the context scope', () => {
860
+ type Scope = {
861
+ foo: string
862
+ bar: number
863
+ }
864
+
865
+ type ModuleScope = {
866
+ bar: number
867
+ }
868
+
869
+ const middleware = (context: Context<ModuleScope>) => {
870
+ // This middleware only needs 'bar', not 'foo'
871
+ expect(context.resolve('bar')).toBe(42)
872
+ }
873
+
874
+ const context = new Context<Scope>()
875
+ context.registerValue('foo', 'test')
876
+ context.registerValue('bar', 42)
877
+
878
+ // This should work - the context has more than what the middleware needs
879
+ return context.use(middleware)
880
+ })
881
+
882
+ it('should be able to apply a full context to a partial', () => {
883
+ type Scope = {
884
+ foo: string
885
+ bar: number
886
+ }
887
+
888
+ type ModuleScope = {
889
+ bar: number
890
+ }
891
+
892
+ const context = new Context<Scope>()
893
+
894
+ // Using the type-safe narrow method - this provides the proper covariance
895
+ const partialContext: Context<ModuleScope> = context.narrow<ModuleScope>()
896
+
897
+ expect(context).toBeInstanceOf(Context)
898
+ expect(partialContext).toBeInstanceOf(Context)
899
+ // Both references point to the same instance
900
+ expect(partialContext).toBe(context)
901
+ })
902
+
903
+ it('should cache singleton dependencies of dependencies correctly', async () => {
904
+ interface ServiceA {
905
+ id: string
906
+ message: string
907
+ }
908
+
909
+ interface ServiceB {
910
+ id: string
911
+ serviceA: ServiceA
912
+ }
913
+
914
+ interface ServiceC {
915
+ id: string
916
+ serviceA: ServiceA
917
+ serviceB: ServiceB
918
+ }
919
+
920
+ interface ServiceD {
921
+ id: string
922
+ serviceA: ServiceA
923
+ serviceB: ServiceB
924
+ serviceC: ServiceC
925
+ }
926
+
927
+ type Scope = {
928
+ serviceA: Promise<ServiceA>
929
+ serviceB: Promise<ServiceB>
930
+ serviceC: Promise<ServiceC>
931
+ serviceD: Promise<ServiceD>
932
+ }
933
+
934
+ const context = new Context<Scope>()
935
+
936
+ let serviceAInstances = 0
937
+ let serviceBInstances = 0
938
+ let serviceCInstances = 0
939
+ let serviceDInstances = 0
940
+
941
+ // ServiceA - async factory - singleton
942
+ context.registerAsyncFactory(
943
+ 'serviceA',
944
+ async (): Promise<ServiceA> => {
945
+ serviceAInstances++
946
+ return {
947
+ id: `serviceA-${serviceAInstances}`,
948
+ message: 'I am Service A',
949
+ }
950
+ },
951
+ [],
952
+ { singleton: true },
953
+ )
954
+
955
+ // ServiceB - async factory depends on ServiceA - singleton
956
+ context.registerAsyncFactory(
957
+ 'serviceB',
958
+ async (serviceA: ServiceA): Promise<ServiceB> => {
959
+ serviceBInstances++
960
+ return {
961
+ id: `serviceB-${serviceBInstances}`,
962
+ serviceA,
963
+ }
964
+ },
965
+ ['serviceA'],
966
+ { singleton: true },
967
+ )
968
+
969
+ // ServiceC - async factory depends on ServiceB and ServiceA - singleton
970
+ context.registerAsyncFactory(
971
+ 'serviceC',
972
+ async (serviceA: ServiceA, serviceB: ServiceB): Promise<ServiceC> => {
973
+ serviceCInstances++
974
+ return {
975
+ id: `serviceC-${serviceCInstances}`,
976
+ serviceA,
977
+ serviceB,
978
+ }
979
+ },
980
+ ['serviceA', 'serviceB'],
981
+ { singleton: true },
982
+ )
983
+
984
+ // ServiceD - async factory depends on ServiceA, ServiceB, and ServiceC - NOT a singleton
985
+ context.registerAsyncFactory(
986
+ 'serviceD',
987
+ async (serviceA: ServiceA, serviceB: ServiceB, serviceC: ServiceC): Promise<ServiceD> => {
988
+ serviceDInstances++
989
+ return {
990
+ id: `serviceD-${serviceDInstances}`,
991
+ serviceA,
992
+ serviceB,
993
+ serviceC,
994
+ }
995
+ },
996
+ ['serviceA', 'serviceB', 'serviceC'],
997
+ )
998
+
999
+ // First resolution - all should be created
1000
+ const serviceD1 = await context.resolve('serviceD')
1001
+
1002
+ expect(serviceAInstances).toBe(1) // ServiceA should only be created once
1003
+ expect(serviceBInstances).toBe(1) // ServiceB should only be created once
1004
+ expect(serviceCInstances).toBe(1) // ServiceC should only be created once
1005
+ expect(serviceDInstances).toBe(1) // ServiceD should be created
1006
+
1007
+ // Second resolution - only ServiceD should be recreated
1008
+ const serviceD2 = await context.resolve('serviceD')
1009
+
1010
+ expect(serviceAInstances).toBe(1) // ServiceA should still be only created once
1011
+ expect(serviceBInstances).toBe(1) // ServiceB should still be only created once
1012
+ expect(serviceCInstances).toBe(1) // ServiceC should still be only created once
1013
+ expect(serviceDInstances).toBe(2) // ServiceD should be created again
1014
+
1015
+ // Third resolution - again only ServiceD should be recreated
1016
+ const serviceD3 = await context.resolve('serviceD')
1017
+
1018
+ expect(serviceAInstances).toBe(1) // ServiceA should still be only created once
1019
+ expect(serviceBInstances).toBe(1) // ServiceB should still be only created once
1020
+ expect(serviceCInstances).toBe(1) // ServiceC should still be only created once
1021
+ expect(serviceDInstances).toBe(3) // ServiceD should be created again
1022
+
1023
+ // Verify that singleton dependencies are the same instances (use toEqual since we have proxy objects)
1024
+ expect(serviceD1.serviceA).toEqual(serviceD2.serviceA)
1025
+ expect(serviceD1.serviceA).toEqual(serviceD3.serviceA)
1026
+ expect(serviceD1.serviceB).toEqual(serviceD2.serviceB)
1027
+ expect(serviceD1.serviceB).toEqual(serviceD3.serviceB)
1028
+ expect(serviceD1.serviceC).toEqual(serviceD2.serviceC)
1029
+ expect(serviceD1.serviceC).toEqual(serviceD3.serviceC)
1030
+
1031
+ // Verify nested dependencies are also the same
1032
+ expect(serviceD1.serviceB.serviceA).toEqual(serviceD2.serviceB.serviceA)
1033
+ expect(serviceD1.serviceC.serviceA).toEqual(serviceD2.serviceC.serviceA)
1034
+ expect(serviceD1.serviceC.serviceB).toEqual(serviceD2.serviceC.serviceB)
1035
+
1036
+ // Verify the IDs to confirm singletons were reused
1037
+ expect(serviceD1.serviceA.id).toBe('serviceA-1')
1038
+ expect(serviceD1.serviceB.id).toBe('serviceB-1')
1039
+ expect(serviceD1.serviceC.id).toBe('serviceC-1')
1040
+ expect(serviceD1.id).toBe('serviceD-1')
1041
+
1042
+ expect(serviceD2.serviceA.id).toBe('serviceA-1') // Same instance
1043
+ expect(serviceD2.serviceB.id).toBe('serviceB-1') // Same instance
1044
+ expect(serviceD2.serviceC.id).toBe('serviceC-1') // Same instance
1045
+ expect(serviceD2.id).toBe('serviceD-2') // New instance
1046
+
1047
+ expect(serviceD3.serviceA.id).toBe('serviceA-1') // Same instance
1048
+ expect(serviceD3.serviceB.id).toBe('serviceB-1') // Same instance
1049
+ expect(serviceD3.serviceC.id).toBe('serviceC-1') // Same instance
1050
+ expect(serviceD3.id).toBe('serviceD-3') // New instance
1051
+ })
635
1052
  })