opinionated-machine 9.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.
- package/CHANGELOG.md +85 -0
- package/README.md +222 -7
- package/dist/lib/DIContext.js +33 -27
- package/dist/lib/DIContext.js.map +1 -1
- package/dist/lib/routes/fastifyRouteBuilder.js +79 -21
- package/dist/lib/routes/fastifyRouteBuilder.js.map +1 -1
- package/dist/lib/routes/fastifyRouteTypes.d.ts +97 -12
- package/dist/lib/routes/fastifyRouteTypes.js.map +1 -1
- package/dist/lib/routes/fastifyRouteUtils.js +15 -5
- package/dist/lib/routes/fastifyRouteUtils.js.map +1 -1
- package/dist/lib/routes/index.d.ts +2 -1
- package/dist/lib/routes/index.js +2 -0
- package/dist/lib/routes/index.js.map +1 -1
- package/dist/lib/routes/sseResponseSchema.d.ts +35 -0
- package/dist/lib/routes/sseResponseSchema.js +115 -0
- package/dist/lib/routes/sseResponseSchema.js.map +1 -0
- package/dist/lib/sse/SSESessionSpy.d.ts +35 -7
- package/dist/lib/sse/SSESessionSpy.js +30 -1
- package/dist/lib/sse/SSESessionSpy.js.map +1 -1
- package/dist/lib/sse/index.d.ts +1 -1
- package/dist/lib/sse/index.js.map +1 -1
- package/dist/lib/testing/apiSseInjectHelpers.d.ts +64 -0
- package/dist/lib/testing/apiSseInjectHelpers.js +212 -0
- package/dist/lib/testing/apiSseInjectHelpers.js.map +1 -0
- package/dist/lib/testing/apiSseTestTypes.d.ts +202 -0
- package/dist/lib/testing/apiSseTestTypes.js +2 -0
- package/dist/lib/testing/apiSseTestTypes.js.map +1 -0
- package/dist/lib/testing/index.d.ts +4 -1
- package/dist/lib/testing/index.js +2 -0
- package/dist/lib/testing/index.js.map +1 -1
- package/dist/lib/testing/sseHttpClient.d.ts +36 -4
- package/dist/lib/testing/sseHttpClient.js +29 -6
- package/dist/lib/testing/sseHttpClient.js.map +1 -1
- package/dist/lib/testing/sseInjectHelpers.js +1 -12
- package/dist/lib/testing/sseInjectHelpers.js.map +1 -1
- package/dist/lib/testing/sseInjectShared.d.ts +7 -0
- package/dist/lib/testing/sseInjectShared.js +19 -0
- package/dist/lib/testing/sseInjectShared.js.map +1 -0
- package/dist/lib/testing/sseSessionSpyFactory.d.ts +109 -0
- package/dist/lib/testing/sseSessionSpyFactory.js +100 -0
- package/dist/lib/testing/sseSessionSpyFactory.js.map +1 -0
- package/package.json +1 -1
|
@@ -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"}
|