@declaro/core 2.0.0-y.0 → 2.1.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.
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,10 +1,10 @@
1
1
  import { Context } from './context'
2
2
  import { sleep } from '../timing'
3
3
  import { ContextConsumer } from './context-consumer'
4
- import { describe, it, vi } from 'vitest'
4
+ import { describe, it, vi, expect } from 'vitest'
5
5
 
6
6
  describe('Context', () => {
7
- it('Should allow simple value dependency injection', async ({ expect }) => {
7
+ it('Should allow simple value dependency injection', async () => {
8
8
  const context = new Context()
9
9
 
10
10
  context.provide('test', 'Hello World')
@@ -20,7 +20,7 @@ describe('Context', () => {
20
20
  expect(number).toBe(42)
21
21
  })
22
22
 
23
- it('Should allow subscription to arbitrary events', async ({ expect }) => {
23
+ it('Should allow subscription to arbitrary events', async () => {
24
24
  const customEventCall = vi.fn()
25
25
 
26
26
  const context = new Context()
@@ -38,7 +38,7 @@ describe('Context', () => {
38
38
  expect(customEventCall.mock.calls.length).toBe(1)
39
39
  })
40
40
 
41
- it('Should extend other contexts', async ({ expect }) => {
41
+ it('Should extend other contexts', async () => {
42
42
  const context1 = new Context()
43
43
  const context2 = new Context()
44
44
  const context3 = new Context()
@@ -65,7 +65,7 @@ describe('Context', () => {
65
65
  expect(symbolValue).toBe(42)
66
66
  })
67
67
 
68
- it('Should nest contexts', async ({ expect }) => {
68
+ it('Should nest contexts', async () => {
69
69
  const context1 = new Context()
70
70
  const context2 = new Context()
71
71
 
@@ -81,7 +81,7 @@ describe('Context', () => {
81
81
  expect(a2).toBe(2)
82
82
  })
83
83
 
84
- it('Should allow async middleware', async ({ expect }) => {
84
+ it('Should allow async middleware', async () => {
85
85
  const context = new Context()
86
86
 
87
87
  await context.use(async (context) => {
@@ -96,7 +96,7 @@ describe('Context', () => {
96
96
  expect(test2).toBe('Hello Test')
97
97
  })
98
98
 
99
- it('Should allow singletons', ({ expect }) => {
99
+ it('Should allow singletons', () => {
100
100
  const context = new Context()
101
101
 
102
102
  const KEY = Symbol()
@@ -107,7 +107,7 @@ describe('Context', () => {
107
107
  expect(instance).toBe(1)
108
108
  })
109
109
 
110
- it('Should allow hydration', ({ expect }) => {
110
+ it('Should allow hydration', () => {
111
111
  const context = new Context()
112
112
 
113
113
  class Test extends ContextConsumer {
@@ -123,7 +123,7 @@ describe('Context', () => {
123
123
  expect(context.hydrate(Test).test()).toBe('Yes')
124
124
  })
125
125
 
126
- it('Should allow hydration with args', ({ expect }) => {
126
+ it('Should allow hydration with args', () => {
127
127
  const context = new Context()
128
128
 
129
129
  class Test extends ContextConsumer {
@@ -2,7 +2,7 @@ import { merge } from './objects'
2
2
  import { describe, expect, it } from 'vitest'
3
3
 
4
4
  describe('Object manipulation tests', () => {
5
- it('Should merge objects', ({ expect }) => {
5
+ it('Should merge objects', () => {
6
6
  const CUSTOM_KEY = Symbol()
7
7
 
8
8
  const objectA = {
@@ -56,7 +56,7 @@ describe('Object manipulation tests', () => {
56
56
  expect(output.characters[3]).toBe('Chewie')
57
57
  })
58
58
 
59
- it('Should add non-array items to matching arrays', ({ expect }) => {
59
+ it('Should add non-array items to matching arrays', () => {
60
60
  const a = {
61
61
  val: 4,
62
62
  arr: [1, 2],
@@ -78,7 +78,7 @@ describe('Object manipulation tests', () => {
78
78
  expect(c.arr[2]).toBe(3)
79
79
  })
80
80
 
81
- it('Should handle nulls gracefully', ({ expect }) => {
81
+ it('Should handle nulls gracefully', () => {
82
82
  const a = {
83
83
  val: null,
84
84
  obj: undefined,
@@ -94,11 +94,11 @@ describe('Object manipulation tests', () => {
94
94
 
95
95
  const c = merge(a, b)
96
96
 
97
- expect(c.val[0]).toBe(5)
98
- expect(c.val[1]).toBe(6)
97
+ expect(c.val?.[0]).toBe(5)
98
+ expect(c.val?.[1]).toBe(6)
99
99
 
100
- expect(c.obj.a).toBe(1)
101
- expect(c.obj.b).toBe(2)
100
+ expect(c.obj?.a).toBe(1)
101
+ expect(c.obj?.b).toBe(2)
102
102
  })
103
103
 
104
104
  // it('Should handle circular structures', () => {
@@ -1,22 +1,26 @@
1
- export function merge<T, Source1>(target: T, source1: Source1): T & Source1
1
+ // K extends keyof B ? B[K] : K extends keyof A ? A[K] : never
2
+
3
+ import type { Merge } from '../typescript'
4
+
5
+ export function merge<T, Source1>(target: T, source1: Source1): Merge<T, Source1>
2
6
  export function merge<T, Source1, Source2>(
3
7
  target: T,
4
8
  source1: Source1,
5
9
  source2: Source2,
6
- ): T & Source1 & Source2
10
+ ): Merge<Merge<T, Source1>, Source2>
7
11
  export function merge<T, Source1, Source2, Source3>(
8
12
  target: T,
9
13
  source1: Source1,
10
14
  source2: Source2,
11
15
  source3: Source3,
12
- ): T & Source1 & Source2 & Source3
16
+ ): Merge<Merge<Merge<T, Source1>, Source2>, Source3>
13
17
  export function merge<T, Source1, Source2, Source3, Source4>(
14
18
  target: T,
15
19
  source1: Source1,
16
20
  source2: Source2,
17
21
  source3: Source3,
18
22
  source4: Source4,
19
- ): T & Source1 & Source2 & Source3 & Source4
23
+ ): Merge<Merge<Merge<Merge<T, Source1>, Source2>, Source3>, Source4>
20
24
  export function merge<T, Source1, Source2, Source3, Source4, Source5>(
21
25
  target: T,
22
26
  source1: Source1,
@@ -24,7 +28,7 @@ export function merge<T, Source1, Source2, Source3, Source4, Source5>(
24
28
  source3: Source3,
25
29
  source4: Source4,
26
30
  source5: Source5,
27
- ): T & Source1 & Source2 & Source3 & Source4 & Source5
31
+ ): Merge<Merge<Merge<Merge<Merge<T, Source1>, Source2>, Source3>, Source4>, Source5>
28
32
  export function merge(target: any, ...sources: any[]): any[] {
29
33
  const recursions = []
30
34
  return sources.reduce((workingCopy, source) => {
@@ -38,10 +42,7 @@ export function merge(target: any, ...sources: any[]): any[] {
38
42
  value = [existingValue, ...value].filter((item) => !!item)
39
43
  } else if (Array.isArray(existingValue) && !Array.isArray(value)) {
40
44
  value = [...(existingValue ?? []), value]
41
- } else if (
42
- typeof value === 'object' &&
43
- typeof existingValue === 'object'
44
- ) {
45
+ } else if (typeof value === 'object' && typeof existingValue === 'object') {
45
46
  value = {
46
47
  ...existingValue,
47
48
  ...value,
@@ -1,11 +1,13 @@
1
+ import type { StandardSchemaV1 } from '@standard-schema/spec'
2
+
1
3
  export interface IError {
2
4
  code: number
3
5
  message: string
4
- meta: any
6
+ meta?: any
5
7
  }
6
8
 
7
9
  export abstract class BaseError extends Error implements IError {
8
- public readonly meta: any
10
+ public readonly meta?: any // Made optional
9
11
  public readonly code: number = 500
10
12
 
11
13
  constructor(message: string, meta?: any) {
@@ -36,12 +38,26 @@ export class SystemError extends BaseError {
36
38
  }
37
39
  }
38
40
 
41
+ export interface ValidationErrorMeta {
42
+ result: StandardSchemaV1.Result<any>
43
+ [key: string]: any
44
+ }
45
+
39
46
  export class ValidationError extends BaseError {
40
47
  public readonly code: number = 400
48
+ public readonly meta?: ValidationErrorMeta
41
49
 
42
- constructor(message: string, meta?: any) {
50
+ constructor(message: string, meta?: ValidationErrorMeta) {
43
51
  super(message, meta)
44
52
  this.name = 'ValidationError'
53
+ this.meta = meta
54
+ }
55
+
56
+ public toJSON() {
57
+ return {
58
+ ...super.toJSON(),
59
+ result: this.meta?.result,
60
+ }
45
61
  }
46
62
  }
47
63
 
@@ -14,6 +14,10 @@ describe('Event manager', () => {
14
14
  expect(event).toBe(testEvent)
15
15
  })
16
16
 
17
+ eventManager.on<TestEvent>('testx', (event) => {
18
+ const t = event.type
19
+ })
20
+
17
21
  eventManager.on('test', mockListener)
18
22
 
19
23
  await eventManager.emitAll(testEvent)
@@ -95,4 +99,129 @@ describe('Event manager', () => {
95
99
  expect(listener1.mock.calls.length).toBe(2)
96
100
  expect(listener2.mock.calls.length).toBe(2)
97
101
  })
102
+
103
+ it('should be able to forward events to another event manager', async () => {
104
+ const cb1 = vi.fn()
105
+ const cb2 = vi.fn()
106
+
107
+ const eventManager1 = new EventManager()
108
+ eventManager1.on('test', cb1)
109
+
110
+ const eventManager2 = new EventManager()
111
+ eventManager2.on('test', cb2)
112
+
113
+ eventManager2.forwardTo(eventManager1)
114
+
115
+ const testEvent = new TestEvent('test')
116
+ await eventManager2.emitAll(testEvent)
117
+
118
+ expect(cb1.mock.calls.length).toBe(1)
119
+ expect(cb2.mock.calls.length).toBe(1)
120
+ expect(cb1.mock.calls[0][0]).toBe(testEvent)
121
+ expect(cb2.mock.calls[0][0]).toBe(testEvent)
122
+ expect(eventManager1.getListeners('test').length).toBe(1)
123
+ expect(eventManager2.getListeners('test').length).toBe(2)
124
+ })
125
+
126
+ it('should await forwarded listeners', async () => {
127
+ const cb1 = vi.fn(async () => {
128
+ await new Promise((resolve) => setTimeout(resolve, 10))
129
+ })
130
+ const cb2 = vi.fn(async () => {
131
+ await new Promise((resolve) => setTimeout(resolve, 10))
132
+ })
133
+
134
+ const eventManager1 = new EventManager()
135
+ eventManager1.on('test', cb1)
136
+
137
+ const eventManager2 = new EventManager()
138
+ eventManager2.on('test', cb2)
139
+
140
+ eventManager2.forwardTo(eventManager1)
141
+
142
+ const testEvent = new TestEvent('test')
143
+ await eventManager2.emitAll(testEvent)
144
+
145
+ expect(cb1.mock.calls.length).toBe(1)
146
+ expect(cb2.mock.calls.length).toBe(1)
147
+
148
+ // Ensure all callbacks have finished by checking the resolved state of the promises
149
+ await expect(cb1.mock.results[0].value).resolves.toBeUndefined()
150
+ await expect(cb2.mock.results[0].value).resolves.toBeUndefined()
151
+
152
+ await eventManager2.emitAsync(testEvent)
153
+
154
+ expect(cb1.mock.calls.length).toBe(2)
155
+ expect(cb2.mock.calls.length).toBe(2)
156
+
157
+ // Ensure all callbacks have finished by checking the resolved state of the promises
158
+ await expect(cb1.mock.results[1].value).resolves.toBeUndefined()
159
+ await expect(cb2.mock.results[1].value).resolves.toBeUndefined()
160
+ })
161
+
162
+ it('should extend another event manager', async () => {
163
+ const eventManager1 = new EventManager()
164
+ const eventManager2 = new EventManager()
165
+
166
+ const cb1 = vi.fn()
167
+ const cb2 = vi.fn()
168
+
169
+ eventManager1.on('test', cb1)
170
+ eventManager2.on('test', cb2)
171
+
172
+ eventManager2.extend(eventManager1)
173
+
174
+ const testEvent = new TestEvent('test')
175
+
176
+ await eventManager2.emitAll(testEvent)
177
+ expect(cb1.mock.calls.length).toBe(1)
178
+ expect(cb2.mock.calls.length).toBe(1)
179
+ expect(cb1.mock.calls[0][0]).toBe(testEvent)
180
+ expect(cb2.mock.calls[0][0]).toBe(testEvent)
181
+ })
182
+
183
+ it('should correctly type the event listeners', async () => {
184
+ const eventManager = new EventManager()
185
+
186
+ interface CustomEvent extends IEvent {
187
+ type: 'custom'
188
+ data: string
189
+ }
190
+
191
+ const customListener = vi.fn((event: CustomEvent) => {
192
+ expect(event.type).toBe('custom')
193
+ expect(event.data).toBe('test data')
194
+ })
195
+
196
+ eventManager.on<CustomEvent>('custom', customListener)
197
+
198
+ const testEvent: CustomEvent = { type: 'custom', data: 'test data' }
199
+ await eventManager.emitAll(testEvent)
200
+
201
+ expect(customListener.mock.calls.length).toBe(1)
202
+ })
203
+
204
+ it('should bubble up errors from listeners', async () => {
205
+ const eventManager = new EventManager()
206
+ const testEvent = new TestEvent('test')
207
+ const errorMessage = 'Test error from listener'
208
+
209
+ // Test that emitAll bubbles up errors
210
+ const throwingListener = vi.fn(() => {
211
+ throw new Error(errorMessage)
212
+ })
213
+
214
+ eventManager.on('test', throwingListener)
215
+
216
+ await expect(eventManager.emitAll(testEvent)).rejects.toThrow(errorMessage)
217
+ expect(throwingListener.mock.calls.length).toBe(1)
218
+
219
+ // Test synchronous emit throws immediately
220
+ expect(() => eventManager.emit(testEvent)).toThrow(errorMessage)
221
+ expect(throwingListener.mock.calls.length).toBe(2)
222
+
223
+ // Test that emitAsync also bubbles up errors
224
+ await expect(eventManager.emitAsync(testEvent)).rejects.toThrow(errorMessage)
225
+ expect(throwingListener.mock.calls.length).toBe(3)
226
+ })
98
227
  })
@@ -1,12 +1,12 @@
1
1
  export interface IEvent {
2
- type: string
2
+ type: Readonly<string>
3
3
  }
4
4
 
5
- export type Listener = (event: IEvent) => any
5
+ export type Listener<E extends IEvent> = (event: E) => any
6
6
 
7
7
  export class EventManager<E extends IEvent = IEvent> {
8
8
  protected readonly listeners: {
9
- [key: string]: Listener[]
9
+ [key: string]: Listener<E>[]
10
10
  } = {}
11
11
 
12
12
  getListeners(event: string) {
@@ -20,16 +20,31 @@ export class EventManager<E extends IEvent = IEvent> {
20
20
  return Object.keys(this.listeners)
21
21
  }
22
22
 
23
- on(event: string | string[], listener: Listener) {
23
+ extend(eventManager: EventManager) {
24
+ const events = eventManager.getEvents()
25
+ events.forEach((event) => {
26
+ this.getListenerArray(event).push(...eventManager.getListeners(event))
27
+ })
28
+ }
29
+
30
+ forwardTo(eventManager: EventManager) {
31
+ const cancel = this.on('*', async (event) => {
32
+ await eventManager.emitAsync(event)
33
+ })
34
+
35
+ return cancel
36
+ }
37
+
38
+ on<Event extends E>(event: Event['type'] | Event['type'][], listener: Listener<Event>) {
24
39
  const events = Array.isArray(event) ? event : [event]
25
40
 
26
41
  events.forEach((e) => {
27
- this.getListenerArray(e).push(listener)
42
+ this.getListenerArray(e).push(listener as Listener<E>)
28
43
  })
29
44
 
30
45
  return () => {
31
46
  events.forEach((e) => {
32
- const index = this.getListeners(e).indexOf(listener)
47
+ const index = this.getListeners(e).indexOf(listener as Listener<E>)
33
48
  if (index > -1) {
34
49
  this.getListenerArray(e).splice(index, 1)
35
50
  }
@@ -38,14 +53,10 @@ export class EventManager<E extends IEvent = IEvent> {
38
53
  }
39
54
 
40
55
  async emitAsync(event: E) {
41
- await this.getListeners(event.type)
42
- .reduce(async (promise, listener) => {
43
- await promise
44
- return await listener(event)
45
- }, Promise.resolve())
46
- .catch((e) => {
47
- console.error('Error in event listener', e)
48
- })
56
+ await this.getListeners(event.type).reduce(async (promise, listener) => {
57
+ await promise
58
+ return await listener(event)
59
+ }, Promise.resolve())
49
60
  }
50
61
 
51
62
  async emitAll(event: E) {
@@ -1,14 +1,29 @@
1
1
  import { Context } from '../context/context'
2
2
  import { useRequest } from './request'
3
3
 
4
- export type HeaderMap = Record<string, string>
5
-
4
+ /**
5
+ * Get the headers from the request.
6
+ *
7
+ * @deprecated You can now inject headers directly from Context<RequestScope>.
8
+ * This function will be removed in a future version.
9
+ * @param context The context to use for the request.
10
+ * @returns The headers of the request, or an empty object if not available.
11
+ */
6
12
  export function useHeaders(context: Context) {
7
13
  const request = useRequest(context)
8
14
 
9
15
  return request?.headers ?? {}
10
16
  }
11
17
 
18
+ /**
19
+ * Get a specific header from the request.
20
+ *
21
+ * @deprecated You can now inject headers directly from Context<RequestScope>.
22
+ * This function will be removed in a future version.
23
+ * @param context The context to use for the request.
24
+ * @param header The name of the header to retrieve.
25
+ * @returns The value of the specified header, or undefined if not available.
26
+ */
12
27
  export function useHeader(context: Context, header: string) {
13
28
  const headers = useHeaders(context)
14
29
 
@@ -1,43 +1,52 @@
1
- import { Context, type ContextMiddleware } from '../context/context'
2
1
  import type { IncomingMessage, ServerResponse } from 'http'
3
-
4
- export const REQUEST_CONTEXT_MIDDLEWARE = Symbol('declaro:request-context-middleware')
5
-
6
- export function useRequestMiddleware(context: Context) {
7
- const middleware = context.inject<ContextMiddleware[]>(REQUEST_CONTEXT_MIDDLEWARE)
2
+ import { Context, type ContextMiddleware, type DeclaroScope } from '../context/context'
3
+
4
+ /**
5
+ * Get the request middleware for the current context.
6
+ * @param context The context to retrieve middleware from.
7
+ * @returns An array of request middleware functions.
8
+ * @deprecated Use `context.scope.requestMiddleware` instead.
9
+ */
10
+ export function useRequestMiddleware<S extends DeclaroScope>(context: Context<S>) {
11
+ const middleware = context.resolve('requestMiddleware', {
12
+ strict: false,
13
+ })
8
14
 
9
15
  return middleware ?? []
10
16
  }
11
17
 
12
- export function provideRequestMiddleware(context: Context, ...middleware: ContextMiddleware[]) {
13
- const existingMiddleware = useRequestMiddleware(context)
18
+ export function provideRequestMiddleware<S extends DeclaroScope>(
19
+ context: Context<S>,
20
+ ...middleware: ContextMiddleware<Context>[]
21
+ ) {
22
+ const existingMiddleware = context.scope.requestMiddleware ?? []
14
23
 
15
24
  const extendedMiddleware = [...existingMiddleware, ...middleware]
16
25
 
17
- context.provide(REQUEST_CONTEXT_MIDDLEWARE, extendedMiddleware)
26
+ context.registerValue('requestMiddleware', extendedMiddleware as any)
18
27
 
19
28
  return extendedMiddleware
20
29
  }
21
30
 
22
- export const REQUEST_NODE_MIDDLEWARE = Symbol('declaro:request-node-middleware')
23
-
24
31
  export type NodeListener = (req: IncomingMessage, res: ServerResponse) => void
25
32
  export type NodePromisifiedHandler = (req: IncomingMessage, res: ServerResponse) => Promise<any>
26
33
  export type NodeMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => any) => any
27
34
  export type AllNodeMiddleware = NodeListener | NodePromisifiedHandler | NodeMiddleware
28
35
 
29
- export function useNodeMiddleware(context: Context) {
30
- const middleware = context.inject<AllNodeMiddleware[]>(REQUEST_NODE_MIDDLEWARE)
36
+ export function useNodeMiddleware<S extends DeclaroScope>(context: Context<S>) {
37
+ const middleware = context.resolve('nodeMiddleware', {
38
+ strict: false,
39
+ })
31
40
 
32
41
  return middleware ?? []
33
42
  }
34
43
 
35
- export function provideNodeMiddleware(context: Context, ...middleware: AllNodeMiddleware[]) {
44
+ export function provideNodeMiddleware<S extends DeclaroScope>(context: Context<S>, ...middleware: AllNodeMiddleware[]) {
36
45
  const existingMiddleware = useNodeMiddleware(context)
37
46
 
38
47
  const extendedMiddleware = [...existingMiddleware, ...middleware]
39
48
 
40
- context.provide(REQUEST_NODE_MIDDLEWARE, extendedMiddleware)
49
+ context.registerValue('nodeMiddleware', extendedMiddleware)
41
50
 
42
51
  return extendedMiddleware
43
52
  }
@@ -1,20 +1,41 @@
1
- import { Context } from '../context/context'
2
1
  import type { IncomingMessage } from 'http'
3
-
4
- export const REQUEST = Symbol('declaro:request')
2
+ import { Context, type DeclaroRequestScope } from '../context/context'
5
3
 
6
4
  export interface Request extends IncomingMessage {}
7
5
 
8
- export function provideRequest(context: Context, request: Request) {
9
- context.provide(REQUEST, request)
6
+ /**
7
+ * Provide a request to a context that supports request scope.
8
+ * The context must have all properties required by DeclaroRequestScope.
9
+ *
10
+ * @param context A context that includes DeclaroRequestScope properties
11
+ * @param request The request to provide
12
+ */
13
+ export function provideRequest<S extends DeclaroRequestScope>(context: Context<S>, request: Request) {
14
+ context.registerValue('request', request)
10
15
  }
11
16
 
17
+ /**
18
+ * Get the request from the context.
19
+ *
20
+ * @deprecated You can now inject the request directly from Context<RequestScope>.
21
+ * This function will be removed in a future version.
22
+ * @param context The context to use for the request.
23
+ * @returns
24
+ */
12
25
  export function useRequest(context: Context) {
13
- const request = context.inject<Request | undefined>(REQUEST)
26
+ const request = context.resolve('request')
14
27
 
15
28
  return request
16
29
  }
17
30
 
31
+ /**
32
+ * Get the HTTP method of the request.
33
+ *
34
+ * @deprecated You can now inject the request directly from Context<RequestScope>.
35
+ * This function will be removed in a future version.
36
+ * @param context The context to use for the request.
37
+ * @returns The HTTP method of the request, or undefined if not available.
38
+ */
18
39
  export function useRequestMethod(context: Context) {
19
40
  const request = useRequest(context)
20
41
 
package/src/http/url.ts CHANGED
@@ -2,11 +2,11 @@ import { Context } from '../context/context'
2
2
  import { URL } from 'whatwg-url'
3
3
  import { useRequest } from './request'
4
4
 
5
- export function useRequestURL(context: Context) {
5
+ export function useRequestURL(context: Context): URL | undefined {
6
6
  const request = useRequest(context)
7
7
  const url = request?.url && new URL(request?.url)
8
8
 
9
- return url
9
+ return url as URL
10
10
  }
11
11
 
12
12
  export function useRequestURLString(context: Context) {
@@ -33,7 +33,7 @@ export function useRequestQuery(context: Context): Record<string, string> {
33
33
  const qs = url?.searchParams
34
34
 
35
35
  // convert qs to an object of key value pairs
36
- const qsMap = [...qs?.entries()].reduce((qsMap, [key, value]) => {
36
+ const qsMap = [...(qs?.entries() as any)].reduce((qsMap, [key, value]) => {
37
37
  qsMap[key] = value
38
38
  return qsMap
39
39
  }, {} as Record<string, string>)
package/src/index.ts CHANGED
@@ -1,19 +1,50 @@
1
+ export type {
2
+ /**
3
+ * @deprecated Import AppScope from '#scope' instead.
4
+ *
5
+ * Migration: Change `import { AppScope } from '@declaro/core'` to `import { AppScope } from '#scope'`
6
+ *
7
+ * The #scope import allows for better type augmentation and module resolution.
8
+ */
9
+ AppScope,
10
+ /**
11
+ * @deprecated Import RequestScope from '#scope' instead.
12
+ *
13
+ * Migration: Change `import { RequestScope } from '@declaro/core'` to `import { RequestScope } from '#scope'`
14
+ *
15
+ * The #scope import allows for better type augmentation and module resolution.
16
+ */
17
+ RequestScope,
18
+ } from '#scope'
19
+
20
+ export * from '@standard-schema/spec'
21
+
1
22
  export * from './typescript'
2
23
  export * from './app'
24
+ export * from './application/create-request-context'
25
+ export * from './application/use-declaro'
3
26
  export * from './context/context'
4
27
  export * from './context/context-consumer'
5
28
  export * from './context/validators'
29
+ export * from './context/async-context'
6
30
  export * from './dataflow'
7
31
  export * from './events'
8
32
  export * from './validation'
9
33
  export * from './timing'
10
- export * from './schema'
11
- export * from './helpers'
34
+ export * from './scope'
12
35
  export * from './errors/errors'
13
- export * from './schema/formats'
14
36
  export * from './pipelines'
15
37
  export * from './http/headers'
16
38
  export * from './http/request-context'
17
39
  export * from './http/request'
18
40
  export * from './http/url'
19
41
  export * from './auth/permission-validator'
42
+ export * from './schema/model'
43
+ export * from './schema/json-schema'
44
+ export * from './schema/labels'
45
+ export * from './schema/model-schema'
46
+ export * from './schema/schema-mixin'
47
+ export * from './schema/test/mock-model'
48
+
49
+ export * from './shared/utils/action-descriptor'
50
+ export * from './shared/utils/schema-utils'