Skip to content

Commit

Permalink
refactor(@remix-run/server-runtime): define RemixServerRuntime interface
Browse files Browse the repository at this point in the history
  • Loading branch information
pcattori committed Mar 17, 2022
1 parent e3ac1a9 commit 8bff58c
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 240 deletions.
72 changes: 6 additions & 66 deletions packages/remix-server-runtime/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,79 +5,19 @@ import { parse, serialize } from "cookie";
// global `sign` and `unsign` functions.
//import { sign, unsign } from "./cookieSigning";
import "./cookieSigning";
import type { Cookie, CreateCookie, IsCookie } from "./interface/cookies";

export type { CookieParseOptions, CookieSerializeOptions };

export interface CookieSignatureOptions {
/**
* An array of secrets that may be used to sign/unsign the value of a cookie.
*
* The array makes it easy to rotate secrets. New secrets should be added to
* the beginning of the array. `cookie.serialize()` will always use the first
* value in the array, but `cookie.parse()` may use any of them so that
* cookies that were signed with older secrets still work.
*/
secrets?: string[];
}

export type CookieOptions = CookieParseOptions &
CookieSerializeOptions &
CookieSignatureOptions;

/**
* A HTTP cookie.
*
* A Cookie is a logical container for metadata about a HTTP cookie; its name
* and options. But it doesn't contain a value. Instead, it has `parse()` and
* `serialize()` methods that allow a single instance to be reused for
* parsing/encoding multiple different values.
*
* @see https://remix.run/api/remix#cookie-api
*/
export interface Cookie {
/**
* The name of the cookie, used in the `Cookie` and `Set-Cookie` headers.
*/
readonly name: string;

/**
* True if this cookie uses one or more secrets for verification.
*/
readonly isSigned: boolean;

/**
* The Date this cookie expires.
*
* Note: This is calculated at access time using `maxAge` when no `expires`
* option is provided to `createCookie()`.
*/
readonly expires?: Date;

/**
* Parses a raw `Cookie` header and returns the value of this cookie or
* `null` if it's not present.
*/
parse(
cookieHeader: string | null,
options?: CookieParseOptions
): Promise<any>;

/**
* Serializes the given value to a string and returns the `Set-Cookie`
* header.
*/
serialize(value: any, options?: CookieSerializeOptions): Promise<string>;
}

/**
* Creates a logical container for managing a browser cookie from the server.
*
* @see https://remix.run/api/remix#createcookie
*/
export function createCookie(
name: string,
cookieOptions: CookieOptions = {}
): Cookie {
export const createCookie: CreateCookie = (
name,
cookieOptions = {}
) => {
let { secrets, ...options } = {
secrets: [],
path: "/",
Expand Down Expand Up @@ -124,7 +64,7 @@ export function createCookie(
*
* @see https://remix.run/api/remix#iscookie
*/
export function isCookie(object: any): object is Cookie {
export const isCookie: IsCookie = (object): object is Cookie => {
return (
object != null &&
typeof object.name === "string" &&
Expand Down
6 changes: 4 additions & 2 deletions packages/remix-server-runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ export type {
export type {
CookieParseOptions,
CookieSerializeOptions,
} from "./cookies";
export type {
CookieSignatureOptions,
CookieOptions,
Cookie,
} from "./cookies";
} from './interface/cookies'
export { createCookie, isCookie } from "./cookies";

export type { AppLoadContext, AppData } from "./data";
Expand Down Expand Up @@ -50,7 +52,7 @@ export type {
Session,
SessionStorage,
SessionIdStorageStrategy,
} from "./sessions";
} from "./interface/sessions";
export { createSession, isSession, createSessionStorage } from "./sessions";
export { createCookieSessionStorage } from "./sessions/cookieStorage";
export { createMemorySessionStorage } from "./sessions/memoryStorage";
65 changes: 65 additions & 0 deletions packages/remix-server-runtime/interface/cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { CookieParseOptions, CookieSerializeOptions } from "cookie";

export interface CookieSignatureOptions {
/**
* An array of secrets that may be used to sign/unsign the value of a cookie.
*
* The array makes it easy to rotate secrets. New secrets should be added to
* the beginning of the array. `cookie.serialize()` will always use the first
* value in the array, but `cookie.parse()` may use any of them so that
* cookies that were signed with older secrets still work.
*/
secrets?: string[];
}

export type CookieOptions = CookieParseOptions &
CookieSerializeOptions &
CookieSignatureOptions;

/**
* A HTTP cookie.
*
* A Cookie is a logical container for metadata about a HTTP cookie; its name
* and options. But it doesn't contain a value. Instead, it has `parse()` and
* `serialize()` methods that allow a single instance to be reused for
* parsing/encoding multiple different values.
*
* @see https://remix.run/api/remix#cookie-api
*/
export interface Cookie {
/**
* The name of the cookie, used in the `Cookie` and `Set-Cookie` headers.
*/
readonly name: string;

/**
* True if this cookie uses one or more secrets for verification.
*/
readonly isSigned: boolean;

/**
* The Date this cookie expires.
*
* Note: This is calculated at access time using `maxAge` when no `expires`
* option is provided to `createCookie()`.
*/
readonly expires?: Date;

/**
* Parses a raw `Cookie` header and returns the value of this cookie or
* `null` if it's not present.
*/
parse(
cookieHeader: string | null,
options?: CookieParseOptions
): Promise<any>;

/**
* Serializes the given value to a string and returns the `Set-Cookie`
* header.
*/
serialize(value: any, options?: CookieSerializeOptions): Promise<string>;
}

export type CreateCookie = (name: string, cookieOptions?: CookieOptions) => Cookie
export type IsCookie = (object: any) => object is Cookie
17 changes: 17 additions & 0 deletions packages/remix-server-runtime/interface/remixServerRuntime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { CreateCookie, IsCookie } from "./cookies"
import type { Json, Redirect } from "./responses"
import type { CreateSession, CreateSessionStorage, IsSession } from "./sessions"
import type { CreateCookieSessionStorage } from "./sessions/cookieStorage"
import type { CreateMemorySessionStorage } from "./sessions/memoryStorage"

export interface RemixServerRuntime {
createCookie: CreateCookie,
isCookie: IsCookie,
createSession: CreateSession,
isSession: IsSession,
createSessionStorage: CreateSessionStorage,
createCookieSessionStorage: CreateCookieSessionStorage,
createMemorySessionStorage: CreateMemorySessionStorage,
json: Json,
redirect: Redirect,
}
2 changes: 2 additions & 0 deletions packages/remix-server-runtime/interface/responses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type Json = <Data>(data: Data, init?: number | ResponseInit) => Response
export type Redirect = (url: string, init?: number | ResponseInit) => Response
138 changes: 138 additions & 0 deletions packages/remix-server-runtime/interface/sessions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import type { CookieParseOptions, CookieSerializeOptions } from "cookie";

import type { Cookie, CookieOptions } from "./cookies";

/**
* An object of name/value pairs to be used in the session.
*/
export interface SessionData {
[name: string]: any;
}

/**
* Session persists data across HTTP requests.
*
* @see https://remix.run/api/remix#session-api
*/
export interface Session {
/**
* A unique identifier for this session.
*
* Note: This will be the empty string for newly created sessions and
* sessions that are not backed by a database (i.e. cookie-based sessions).
*/
readonly id: string;

/**
* The raw data contained in this session.
*
* This is useful mostly for SessionStorage internally to access the raw
* session data to persist.
*/
readonly data: SessionData;

/**
* Returns `true` if the session has a value for the given `name`, `false`
* otherwise.
*/
has(name: string): boolean;

/**
* Returns the value for the given `name` in this session.
*/
get(name: string): any;

/**
* Sets a value in the session for the given `name`.
*/
set(name: string, value: any): void;

/**
* Sets a value in the session that is only valid until the next `get()`.
* This can be useful for temporary values, like error messages.
*/
flash(name: string, value: any): void;

/**
* Removes a value from the session.
*/
unset(name: string): void;
}

/**
* SessionStorage stores session data between HTTP requests and knows how to
* parse and create cookies.
*
* A SessionStorage creates Session objects using a `Cookie` header as input.
* Then, later it generates the `Set-Cookie` header to be used in the response.
*/
export interface SessionStorage {
/**
* Parses a Cookie header from a HTTP request and returns the associated
* Session. If there is no session associated with the cookie, this will
* return a new Session with no data.
*/
getSession(
cookieHeader?: string | null,
options?: CookieParseOptions
): Promise<Session>;

/**
* Stores all data in the Session and returns the Set-Cookie header to be
* used in the HTTP response.
*/
commitSession(
session: Session,
options?: CookieSerializeOptions
): Promise<string>;

/**
* Deletes all data associated with the Session and returns the Set-Cookie
* header to be used in the HTTP response.
*/
destroySession(
session: Session,
options?: CookieSerializeOptions
): Promise<string>;
}

/**
* SessionIdStorageStrategy is designed to allow anyone to easily build their
* own SessionStorage using `createSessionStorage(strategy)`.
*
* This strategy describes a common scenario where the session id is stored in
* a cookie but the actual session data is stored elsewhere, usually in a
* database or on disk. A set of create, read, update, and delete operations
* are provided for managing the session data.
*/
export interface SessionIdStorageStrategy {
/**
* The Cookie used to store the session id, or options used to automatically
* create one.
*/
cookie?: Cookie | (CookieOptions & { name?: string });

/**
* Creates a new record with the given data and returns the session id.
*/
createData: (data: SessionData, expires?: Date) => Promise<string>;

/**
* Returns data for a given session id, or `null` if there isn't any.
*/
readData: (id: string) => Promise<SessionData | null>;

/**
* Updates data for the given session id.
*/
updateData: (id: string, data: SessionData, expires?: Date) => Promise<void>;

/**
* Deletes data for a given session id from the data store.
*/
deleteData: (id: string) => Promise<void>;
}

export type CreateSession = (initialData?: SessionData, id?: string) => Session
export type IsSession = (object: any) => object is Session
export type CreateSessionStorage = (strategy: SessionIdStorageStrategy) => SessionStorage
11 changes: 11 additions & 0 deletions packages/remix-server-runtime/interface/sessions/cookieStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { SessionIdStorageStrategy, SessionStorage } from "../sessions";

export interface CookieSessionStorageOptions {
/**
* The Cookie used to store the session data on the client, or options used
* to automatically create one.
*/
cookie?: SessionIdStorageStrategy["cookie"];
}

export type CreateCookieSessionStorage = (options?: CookieSessionStorageOptions) => SessionStorage
11 changes: 11 additions & 0 deletions packages/remix-server-runtime/interface/sessions/memoryStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { SessionIdStorageStrategy, SessionStorage } from "../sessions";

export interface MemorySessionStorageOptions {
/**
* The Cookie used to store the session id on the client, or options used
* to automatically create one.
*/
cookie?: SessionIdStorageStrategy["cookie"];
}

export type CreateMemorySessionStorage = (options?: MemorySessionStorageOptions) => SessionStorage
Loading

0 comments on commit 8bff58c

Please sign in to comment.