Skip to content
Merged
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
45 changes: 33 additions & 12 deletions packages/astro/src/core/session/vite-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { fileURLToPath } from 'node:url';

import { type BuiltinDriverName, builtinDrivers } from 'unstorage';
import type { Plugin as VitePlugin } from 'vite';
import type { AstroSettings } from '../../types/astro.js';
import { SessionStorageInitError } from '../errors/errors-data.js';
import { AstroError } from '../errors/index.js';

const VIRTUAL_SESSION_DRIVER_ID = 'virtual:astro:session-driver';
const RESOLVED_VIRTUAL_SESSION_DRIVER_ID = '\0' + VIRTUAL_SESSION_DRIVER_ID;
Expand All @@ -12,25 +16,42 @@ export function vitePluginSessionDriver({ settings }: { settings: AstroSettings

async resolveId(id) {
if (id === VIRTUAL_SESSION_DRIVER_ID) {
return RESOLVED_VIRTUAL_SESSION_DRIVER_ID;
}
},

async load(id) {
if (id === RESOLVED_VIRTUAL_SESSION_DRIVER_ID) {
if (settings.config.session) {
let sessionDriver: string;
if (settings.config.session.driver === 'fs') {
return await this.resolve(builtinDrivers.fsLite);
sessionDriver = builtinDrivers.fsLite;
} else if (
settings.config.session.driver &&
settings.config.session.driver in builtinDrivers
) {
sessionDriver = builtinDrivers[settings.config.session.driver as BuiltinDriverName];
} else {
return { code: 'export default null;' };
}
if (settings.config.session.driver && settings.config.session.driver in builtinDrivers) {
return await this.resolve(
builtinDrivers[settings.config.session.driver as BuiltinDriverName],
);
const importerPath = fileURLToPath(import.meta.url);
const resolved = await this.resolve(sessionDriver, importerPath);
if (!resolved) {
throw new AstroError({
...SessionStorageInitError,
message: SessionStorageInitError.message(
`Failed to resolve session driver: ${sessionDriver}`,
settings.config.session.driver,
),
});
}
return {
code: `import { default as _default } from '${resolved.id}';\nexport * from '${resolved.id}';\nexport default _default;`,
};
} else {
return RESOLVED_VIRTUAL_SESSION_DRIVER_ID;
return { code: 'export default null;' };
}
}
},

async load(id) {
if (id === RESOLVED_VIRTUAL_SESSION_DRIVER_ID) {
return { code: 'export default null;' };
}
},
};
}
11 changes: 4 additions & 7 deletions packages/integrations/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import { createGetEnv } from './utils/env.js';
import { createRoutesFile, getParts } from './utils/generate-routes-json.js';
import { type ImageService, setImageConfig } from './utils/image-config.js';
import { createConfigPlugin } from './vite-plugin-config.js';

export type { Runtime } from './utils/handler.js';

Expand Down Expand Up @@ -247,6 +248,9 @@ export default function createIntegration(args?: Options): AstroIntegration {
}
},
},
createConfigPlugin({
sessionKVBindingName: SESSION_KV_BINDING_NAME,
}),
],
},
image: setImageConfig(args?.imageService ?? 'compile', config.image, command, logger),
Expand Down Expand Up @@ -316,9 +320,6 @@ export default function createIntegration(args?: Options): AstroIntegration {

setProcessEnv(_config, platformProxy.env);

globalThis.__env__ ??= {};
globalThis.__env__[SESSION_KV_BINDING_NAME] = platformProxy.env[SESSION_KV_BINDING_NAME];

const clientLocalsSymbol = Symbol.for('astro.locals');

server.middlewares.use(async function middleware(req, _res, next) {
Expand Down Expand Up @@ -385,10 +386,6 @@ export default function createIntegration(args?: Options): AstroIntegration {
// in a global way, so we shim their access as `process.env.*`. This is not the recommended way for users to access environment variables. But we'll add this for compatibility for chosen variables. Mainly to support `@astrojs/db`
vite.define = {
'process.env': 'process.env',
// Allows the request handler to know what the binding name is
'globalThis.__ASTRO_SESSION_BINDING_NAME': JSON.stringify(
args?.sessionKVBindingName ?? 'SESSION',
),
'globalThis.__ASTRO_IMAGES_BINDING_NAME': JSON.stringify(
args?.imagesBindingName ?? 'IMAGES',
),
Expand Down
19 changes: 8 additions & 11 deletions packages/integrations/cloudflare/src/utils/handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-expect-error - It is safe to expect the error here.
import { env as globalEnv } from 'cloudflare:workers';
import { sessionKVBindingName } from 'virtual:astro-cloudflare:config';
import type {
Response as CfResponse,
CacheStorage as CloudflareCacheStorage,
Expand Down Expand Up @@ -27,14 +28,8 @@ export interface Runtime<T extends object = object> {
}

declare global {
// This is not a real global, but is injected using Vite define to allow us to specify the session binding name in the config.
var __ASTRO_SESSION_BINDING_NAME: string;

// This is not a real global, but is injected using Vite define to allow us to specify the Images binding name in the config.
var __ASTRO_IMAGES_BINDING_NAME: string;

// Just used to pass the KV binding to unstorage.
var __env__: Partial<Env>;
}

export async function handle(
Expand All @@ -44,11 +39,13 @@ export async function handle(
): Promise<CfResponse> {
const app = createApp(import.meta.env.DEV);
const { pathname } = new URL(request.url);
const bindingName = globalThis.__ASTRO_SESSION_BINDING_NAME;
// Assigning the KV binding to globalThis allows unstorage to access it for session storage.
// unstorage checks in globalThis and globalThis.__env__ for the binding.
globalThis.__env__ ??= {};
globalThis.__env__[bindingName] = env[bindingName];

if (env[sessionKVBindingName]) {
const sessionConfigOptions = app.manifest.sessionConfig?.options ?? {};
Object.assign(sessionConfigOptions, {
binding: env[sessionKVBindingName],
});
}

// static assets fallback, in case default _routes.json is not used
if (app.manifest.assets.has(pathname)) {
Expand Down
24 changes: 24 additions & 0 deletions packages/integrations/cloudflare/src/vite-plugin-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { PluginOption } from 'vite';

const VIRTUAL_CONFIG_ID = 'virtual:astro-cloudflare:config';
const RESOLVED_VIRTUAL_CONFIG_ID = '\0' + VIRTUAL_CONFIG_ID;

interface CloudflareConfig {
sessionKVBindingName: string;
}

export function createConfigPlugin(config: CloudflareConfig): PluginOption {
return {
name: 'vite:astro-cloudflare-config',
resolveId(id) {
if (id === VIRTUAL_CONFIG_ID) {
return RESOLVED_VIRTUAL_CONFIG_ID;
}
},
load(id) {
if (id === RESOLVED_VIRTUAL_CONFIG_ID) {
return `export const sessionKVBindingName = ${JSON.stringify(config.sessionKVBindingName)};`;
}
},
};
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
---
export const prerender = false; // Not needed with 'server' output
const cart = await Astro.session?.get('cart');
const user = await Astro.session?.get('user');
let user = await Astro.session?.get('user');

if(Astro.request.method === 'POST') {
const data = await Astro.request.formData();
const newId = data.get('id');
const newName = data.get('name');

if(newId && newName) {
user = {
id: newId,
name: newName
};
Astro.session?.set('user', user);
}
}
---

<h1>
Sessions
</h1>
<h2>
Cart
</h2>
<p>
<a href="/checkout">🛒 {cart?.length ?? 0} items</a>
</p>
<html>
<head>
<meta charset="utf-8" />
<title>Sessions</title>
</head>
<body>
<h1>Sessions</h1>
<h2>Cart</h2>
<p>
<a href="/checkout">🛒 {cart?.length ?? 0} items</a>
Copy link
Member

Choose a reason for hiding this comment

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

Checkout doesn't exist. Tested locally and it gives 404

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was already in here, look at the diff. I think you added it :D

I only added to the demo to confirm that it was indeed saving to the cache.

</p>

<h2>User</h2>
<p>Id: {user?.id}</p>
<p>Name: {user?.name}</p>

<h2>
User
</h2>
<p>
Id: {user?.id}
</p>
<p>
Name: {user?.name}
</p>
<form method="post">
<h3>Change</h3>
<input type="text" name="id" value={user?.id} />
<input type="text" name="name" value={user?.name} />
<input type="submit" value="Submit" />
</form>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,11 @@
},
"images": {
"binding": "IMAGES"
}
},
"kv_namespaces": [
{
"binding": "SESSION",
"id": "SESSION"
}
Comment on lines +15 to +19
Copy link
Member

Choose a reason for hiding this comment

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

Could you let me know where you found the docs for this? I couldn't make it working

Copy link
Contributor Author

Choose a reason for hiding this comment

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

]
}
2 changes: 1 addition & 1 deletion packages/integrations/cloudflare/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src"],
"include": ["src", "virtual.d.ts"],
"compilerOptions": {
"outDir": "./dist"
}
Expand Down
4 changes: 4 additions & 0 deletions packages/integrations/cloudflare/virtual.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module 'virtual:astro-cloudflare:config' {
export const sessionKVBindingName: string;
// Additional exports can be added here in the future
}
Loading