Repository: sveltejs/kit (js)

Issue:
Better typing for `$env/dynamic/*`
### Describe the problem

When I put together the `$env/dynamic/*` modules, I made a stupid mistake thinking that we couldn't provide generated types similar to `$env/static/*`. This resulted in the dynamic modules being typed like so:

```ts
declare module '$env/dynamic/public' {
  let env: App.PublicEnv;
}
declare module '$env/dynamic/public' {
  let env: App.PrivateEnv;
}
```

This forces users to explicitly type their data using the `App` types, which is annoying -- it's "fake" type safety -- nothing is guaranteeing those keys even exist on your env object.

For some reason (maybe I was tired?) I didn't see the obvious solution...

### Describe the proposed solution

```ts
declare module '$env/dynamic/public' {
  let env: {
    PUBLIC_FOO: string | undefined;
    PUBLIC_BAR: string | undefined;
    [key: string]: string;
  };
}
declare module '$env/dynamic/public' {
  let env: {
    PRIVATE_FOO: string | undefined;
    PRIVATE_BAR: string | undefined;
    [key: string]: string;
  };
}
```

This still allows for arbitrary optional dynamic variables (such as feature flags or environment-dependent settings), but removes the need to use "fake" types to provide autocomplete.

### Alternatives considered

_No response_

### Importance

nice to have

### Additional Information

_No response_

Pull request:
Generate dynamic env types
Closes #6202. 

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [ ] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [x] If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. All changesets should be `patch` until SvelteKit 1.0


Patch:
diff --git a/.changeset/dull-cheetahs-clean.md b/.changeset/dull-cheetahs-clean.md
new file mode 100644
index 000000000..e1c8559ef
--- /dev/null
+++ b/.changeset/dull-cheetahs-clean.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[breaking] remove App.PrivateEnv and App.PublicEnv in favour of generated types
diff --git a/.changeset/lovely-otters-lie.md b/.changeset/lovely-otters-lie.md
new file mode 100644
index 000000000..7643a863b
--- /dev/null
+++ b/.changeset/lovely-otters-lie.md
@@ -0,0 +1,5 @@
+---
+'create-svelte': patch
+---
+
+Remove App.PrivateEnv and App.PublicEnv placeholders
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 13972d5c2..01eddb182 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -169,6 +169,7 @@
     "cuddly-ads-kick",
     "cuddly-files-decide",
     "cuddly-knives-learn",
+    "cuddly-months-sip",
     "cuddly-pens-buy",
     "cuddly-ravens-carry",
     "curly-doors-type",
diff --git a/packages/create-svelte/templates/default/src/app.d.ts b/packages/create-svelte/templates/default/src/app.d.ts
index a4416caba..d7506f89a 100644
--- a/packages/create-svelte/templates/default/src/app.d.ts
+++ b/packages/create-svelte/templates/default/src/app.d.ts
@@ -9,8 +9,4 @@ declare namespace App {
 	// interface PageData {}
 
 	// interface Platform {}
-
-	// interface PrivateEnv {}
-
-	// interface PublicEnv {}
 }
diff --git a/packages/create-svelte/templates/libskeleton/src/app.d.ts b/packages/create-svelte/templates/libskeleton/src/app.d.ts
index 637dfc2d0..3a4446871 100644
--- a/packages/create-svelte/templates/libskeleton/src/app.d.ts
+++ b/packages/create-svelte/templates/libskeleton/src/app.d.ts
@@ -7,6 +7,4 @@ declare namespace App {
 	// interface Locals {}
 	// interface PageData {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }
diff --git a/packages/create-svelte/templates/skeleton/src/app.d.ts b/packages/create-svelte/templates/skeleton/src/app.d.ts
index 7ef2c828b..14ac5d1b5 100644
--- a/packages/create-svelte/templates/skeleton/src/app.d.ts
+++ b/packages/create-svelte/templates/skeleton/src/app.d.ts
@@ -5,6 +5,4 @@ declare namespace App {
 	// interface Locals {}
 	// interface PageData {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index a94320c28..0797eaa38 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,11 @@
 # @sveltejs/kit
 
+## 1.0.0-next.451
+
+### Patch Changes
+
+- Prevent infinite reloads on `/` when root `+layout.server.js` exports `load` ([#6405](https://github.com/sveltejs/kit/pull/6405))
+
 ## 1.0.0-next.450
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 8da082c48..f9e8c37fa 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.450",
+	"version": "1.0.0-next.451",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/kit/scripts/special-types/$env+dynamic+private.md b/packages/kit/scripts/special-types/$env+dynamic+private.md
new file mode 100644
index 000000000..85dc7c70a
--- /dev/null
+++ b/packages/kit/scripts/special-types/$env+dynamic+private.md
@@ -0,0 +1,8 @@
+This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node) (or running [`vite preview`](https://kit.svelte.dev/docs/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#kit-env-publicprefix).
+
+This module cannot be imported into client-side code.
+
+```ts
+import { env } from '$env/dynamic/private';
+console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
+```
diff --git a/packages/kit/scripts/special-types/$env+dynamic+public.md b/packages/kit/scripts/special-types/$env+dynamic+public.md
new file mode 100644
index 000000000..2c2ccdce9
--- /dev/null
+++ b/packages/kit/scripts/special-types/$env+dynamic+public.md
@@ -0,0 +1,8 @@
+Similar to [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), but only includes variables that begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#kit-env-publicprefix) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
+
+Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests — when possible, use `$env/static/public` instead.
+
+```ts
+import { env } from '$env/dynamic/public';
+console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
+```
diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js
index 789348a62..00291a4d0 100644
--- a/packages/kit/src/core/env.js
+++ b/packages/kit/src/core/env.js
@@ -34,7 +34,7 @@ export function create_dynamic_module(type) {
  * @param {Record<string, string>} env
  * @returns {string}
  */
-export function create_types(id, env) {
+export function create_static_types(id, env) {
 	const declarations = Object.keys(env)
 		.filter((k) => valid_identifier.test(k))
 		.map((k) => `\texport const ${k}: string;`)
@@ -43,6 +43,21 @@ export function create_types(id, env) {
 	return `declare module '${id}' {\n${declarations}\n}`;
 }
 
+/**
+ * @param {string} id
+ * @param {Record<string, string>} env
+ * @returns {string}
+ */
+export function create_dynamic_types(id, env) {
+	const properties = Object.keys(env)
+		.filter((k) => valid_identifier.test(k))
+		.map((k) => `\t\t${k}: string;`);
+
+	properties.push(`\t\t[key: string]: string | undefined;`);
+
+	return `declare module '${id}' {\n\texport const env: {\n${properties.join('\n')}\n\t}\n}`;
+}
+
 export const reserved = new Set([
 	'do',
 	'if',
diff --git a/packages/kit/src/core/sync/write_ambient.js b/packages/kit/src/core/sync/write_ambient.js
index 9904a1cee..bcb50dbc6 100644
--- a/packages/kit/src/core/sync/write_ambient.js
+++ b/packages/kit/src/core/sync/write_ambient.js
@@ -1,10 +1,43 @@
+import fs from 'fs';
 import path from 'path';
 import { get_env } from '../../exports/vite/utils.js';
 import { GENERATED_COMMENT } from '../../constants.js';
-import { create_types } from '../env.js';
+import { create_dynamic_types, create_static_types } from '../env.js';
 import { write_if_changed } from './utils.js';
+import { fileURLToPath } from 'url';
 
-const types_reference = '/// <reference types="@sveltejs/kit" />\n\n';
+const descriptions_dir = fileURLToPath(new URL('../../../scripts/special-types', import.meta.url));
+
+/** @param {string} filename */
+function read_description(filename) {
+	const content = fs.readFileSync(`${descriptions_dir}/${filename}`, 'utf8');
+	return `/**\n${content
+		.trim()
+		.split('\n')
+		.map((line) => ` * ${line}`)
+		.join('\n')}\n */`;
+}
+
+/**
+ * @param {{ public: Record<string, string>, private: Record<string, string> }} env
+ */
+const template = (env) => `
+${GENERATED_COMMENT}
+
+/// <reference types="@sveltejs/kit" />
+
+${read_description('$env+static+private.md')}
+${create_static_types('$env/static/private', env.private)}
+
+${read_description('$env+static+public.md')}
+${create_static_types('$env/static/public', env.public)}
+
+${read_description('$env+dynamic+private.md')}
+${create_dynamic_types('$env/dynamic/private', env.private)}
+
+${read_description('$env+dynamic+public.md')}
+${create_dynamic_types('$env/dynamic/public', env.public)}
+`;
 
 /**
  * Writes ambient declarations including types reference to @sveltejs/kit,
@@ -16,12 +49,5 @@ const types_reference = '/// <reference types="@sveltejs/kit" />\n\n';
 export function write_ambient(config, mode) {
 	const env = get_env(config.env, mode);
 
-	write_if_changed(
-		path.join(config.outDir, 'ambient.d.ts'),
-		GENERATED_COMMENT +
-			types_reference +
-			create_types('$env/static/public', env.public) +
-			'\n\n' +
-			create_types('$env/static/private', env.private)
-	);
+	write_if_changed(path.join(config.outDir, 'ambient.d.ts'), template(env));
 }
diff --git a/packages/kit/src/runtime/env-private.js b/packages/kit/src/runtime/env-private.js
index 9fdd71a7f..7352ceb17 100644
--- a/packages/kit/src/runtime/env-private.js
+++ b/packages/kit/src/runtime/env-private.js
@@ -1,4 +1,3 @@
-/** @type {App.PrivateEnv} */
 export let env = {};
 
 /** @type {(environment: Record<string, string>) => void} */
diff --git a/packages/kit/src/runtime/env-public.js b/packages/kit/src/runtime/env-public.js
index 0b8c28cc6..481a87046 100644
--- a/packages/kit/src/runtime/env-public.js
+++ b/packages/kit/src/runtime/env-public.js
@@ -1,4 +1,3 @@
-/** @type {App.PublicEnv} */
 export let env = {};
 
 /** @type {(environment: Record<string, string>) => void} */
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index ebde74cb4..406ea05a0 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -10,14 +10,10 @@
  * 	interface PageData {}
  *
  * 	interface Platform {}
- *
- * 	interface PrivateEnv {}
- *
- * 	interface PublicEnv {}
  * }
  * ```
  *
- * By populating these interfaces, you will gain type safety when using `env`, `event.locals` and `event.platform`.
+ * By populating these interfaces, you will gain type safety when using `event.locals`, `event.platform`, and `data` from `load` functions.
  *
  * Note that since it's an ambient declaration file, you have to be careful when using `import` statements. Once you add an `import`
  * at the top level, the declaration file is no longer considered ambient and you lose access to these typings in other files.
@@ -60,16 +56,6 @@ declare namespace App {
 	 * If your adapter provides [platform-specific context](https://kit.svelte.dev/docs/adapters#supported-environments-platform-specific-context) via `event.platform`, you can specify it here.
 	 */
 	export interface Platform {}
-
-	/**
-	 * The interface that defines the dynamic environment variables exported from `$env/dynamic/private`.
-	 */
-	export interface PrivateEnv extends Record<string, string> {}
-
-	/**
-	 * The interface that defines the dynamic environment variables exported from `$env/dynamic/public`.
-	 */
-	export interface PublicEnv extends Record<string, string> {}
 }
 
 /**
@@ -94,39 +80,6 @@ declare module '$app/environment' {
 	export const prerendering: boolean;
 }
 
-/**
- * This module provides access to runtime environment variables, as defined by the platform you're running on. For example
- * if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node) (or running
- * [`vite preview`](https://kit.svelte.dev/docs/cli)), this is equivalent to `process.env`. This module only includes
- * variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#kit-env-publicprefix).
- *
- * This module cannot be imported into client-side code.
- *
- * ```ts
- * import { env } from '$env/dynamic/private';
- * console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
- * ```
- */
-declare module '$env/dynamic/private' {
-	export let env: App.PrivateEnv;
-}
-
-/**
- * Similar to [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), but only includes
- * variables that begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#kit-env-publicprefix)
- * (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code
- *
- * Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests — when possible, use `$env/static/public` instead.
- *
- * ```ts
- * import { env } from '$env/dynamic/public';
- * console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
- * ```
- */
-declare module '$env/dynamic/public' {
-	export let env: App.PublicEnv;
-}
-
 /**
  * ```ts
  * import {


Test patch:
diff --git a/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/src/app.d.ts b/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/src/app.d.ts
index 4eac1d5c9..917a96399 100644
--- a/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/src/app.d.ts
+++ b/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/src/app.d.ts
@@ -4,6 +4,4 @@
 declare namespace App {
 	// interface Locals {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }
diff --git a/packages/kit/test/build-errors/apps/private-dynamic-env/src/app.d.ts b/packages/kit/test/build-errors/apps/private-dynamic-env/src/app.d.ts
index 4eac1d5c9..917a96399 100644
--- a/packages/kit/test/build-errors/apps/private-dynamic-env/src/app.d.ts
+++ b/packages/kit/test/build-errors/apps/private-dynamic-env/src/app.d.ts
@@ -4,6 +4,4 @@
 declare namespace App {
 	// interface Locals {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }
diff --git a/packages/kit/test/build-errors/apps/private-static-env-dynamic-import/src/app.d.ts b/packages/kit/test/build-errors/apps/private-static-env-dynamic-import/src/app.d.ts
index 4eac1d5c9..917a96399 100644
--- a/packages/kit/test/build-errors/apps/private-static-env-dynamic-import/src/app.d.ts
+++ b/packages/kit/test/build-errors/apps/private-static-env-dynamic-import/src/app.d.ts
@@ -4,6 +4,4 @@
 declare namespace App {
 	// interface Locals {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }
diff --git a/packages/kit/test/build-errors/apps/private-static-env/src/app.d.ts b/packages/kit/test/build-errors/apps/private-static-env/src/app.d.ts
index 4eac1d5c9..917a96399 100644
--- a/packages/kit/test/build-errors/apps/private-static-env/src/app.d.ts
+++ b/packages/kit/test/build-errors/apps/private-static-env/src/app.d.ts
@@ -4,6 +4,4 @@
 declare namespace App {
 	// interface Locals {}
 	// interface Platform {}
-	// interface PrivateEnv {}
-	// interface PublicEnv {}
 }