-
-
Couldn't load subscription status.
- Fork 2.1k
Description
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:
- 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 responseIn all cases, 'unchecked' can be passed to forego checks, but are required to be passed in strict mode.
- .guard([checkAuth])
+ .guard('unchecked')- 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
})- 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:
- oRPC "Contract First" development
- fastify-openapi-glue to generate RESTful APIs from an OpenAPI Schema
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
- Rely on a potential solution for the server
handlehook to conditionally guard certain remote functions based on their path; part of Remote function module level handlers / module info in serverhandle#14528. - Write actual contract tests as a practice.
Importance
would make my life easier