Skip to content

Remote functions: provide spec-driven response contracts and mandatory guards #14612

@theetrain

Description

@theetrain

Describe the problem

Teams that rely on assurances when shipping secure applications look to integration and contract testing, and ideally defining schemas that translate to routes (in the case of RESTful APIs) and request and response parsers. At the time of this writing, SvelteKit's Remote Functions do well to require a Standard Schema or an 'unchecked' declaration for methods that accept input parameters.

To guard these functions with logic that must run before these functions' execution (particularly authentication), the current recommendation is to either inline logic with a reusable function call or with repeated code, or to write a higher order function. These are optional approaches that require teams to explicitly train or document as part of onboarding since there are no protections from neglect or errors at build time nor run time as new features are authored in a project.

Since function request parameters are currently able to be validated with a Standard Schema, the remaining missing pieces for full end-to-end contracts are:

  • The ability to provide a schema declaration for response/output types that automatically parse away unexpected object keys.
  • The ability to explicitly define function guards.
  • Adding an opt-in configuration to require these new features.

Describe the proposed solution

I can imagine writing response schemas and guard methods to be an opt-in feature via a flag such as kit.remoteFunctions.protections = 'strict' | 'request-only' | 'request-and-response', and could take one of a few forms:

  1. As chained methods
import { itemSlug, itemBrief } from './schemas'
import { checkAuth } from '$lib/hooks.server'
import { query } from '$app/server'

export const getItem = query(itemSlug, async (slug) => {
  // ...
})
  .guard([checkAuth]) // error thrown if no `guard` defined
  .validateResponse(itemBrief) // error thrown if no schema provided for response

In all cases, 'unchecked' can be passed to forego checks, but are required to be passed in strict mode.

- .guard([checkAuth])
+ .guard('unchecked')
  1. As additional parameters
import { itemSlug, itemBrief } from './schemas'
import { checkAuth } from '$lib/hooks.server'
import { query } from '$app/server'

export const getItem = query(itemSlug, async (slug) => {
  // ...
}, {
  guard: [checkAuth], // throw error if not provided
  validateResponse(itemBrief), // throw error if not provided
  // other potential hooks or options
})
  1. As new functions or altered parameters
import { itemSlug, itemBrief } from './schemas'
import { checkAuth } from '$lib/hooks.server'
import { advancedQuery } from '$app/server'

export const getItem = advancedQuery({
  preHandler: [checkAuth],
  handler: async function (slug) { /* ... */ },
  requestSchema: itemSlug,
  responseSchema: itemBrief
})

Things I'm uncertain about:

  • How these suggestions would impact single-flight mutations or batching.
  • Whether there's a need for comprehensive hooks (Fastify Hooks for reference), or if an array of functions is all that's needed before a Remote Function's handler gets called.

Other examples:

The outcome of this ticket could aid with desktop applications, as described in #14336.

To be clear, I'd like a way to oblige developers to write response schemas and list pre-run hooks for every Remote Function definition. This takes inspiration from RESTful API tools, but is not intended for writing RESTful APIs.

Alternatives considered

Importance

would make my life easier

Additional Information

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions