@superwall/server 0.2.0 → 0.2.5
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/cache.d.ts +17 -0
- package/dist/cache.js +48 -0
- package/dist/fetcher.d.ts +13 -0
- package/dist/fetcher.js +67 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +18 -0
- package/dist/requires.d.ts +18 -0
- package/dist/requires.js +80 -0
- package/dist/spec.d.ts +19 -0
- package/dist/spec.js +49 -0
- package/dist/superwall.d.ts +16 -0
- package/dist/superwall.js +91 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.js +1 -0
- package/dist/verify.d.ts +1 -0
- package/package.json +25 -9
- package/.turbo/turbo-build.log +0 -3
- package/.turbo/turbo-test.log +0 -73
- package/.turbo/turbo-typecheck.log +0 -1
- package/src/cache.test.ts +0 -70
- package/src/cache.ts +0 -53
- package/src/fetcher.test.ts +0 -183
- package/src/fetcher.ts +0 -116
- package/src/index.ts +0 -50
- package/src/requires.test.ts +0 -219
- package/src/requires.ts +0 -118
- package/src/spec.test.ts +0 -121
- package/src/spec.ts +0 -63
- package/src/superwall.test.ts +0 -151
- package/src/superwall.ts +0 -121
- package/src/types.ts +0 -137
- package/tsconfig.json +0 -7
- /package/{src/verify.ts → dist/verify.js} +0 -0
package/src/superwall.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import type { Entitlements } from "@superwall/core";
|
|
2
|
-
import { InMemoryCache } from "./cache.ts";
|
|
3
|
-
import { fetchEntitlements, type FetcherConfig } from "./fetcher.ts";
|
|
4
|
-
import { findMissing, normalizeSpec } from "./spec.ts";
|
|
5
|
-
import { makeRequires } from "./requires.ts";
|
|
6
|
-
import type {
|
|
7
|
-
CacheAdapter,
|
|
8
|
-
EntitlementSpec,
|
|
9
|
-
RequestInfo,
|
|
10
|
-
RequiresOptions,
|
|
11
|
-
SuperwallInstance,
|
|
12
|
-
SuperwallOptions,
|
|
13
|
-
} from "./types.ts";
|
|
14
|
-
|
|
15
|
-
const DEFAULT_CACHE_TTL_MS = 60_000;
|
|
16
|
-
const DEFAULT_CACHE_MAX = 10_000;
|
|
17
|
-
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Construct a Superwall server instance. One per process — internally
|
|
21
|
-
* shares the cache and outbound HTTP config across all middleware and
|
|
22
|
-
* direct calls.
|
|
23
|
-
*
|
|
24
|
-
* ```ts
|
|
25
|
-
* const sw = Superwall({
|
|
26
|
-
* apiKey: process.env.SUPERWALL_API_KEY!,
|
|
27
|
-
* userId: (req) => req.session?.userId ?? null,
|
|
28
|
-
* })
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export const Superwall = <TReq = unknown>(
|
|
32
|
-
options: SuperwallOptions<TReq>,
|
|
33
|
-
): SuperwallInstance<TReq> => {
|
|
34
|
-
if (!options.apiKey || typeof options.apiKey !== "string") {
|
|
35
|
-
throw new TypeError("Superwall: `apiKey` is required.");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const cacheOpts = options.cache ?? {};
|
|
39
|
-
const ttlMs = cacheOpts.ttlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
40
|
-
const maxEntries = cacheOpts.maxEntries ?? DEFAULT_CACHE_MAX;
|
|
41
|
-
const cache: CacheAdapter =
|
|
42
|
-
cacheOpts.storage === undefined || cacheOpts.storage === "memory"
|
|
43
|
-
? new InMemoryCache(maxEntries)
|
|
44
|
-
: cacheOpts.storage;
|
|
45
|
-
|
|
46
|
-
const fetcherConfig: FetcherConfig = {
|
|
47
|
-
apiKey: options.apiKey,
|
|
48
|
-
environment: options.environment ?? "release",
|
|
49
|
-
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/** Fetch + cache for one userId. Inflight de-dup is intentionally
|
|
53
|
-
* omitted for v0 — callers rarely concurrently lookup the same user
|
|
54
|
-
* thousands of times, and the trade-off (extra closure per request)
|
|
55
|
-
* isn't worth it before we measure. */
|
|
56
|
-
const loadEntitlements = async (userId: string): Promise<{
|
|
57
|
-
entitlements: Entitlements;
|
|
58
|
-
cacheHit: boolean;
|
|
59
|
-
}> => {
|
|
60
|
-
const cached = await cache.get(userId);
|
|
61
|
-
if (cached) return { entitlements: cached.value, cacheHit: true };
|
|
62
|
-
const ents = await fetchEntitlements(fetcherConfig, userId);
|
|
63
|
-
await cache.set(userId, { value: ents, expiresAt: Date.now() + ttlMs });
|
|
64
|
-
return { entitlements: ents, cacheHit: false };
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const emitRequest = (info: RequestInfo): void => {
|
|
68
|
-
if (!options.onRequest) return;
|
|
69
|
-
try {
|
|
70
|
-
options.onRequest(info);
|
|
71
|
-
} catch {
|
|
72
|
-
// Telemetry hooks never break the request.
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const getEntitlements = async (userId: string): Promise<Entitlements> => {
|
|
77
|
-
const start = Date.now();
|
|
78
|
-
const { entitlements, cacheHit } = await loadEntitlements(userId);
|
|
79
|
-
emitRequest({
|
|
80
|
-
userId,
|
|
81
|
-
entitlements: entitlements.active.map((e) => e.id),
|
|
82
|
-
cacheHit,
|
|
83
|
-
durationMs: Date.now() - start,
|
|
84
|
-
});
|
|
85
|
-
return entitlements;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const userHas = async (
|
|
89
|
-
userId: string,
|
|
90
|
-
spec: EntitlementSpec,
|
|
91
|
-
): Promise<boolean> => {
|
|
92
|
-
const normalized = normalizeSpec(spec);
|
|
93
|
-
const ents = await getEntitlements(userId);
|
|
94
|
-
return findMissing(normalized, ents).length === 0;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const invalidate = async (userId: string): Promise<void> => {
|
|
98
|
-
await cache.delete(userId);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const invalidateAll = async (): Promise<void> => {
|
|
102
|
-
await cache.clear();
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const requires = makeRequires<TReq>({
|
|
106
|
-
defaultUserIdExtractor: options.userId,
|
|
107
|
-
loadEntitlements,
|
|
108
|
-
emitRequest,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
requires,
|
|
113
|
-
userHas,
|
|
114
|
-
getEntitlements,
|
|
115
|
-
invalidate,
|
|
116
|
-
invalidateAll,
|
|
117
|
-
};
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// Re-export for tests that need to construct fetcher config directly.
|
|
121
|
-
export type { FetcherConfig };
|
package/src/types.ts
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import type { Entitlements, NetworkEnvironment } from "@superwall/core";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Entitlement spec accepted by `sw.requires()` and `sw.userHas()`.
|
|
5
|
-
*
|
|
6
|
-
* - `"pro"` — single entitlement (must be active)
|
|
7
|
-
* - `["pro", "team"]` — multiple entitlements, ALL required
|
|
8
|
-
* - `{ all: [...] }` — explicit AND
|
|
9
|
-
* - `{ any: [...] }` — OR (any of the listed must be active)
|
|
10
|
-
*/
|
|
11
|
-
export type EntitlementSpec =
|
|
12
|
-
| string
|
|
13
|
-
| ReadonlyArray<string>
|
|
14
|
-
| { readonly all: ReadonlyArray<string> }
|
|
15
|
-
| { readonly any: ReadonlyArray<string> };
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Cache adapter shape. The built-in `"memory"` storage implements this.
|
|
19
|
-
* Plug in Redis, KV, or any async store with the same surface.
|
|
20
|
-
*
|
|
21
|
-
* Keys are app-user IDs. Values are the parsed Entitlements bucket plus
|
|
22
|
-
* the timestamp it expires at.
|
|
23
|
-
*/
|
|
24
|
-
export interface CacheAdapter {
|
|
25
|
-
get(key: string): Promise<CacheEntry | null> | CacheEntry | null;
|
|
26
|
-
set(key: string, value: CacheEntry): Promise<void> | void;
|
|
27
|
-
delete(key: string): Promise<void> | void;
|
|
28
|
-
clear(): Promise<void> | void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface CacheEntry {
|
|
32
|
-
readonly value: Entitlements;
|
|
33
|
-
readonly expiresAt: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface CacheOptions {
|
|
37
|
-
/** Time-to-live in milliseconds. Default 60_000 (60s). */
|
|
38
|
-
ttlMs?: number;
|
|
39
|
-
/** LRU cap; oldest evicted first. Default 10_000. */
|
|
40
|
-
maxEntries?: number;
|
|
41
|
-
/** `"memory"` or a custom adapter. Default `"memory"`. */
|
|
42
|
-
storage?: "memory" | CacheAdapter;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Extracts the user identifier from whatever request shape your framework
|
|
47
|
-
* uses. Safety-critical knob: read from authenticated session, never from
|
|
48
|
-
* request body / query string / unverified header.
|
|
49
|
-
*/
|
|
50
|
-
export type UserIdExtractor<TReq = unknown> = (
|
|
51
|
-
req: TReq,
|
|
52
|
-
) => string | null | undefined | Promise<string | null | undefined>;
|
|
53
|
-
|
|
54
|
-
export interface RequestInfo {
|
|
55
|
-
readonly userId: string;
|
|
56
|
-
readonly entitlements: ReadonlyArray<string>;
|
|
57
|
-
readonly cacheHit: boolean;
|
|
58
|
-
readonly durationMs: number;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface SuperwallOptions<TReq = unknown> {
|
|
62
|
-
/** Superwall API key. Read from `process.env.SUPERWALL_API_KEY`. */
|
|
63
|
-
apiKey: string;
|
|
64
|
-
/** Environment selector. Default `"release"`. */
|
|
65
|
-
environment?: NetworkEnvironment;
|
|
66
|
-
cache?: CacheOptions;
|
|
67
|
-
/** Default userId extractor; can be overridden per-call on `requires()`. */
|
|
68
|
-
userId?: UserIdExtractor<TReq>;
|
|
69
|
-
/** Hook invoked after every entitlement check. Use for tracing. */
|
|
70
|
-
onRequest?: (info: RequestInfo) => void;
|
|
71
|
-
/** Request timeout in milliseconds for outbound calls to Superwall. Default 5_000. */
|
|
72
|
-
timeoutMs?: number;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Narrow internal `fetch` surface. Not exported as a public option — server
|
|
77
|
-
* runtimes (Node 18+, Bun, Deno, Workers) all carry `globalThis.fetch`
|
|
78
|
-
* natively, and unlike the browser there's no CORS proxy use case. Tests
|
|
79
|
-
* stub `globalThis.fetch` directly.
|
|
80
|
-
*/
|
|
81
|
-
export type FetchLike = (
|
|
82
|
-
input: string | URL | Request,
|
|
83
|
-
init?: RequestInit,
|
|
84
|
-
) => Promise<Response>;
|
|
85
|
-
|
|
86
|
-
// Connect-style middleware signature — req/res/next, duck-typed. Compatible
|
|
87
|
-
// with Express, Connect, and shimmable from Bun.serve / Hono / Next.
|
|
88
|
-
|
|
89
|
-
export interface ConnectStyleResponse {
|
|
90
|
-
status(code: number): ConnectStyleResponse;
|
|
91
|
-
json(body: unknown): unknown;
|
|
92
|
-
setHeader?(name: string, value: string): unknown;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export type ConnectStyleNext = (err?: unknown) => void;
|
|
96
|
-
|
|
97
|
-
export type ConnectStyleRequest = Record<string, unknown>;
|
|
98
|
-
|
|
99
|
-
export interface UnauthorizedContext {
|
|
100
|
-
readonly userId: string | null;
|
|
101
|
-
readonly entitlement: string;
|
|
102
|
-
readonly missing: ReadonlyArray<string>;
|
|
103
|
-
readonly reason: "no_user_id" | "not_entitled";
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export interface RequiresOptions<TReq = unknown> {
|
|
107
|
-
/** Per-route userId override (rare). */
|
|
108
|
-
userId?: UserIdExtractor<TReq>;
|
|
109
|
-
/**
|
|
110
|
-
* Custom rejection handler. Default: respond `403 { error:
|
|
111
|
-
* "entitlement_required", entitlement }`.
|
|
112
|
-
*/
|
|
113
|
-
onUnauthorized?: (
|
|
114
|
-
req: TReq,
|
|
115
|
-
res: ConnectStyleResponse,
|
|
116
|
-
ctx: UnauthorizedContext,
|
|
117
|
-
) => void | Promise<void>;
|
|
118
|
-
/**
|
|
119
|
-
* Allow anonymous requests (no userId extracted) to fall through to the
|
|
120
|
-
* route handler. Defaults to `false` — fail closed. Set `true` when the
|
|
121
|
-
* route handler itself decides what to render for guests vs. entitled users.
|
|
122
|
-
*/
|
|
123
|
-
allowAnonymous?: boolean;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// The object returned by Superwall(options). Methods bind back to the
|
|
127
|
-
// shared cache + fetcher so a single instance fronts the entire process.
|
|
128
|
-
export interface SuperwallInstance<TReq = unknown> {
|
|
129
|
-
requires(
|
|
130
|
-
spec: EntitlementSpec,
|
|
131
|
-
options?: RequiresOptions<TReq>,
|
|
132
|
-
): (req: TReq, res: ConnectStyleResponse, next: ConnectStyleNext) => Promise<void>;
|
|
133
|
-
userHas(userId: string, spec: EntitlementSpec): Promise<boolean>;
|
|
134
|
-
getEntitlements(userId: string): Promise<Entitlements>;
|
|
135
|
-
invalidate(userId: string): Promise<void>;
|
|
136
|
-
invalidateAll(): Promise<void>;
|
|
137
|
-
}
|
package/tsconfig.json
DELETED
|
File without changes
|