Skip to content
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

feat: add support for automatic session driver config #13145

Merged
merged 28 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
dd09093
feat: add support for automatic session driver config
ascorbic Feb 6, 2025
aa98169
chore: fix error logic
ascorbic Feb 6, 2025
154eb97
Lint test
ascorbic Feb 6, 2025
8dff1a1
Merge branch 'main' into session-config2
ascorbic Feb 6, 2025
bc4de01
Add node support
ascorbic Feb 6, 2025
4c3ded8
Add node test fixture
ascorbic Feb 6, 2025
d22222a
Merge branch 'main' into session-config2
ascorbic Feb 6, 2025
e1e2328
Lock
ascorbic Feb 6, 2025
f0545d0
Add Netlify support
ascorbic Feb 6, 2025
680f928
Use workspace Astro version
ascorbic Feb 6, 2025
bb76d36
Format
ascorbic Feb 6, 2025
be1e237
Changeset
ascorbic Feb 6, 2025
78cd5eb
Merge branch 'main' into session-config2
ascorbic Feb 7, 2025
e422b63
Add tests
ascorbic Feb 7, 2025
41e0474
Add dep for tests
ascorbic Feb 7, 2025
c9c63a6
Merge branch 'main' into session-config2
ascorbic Feb 7, 2025
304303e
chore: fix repo URL
ascorbic Feb 7, 2025
db142f0
temp log
ascorbic Feb 7, 2025
3bb038e
Fix module resoltuion
ascorbic Feb 7, 2025
f6223c4
Merge branch 'main' into session-config2
ascorbic Feb 7, 2025
3ddeaf0
[skip ci] Update changeset
ascorbic Feb 7, 2025
d0fac27
chore: bump peer dependencies
ascorbic Feb 10, 2025
704fe3e
Changes from review
ascorbic Feb 10, 2025
db89093
Changeset changes from review
ascorbic Feb 10, 2025
7d4f1da
Apply suggestions from code review
ascorbic Feb 12, 2025
1801e55
More changeset detail
ascorbic Feb 12, 2025
1f29a2e
Merge branch 'main' into session-config2
ascorbic Feb 12, 2025
f673dee
Lock
ascorbic Feb 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .changeset/tricky-insects-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
'astro': minor
ematipico marked this conversation as resolved.
Show resolved Hide resolved
---

:warning: **BREAKING CHANGE FOR EXPERIMENTAL SESSIONS ONLY** :warning:

Changes the `experimental.session` option to a boolean flag and moves session config to a top-level value. This change is to allow the new automatic session driver support. You now need to separately enable the `experimental.session` flag, and then configure the session driver using the top-level `session` key if providing manual configuration.

```diff
ascorbic marked this conversation as resolved.
Show resolved Hide resolved
defineConfig({
// ...
experimental: {
- session: {
- driver: 'fs',
- },
+ session: true,
},
+ session: {
+ driver: 'fs',
+ },
});
```

You do not need to configure the session driver if you are using the Node or Netlify adapters and want to use the default session drivers for each adapter. The default session driver for the Node adapter is `fs`, and the default session driver for the Netlify adapter is `netlify-blobs`.

For example, if you are using the Node adapter, you can just enable the flag:

```js
defineConfig({
// ...
adapter: node({
mode: 'standalone',
}),
experimental: {
session: true,
},
});
```
This will configure the session driver to use the default `fs` driver for the Node adapter. See the release notes for `@astrojs/node` and `@astrojs/netlify` for more information on the default session drivers for each adapter.

If you enable the flag but are using an adapter that does not have a default session driver, you will need to configure the session driver manually or the build will fail.
ematipico marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 2 additions & 2 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function vitePluginManifest(options: StaticBuildOptions, internals: BuildInterna
];

const resolvedDriver = await resolveSessionDriver(
options.settings.config.experimental?.session?.driver,
options.settings.config.session?.driver,
);

const contents = [
Expand Down Expand Up @@ -304,6 +304,6 @@ function buildManifest(
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
serverIslandNameMap: Array.from(settings.serverIslandNameMap),
key: encodedKey,
sessionConfig: settings.config.experimental.session,
sessionConfig: settings.config.session,
};
}
53 changes: 27 additions & 26 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,14 @@ export const ASTRO_CONFIG_DEFAULTS = {
schema: {},
validateSecrets: false,
},
session: undefined,
experimental: {
clientPrerender: false,
contentIntellisense: false,
responsiveImages: false,
svg: false,
serializeConfig: false,
session: false,
},
} satisfies AstroUserConfig & { server: { open: boolean } };

