@holo-js/session 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/drivers/redis-adapter.d.ts +71 -0
- package/dist/drivers/redis-adapter.mjs +186 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.mjs +459 -0
- package/dist/redis-nJc4PF7B.d.ts +91 -0
- package/package.json +48 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { NormalizedSessionRedisStoreConfig } from '@holo-js/config';
|
|
2
|
+
import { i as SessionRedisDriverAdapter, a as SessionRecord } from '../redis-nJc4PF7B.js';
|
|
3
|
+
|
|
4
|
+
interface SessionRedisAdapterOptions {
|
|
5
|
+
readonly now?: () => Date;
|
|
6
|
+
}
|
|
7
|
+
type RedisStandaloneOptions = {
|
|
8
|
+
readonly host?: string;
|
|
9
|
+
readonly port?: number;
|
|
10
|
+
readonly path?: string;
|
|
11
|
+
readonly password?: string;
|
|
12
|
+
readonly username?: string;
|
|
13
|
+
readonly db?: number;
|
|
14
|
+
readonly tls?: Record<string, never>;
|
|
15
|
+
readonly lazyConnect: true;
|
|
16
|
+
readonly maxRetriesPerRequest: number;
|
|
17
|
+
};
|
|
18
|
+
type RedisClusterOptions = {
|
|
19
|
+
readonly redisOptions: RedisStandaloneOptions;
|
|
20
|
+
};
|
|
21
|
+
type RedisClusterStartupNode = {
|
|
22
|
+
readonly host: string;
|
|
23
|
+
readonly port: number;
|
|
24
|
+
readonly tls?: Record<string, never>;
|
|
25
|
+
};
|
|
26
|
+
type RedisClientLike = {
|
|
27
|
+
connect?(): Promise<unknown>;
|
|
28
|
+
get(key: string): Promise<string | null>;
|
|
29
|
+
set(key: string, value: string, mode: 'PX', durationMs: number): Promise<'OK' | null>;
|
|
30
|
+
del(key: string): Promise<number>;
|
|
31
|
+
quit(): Promise<unknown>;
|
|
32
|
+
disconnect(): void;
|
|
33
|
+
};
|
|
34
|
+
declare function isRedisUrlTarget(value: string): boolean;
|
|
35
|
+
declare function isRedisSocketConnectionTarget(value: string): boolean;
|
|
36
|
+
declare function toRedisSocketPath(value: string): string;
|
|
37
|
+
declare function createStandaloneOptions(config: NormalizedSessionRedisStoreConfig): RedisStandaloneOptions;
|
|
38
|
+
declare function createClusterOptions(config: NormalizedSessionRedisStoreConfig): RedisClusterOptions;
|
|
39
|
+
declare function parseClusterNodeUrl(node: string, label: string): RedisClusterStartupNode;
|
|
40
|
+
declare function resolveClusterStartupNodes(config: NormalizedSessionRedisStoreConfig): readonly RedisClusterStartupNode[];
|
|
41
|
+
declare function createRedisClient(config: NormalizedSessionRedisStoreConfig): RedisClientLike;
|
|
42
|
+
declare function serializeSessionRecord(record: SessionRecord): string;
|
|
43
|
+
declare function deserializeSessionRecord(value: string): SessionRecord | null;
|
|
44
|
+
declare class RedisSessionAdapter implements SessionRedisDriverAdapter {
|
|
45
|
+
private readonly client;
|
|
46
|
+
private readonly prefix;
|
|
47
|
+
private readonly now;
|
|
48
|
+
constructor(config: NormalizedSessionRedisStoreConfig, options?: SessionRedisAdapterOptions);
|
|
49
|
+
private qualifyKey;
|
|
50
|
+
connect(): Promise<void>;
|
|
51
|
+
get(sessionId: string): Promise<SessionRecord | null>;
|
|
52
|
+
set(record: SessionRecord): Promise<void>;
|
|
53
|
+
del(sessionId: string): Promise<void>;
|
|
54
|
+
close(): Promise<void>;
|
|
55
|
+
disconnect(): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
declare function createSessionRedisAdapter(config: NormalizedSessionRedisStoreConfig, options?: SessionRedisAdapterOptions): RedisSessionAdapter;
|
|
58
|
+
declare const sessionRedisAdapterInternals: {
|
|
59
|
+
createClusterOptions: typeof createClusterOptions;
|
|
60
|
+
createRedisClient: typeof createRedisClient;
|
|
61
|
+
createStandaloneOptions: typeof createStandaloneOptions;
|
|
62
|
+
deserializeSessionRecord: typeof deserializeSessionRecord;
|
|
63
|
+
isRedisSocketConnectionTarget: typeof isRedisSocketConnectionTarget;
|
|
64
|
+
isRedisUrlTarget: typeof isRedisUrlTarget;
|
|
65
|
+
parseClusterNodeUrl: typeof parseClusterNodeUrl;
|
|
66
|
+
resolveClusterStartupNodes: typeof resolveClusterStartupNodes;
|
|
67
|
+
serializeSessionRecord: typeof serializeSessionRecord;
|
|
68
|
+
toRedisSocketPath: typeof toRedisSocketPath;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export { RedisSessionAdapter, type SessionRedisAdapterOptions, createSessionRedisAdapter, sessionRedisAdapterInternals };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// src/drivers/redis-adapter.ts
|
|
2
|
+
import Redis from "ioredis";
|
|
3
|
+
function isRedisUrlTarget(value) {
|
|
4
|
+
return value.startsWith("redis://") || value.startsWith("rediss://");
|
|
5
|
+
}
|
|
6
|
+
function isRedisSocketConnectionTarget(value) {
|
|
7
|
+
return value.startsWith("unix://") || value.startsWith("/");
|
|
8
|
+
}
|
|
9
|
+
function toRedisSocketPath(value) {
|
|
10
|
+
return value.startsWith("unix://") ? value.slice("unix://".length) : value;
|
|
11
|
+
}
|
|
12
|
+
function createStandaloneOptions(config) {
|
|
13
|
+
return {
|
|
14
|
+
...!isRedisSocketConnectionTarget(config.host) ? {
|
|
15
|
+
host: config.host,
|
|
16
|
+
port: config.port
|
|
17
|
+
} : {
|
|
18
|
+
path: toRedisSocketPath(config.host)
|
|
19
|
+
},
|
|
20
|
+
password: config.password,
|
|
21
|
+
username: config.username,
|
|
22
|
+
db: config.db,
|
|
23
|
+
lazyConnect: true,
|
|
24
|
+
maxRetriesPerRequest: 3
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function createClusterOptions(config) {
|
|
28
|
+
if (typeof config.db === "number" && config.db !== 0) {
|
|
29
|
+
throw new Error("[@holo-js/session] Redis Cluster does not support selecting a non-zero database. Remove db or set it to 0.");
|
|
30
|
+
}
|
|
31
|
+
const startupNodes = resolveClusterStartupNodes(config);
|
|
32
|
+
return {
|
|
33
|
+
redisOptions: {
|
|
34
|
+
password: config.password,
|
|
35
|
+
username: config.username,
|
|
36
|
+
lazyConnect: true,
|
|
37
|
+
maxRetriesPerRequest: 3,
|
|
38
|
+
...startupNodes.some((node) => typeof node.tls !== "undefined") ? { tls: {} } : {}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function parseClusterNodeUrl(node, label) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = new URL(node);
|
|
45
|
+
if (parsed.protocol !== "redis:" && parsed.protocol !== "rediss:") {
|
|
46
|
+
throw new Error(`unsupported protocol "${parsed.protocol}"`);
|
|
47
|
+
}
|
|
48
|
+
if (!parsed.hostname) {
|
|
49
|
+
throw new Error("missing hostname");
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
host: parsed.hostname,
|
|
53
|
+
port: parsed.port ? Number.parseInt(parsed.port, 10) : 6379,
|
|
54
|
+
...parsed.protocol === "rediss:" ? { tls: {} } : {}
|
|
55
|
+
};
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw new Error(`[@holo-js/session] ${label} is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function resolveClusterStartupNodes(config) {
|
|
61
|
+
return (config.clusters ?? []).map((node, index) => {
|
|
62
|
+
const label = `Session Redis store "${config.name}" cluster node ${index + 1}`;
|
|
63
|
+
if (typeof node.url === "string") {
|
|
64
|
+
return parseClusterNodeUrl(node.url, `${label} url`);
|
|
65
|
+
}
|
|
66
|
+
if (isRedisSocketConnectionTarget(node.host)) {
|
|
67
|
+
throw new Error(`[@holo-js/session] ${label} cannot use a Unix socket path in Redis cluster mode.`);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
host: node.host,
|
|
71
|
+
port: node.port
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function createRedisClient(config) {
|
|
76
|
+
const RedisConstructor = Redis;
|
|
77
|
+
if (typeof config.url === "string" && isRedisUrlTarget(config.url)) {
|
|
78
|
+
return new RedisConstructor(config.url, {
|
|
79
|
+
password: config.password,
|
|
80
|
+
username: config.username,
|
|
81
|
+
db: config.db,
|
|
82
|
+
lazyConnect: true,
|
|
83
|
+
maxRetriesPerRequest: 3
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (config.clusters && config.clusters.length > 0) {
|
|
87
|
+
return new RedisConstructor.Cluster(
|
|
88
|
+
resolveClusterStartupNodes(config),
|
|
89
|
+
createClusterOptions(config)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return new RedisConstructor(createStandaloneOptions(config));
|
|
93
|
+
}
|
|
94
|
+
function serializeSessionRecord(record) {
|
|
95
|
+
return JSON.stringify({
|
|
96
|
+
id: record.id,
|
|
97
|
+
store: record.store,
|
|
98
|
+
data: record.data,
|
|
99
|
+
createdAt: record.createdAt.toISOString(),
|
|
100
|
+
lastActivityAt: record.lastActivityAt.toISOString(),
|
|
101
|
+
expiresAt: record.expiresAt.toISOString(),
|
|
102
|
+
...typeof record.rememberTokenHash === "undefined" ? {} : { rememberTokenHash: record.rememberTokenHash }
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function deserializeSessionRecord(value) {
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(value);
|
|
108
|
+
if (typeof parsed.id !== "string" || typeof parsed.store !== "string" || typeof parsed.data !== "object" || parsed.data === null || Array.isArray(parsed.data) || typeof parsed.createdAt !== "string" || typeof parsed.lastActivityAt !== "string" || typeof parsed.expiresAt !== "string" || typeof parsed.rememberTokenHash !== "undefined" && typeof parsed.rememberTokenHash !== "string") {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const createdAtMs = Date.parse(parsed.createdAt);
|
|
112
|
+
const lastActivityAtMs = Date.parse(parsed.lastActivityAt);
|
|
113
|
+
const expiresAtMs = Date.parse(parsed.expiresAt);
|
|
114
|
+
if (Number.isNaN(createdAtMs) || Number.isNaN(lastActivityAtMs) || Number.isNaN(expiresAtMs)) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return Object.freeze({
|
|
118
|
+
id: parsed.id,
|
|
119
|
+
store: parsed.store,
|
|
120
|
+
data: Object.freeze({ ...parsed.data }),
|
|
121
|
+
createdAt: new Date(createdAtMs),
|
|
122
|
+
lastActivityAt: new Date(lastActivityAtMs),
|
|
123
|
+
expiresAt: new Date(expiresAtMs),
|
|
124
|
+
...typeof parsed.rememberTokenHash === "undefined" ? {} : { rememberTokenHash: parsed.rememberTokenHash }
|
|
125
|
+
});
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
var RedisSessionAdapter = class {
|
|
131
|
+
client;
|
|
132
|
+
prefix;
|
|
133
|
+
now;
|
|
134
|
+
constructor(config, options = {}) {
|
|
135
|
+
this.client = createRedisClient(config);
|
|
136
|
+
this.prefix = config.prefix;
|
|
137
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
138
|
+
}
|
|
139
|
+
qualifyKey(sessionId) {
|
|
140
|
+
return `${this.prefix}${sessionId}`;
|
|
141
|
+
}
|
|
142
|
+
async connect() {
|
|
143
|
+
await this.client.connect?.();
|
|
144
|
+
}
|
|
145
|
+
async get(sessionId) {
|
|
146
|
+
const encoded = await this.client.get(this.qualifyKey(sessionId));
|
|
147
|
+
return encoded ? deserializeSessionRecord(encoded) : null;
|
|
148
|
+
}
|
|
149
|
+
async set(record) {
|
|
150
|
+
const ttlMs = Math.max(1, record.expiresAt.getTime() - this.now().getTime());
|
|
151
|
+
await this.client.set(this.qualifyKey(record.id), serializeSessionRecord(record), "PX", ttlMs);
|
|
152
|
+
}
|
|
153
|
+
async del(sessionId) {
|
|
154
|
+
await this.client.del(this.qualifyKey(sessionId));
|
|
155
|
+
}
|
|
156
|
+
async close() {
|
|
157
|
+
try {
|
|
158
|
+
await this.client.quit();
|
|
159
|
+
} catch {
|
|
160
|
+
this.client.disconnect();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async disconnect() {
|
|
164
|
+
this.client.disconnect();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
function createSessionRedisAdapter(config, options) {
|
|
168
|
+
return new RedisSessionAdapter(config, options);
|
|
169
|
+
}
|
|
170
|
+
var sessionRedisAdapterInternals = {
|
|
171
|
+
createClusterOptions,
|
|
172
|
+
createRedisClient,
|
|
173
|
+
createStandaloneOptions,
|
|
174
|
+
deserializeSessionRecord,
|
|
175
|
+
isRedisSocketConnectionTarget,
|
|
176
|
+
isRedisUrlTarget,
|
|
177
|
+
parseClusterNodeUrl,
|
|
178
|
+
resolveClusterStartupNodes,
|
|
179
|
+
serializeSessionRecord,
|
|
180
|
+
toRedisSocketPath
|
|
181
|
+
};
|
|
182
|
+
export {
|
|
183
|
+
RedisSessionAdapter,
|
|
184
|
+
createSessionRedisAdapter,
|
|
185
|
+
sessionRedisAdapterInternals
|
|
186
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { S as SessionRuntimeBindings, R as RememberTokenOptions, a as SessionRecord, C as CookieSerializeOptions, b as CreateSessionInput, c as SessionRuntimeFacade, d as ReadSessionOptions, e as RotateSessionOptions, T as TouchSessionOptions, f as SessionStore, g as SessionFacade } from './redis-nJc4PF7B.js';
|
|
2
|
+
export { h as SessionCookieHelpers, i as SessionRedisDriverAdapter, j as createRedisSessionStore } from './redis-nJc4PF7B.js';
|
|
3
|
+
export { HoloSessionConfig, HoloSessionCookieConfig, NormalizedHoloSessionConfig, SessionCookieSameSite, defineSessionConfig } from '@holo-js/config';
|
|
4
|
+
|
|
5
|
+
declare function getSessionRuntimeBindings(): SessionRuntimeBindings;
|
|
6
|
+
declare function hashRememberToken(secret: string): string;
|
|
7
|
+
declare function createSessionId(): string;
|
|
8
|
+
declare function createRememberSecret(): string;
|
|
9
|
+
declare function normalizeCookieOptions(options?: CookieSerializeOptions): Required<Omit<CookieSerializeOptions, 'domain' | 'expires'>> & Pick<CookieSerializeOptions, 'domain' | 'expires'>;
|
|
10
|
+
declare function serializeCookie(name: string, value: string, options?: CookieSerializeOptions): string;
|
|
11
|
+
declare function parseCookieHeader(header: string | null | undefined): Readonly<Record<string, string>>;
|
|
12
|
+
declare function isExpired(record: SessionRecord): boolean;
|
|
13
|
+
declare function createSession(input?: CreateSessionInput): Promise<SessionRecord>;
|
|
14
|
+
declare function writeSession(record: SessionRecord): Promise<SessionRecord>;
|
|
15
|
+
declare function readSession(sessionId: string, options?: ReadSessionOptions): Promise<SessionRecord | null>;
|
|
16
|
+
declare function touchSession(sessionId: string, options?: TouchSessionOptions): Promise<SessionRecord | null>;
|
|
17
|
+
declare function rotateSession(sessionId: string, options?: RotateSessionOptions): Promise<SessionRecord>;
|
|
18
|
+
declare function invalidateSession(sessionId: string, options?: ReadSessionOptions): Promise<void>;
|
|
19
|
+
declare function issueRememberMeToken(sessionId: string, options?: RememberTokenOptions): Promise<string>;
|
|
20
|
+
declare function consumeRememberMeToken(token: string, options?: RememberTokenOptions): Promise<SessionRecord | null>;
|
|
21
|
+
declare const cookies: Readonly<{
|
|
22
|
+
make(name: string, value: string, options?: CookieSerializeOptions): string;
|
|
23
|
+
forget(name: string, options?: CookieSerializeOptions): string;
|
|
24
|
+
}>;
|
|
25
|
+
declare function cookie(name: string, value: string, options?: CookieSerializeOptions): string;
|
|
26
|
+
declare function sessionCookie(value: string, options?: CookieSerializeOptions): string;
|
|
27
|
+
declare function rememberMeCookie(value: string, options?: CookieSerializeOptions): string;
|
|
28
|
+
declare function configureSessionRuntime(bindings?: SessionRuntimeBindings): void;
|
|
29
|
+
declare function getSessionRuntime(): SessionRuntimeFacade;
|
|
30
|
+
declare function resetSessionRuntime(): void;
|
|
31
|
+
declare const sessionRuntimeInternals: {
|
|
32
|
+
createRememberSecret: typeof createRememberSecret;
|
|
33
|
+
createSessionId: typeof createSessionId;
|
|
34
|
+
getSessionRuntimeBindings: typeof getSessionRuntimeBindings;
|
|
35
|
+
hashRememberToken: typeof hashRememberToken;
|
|
36
|
+
isExpired: typeof isExpired;
|
|
37
|
+
normalizeCookieOptions: typeof normalizeCookieOptions;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
interface SessionDatabaseDriverAdapter {
|
|
41
|
+
read(sessionId: string): Promise<SessionRecord | null>;
|
|
42
|
+
write(record: SessionRecord): Promise<void>;
|
|
43
|
+
delete(sessionId: string): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
declare function createDatabaseSessionStore(adapter: SessionDatabaseDriverAdapter): SessionStore;
|
|
46
|
+
|
|
47
|
+
declare function serializeRecord(record: SessionRecord): string;
|
|
48
|
+
declare function deserializeRecord(raw: string): SessionRecord;
|
|
49
|
+
declare function getRecordPath(root: string, sessionId: string): string;
|
|
50
|
+
declare function createFileSessionStore(root: string): SessionStore;
|
|
51
|
+
declare const fileSessionDriverInternals: {
|
|
52
|
+
deserializeRecord: typeof deserializeRecord;
|
|
53
|
+
getRecordPath: typeof getRecordPath;
|
|
54
|
+
serializeRecord: typeof serializeRecord;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
declare const session: SessionFacade;
|
|
58
|
+
|
|
59
|
+
export { CookieSerializeOptions, CreateSessionInput, ReadSessionOptions, RememberTokenOptions, RotateSessionOptions, type SessionDatabaseDriverAdapter, SessionFacade, SessionRecord, SessionRuntimeBindings, SessionRuntimeFacade, SessionStore, TouchSessionOptions, configureSessionRuntime, consumeRememberMeToken, cookie, cookies, createDatabaseSessionStore, createFileSessionStore, createSession, session as default, fileSessionDriverInternals, getSessionRuntime, invalidateSession, issueRememberMeToken, parseCookieHeader, readSession, rememberMeCookie, resetSessionRuntime, rotateSession, serializeCookie, sessionCookie, sessionRuntimeInternals, touchSession, writeSession };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// src/runtime.ts
|
|
2
|
+
import { createHash, randomBytes, randomUUID } from "crypto";
|
|
3
|
+
function getSessionRuntimeState() {
|
|
4
|
+
const runtime = globalThis;
|
|
5
|
+
runtime.__holoSessionRuntime__ ??= {};
|
|
6
|
+
return runtime.__holoSessionRuntime__;
|
|
7
|
+
}
|
|
8
|
+
function getSessionRuntimeBindings() {
|
|
9
|
+
const bindings = getSessionRuntimeState().bindings;
|
|
10
|
+
if (!bindings) {
|
|
11
|
+
throw new Error("[@holo-js/session] Session runtime is not configured yet.");
|
|
12
|
+
}
|
|
13
|
+
return bindings;
|
|
14
|
+
}
|
|
15
|
+
function getStore(name) {
|
|
16
|
+
const bindings = getSessionRuntimeBindings();
|
|
17
|
+
const storeName = name?.trim() || bindings.config.driver;
|
|
18
|
+
const store = bindings.stores[storeName];
|
|
19
|
+
if (!store) {
|
|
20
|
+
throw new Error(`[@holo-js/session] Session store "${storeName}" is not configured.`);
|
|
21
|
+
}
|
|
22
|
+
return { name: storeName, store, config: bindings.config };
|
|
23
|
+
}
|
|
24
|
+
function ensureDate(value) {
|
|
25
|
+
return new Date(value.getTime());
|
|
26
|
+
}
|
|
27
|
+
function now() {
|
|
28
|
+
return /* @__PURE__ */ new Date();
|
|
29
|
+
}
|
|
30
|
+
function createExpiryDate(minutes) {
|
|
31
|
+
return new Date(Date.now() + minutes * 6e4);
|
|
32
|
+
}
|
|
33
|
+
function hashRememberToken(secret) {
|
|
34
|
+
return createHash("sha256").update(secret).digest("hex");
|
|
35
|
+
}
|
|
36
|
+
function createSessionId() {
|
|
37
|
+
return randomUUID();
|
|
38
|
+
}
|
|
39
|
+
function normalizeSessionKey(input) {
|
|
40
|
+
return input.name?.trim() || input.id?.trim() || createSessionId();
|
|
41
|
+
}
|
|
42
|
+
function normalizeSessionData(input) {
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
...input.value ?? input.data ?? {}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function createRememberSecret() {
|
|
48
|
+
return randomBytes(24).toString("base64url");
|
|
49
|
+
}
|
|
50
|
+
function createRememberTokenIssuedAt() {
|
|
51
|
+
return Date.now().toString(36);
|
|
52
|
+
}
|
|
53
|
+
function parseRememberTokenIssuedAt(value) {
|
|
54
|
+
const parsed = Number.parseInt(value, 36);
|
|
55
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
56
|
+
}
|
|
57
|
+
function parseRememberMeToken(token) {
|
|
58
|
+
const firstSeparator = token.indexOf(".");
|
|
59
|
+
if (firstSeparator <= 0) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const secondSeparator = token.indexOf(".", firstSeparator + 1);
|
|
63
|
+
if (secondSeparator <= firstSeparator + 1) {
|
|
64
|
+
const sessionId2 = token.slice(0, firstSeparator);
|
|
65
|
+
const secret2 = token.slice(firstSeparator + 1);
|
|
66
|
+
return sessionId2 && secret2 ? { sessionId: sessionId2, secretPayload: secret2 } : null;
|
|
67
|
+
}
|
|
68
|
+
const sessionId = token.slice(0, firstSeparator);
|
|
69
|
+
const issuedAtRaw = token.slice(firstSeparator + 1, secondSeparator);
|
|
70
|
+
const secret = token.slice(secondSeparator + 1);
|
|
71
|
+
const issuedAt = parseRememberTokenIssuedAt(issuedAtRaw);
|
|
72
|
+
if (!sessionId || !issuedAtRaw || !secret || issuedAt === null) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
sessionId,
|
|
77
|
+
secretPayload: `${issuedAtRaw}.${secret}`,
|
|
78
|
+
issuedAt
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function normalizeCookieOptions(options = {}) {
|
|
82
|
+
const config = getSessionRuntimeState().bindings?.config.cookie;
|
|
83
|
+
return {
|
|
84
|
+
path: options.path ?? config?.path ?? "/",
|
|
85
|
+
domain: options.domain ?? config?.domain,
|
|
86
|
+
secure: options.secure ?? config?.secure ?? false,
|
|
87
|
+
httpOnly: options.httpOnly ?? config?.httpOnly ?? true,
|
|
88
|
+
sameSite: options.sameSite ?? config?.sameSite ?? "lax",
|
|
89
|
+
partitioned: options.partitioned ?? config?.partitioned ?? false,
|
|
90
|
+
maxAge: options.maxAge ?? (config?.maxAge ?? 0) * 60,
|
|
91
|
+
expires: options.expires
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function serializeCookie(name, value, options = {}) {
|
|
95
|
+
if (!name.trim()) {
|
|
96
|
+
throw new Error("[@holo-js/session] Cookie name must be a non-empty string.");
|
|
97
|
+
}
|
|
98
|
+
const normalized = normalizeCookieOptions(options);
|
|
99
|
+
const attributes = [
|
|
100
|
+
`${encodeURIComponent(name)}=${encodeURIComponent(value)}`,
|
|
101
|
+
`Path=${normalized.path}`
|
|
102
|
+
];
|
|
103
|
+
if (normalized.domain) {
|
|
104
|
+
attributes.push(`Domain=${normalized.domain}`);
|
|
105
|
+
}
|
|
106
|
+
if (normalized.maxAge > 0) {
|
|
107
|
+
attributes.push(`Max-Age=${normalized.maxAge}`);
|
|
108
|
+
}
|
|
109
|
+
if (normalized.expires) {
|
|
110
|
+
attributes.push(`Expires=${normalized.expires.toUTCString()}`);
|
|
111
|
+
}
|
|
112
|
+
if (normalized.secure) {
|
|
113
|
+
attributes.push("Secure");
|
|
114
|
+
}
|
|
115
|
+
if (normalized.httpOnly) {
|
|
116
|
+
attributes.push("HttpOnly");
|
|
117
|
+
}
|
|
118
|
+
attributes.push(`SameSite=${normalized.sameSite[0].toUpperCase()}${normalized.sameSite.slice(1)}`);
|
|
119
|
+
if (normalized.partitioned) {
|
|
120
|
+
attributes.push("Partitioned");
|
|
121
|
+
}
|
|
122
|
+
return attributes.join("; ");
|
|
123
|
+
}
|
|
124
|
+
function parseCookieHeader(header) {
|
|
125
|
+
if (!header) {
|
|
126
|
+
return Object.freeze({});
|
|
127
|
+
}
|
|
128
|
+
const entries = header.split(";").map((segment) => segment.trim()).filter(Boolean).map((segment) => {
|
|
129
|
+
const separator = segment.indexOf("=");
|
|
130
|
+
if (separator <= 0) {
|
|
131
|
+
return void 0;
|
|
132
|
+
}
|
|
133
|
+
const key = decodeURIComponent(segment.slice(0, separator));
|
|
134
|
+
const value = decodeURIComponent(segment.slice(separator + 1));
|
|
135
|
+
return [key, value];
|
|
136
|
+
}).filter((entry) => !!entry);
|
|
137
|
+
return Object.freeze(Object.fromEntries(entries));
|
|
138
|
+
}
|
|
139
|
+
function isExpired(record) {
|
|
140
|
+
return record.expiresAt.getTime() <= Date.now();
|
|
141
|
+
}
|
|
142
|
+
async function readRecordFromStore(sessionId, store) {
|
|
143
|
+
const record = await store.read(sessionId);
|
|
144
|
+
if (!record) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
if (isExpired(record)) {
|
|
148
|
+
await store.delete(sessionId);
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
return record;
|
|
152
|
+
}
|
|
153
|
+
async function readRecord(sessionId, options) {
|
|
154
|
+
const { store } = getStore(options?.store);
|
|
155
|
+
return readRecordFromStore(sessionId, store);
|
|
156
|
+
}
|
|
157
|
+
async function locateRecord(sessionId) {
|
|
158
|
+
const bindings = getSessionRuntimeBindings();
|
|
159
|
+
for (const [storeName, store] of Object.entries(bindings.stores)) {
|
|
160
|
+
const record = await readRecordFromStore(sessionId, store);
|
|
161
|
+
if (record) {
|
|
162
|
+
return {
|
|
163
|
+
record,
|
|
164
|
+
storeName,
|
|
165
|
+
store
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
async function createSession(input = {}) {
|
|
172
|
+
const { name, store, config } = getStore(input.store);
|
|
173
|
+
const currentTime = now();
|
|
174
|
+
const idleExpiry = createExpiryDate(config.idleTimeout);
|
|
175
|
+
const absoluteExpiry = createExpiryDate(config.absoluteLifetime);
|
|
176
|
+
const record = Object.freeze({
|
|
177
|
+
id: normalizeSessionKey(input),
|
|
178
|
+
store: name,
|
|
179
|
+
data: normalizeSessionData(input),
|
|
180
|
+
createdAt: ensureDate(currentTime),
|
|
181
|
+
lastActivityAt: ensureDate(currentTime),
|
|
182
|
+
expiresAt: idleExpiry.getTime() < absoluteExpiry.getTime() ? idleExpiry : absoluteExpiry
|
|
183
|
+
});
|
|
184
|
+
await store.write(record);
|
|
185
|
+
return record;
|
|
186
|
+
}
|
|
187
|
+
async function writeSession(record) {
|
|
188
|
+
const { name, store } = getStore(record.store);
|
|
189
|
+
const nextRecord = Object.freeze({
|
|
190
|
+
...record,
|
|
191
|
+
store: name,
|
|
192
|
+
data: Object.freeze({ ...record.data ?? {} }),
|
|
193
|
+
createdAt: ensureDate(record.createdAt),
|
|
194
|
+
lastActivityAt: ensureDate(record.lastActivityAt),
|
|
195
|
+
expiresAt: ensureDate(record.expiresAt)
|
|
196
|
+
});
|
|
197
|
+
await store.write(nextRecord);
|
|
198
|
+
return nextRecord;
|
|
199
|
+
}
|
|
200
|
+
async function readSession(sessionId, options) {
|
|
201
|
+
return readRecord(sessionId, options);
|
|
202
|
+
}
|
|
203
|
+
async function touchSession(sessionId, options) {
|
|
204
|
+
const record = await readRecord(sessionId, options);
|
|
205
|
+
if (!record) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const { store, config } = getStore(options?.store ?? record.store);
|
|
209
|
+
const idleExpiry = createExpiryDate(config.idleTimeout);
|
|
210
|
+
const absoluteExpiry = new Date(record.createdAt.getTime() + config.absoluteLifetime * 6e4);
|
|
211
|
+
const touched = Object.freeze({
|
|
212
|
+
...record,
|
|
213
|
+
lastActivityAt: now(),
|
|
214
|
+
expiresAt: idleExpiry.getTime() < absoluteExpiry.getTime() ? idleExpiry : absoluteExpiry
|
|
215
|
+
});
|
|
216
|
+
await store.write(touched);
|
|
217
|
+
return touched;
|
|
218
|
+
}
|
|
219
|
+
async function rotateSession(sessionId, options = {}) {
|
|
220
|
+
const located = await locateRecord(sessionId);
|
|
221
|
+
if (!located) {
|
|
222
|
+
throw new Error(`[@holo-js/session] Session "${sessionId}" was not found.`);
|
|
223
|
+
}
|
|
224
|
+
const { store, name } = getStore(options.store ?? located.record.store);
|
|
225
|
+
const rotated = Object.freeze({
|
|
226
|
+
...located.record,
|
|
227
|
+
id: options.newId?.trim() || createSessionId(),
|
|
228
|
+
store: name
|
|
229
|
+
});
|
|
230
|
+
await store.write(rotated);
|
|
231
|
+
if (located.storeName !== name || rotated.id !== sessionId) {
|
|
232
|
+
await located.store.delete(sessionId);
|
|
233
|
+
}
|
|
234
|
+
return rotated;
|
|
235
|
+
}
|
|
236
|
+
async function invalidateSession(sessionId, options) {
|
|
237
|
+
const { store } = getStore(options?.store);
|
|
238
|
+
await store.delete(sessionId);
|
|
239
|
+
}
|
|
240
|
+
async function issueRememberMeToken(sessionId, options) {
|
|
241
|
+
const record = await readRecord(sessionId, options);
|
|
242
|
+
if (!record) {
|
|
243
|
+
throw new Error(`[@holo-js/session] Session "${sessionId}" was not found.`);
|
|
244
|
+
}
|
|
245
|
+
const { store } = getStore(options?.store ?? record.store);
|
|
246
|
+
const issuedAt = createRememberTokenIssuedAt();
|
|
247
|
+
const secret = createRememberSecret();
|
|
248
|
+
const updated = Object.freeze({
|
|
249
|
+
...record,
|
|
250
|
+
rememberTokenHash: hashRememberToken(`${issuedAt}.${secret}`)
|
|
251
|
+
});
|
|
252
|
+
await store.write(updated);
|
|
253
|
+
return `${record.id}.${issuedAt}.${secret}`;
|
|
254
|
+
}
|
|
255
|
+
async function consumeRememberMeToken(token, options) {
|
|
256
|
+
const parsed = parseRememberMeToken(token);
|
|
257
|
+
if (!parsed) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
const bindings = getSessionRuntimeBindings();
|
|
261
|
+
const stores = options?.store ? [getStore(options.store).store] : Object.values(bindings.stores);
|
|
262
|
+
let record = null;
|
|
263
|
+
for (const store of stores) {
|
|
264
|
+
record = await store.read(parsed.sessionId);
|
|
265
|
+
if (record) {
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (!record?.rememberTokenHash) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
if (parsed.issuedAt) {
|
|
273
|
+
const rememberExpiry = parsed.issuedAt + getSessionRuntimeBindings().config.rememberMeLifetime * 6e4;
|
|
274
|
+
if (rememberExpiry <= Date.now()) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return record.rememberTokenHash === hashRememberToken(parsed.secretPayload) ? record : null;
|
|
279
|
+
}
|
|
280
|
+
var cookies = Object.freeze({
|
|
281
|
+
make(name, value, options) {
|
|
282
|
+
return serializeCookie(name, value, options);
|
|
283
|
+
},
|
|
284
|
+
forget(name, options = {}) {
|
|
285
|
+
return serializeCookie(name, "", {
|
|
286
|
+
...options,
|
|
287
|
+
expires: /* @__PURE__ */ new Date(0),
|
|
288
|
+
maxAge: 0
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
function cookie(name, value, options) {
|
|
293
|
+
return cookies.make(name, value, options);
|
|
294
|
+
}
|
|
295
|
+
function sessionCookie(value, options) {
|
|
296
|
+
const name = getSessionRuntimeState().bindings?.config.cookie.name ?? "holo_session";
|
|
297
|
+
return cookie(name, value, options);
|
|
298
|
+
}
|
|
299
|
+
function rememberMeCookie(value, options) {
|
|
300
|
+
const bindings = getSessionRuntimeState().bindings;
|
|
301
|
+
const name = `${bindings?.config.cookie.name ?? "holo_session"}_remember`;
|
|
302
|
+
const maxAge = options?.maxAge ?? (bindings?.config.rememberMeLifetime ?? 0) * 60;
|
|
303
|
+
return cookie(name, value, {
|
|
304
|
+
...options,
|
|
305
|
+
maxAge
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function configureSessionRuntime(bindings) {
|
|
309
|
+
getSessionRuntimeState().bindings = bindings;
|
|
310
|
+
}
|
|
311
|
+
function getSessionRuntime() {
|
|
312
|
+
return {
|
|
313
|
+
create: createSession,
|
|
314
|
+
write: writeSession,
|
|
315
|
+
read: readSession,
|
|
316
|
+
rotate: rotateSession,
|
|
317
|
+
invalidate: invalidateSession,
|
|
318
|
+
touch: touchSession,
|
|
319
|
+
issueRememberMeToken,
|
|
320
|
+
consumeRememberMeToken,
|
|
321
|
+
cookie,
|
|
322
|
+
sessionCookie,
|
|
323
|
+
rememberMeCookie
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function resetSessionRuntime() {
|
|
327
|
+
getSessionRuntimeState().bindings = void 0;
|
|
328
|
+
}
|
|
329
|
+
var sessionRuntimeInternals = {
|
|
330
|
+
createRememberSecret,
|
|
331
|
+
createSessionId,
|
|
332
|
+
getSessionRuntimeBindings,
|
|
333
|
+
hashRememberToken,
|
|
334
|
+
isExpired,
|
|
335
|
+
normalizeCookieOptions
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// src/contracts.ts
|
|
339
|
+
import { defineSessionConfig } from "@holo-js/config";
|
|
340
|
+
|
|
341
|
+
// src/drivers/database.ts
|
|
342
|
+
function createDatabaseSessionStore(adapter) {
|
|
343
|
+
return {
|
|
344
|
+
read(sessionId) {
|
|
345
|
+
return adapter.read(sessionId);
|
|
346
|
+
},
|
|
347
|
+
write(record) {
|
|
348
|
+
return adapter.write(record);
|
|
349
|
+
},
|
|
350
|
+
delete(sessionId) {
|
|
351
|
+
return adapter.delete(sessionId);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/drivers/file.ts
|
|
357
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
358
|
+
import { join } from "path";
|
|
359
|
+
function serializeRecord(record) {
|
|
360
|
+
return JSON.stringify({
|
|
361
|
+
...record,
|
|
362
|
+
createdAt: record.createdAt.toISOString(),
|
|
363
|
+
lastActivityAt: record.lastActivityAt.toISOString(),
|
|
364
|
+
expiresAt: record.expiresAt.toISOString()
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function deserializeRecord(raw) {
|
|
368
|
+
const parsed = JSON.parse(raw);
|
|
369
|
+
return Object.freeze({
|
|
370
|
+
...parsed,
|
|
371
|
+
createdAt: new Date(parsed.createdAt),
|
|
372
|
+
lastActivityAt: new Date(parsed.lastActivityAt),
|
|
373
|
+
expiresAt: new Date(parsed.expiresAt)
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
function getRecordPath(root, sessionId) {
|
|
377
|
+
return join(root, `${encodeURIComponent(sessionId)}.json`);
|
|
378
|
+
}
|
|
379
|
+
function createFileSessionStore(root) {
|
|
380
|
+
return {
|
|
381
|
+
async read(sessionId) {
|
|
382
|
+
const contents = await readFile(getRecordPath(root, sessionId), "utf8").catch(() => void 0);
|
|
383
|
+
return contents ? deserializeRecord(contents) : null;
|
|
384
|
+
},
|
|
385
|
+
async write(record) {
|
|
386
|
+
await mkdir(root, { recursive: true });
|
|
387
|
+
await writeFile(getRecordPath(root, record.id), serializeRecord(record), "utf8");
|
|
388
|
+
},
|
|
389
|
+
async delete(sessionId) {
|
|
390
|
+
await rm(getRecordPath(root, sessionId), { force: true });
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
var fileSessionDriverInternals = {
|
|
395
|
+
deserializeRecord,
|
|
396
|
+
getRecordPath,
|
|
397
|
+
serializeRecord
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/drivers/redis.ts
|
|
401
|
+
function createRedisSessionStore(adapter) {
|
|
402
|
+
return {
|
|
403
|
+
read(sessionId) {
|
|
404
|
+
return adapter.get(sessionId);
|
|
405
|
+
},
|
|
406
|
+
write(record) {
|
|
407
|
+
return adapter.set(record);
|
|
408
|
+
},
|
|
409
|
+
delete(sessionId) {
|
|
410
|
+
return adapter.del(sessionId);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// src/index.ts
|
|
416
|
+
var session = Object.assign(
|
|
417
|
+
(input) => createSession(input),
|
|
418
|
+
{
|
|
419
|
+
create: createSession,
|
|
420
|
+
write: writeSession,
|
|
421
|
+
read: readSession,
|
|
422
|
+
rotate: rotateSession,
|
|
423
|
+
invalidate: invalidateSession,
|
|
424
|
+
touch: touchSession,
|
|
425
|
+
issueRememberMeToken,
|
|
426
|
+
consumeRememberMeToken,
|
|
427
|
+
cookie,
|
|
428
|
+
sessionCookie,
|
|
429
|
+
rememberMeCookie,
|
|
430
|
+
cookies
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
var src_default = session;
|
|
434
|
+
export {
|
|
435
|
+
configureSessionRuntime,
|
|
436
|
+
consumeRememberMeToken,
|
|
437
|
+
cookie,
|
|
438
|
+
cookies,
|
|
439
|
+
createDatabaseSessionStore,
|
|
440
|
+
createFileSessionStore,
|
|
441
|
+
createRedisSessionStore,
|
|
442
|
+
createSession,
|
|
443
|
+
src_default as default,
|
|
444
|
+
defineSessionConfig,
|
|
445
|
+
fileSessionDriverInternals,
|
|
446
|
+
getSessionRuntime,
|
|
447
|
+
invalidateSession,
|
|
448
|
+
issueRememberMeToken,
|
|
449
|
+
parseCookieHeader,
|
|
450
|
+
readSession,
|
|
451
|
+
rememberMeCookie,
|
|
452
|
+
resetSessionRuntime,
|
|
453
|
+
rotateSession,
|
|
454
|
+
serializeCookie,
|
|
455
|
+
sessionCookie,
|
|
456
|
+
sessionRuntimeInternals,
|
|
457
|
+
touchSession,
|
|
458
|
+
writeSession
|
|
459
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { SessionCookieSameSite, NormalizedHoloSessionConfig } from '@holo-js/config';
|
|
2
|
+
|
|
3
|
+
interface CreateSessionInput {
|
|
4
|
+
readonly store?: string;
|
|
5
|
+
readonly data?: Readonly<Record<string, unknown>>;
|
|
6
|
+
readonly id?: string;
|
|
7
|
+
readonly name?: string;
|
|
8
|
+
readonly value?: Readonly<Record<string, unknown>>;
|
|
9
|
+
}
|
|
10
|
+
interface ReadSessionOptions {
|
|
11
|
+
readonly store?: string;
|
|
12
|
+
}
|
|
13
|
+
interface TouchSessionOptions {
|
|
14
|
+
readonly store?: string;
|
|
15
|
+
}
|
|
16
|
+
interface RotateSessionOptions {
|
|
17
|
+
readonly store?: string;
|
|
18
|
+
readonly newId?: string;
|
|
19
|
+
}
|
|
20
|
+
interface RememberTokenOptions {
|
|
21
|
+
readonly store?: string;
|
|
22
|
+
}
|
|
23
|
+
interface SessionRecord {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly store: string;
|
|
26
|
+
readonly data: Readonly<Record<string, unknown>>;
|
|
27
|
+
readonly createdAt: Date;
|
|
28
|
+
readonly lastActivityAt: Date;
|
|
29
|
+
readonly expiresAt: Date;
|
|
30
|
+
readonly rememberTokenHash?: string;
|
|
31
|
+
}
|
|
32
|
+
interface CookieSerializeOptions {
|
|
33
|
+
readonly path?: string;
|
|
34
|
+
readonly domain?: string;
|
|
35
|
+
readonly secure?: boolean;
|
|
36
|
+
readonly httpOnly?: boolean;
|
|
37
|
+
readonly sameSite?: SessionCookieSameSite;
|
|
38
|
+
readonly partitioned?: boolean;
|
|
39
|
+
readonly maxAge?: number;
|
|
40
|
+
readonly expires?: Date;
|
|
41
|
+
}
|
|
42
|
+
interface SessionStore {
|
|
43
|
+
read(sessionId: string): Promise<SessionRecord | null>;
|
|
44
|
+
write(record: SessionRecord): Promise<void>;
|
|
45
|
+
delete(sessionId: string): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
interface SessionRuntimeBindings {
|
|
48
|
+
readonly config: NormalizedHoloSessionConfig;
|
|
49
|
+
readonly stores: Readonly<Record<string, SessionStore>>;
|
|
50
|
+
}
|
|
51
|
+
interface SessionRuntimeFacade {
|
|
52
|
+
create(input?: CreateSessionInput): Promise<SessionRecord>;
|
|
53
|
+
write(record: SessionRecord): Promise<SessionRecord>;
|
|
54
|
+
read(sessionId: string, options?: ReadSessionOptions): Promise<SessionRecord | null>;
|
|
55
|
+
rotate(sessionId: string, options?: RotateSessionOptions): Promise<SessionRecord>;
|
|
56
|
+
invalidate(sessionId: string, options?: ReadSessionOptions): Promise<void>;
|
|
57
|
+
touch(sessionId: string, options?: TouchSessionOptions): Promise<SessionRecord | null>;
|
|
58
|
+
issueRememberMeToken(sessionId: string, options?: RememberTokenOptions): Promise<string>;
|
|
59
|
+
consumeRememberMeToken(token: string, options?: RememberTokenOptions): Promise<SessionRecord | null>;
|
|
60
|
+
cookie(name: string, value: string, options?: CookieSerializeOptions): string;
|
|
61
|
+
sessionCookie(value: string, options?: CookieSerializeOptions): string;
|
|
62
|
+
rememberMeCookie(value: string, options?: CookieSerializeOptions): string;
|
|
63
|
+
}
|
|
64
|
+
interface SessionCookieHelpers {
|
|
65
|
+
make(name: string, value: string, options?: CookieSerializeOptions): string;
|
|
66
|
+
forget(name: string, options?: CookieSerializeOptions): string;
|
|
67
|
+
}
|
|
68
|
+
interface SessionFacade {
|
|
69
|
+
(input?: CreateSessionInput): Promise<SessionRecord>;
|
|
70
|
+
create(input?: CreateSessionInput): Promise<SessionRecord>;
|
|
71
|
+
write(record: SessionRecord): Promise<SessionRecord>;
|
|
72
|
+
read(sessionId: string, options?: ReadSessionOptions): Promise<SessionRecord | null>;
|
|
73
|
+
rotate(sessionId: string, options?: RotateSessionOptions): Promise<SessionRecord>;
|
|
74
|
+
invalidate(sessionId: string, options?: ReadSessionOptions): Promise<void>;
|
|
75
|
+
touch(sessionId: string, options?: TouchSessionOptions): Promise<SessionRecord | null>;
|
|
76
|
+
issueRememberMeToken(sessionId: string, options?: RememberTokenOptions): Promise<string>;
|
|
77
|
+
consumeRememberMeToken(token: string, options?: RememberTokenOptions): Promise<SessionRecord | null>;
|
|
78
|
+
cookie(name: string, value: string, options?: CookieSerializeOptions): string;
|
|
79
|
+
sessionCookie(value: string, options?: CookieSerializeOptions): string;
|
|
80
|
+
rememberMeCookie(value: string, options?: CookieSerializeOptions): string;
|
|
81
|
+
cookies: SessionCookieHelpers;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface SessionRedisDriverAdapter {
|
|
85
|
+
get(sessionId: string): Promise<SessionRecord | null>;
|
|
86
|
+
set(record: SessionRecord): Promise<void>;
|
|
87
|
+
del(sessionId: string): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
declare function createRedisSessionStore(adapter: SessionRedisDriverAdapter): SessionStore;
|
|
90
|
+
|
|
91
|
+
export { type CookieSerializeOptions as C, type RememberTokenOptions as R, type SessionRuntimeBindings as S, type TouchSessionOptions as T, type SessionRecord as a, type CreateSessionInput as b, type SessionRuntimeFacade as c, type ReadSessionOptions as d, type RotateSessionOptions as e, type SessionStore as f, type SessionFacade as g, type SessionCookieHelpers as h, type SessionRedisDriverAdapter as i, createRedisSessionStore as j };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@holo-js/session",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Holo-JS Framework - session contracts, cookie helpers, and session config helpers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"default": "./dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"./drivers/redis-adapter": {
|
|
14
|
+
"types": "./dist/drivers/redis-adapter.d.ts",
|
|
15
|
+
"import": "./dist/drivers/redis-adapter.mjs",
|
|
16
|
+
"default": "./dist/drivers/redis-adapter.mjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.mjs",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"stub": "tsup",
|
|
27
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
28
|
+
"test": "vitest --run"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@holo-js/config": "^0.1.3"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"ioredis": "catalog:"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"ioredis": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.10.2",
|
|
43
|
+
"ioredis": "catalog:",
|
|
44
|
+
"tsup": "^8.3.5",
|
|
45
|
+
"typescript": "^5.7.2",
|
|
46
|
+
"vitest": "^2.1.8"
|
|
47
|
+
}
|
|
48
|
+
}
|