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

wip: sessions API #12441

Draft
wants to merge 17 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/poor-mangos-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Adds experimental session support
1 change: 1 addition & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
"tsconfck": "^3.1.4",
"ultrahtml": "^1.5.3",
"unist-util-visit": "^5.0.0",
"unstorage": "^1.12.0",
"vfile": "^6.0.3",
"vite": "6.0.0-beta.6",
"vitefu": "^1.0.3",
Expand Down
14 changes: 10 additions & 4 deletions packages/astro/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import type { UserConfig as ViteUserConfig, UserConfigFn as ViteUserConfigFn } from 'vite';
import { createRouteManifest } from '../core/routing/index.js';
import type { AstroInlineConfig, AstroUserConfig, Locales } from '../types/public/config.js';
import type {
AstroInlineConfig,
AstroUserConfig,
Locales,
SessionDriverName,
} from '../types/public/config.js';
import { createDevelopmentManifest } from '../vite-plugin-astro-server/plugin.js';

/**
* See the full Astro Configuration API Documentation
* https://astro.build/config
*/
export function defineConfig<const TLocales extends Locales = never>(
config: AstroUserConfig<TLocales>,
) {
export function defineConfig<
const TLocales extends Locales = never,
const TDriver extends SessionDriverName = never,
>(config: AstroUserConfig<TLocales, TDriver>) {
return config;
}

Expand Down
9 changes: 9 additions & 0 deletions packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { RenderContext } from '../render-context.js';
import { createAssetLink } from '../render/ssr-element.js';
import { createDefaultRoutes, injectDefaultRoutes } from '../routing/default.js';
import { matchRoute } from '../routing/match.js';
import { type AstroSession, PERSIST_SYMBOL } from '../session.js';
import { AppPipeline } from './pipeline.js';

export { deserializeManifest } from './common.js';
Expand Down Expand Up @@ -277,6 +278,7 @@ export class App {
const defaultStatus = this.#getDefaultStatusCode(routeData, pathname);

let response;
let session: AstroSession | undefined;
try {
// Load route module. We also catch its error here if it fails on initialization
const mod = await this.#pipeline.getModuleForRoute(routeData);
Expand All @@ -289,10 +291,13 @@ export class App {
routeData,
status: defaultStatus,
});
session = renderContext.session;
response = await renderContext.render(await mod.page());
} catch (err: any) {
this.#logger.error(null, err.stack || err.message || String(err));
return this.#renderError(request, { locals, status: 500, error: err });
} finally {
session?.[PERSIST_SYMBOL]();
}

if (
Expand Down Expand Up @@ -376,6 +381,7 @@ export class App {
}
}
const mod = await this.#pipeline.getModuleForRoute(errorRouteData);
let session: AstroSession | undefined;
try {
const renderContext = await RenderContext.create({
locals,
Expand All @@ -387,6 +393,7 @@ export class App {
status,
props: { error },
});
session = renderContext.session;
const response = await renderContext.render(await mod.page());
return this.#mergeResponses(response, originalResponse);
} catch {
Expand All @@ -399,6 +406,8 @@ export class App {
skipMiddleware: true,
});
}
} finally {
session?.[PERSIST_SYMBOL]();
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RoutingStrategies } from '../../i18n/utils.js';
import type { ComponentInstance, SerializedRouteData } from '../../types/astro.js';
import type { AstroMiddlewareInstance } from '../../types/public/common.js';
import type { Locales } from '../../types/public/config.js';
import type { Locales, SessionConfig } from '../../types/public/config.js';
import type {
RouteData,
SSRComponentMetadata,
Expand Down Expand Up @@ -70,6 +70,7 @@ export type SSRManifest = {
middleware?: () => Promise<AstroMiddlewareInstance> | AstroMiddlewareInstance;
checkOrigin: boolean;
envGetSecretEnabled: boolean;
sessionConfig?: SessionConfig<any>;
};

export type SSRManifestI18n = {
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,5 +277,6 @@ function buildManifest(
envGetSecretEnabled:
(unwrapSupportKind(settings.adapter?.supportedAstroFeatures.envGetSecret) ??
'unsupported') !== 'unsupported',
sessionConfig: settings.config.experimental.session,
};
}
25 changes: 25 additions & 0 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,31 @@ 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(),
})
.optional(),
svg: z
.union([
z.boolean(),
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/create-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import markdownVitePlugin from '../vite-plugin-markdown/index.js';
import astroScannerPlugin from '../vite-plugin-scanner/index.js';
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
import vitePluginSessions from '../vite-plugin-sessions/index.js';
import { vitePluginSSRManifest } from '../vite-plugin-ssr-manifest/index.js';
import type { SSRManifest } from './app/types.js';
import type { Logger } from './logger/core.js';
Expand Down Expand Up @@ -143,6 +144,7 @@ export async function createVite(
astroLoadFallbackPlugin({ fs, root: settings.config.root }),
astroVitePlugin({ settings, logger }),
astroScriptsPlugin({ settings }),
vitePluginSessions({ settings }),
// The server plugin is for dev only and having it run during the build causes
// the build to run very slow as the filewatcher is triggered often.
command === 'dev' && vitePluginAstroServer({ settings, logger, fs, manifest, ssrManifest }), // ssrManifest is only required in dev mode, where it gets created before a Vite instance is created, and get passed to this function
Expand Down
30 changes: 30 additions & 0 deletions packages/astro/src/core/errors/errors-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,36 @@ export const AstroResponseHeadersReassigned = {
hint: 'Consider using `Astro.response.headers.add()`, and `Astro.response.headers.delete()`.',
} satisfies ErrorData;

/**
* @docs
* @see
* - [experimental.session](https://5-0-0-beta.docs.astro.build/en/reference/configuration-reference/#experimentalsession)
* @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://5-0-0-beta.docs.astro.build/en/reference/configuration-reference/#experimentalsession',
} satisfies ErrorData;

/**
* @docs
* @see
* - [experimental.session](https://5-0-0-beta.docs.astro.build/en/reference/configuration-reference/#experimentalsession)
* @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://5-0-0-beta.docs.astro.build/en/reference/configuration-reference/#experimentalsession',
} satisfies ErrorData;

/**
* @docs
* @description
Expand Down
10 changes: 8 additions & 2 deletions packages/astro/src/core/render-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { type Pipeline, Slots, getParams, getProps } from './render/index.js';
import { isRoute404or500 } from './routing/match.js';
import { copyRequest, getOriginPathname, setOriginPathname } from './routing/rewrite.js';
import { SERVER_ISLAND_COMPONENT } from './server-islands/endpoint.js';
import { AstroSession } from './session.js';

export const apiContextRoutesSymbol = Symbol.for('context.routes');

Expand All @@ -53,6 +54,9 @@ export class RenderContext {
protected url = new URL(request.url),
public props: Props = {},
public partial: undefined | boolean = undefined,
public session: AstroSession | undefined = pipeline.manifest.sessionConfig
? new AstroSession(cookies, pipeline.manifest.sessionConfig)
: undefined,
) {}

/**
Expand Down Expand Up @@ -297,7 +301,7 @@ export class RenderContext {

createActionAPIContext(): ActionAPIContext {
const renderContext = this;
const { cookies, params, pipeline, url } = this;
const { cookies, params, pipeline, url, session } = this;
const generator = `Astro v${ASTRO_VERSION}`;

const rewrite = async (reroutePayload: RewritePayload) => {
Expand Down Expand Up @@ -335,6 +339,7 @@ export class RenderContext {
get originPathname() {
return getOriginPathname(renderContext.request);
},
session,
};
}

Expand Down Expand Up @@ -467,7 +472,7 @@ export class RenderContext {
astroStaticPartial: AstroGlobalPartial,
): Omit<AstroGlobal, 'props' | 'self' | 'slots'> {
const renderContext = this;
const { cookies, locals, params, pipeline, url } = this;
const { cookies, locals, params, pipeline, url, session } = this;
const { response } = result;
const redirect = (path: string, status = 302) => {
// If the response is already sent, error as we cannot proceed with the redirect.
Expand All @@ -489,6 +494,7 @@ export class RenderContext {
routePattern: this.routeData.route,
isPrerendered: this.routeData.prerender,
cookies,
session,
get clientAddress() {
return renderContext.clientAddress();
},
Expand Down
Loading
Loading