App-level types
See original GitHub issueDescribe the problem
Various types in a Kit app are app-level, by which I mean they’re the same wherever they’re used — event.locals
, event.platform
, session
, and arguably stuff
(which, even though it’s scoped to load
, can be used globally as $page.stuff
and is therefore probably best thought of and used as a consistently-shaped object, where input stuff
and output stuff
in load
are both Partial<Stuff>
).
It’s wasteful, therefore, that to take full advantage of type safety it’s necessary to use generic arguments everywhere:
<script context="module">
/**
* @type {import('@sveltejs/kit').Load<{
* pageParams: PageParams;
* session: { foo: number };
* stuff: Stuff;
* }, {
* props: Props;
* stuff: Stuff
* }>} */
export function load({...}) {...}
</script>
/**
* @type {import('@sveltejs/kit').RequestHandler<
* Locals,
* Platform,
* Output
* >}
*/
export function get({...}) {...}
/**
* @type {import('@sveltejs/kit').Handle<
* Locals,
* Platform
* >}
*/
export async function handle(...) {...}
Aside: why is it
pageParams
rather thanparams
?
It’s also a problem that RequestHandler
and Handle
take positional generic arguments — it meant that the addition of Platform
was a breaking change, for example. And why doesn’t RequestHandler
accept params
? If we fixed that, it would be another breaking change.
With app-level types (and assuming we were to treat stuff
as app-level), the examples above could be rewritten thusly:
<script context="module">
/**
* @type {import('@sveltejs/kit').Load<{
* pageParams: PageParams;
* props: Props;
* }>} */
export function load({...}) {...}
</script>
/** @type {import('@sveltejs/kit').RequestHandler<Output>} */
export function get({...}) {...}
/** @type {import('@sveltejs/kit').Handle} */
export async function handle(...) {...}
Stretch goal — no manual typing at all
A priori, it’s silly that you have to type params
— SvelteKit already has that information. It should make it available… somehow.
In an ideal world, it wouldn’t be necessary to type props
either — that would be inferred from the props in the <script>
block.
In an even idealer world, TypeScript would support implicit module typing and you wouldn’t even have to declare types.
I’ve no idea how feasible any of this is in the real world (maybe there’s something sneaky/clever we could do with preprocess
?) but this is the development experience we should be aiming for, even if we fall short.
Describe the proposed solution
I’m not much of a TypeScript expert, so I don’t really know how we would do this. @dummdidumm had a suggestion:
// src/app.d.ts
declare namespace SvelteKit {
export interface Session { .. }
}
// inside SvelteKit
declare module "$app/stores" {
export let session: Writable<SvelteKit.Session>
}
Alternatives considered
No response
Importance
would make my life easier
Additional Information
No response
Issue Analytics
- State:
- Created 2 years ago
- Reactions:4
- Comments:12 (8 by maintainers)
I found a working solution which might be even more idiomatic than the namespace approach:
Edit: Turns out we don’t even need this, TS falls back to
any
for types it does not know. 2. Add the following to for example$app/stores
:Session
is not defined anywhere so this is a type error (which is why we add@ts-ignore
above), but thanks to the helper type it will fall back to any 3. Add the following to the user’sglobal.d.ts
:TypeScript will merge the module declarations, which means
Session
is now defined, which means intellisense will now use that type instead ofany
.So the way forward would be to
global.d.ts
increate-svelte
with initially empty module declarations for every declaration we support to type that way with some additional comments on how to take advantage of thisI’d argue that the namespace is more idiomatic. It so happens that
Session
is used by$app/stores
, but thesession
argument inload
functions also needs to be typed, as dolocals
andstuff
andplatform
which aren’t available via a store.Separately, I think
app.d.ts
might be a better name thanglobal.d.ts
, since it’s the language we use elsewhere, though I don’t feel strongly on that point.