y-gui R2 Storage Solution: How User Data Is Structured in Cloudflare R2
y-gui persists user-specific data in a Cloudflare R2 bucket bound as CHAT_R2, organizing all files under unique user prefixes generated from MD5-hashed and sanitized email addresses to ensure complete data isolation.
The y-gui application leverages Cloudflare's R2 object storage as its primary persistence layer for user data. By implementing a deterministic prefix generation strategy based on user email addresses, y-gui creates isolated namespaces within a single R2 bucket. This article examines the complete y-gui storage solution R2 implementation, including the user data structure, prefix calculation logic, and practical implementation details drawn directly from the source code.
Cloudflare R2 Storage Architecture in y-gui
The storage layer is defined in backend/src/worker-configuration.d.ts, where the R2 bucket is bound to the Cloudflare Worker environment as CHAT_R2. This binding allows the application to perform object storage operations directly within the serverless runtime without external API calls.
R2 Bucket Binding and Configuration
The worker configuration declares CHAT_R2 as an R2Bucket type, making it available throughout the application. The actual bucket connection is managed through backend/wrangler.toml and backend/wrangler.toml.example, which define the R2 bucket binding for deployment.
User Data Isolation Strategy
Rather than provisioning separate buckets per user, y-gui implements a prefix-based isolation strategy. All user data resides in the same CHAT_R2 bucket, but each user operates within a unique directory prefix. This approach simplifies infrastructure management while maintaining strict data separation and access control.
How y-gui Structures User Data in R2
The user data structure centers on a deterministic prefix generation algorithm implemented in backend/src/utils/user.ts. This utility creates unique identifiers that serve as the root directory for all user-specific files.
Generating the User Prefix from Email
The calculateUserPrefix function transforms a user's email address into a unique storage prefix through a three-step process:
- The email is hashed using MD5 to create a unique identifier.
- Special characters are sanitized:
@becomes_at_and.becomes_dot_. - The final prefix combines both elements in the format:
{md5(email)}_{sanitizedEmail}
For example, alice@example.com becomes 3b5d5c3712955042212316173ccf37be_alice_at_example_dot_com.
File Organization Under User Directories
Once the prefix is generated, all user data is stored as JSONL (JSON Lines) files under that prefix path. The repository layer in backend/src/repository/r2/chat-r2-repository.ts handles CRUD operations for files such as:
{userPrefix}/integration_config.jsonl— Stores third-party service configurations{userPrefix}/chat_history.jsonl— Persists conversation records
This structure ensures that querying for a specific prefix returns all objects belonging to that user, while the flat bucket architecture enables global operations like maintenance or analytics.
Working with User Data: Code Examples
The following examples demonstrate how to interact with the y-gui R2 storage solution using the utility functions provided in the codebase.
Calculating User Prefixes
Use the calculateUserPrefix function from backend/src/utils/user.ts to generate the storage prefix for any user email:
// Generate a user‑specific prefix
import { calculateUserPrefix } from './utils/user';
const email = 'alice@example.com';
const prefix = await calculateUserPrefix(email);
// Example output: "3b5d5c3712955042212316173ccf37be_alice_at_example_dot_com"
Listing All User Directories
The listUserPrefixes function in backend/src/utils/storage.ts scans the entire R2 bucket and returns all unique user prefixes:
// List all user prefixes stored in the R2 bucket
import { listUserPrefixes } from './utils/storage';
const prefixes = await listUserPrefixes(env.CHAT_R2);
// prefixes = ['3b5d5c3712955042212316173ccf37be_alice_at_example_dot_com', ...]
This utility extracts the directory portion of each object key (everything before the final /), deduplicates the results, and enables bulk operations such as global token refreshes or maintenance tasks across all user accounts.
Storing Files in R2
To persist data for a specific user, construct the object key using the prefix and invoke the put method on the CHAT_R2 binding:
// Storing a new JSONL file for a user
const key = `${prefix}/integration_config.jsonl`;
await env.CHAT_R2.put(key, jsonlData, {
httpMetadata: { contentType: 'application/jsonl' },
});
Key Implementation Files
The y-gui R2 storage solution is implemented across the following source files:
| File | Role |
|---|---|
backend/src/worker-configuration.d.ts |
Declares the CHAT_R2 binding for the Cloudflare worker. |
backend/src/utils/user.ts |
Generates the unique user prefix from an e‑mail address. |
backend/src/utils/storage.ts |
Provides listUserPrefixes to enumerate all user directories in R2. |
backend/src/repository/r2/chat-r2-repository.ts |
Handles CRUD operations for chat data stored under user‑prefix folders. |
backend/wrangler.toml & backend/wrangler.toml.example |
Configuration for deploying the worker with the R2 bucket binding. |
Summary
The y-gui application implements a robust storage solution using Cloudflare R2 with the following key characteristics:
- Single bucket architecture: All user data resides in one
CHAT_R2bucket bound to the Cloudflare Worker. - Deterministic user prefixes: Each user is assigned a unique directory prefix generated from an MD5 hash of their email plus a sanitized version of the address (replacing
@with_at_and.with_dot_). - Isolated file organization: User-specific files such as
integration_config.jsonlandchat_history.jsonlare stored under the user's prefix, ensuring complete data separation. - Bulk operation support: The
listUserPrefixesutility enables scanning the entire bucket to identify all active users for maintenance or global updates.
Frequently Asked Questions
What storage solution does y-gui use?
y-gui uses Cloudflare R2 as its primary storage solution. The R2 bucket is bound to the Cloudflare Worker as CHAT_R2, allowing direct object storage operations within the serverless environment. This provides S3-compatible API access without egress fees, making it cost-effective for storing user chat histories and configuration data.
How does y-gui ensure user data isolation in a shared R2 bucket?
y-gui ensures isolation through prefix-based namespacing rather than separate buckets. Each user receives a unique directory prefix generated by the calculateUserPrefix function in backend/src/utils/user.ts. This prefix combines an MD5 hash of the user's email with a sanitized version of the address, ensuring that all files belonging to a user are stored under their exclusive namespace while sharing the underlying CHAT_R2 bucket infrastructure.
What is the format of user data files in y-gui?
User data is stored as JSONL (JSON Lines) files, with each line representing a separate JSON record. Common files include integration_config.jsonl for third-party service settings and chat_history.jsonl for conversation records. This append-only format supports efficient streaming writes and reads, which is optimal for chat applications that generate continuous event logs. Files are organized under each user's unique prefix in the R2 bucket.
How can I list all users stored in the y-gui R2 bucket?
Use the listUserPrefixes function exported from backend/src/utils/storage.ts. This utility scans all objects in the CHAT_R2 bucket, extracts the directory portion of each object key (everything before the final /), and returns a deduplicated list of active user prefixes. This enables bulk operations such as global token refreshes, maintenance tasks, or analytics across all user accounts without requiring a separate user registry database.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →