Skip to content

fix(@angular/ssr): allow using function with optional request context argument #28845

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

pieh
Copy link

@pieh pieh commented Nov 12, 2024

PR Checklist

Please check to confirm your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

What is the current behavior?

When using createRequestHandler on Netlify edge function compatible request handler function I am forced to use @ts-expect-error directive currently:

import { AngularAppEngine, createRequestHandler } from '@angular/ssr'
const angularAppEngine = new AngularAppEngine()

// @ts-expect-error - createRequestHandler expects a function with single Request argument and doesn't allow context argument
export const reqHandler = createRequestHandler(async (request: Request, context: any) => {
  const result = await angularAppEngine.handle(request, context)
  return result || new Response('Not found', { status: 404 })
})

Issue Number: N/A

What is the new behavior?

Don't need to use @ts-expect-error directive anymore.

import { AngularAppEngine, createRequestHandler } from '@angular/ssr'
const angularAppEngine = new AngularAppEngine()

export const reqHandler = createRequestHandler(async (request: Request, context: any) => {
  const result = await angularAppEngine.handle(request, context)
  return result || new Response('Not found', { status: 404 })
})

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

From what I understand applying metadata done by createRequestHandler is used to recognize in dev mode that exported handle can be used to handle requests and falling back to "default" when it's not (and ignoring any customizations in server.ts file in dev mode because of it), so there doesn't seem to exist workaround the current type while still having access to second parameter?

@pieh pieh force-pushed the fix/ssr-allow-optional-request-context-argument branch from 1c4246e to b19a5e0 Compare November 13, 2024 09:10
@alan-agius4 alan-agius4 self-assigned this Nov 19, 2024
@alan-agius4 alan-agius4 self-requested a review November 19, 2024 09:05
@@ -16,6 +16,8 @@
*/
export type RequestHandlerFunction = (
request: Request,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...ignoredArgs: any[]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using any here, can we instead use generics to infer the type?

Example

export type RequestHandlerFunction<T = unknown> = (
  request: Request,
  requestContext?: T,
) => Promise<Response | null> | null | Response;

export function createRequestHandler<T = unknown>(
  handler: RequestHandlerFunction<T>,
): RequestHandlerFunction<T> {

}

@angular angular deleted a comment from google-cla bot Nov 20, 2024
@pieh
Copy link
Author

pieh commented Nov 20, 2024

I found alternative that doesn't require type modification - if anyone is interested it's something like this (attempted to simplify it in case other would face similar issue to solve):

import { AsyncLocalStorage } from 'node:async_hooks'

import { AngularAppEngine, createRequestHandler } from '@angular/ssr'
import type { Context } from '@netlify/edge-functions'

const angularAppEngine = new AngularAppEngine()
const ContextStorage = new AsyncLocalStorage<Context>()

function requestHandlerImpl(request: Request) {
  const context = ContextStorage.getStore()
  // context will be undefined in dev, but should be defined in prod

  return angularAppEngine.handle(request, context)
}

// this will be production deployment entry point - it sets context on the async local storage and we can access it later in common handler implementation without passing it as function argument
export default function prodHandler(request: Request, context: Context) {
  return ContextStorage.run(context, () => requestHandlerImpl(request))
}

// dev
export const reqHandler = createRequestHandler(requestHandlerImpl)

So I'll close this PR as given that there is pretty portable solution that can be adjusted to work with any provider - attempting to adjust the types for specific signature is unnecessary risk to take and would be annoying to deal with in the future if another incompatible signature would need to be handled.

@pieh pieh closed this Nov 20, 2024
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Dec 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants