opinionated-machine 10.0.0 → 10.2.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.
@@ -0,0 +1,100 @@
1
+ import { SSESessionSpy } from "../sse/SSESessionSpy.js";
2
+ const isThenable = (value) => typeof value?.then === 'function';
3
+ /**
4
+ * Run a route's own lifecycle hook, then notify the spy — even if the hook fails.
5
+ *
6
+ * The spy is an observer of the connection, not a participant in it: a hook that
7
+ * throws is a failure of the app under test, and swallowing the connection event
8
+ * on top of it would surface as an unrelated `waitForConnection` timeout much
9
+ * later. The hook's own failure is propagated to the route builder unchanged.
10
+ *
11
+ * The spy is notified *after* the hook, mirroring `AbstractSSEController`, so a
12
+ * hook that enriches the session does so before any waiter observes it.
13
+ */
14
+ function runHookThenNotify(hook, notify) {
15
+ let result;
16
+ try {
17
+ result = hook();
18
+ }
19
+ catch (error) {
20
+ notify();
21
+ throw error;
22
+ }
23
+ if (isThenable(result)) {
24
+ return result.then(() => notify(), (error) => {
25
+ notify();
26
+ throw error;
27
+ });
28
+ }
29
+ notify();
30
+ }
31
+ /**
32
+ * Create a standalone connection spy plus the route hooks that drive it.
33
+ *
34
+ * `AbstractSSEController` exposes a spy of its own via `connectionSpy` (gated on
35
+ * `{ enableConnectionSpy: true }`). Routes built with `buildApiRoute` have no
36
+ * such controller, so this factory drives a spy from the route's `onConnect` /
37
+ * `onClose` hooks instead. That removes the race between `connect()` returning
38
+ * (HTTP headers received) and the server-side handler finishing its connection
39
+ * registration.
40
+ *
41
+ * **The hooks have to be passed to the `buildApiRoute()` call that builds the
42
+ * route.** The route builder captures them when it constructs the handler, so
43
+ * attaching them to an already-built `RouteOptions` object has no effect. A
44
+ * route owned by production code therefore has to accept SSE route options (or
45
+ * the hooks themselves) as a parameter for a test to be able to spy on it.
46
+ *
47
+ * **Only useful for sessions that outlive the handler** — `keepAlive` sessions.
48
+ * An `autoClose` route closes its session as the handler returns, before the
49
+ * test can claim it, so `awaitServerConnection` on such a route races and
50
+ * usually times out. Omit `awaitServerConnection` there and assert on the
51
+ * events the client received instead.
52
+ *
53
+ * The spy observes `@lokalise/fastify-api-contracts` sessions by default, which
54
+ * is what `buildApiRoute` hands to its hooks. Pass this package's `SSESession`
55
+ * as the type argument when wiring it to `buildFastifyRoute` instead.
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const { spy, routeOptions } = createSSESessionSpy()
60
+ *
61
+ * // in the app-under-test's route registration
62
+ * app.route(buildApiRoute(contract, handler, { ...routeOptions }))
63
+ *
64
+ * // in the test
65
+ * const { client, serverConnection } = await SSEHttpClient.connect(baseUrl, path, {
66
+ * awaitServerConnection: { spy },
67
+ * })
68
+ * // serverConnection is registered server-side and ready to use
69
+ * ```
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * // Route with lifecycle hooks of its own: compose instead of replacing them
74
+ * const { spy, withSpy } = createSSESessionSpy()
75
+ *
76
+ * app.route(buildApiRoute(contract, handler, withSpy(productionRouteOptions)))
77
+ * ```
78
+ */
79
+ export function createSSESessionSpy() {
80
+ const spy = new SSESessionSpy();
81
+ // `TOptions extends object` rather than `extends SSESessionSpyHooks`: with an
82
+ // all-optional constraint, TypeScript falls back to the constraint itself for
83
+ // an argument that declares no hooks, which would drop every other route
84
+ // option from the result type. Hook compatibility is enforced by the
85
+ // intersection in the parameter position instead.
86
+ const withSpy = (options) => {
87
+ const { onConnect, onClose, ...passThrough } = options ?? {};
88
+ return {
89
+ ...passThrough,
90
+ onConnect: (connection) => runHookThenNotify(() => onConnect?.(connection), () => spy.addConnection(connection)),
91
+ onClose: (connection, initiator) => runHookThenNotify(() => onClose?.(connection, initiator), () => spy.addDisconnection(connection.id)),
92
+ };
93
+ };
94
+ return {
95
+ spy,
96
+ routeOptions: withSpy(),
97
+ withSpy,
98
+ };
99
+ }
100
+ //# sourceMappingURL=sseSessionSpyFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sseSessionSpyFactory.js","sourceRoot":"","sources":["../../../lib/testing/sseSessionSpyFactory.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwB,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAiE7E,MAAM,UAAU,GAAG,CAAC,KAAc,EAA6B,EAAE,CAC/D,OAAQ,KAAwC,EAAE,IAAI,KAAK,UAAU,CAAA;AAEvE;;;;;;;;;;GAUG;AACH,SAAS,iBAAiB,CACxB,IAAgC,EAChC,MAAkB;IAElB,IAAI,MAA4B,CAAA;IAChC,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,EAAE,CAAA;IACjB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,EAAE,CAAA;QACR,MAAM,KAAK,CAAA;IACb,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACvB,OAAO,MAAM,CAAC,IAAI,CAChB,GAAG,EAAE,CAAC,MAAM,EAAE,EACd,CAAC,KAAc,EAAE,EAAE;YACjB,MAAM,EAAE,CAAA;YACR,MAAM,KAAK,CAAA;QACb,CAAC,CACF,CAAA;IACH,CAAC;IAED,MAAM,EAAE,CAAA;AACV,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,UAAU,mBAAmB;IAGjC,MAAM,GAAG,GAAG,IAAI,aAAa,EAAY,CAAA;IAEzC,8EAA8E;IAC9E,8EAA8E;IAC9E,yEAAyE;IACzE,qEAAqE;IACrE,kDAAkD;IAClD,MAAM,OAAO,GAAG,CACd,OAAiD,EAC8B,EAAE;QACjF,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,WAAW,EAAE,GAC1C,OAAO,IAAK,EAA8C,CAAA;QAE5D,OAAO;YACL,GAAI,WAAuD;YAC3D,SAAS,EAAE,CAAC,UAAU,EAAE,EAAE,CACxB,iBAAiB,CACf,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,EAC7B,GAAG,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC,UAAU,CAAC,CACpC;YACH,OAAO,EAAE,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,CACjC,iBAAiB,CACf,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,EACtC,GAAG,EAAE,CAAC,GAAG,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC,CAC1C;SACJ,CAAA;IACH,CAAC,CAAA;IAED,OAAO;QACL,GAAG;QACH,YAAY,EAAE,OAAO,EAAE;QACvB,OAAO;KACR,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opinionated-machine",
3
- "version": "10.0.0",
3
+ "version": "10.2.0",
4
4
  "description": "Very opinionated DI framework for fastify, built on top of awilix ",
5
5
  "type": "module",
6
6
  "license": "MIT",