Expand Down Expand Up @@ -522,6 +524,30 @@ export const AstroConfigSchema = z.object({
.strict()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.env),
session: z
.object({
driver: z.string(),
options: z.record(z.any()).optional(),
cookie: z
.object({
name: z.string().optional(),
domain: z.string().optional(),
path: z.string().optional(),
maxAge: z.number().optional(),
sameSite: z.union([z.enum(['strict', 'lax', 'none']), z.boolean()]).optional(),
secure: z.boolean().optional(),
})
.or(z.string())
.transform((val) => {
if (typeof val === 'string') {
return { name: val };
}
return val;
})
.optional(),
ttl: z.number().optional(),
})
.optional(),
experimental: z
.object({
clientPrerender: z
Expand All @@ -536,32 +562,7 @@ export const AstroConfigSchema = z.object({
.boolean()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.responsiveImages),
session: z
.object({
driver: z.string(),
options: z.record(z.any()).optional(),
cookie: z
.union([
z.object({
name: z.string().optional(),
domain: z.string().optional(),
path: z.string().optional(),
maxAge: z.number().optional(),
sameSite: z.union([z.enum(['strict', 'lax', 'none']), z.boolean()]).optional(),
secure: z.boolean().optional(),
}),
z.string(),
])
.transform((val) => {
if (typeof val === 'string') {
return { name: val };
}
return val;
})
.optional(),
ttl: z.number().optional(),
})
.optional(),
session: z.boolean().optional(),
svg: z
.union([
z.boolean(),
Expand Down
118 changes: 86 additions & 32 deletions packages/astro/src/core/errors/errors-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -881,38 +881,6 @@ export const AstroResponseHeadersReassigned = {
hint: 'Consider using `Astro.response.headers.add()`, and `Astro.response.headers.delete()`.',
} satisfies ErrorData;

/**
* @docs
* @message Error when initializing session storage with driver `DRIVER`. `ERROR`
* @see
* - [experimental.session](https://docs.astro.build/en/reference/experimental-flags/sessions/)
* @description
* Thrown when the session storage could not be initialized.
*/
export const SessionStorageInitError = {
name: 'SessionStorageInitError',
title: 'Session storage could not be initialized.',
message: (error: string, driver?: string) =>
`Error when initializing session storage${driver ? ` with driver \`${driver}\`` : ''}. \`${error ?? ''}\``,
hint: 'For more information, see https://docs.astro.build/en/reference/experimental-flags/sessions/',
} satisfies ErrorData;

/**
* @docs
* @message Error when saving session data with driver `DRIVER`. `ERROR`
* @see
* - [experimental.session](https://docs.astro.build/en/reference/experimental-flags/sessions/)
* @description
* Thrown when the session data could not be saved.
*/
export const SessionStorageSaveError = {
name: 'SessionStorageSaveError',
title: 'Session data could not be saved.',
message: (error: string, driver?: string) =>
`Error when saving session data${driver ? ` with driver \`${driver}\`` : ''}. \`${error ?? ''}\``,
hint: 'For more information, see https://docs.astro.build/en/reference/experimental-flags/sessions/',
} satisfies ErrorData;

/**
* @docs
* @description
Expand Down Expand Up @@ -1838,6 +1806,92 @@ export const ActionCalledFromServerError = {
// Generic catch-all - Only use this in extreme cases, like if there was a cosmic ray bit flip.
export const UnknownError = { name: 'UnknownError', title: 'Unknown Error.' } satisfies ErrorData;


/**
* @docs
* @kind heading
* @name Session Errors
*/
// Session Errors
/**
* @docs
* @see
* - [On-demand rendering](https://docs.astro.build/en/guides/on-demand-rendering/)
* @description
* Your project must have a server output to use sessions.
*/
export const SessionWithoutServerOutputError = {
name: 'SessionWithoutServerOutputError',
title: 'Sessions must be used with server output.',
message:
'A server is required to use sessions. To deploy routes to a server, add an adapter to your Astro config and configure your route for on-demand rendering',
hint: 'Add an adapter and enable on-demand rendering: https://docs.astro.build/en/guides/on-demand-rendering/',
} satisfies ErrorData;

/**
* @docs
* @message Error when initializing session storage with driver `DRIVER`. `ERROR`
* @see
* - [experimental.session](https://docs.astro.build/en/reference/experimental-flags/sessions/)
* @description
* Thrown when the session storage could not be initialized.
*/
export const SessionStorageInitError = {
name: 'SessionStorageInitError',
title: 'Session storage could not be initialized.',
message: (error: string, driver?: string) =>
`Error when initializing session storage${driver ? ` with driver \`${driver}\`` : ''}. \`${error ?? ''}\``,
hint: 'For more information, see https://docs.astro.build/en/reference/experimental-flags/sessions/',
} satisfies ErrorData;

/**
* @docs
* @message Error when saving session data with driver `DRIVER`. `ERROR`
* @see
* - [experimental.session](https://docs.astro.build/en/reference/experimental-flags/sessions/)
* @description
* Thrown when the session data could not be saved.
*/
export const SessionStorageSaveError = {
name: 'SessionStorageSaveError',
title: 'Session data could not be saved.',
message: (error: string, driver?: string) =>
`Error when saving session data${driver ? ` with driver \`${driver}\`` : ''}. \`${error ?? ''}\``,
hint: 'For more information, see https://docs.astro.build/en/reference/experimental-flags/sessions/',
} satisfies ErrorData;

/**
* @docs
* @message The `experimental.session` flag was set to `true`, but no storage was configured. Either configure the storage manually or use an adapter that provides session storage
* @see
* - [experimental.session](https://docs.astro.build/en/reference/experimental-flags/sessions/)
* @description
* Thrown when session storage is enabled but not configured.
*/
export const SessionConfigMissingError = {
name: 'SessionConfigMissingError',
title: 'Session storage was enabled but not configured.',
message: 'The `experimental.session` flag was set to `true`, but no storage was configured. Either configure the storage manually or use an adapter that provides session storage',
hint: 'See https://docs.astro.build/en/reference/experimental-flags/sessions/',
} satisfies ErrorData;

/**
* @docs
* @message Session config was provided without enabling the `experimental.session` flag
* @see
* - [experimental.session](https://docs.astro.build/en/reference/experimental-flags/sessions/)
* @description
* Thrown when session storage is configured but the `experimental.session` flag is not enabled.
*/
export const SessionConfigWithoutFlagError = {
name: 'SessionConfigWithoutFlagError',
title: 'Session flag not set',
message: 'Session config was provided without enabling the `experimental.session` flag',
hint: 'See https://docs.astro.build/en/reference/experimental-flags/sessions/',
} satisfies ErrorData;



/*
* Adding an error? Follow these steps:
* 1. Determine in which category it belongs (Astro, Vite, CSS, Content Collections etc.)
Expand Down
24 changes: 23 additions & 1 deletion packages/astro/src/core/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@ import type {
} from '../types/public/config.js';
import type { AstroCookies } from './cookies/cookies.js';
import type { AstroCookieSetOptions } from './cookies/cookies.js';
import { SessionStorageInitError, SessionStorageSaveError } from './errors/errors-data.js';
import {
SessionConfigMissingError,
SessionConfigWithoutFlagError,
SessionStorageInitError,
SessionStorageSaveError,
SessionWithoutServerOutputError,
} from './errors/errors-data.js';
import { AstroError } from './errors/index.js';
import type { AstroSettings } from '../types/astro.js';

export const PERSIST_SYMBOL = Symbol();

Expand Down Expand Up @@ -474,3 +481,18 @@ export function resolveSessionDriver(driver: string | undefined): Promise<string
}
return driver;
}

export function validateSessionConfig(settings: AstroSettings): void {
Copy link
Member

Choose a reason for hiding this comment

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

Why don't we validate the configuration inside the zod schema using refine or superRefine?

EDIT: I understand why we need a function, I think it's worth adding a comment that explains that

let error: AstroError | null = null;
if (settings.config.experimental.session && !settings.config.session?.driver) {
error = new AstroError(SessionConfigMissingError);
} else if (settings.config.session?.driver && !settings.config.experimental.session) {
error = new AstroError(SessionConfigWithoutFlagError);
} else if (settings.buildOutput === 'static') {
error = new AstroError(SessionWithoutServerOutputError);
}
if (error) {
error.stack = undefined;
throw error;
}
}
2 changes: 2 additions & 0 deletions packages/astro/src/integrations/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {
} from '../types/public/integrations.js';
import type { RouteData } from '../types/public/internal.js';
import { validateSupportedFeatures } from './features-validation.js';
import { validateSessionConfig } from '../core/session.js';

async function withTakingALongTimeMsg<T>({
name,
Expand Down Expand Up @@ -369,6 +370,7 @@ export async function runHookConfigDone({
});
}
}
validateSessionConfig(settings);
}

export async function runHookServerSetup({
Expand Down
Loading
Loading