@volter/twin-upstash 0.1.0
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/LICENSE +202 -0
- package/README.md +202 -0
- package/api/src/fetch.ts +54 -0
- package/api/src/generated/surface.gen.json +1 -0
- package/api/src/generated/ui.gen.json +1 -0
- package/api/src/index.ts +19 -0
- package/api/src/key-gate.ts +30 -0
- package/api/src/manifest.ts +103 -0
- package/api/src/screens/developer-api.tsx +106 -0
- package/api/src/screens/qstash.tsx +99 -0
- package/api/src/screens/session.tsx +125 -0
- package/api/src/screens/teams.tsx +114 -0
- package/api/src/semantics/backups.ts +90 -0
- package/api/src/semantics/index.ts +191 -0
- package/api/src/semantics/shared.ts +42 -0
- package/api/src/semantics/teams.ts +108 -0
- package/api/src/semantics/time.ts +40 -0
- package/dist/api/src/fetch.d.ts +15 -0
- package/dist/api/src/fetch.js +44 -0
- package/dist/api/src/fetch.ts +54 -0
- package/dist/api/src/generated/surface.gen.json +1 -0
- package/dist/api/src/generated/ui.gen.json +1 -0
- package/dist/api/src/index.ts +19 -0
- package/dist/api/src/key-gate.d.ts +3 -0
- package/dist/api/src/key-gate.js +30 -0
- package/dist/api/src/key-gate.ts +30 -0
- package/dist/api/src/manifest.d.ts +2 -0
- package/dist/api/src/manifest.js +81 -0
- package/dist/api/src/manifest.ts +103 -0
- package/dist/api/src/screens/developer-api.d.ts +3 -0
- package/dist/api/src/screens/developer-api.js +101 -0
- package/dist/api/src/screens/developer-api.tsx +106 -0
- package/dist/api/src/screens/qstash.d.ts +3 -0
- package/dist/api/src/screens/qstash.js +92 -0
- package/dist/api/src/screens/qstash.tsx +99 -0
- package/dist/api/src/screens/session.d.ts +9 -0
- package/dist/api/src/screens/session.js +118 -0
- package/dist/api/src/screens/session.tsx +125 -0
- package/dist/api/src/screens/teams.d.ts +3 -0
- package/dist/api/src/screens/teams.js +99 -0
- package/dist/api/src/screens/teams.tsx +114 -0
- package/dist/api/src/semantics/backups.d.ts +7 -0
- package/dist/api/src/semantics/backups.js +75 -0
- package/dist/api/src/semantics/backups.ts +90 -0
- package/dist/api/src/semantics/index.d.ts +10 -0
- package/dist/api/src/semantics/index.js +191 -0
- package/dist/api/src/semantics/index.ts +191 -0
- package/dist/api/src/semantics/shared.d.ts +21 -0
- package/dist/api/src/semantics/shared.js +34 -0
- package/dist/api/src/semantics/shared.ts +42 -0
- package/dist/api/src/semantics/teams.d.ts +13 -0
- package/dist/api/src/semantics/teams.js +100 -0
- package/dist/api/src/semantics/teams.ts +108 -0
- package/dist/api/src/semantics/time.d.ts +2 -0
- package/dist/api/src/semantics/time.js +34 -0
- package/dist/api/src/semantics/time.ts +40 -0
- package/dist/qstash/src/doors.d.ts +6 -0
- package/dist/qstash/src/doors.js +33 -0
- package/dist/qstash/src/doors.ts +51 -0
- package/dist/qstash/src/egress.d.ts +7 -0
- package/dist/qstash/src/egress.js +66 -0
- package/dist/qstash/src/egress.ts +58 -0
- package/dist/qstash/src/fetch.d.ts +7 -0
- package/dist/qstash/src/fetch.js +48 -0
- package/dist/qstash/src/fetch.ts +46 -0
- package/dist/qstash/src/generated/surface.gen.json +1 -0
- package/dist/qstash/src/generated/ui.gen.json +1 -0
- package/dist/qstash/src/index.ts +35 -0
- package/dist/qstash/src/manifest.d.ts +10 -0
- package/dist/qstash/src/manifest.js +105 -0
- package/dist/qstash/src/manifest.ts +134 -0
- package/dist/qstash/src/semantics/account.d.ts +29 -0
- package/dist/qstash/src/semantics/account.js +91 -0
- package/dist/qstash/src/semantics/account.ts +98 -0
- package/dist/qstash/src/semantics/delivery.d.ts +17 -0
- package/dist/qstash/src/semantics/delivery.js +274 -0
- package/dist/qstash/src/semantics/delivery.ts +264 -0
- package/dist/qstash/src/semantics/dlq.d.ts +4 -0
- package/dist/qstash/src/semantics/dlq.js +51 -0
- package/dist/qstash/src/semantics/dlq.ts +61 -0
- package/dist/qstash/src/semantics/index.d.ts +2 -0
- package/dist/qstash/src/semantics/index.js +10 -0
- package/dist/qstash/src/semantics/index.ts +13 -0
- package/dist/qstash/src/semantics/keys.d.ts +2 -0
- package/dist/qstash/src/semantics/keys.js +9 -0
- package/dist/qstash/src/semantics/keys.ts +14 -0
- package/dist/qstash/src/semantics/messages.d.ts +74 -0
- package/dist/qstash/src/semantics/messages.js +233 -0
- package/dist/qstash/src/semantics/messages.ts +249 -0
- package/dist/qstash/src/semantics/queues.d.ts +2 -0
- package/dist/qstash/src/semantics/queues.js +60 -0
- package/dist/qstash/src/semantics/queues.ts +66 -0
- package/dist/qstash/src/semantics/schedules.d.ts +19 -0
- package/dist/qstash/src/semantics/schedules.js +125 -0
- package/dist/qstash/src/semantics/schedules.ts +132 -0
- package/dist/qstash/src/semantics/shared.d.ts +45 -0
- package/dist/qstash/src/semantics/shared.js +115 -0
- package/dist/qstash/src/semantics/shared.ts +121 -0
- package/dist/qstash/src/semantics/urlgroups.d.ts +2 -0
- package/dist/qstash/src/semantics/urlgroups.js +58 -0
- package/dist/qstash/src/semantics/urlgroups.ts +69 -0
- package/dist/qstash/src/semantics/workflows.d.ts +44 -0
- package/dist/qstash/src/semantics/workflows.js +379 -0
- package/dist/qstash/src/semantics/workflows.ts +401 -0
- package/dist/qstash/src/signing.d.ts +4 -0
- package/dist/qstash/src/signing.js +16 -0
- package/dist/qstash/src/signing.ts +19 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +35 -0
- package/dist/src/generated/surface.gen.json +1 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.js +124 -0
- package/dist/src/manifest.d.ts +14 -0
- package/dist/src/manifest.js +8 -0
- package/dist/src/upstash-budget.d.ts +85 -0
- package/dist/src/upstash-budget.js +440 -0
- package/dist/src/upstash-capabilities.d.ts +4 -0
- package/dist/src/upstash-capabilities.js +1286 -0
- package/dist/src/upstash-conformance.d.ts +7 -0
- package/dist/src/upstash-conformance.js +119 -0
- package/dist/src/upstash-connector.d.ts +115 -0
- package/dist/src/upstash-connector.js +309 -0
- package/dist/src/upstash-lua.d.ts +140 -0
- package/dist/src/upstash-lua.js +1229 -0
- package/dist/src/upstash-server.d.ts +29 -0
- package/dist/src/upstash-server.js +81 -0
- package/dist/src/upstash-store.d.ts +114 -0
- package/dist/src/upstash-store.js +1663 -0
- package/dist/src/upstash-twin.d.ts +73 -0
- package/dist/src/upstash-twin.js +437 -0
- package/package.json +59 -0
- package/qstash/src/doors.ts +51 -0
- package/qstash/src/egress.ts +58 -0
- package/qstash/src/fetch.ts +46 -0
- package/qstash/src/generated/surface.gen.json +1 -0
- package/qstash/src/generated/ui.gen.json +1 -0
- package/qstash/src/index.ts +35 -0
- package/qstash/src/manifest.ts +134 -0
- package/qstash/src/semantics/account.ts +98 -0
- package/qstash/src/semantics/delivery.ts +264 -0
- package/qstash/src/semantics/dlq.ts +61 -0
- package/qstash/src/semantics/index.ts +13 -0
- package/qstash/src/semantics/keys.ts +14 -0
- package/qstash/src/semantics/messages.ts +249 -0
- package/qstash/src/semantics/queues.ts +66 -0
- package/qstash/src/semantics/schedules.ts +132 -0
- package/qstash/src/semantics/shared.ts +121 -0
- package/qstash/src/semantics/urlgroups.ts +69 -0
- package/qstash/src/semantics/workflows.ts +401 -0
- package/qstash/src/signing.ts +19 -0
- package/src/cli.ts +36 -0
- package/src/generated/surface.gen.json +1 -0
- package/src/index.ts +203 -0
- package/src/manifest.ts +26 -0
- package/src/upstash-budget.ts +486 -0
- package/src/upstash-capabilities.ts +1418 -0
- package/src/upstash-conformance.ts +131 -0
- package/src/upstash-connector.ts +340 -0
- package/src/upstash-lua.ts +1120 -0
- package/src/upstash-server.ts +103 -0
- package/src/upstash-store.ts +1437 -0
- package/src/upstash-twin.ts +465 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// upstash twin HTTP server — serve the Upstash Redis REST twin over HTTP so the real
|
|
2
|
+
// `@upstash/redis` SDK (pointed at it with nothing but its own public `url` option) works
|
|
3
|
+
// UNMODIFIED, and so `@upstash/ratelimit` on top of that SDK enforces a real limit.
|
|
4
|
+
//
|
|
5
|
+
// ── THE INJECTED CLOCK ────────────────────────────────────────────────────────────────────────
|
|
6
|
+
// `now` is the twin's whole notion of time: it produces the `occurredAt` the kernel stamps each
|
|
7
|
+
// action with AND the instant every TTL is measured against. It defaults to the wall clock (a
|
|
8
|
+
// server has to), but a test passes its own so that TTL expiry, `EXPIRE`/`TTL` arithmetic and
|
|
9
|
+
// stream ids become data it controls rather than a race it hopes to win. This is the seam the
|
|
10
|
+
// repo's no-wall-clock discipline requires — see the store module's header.
|
|
11
|
+
//
|
|
12
|
+
// FETCH-FIRST (runtime contract R12b): the surface is the plain `createUpstashRedisTwinFetch` and
|
|
13
|
+
// the SERVER is one line of `Bun.serve` around it. This is a CUSTOM fetch, not the kernel adapter
|
|
14
|
+
// (`createTwinFetchFromHandler`): replies carry ONLY the handler's own headers (the REST envelope
|
|
15
|
+
// declares its own content type; a 405 carries none at all), and the clock is an injectable seam.
|
|
16
|
+
import { serveHttp } from '@volter/world-core';
|
|
17
|
+
import { handleUpstashRedisTwinRequest } from './upstash-twin.ts';
|
|
18
|
+
import { upstashRedisOwners } from './upstash-store.ts';
|
|
19
|
+
import { API_HOST, CONSOLE_HOST, createUpstashApiLaneFetch } from '../api/src/fetch.ts';
|
|
20
|
+
import { createQstashLaneFetch } from '../qstash/src/fetch.ts';
|
|
21
|
+
import { worldNow, statefulTwinManifest} from '@volter/world-core';
|
|
22
|
+
|
|
23
|
+
export type UpstashRedisServerOptions = {
|
|
24
|
+
root?: string;
|
|
25
|
+
port?: number;
|
|
26
|
+
readOnly?: boolean;
|
|
27
|
+
/** The token the twin demands. Omit to accept any non-empty credential (still 401s a missing one). */
|
|
28
|
+
token?: string;
|
|
29
|
+
/** The injected clock. Returns the ISO instant this request "happens at". */
|
|
30
|
+
now?: () => string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Options every Upstash Redis-twin HTTP surface needs, independent of who owns the socket. */
|
|
34
|
+
export interface UpstashRedisTwinFetchOptions {
|
|
35
|
+
root?: string;
|
|
36
|
+
readOnly?: boolean;
|
|
37
|
+
/** The token the twin demands. Omit to accept any non-empty credential (still 401s a missing one). */
|
|
38
|
+
token?: string;
|
|
39
|
+
/** The injected clock. Returns the ISO instant this request "happens at". */
|
|
40
|
+
now?: () => string;
|
|
41
|
+
/** the QStash lane's fetch (../qstash/src/fetch.ts), when the caller has one already */
|
|
42
|
+
qstash?: (request: Request) => Promise<Response>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** QStash's hosts: qstash.upstash.io and qstash-<region>.upstash.io (both documents' `servers`). */
|
|
46
|
+
const QSTASH_HOST = /^qstash(-[a-z0-9-]+)?\.upstash\.io$/;
|
|
47
|
+
/** The QStash lane's requests at the pack's own address: its API (an application's QSTASH_URL is the pack's address, and
|
|
48
|
+
* every QStash path begins `/v2/`, which no Redis command is) and the World's doors for it. */
|
|
49
|
+
const QSTASH_PATH = /^\/(v2\/|_twin\/(deliveries|destinations)(\/|$))/;
|
|
50
|
+
|
|
51
|
+
/** The pack's fetch, with `owners()`: per id of Redis's command table, the command core (`handler`) or the gap. */
|
|
52
|
+
export function createUpstashRedisTwinFetch(options: UpstashRedisTwinFetchOptions = {}): ((request: Request) => Promise<Response>) & { owners: typeof upstashRedisOwners } {
|
|
53
|
+
const readOnly = options.readOnly ?? false;
|
|
54
|
+
const now = options.now ?? (() => worldNow());
|
|
55
|
+
// api.upstash.com's Developer API and console.upstash.com's pages are the pack's `api` lane (../api/src/fetch.ts)
|
|
56
|
+
const api = createUpstashApiLaneFetch({ ...(options.root !== undefined ? { root: options.root } : {}), readOnly, clock: now });
|
|
57
|
+
const qstash = options.qstash ?? createQstashLaneFetch({ ...(options.root !== undefined ? { root: options.root } : {}), readOnly, clock: now });
|
|
58
|
+
const fetch = async function upstashRedisTwinFetch(request: Request): Promise<Response> {
|
|
59
|
+
const url = new URL(request.url);
|
|
60
|
+
// the vendor host a redirected request names in x-volter-twin-original-host (the injector's fetch path, a hosted
|
|
61
|
+
// World), else its Host, else its URL's (vercel-server.ts's precedent): the lane's two hosts go to the lane
|
|
62
|
+
const host = (request.headers.get('x-volter-twin-original-host') ?? request.headers.get('host') ?? url.host).split(':')[0]!.toLowerCase();
|
|
63
|
+
const vendorHost = host === API_HOST || host === CONSOLE_HOST ? host : url.hostname === API_HOST || url.hostname === CONSOLE_HOST ? url.hostname : undefined;
|
|
64
|
+
if (vendorHost !== undefined) return api(request, vendorHost);
|
|
65
|
+
if (QSTASH_HOST.test(host) || QSTASH_HOST.test(url.hostname) || QSTASH_PATH.test(url.pathname)) return qstash(request);
|
|
66
|
+
// GET /twin — the discovery manifest (education inside the twin).
|
|
67
|
+
if (request.method === 'GET' && url.pathname.replace(/\/+$/, '') === '/twin') return discovery();
|
|
68
|
+
// a daily backup due before this request is made first, so it holds the keys as they were (../api/src/semantics/time.ts)
|
|
69
|
+
await api.catchUp(request);
|
|
70
|
+
const body = request.method !== 'GET' && request.method !== 'HEAD' ? await request.text() : '';
|
|
71
|
+
const headers: Record<string, string> = {};
|
|
72
|
+
request.headers.forEach((value, key) => { headers[key.toLowerCase()] = value; });
|
|
73
|
+
const { status, body: out, headers: outHeaders } = await handleUpstashRedisTwinRequest({
|
|
74
|
+
method: request.method,
|
|
75
|
+
path: url.pathname + (url.search || ''),
|
|
76
|
+
body,
|
|
77
|
+
headers,
|
|
78
|
+
readOnly,
|
|
79
|
+
occurredAt: now(),
|
|
80
|
+
...(options.root !== undefined ? { root: options.root } : {}),
|
|
81
|
+
...(options.token !== undefined ? { token: options.token } : {}),
|
|
82
|
+
});
|
|
83
|
+
// A null body means "no body at all" (405 on a disallowed method, HEAD, CORS preflight) —
|
|
84
|
+
// the real service answers those with zero bytes and, for 405, no content-type either.
|
|
85
|
+
if (out === null) return new Response(null, { status, headers: { ...(outHeaders ?? {}) } });
|
|
86
|
+
return new Response(JSON.stringify(out), { status, headers: { ...(outHeaders ?? {}) } });
|
|
87
|
+
};
|
|
88
|
+
return Object.assign(fetch, { owners: upstashRedisOwners });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `GET /twin`, the discovery manifest. */
|
|
92
|
+
function discovery(): Response {
|
|
93
|
+
return Response.json(statefulTwinManifest({ vendor: 'upstash', twinOf: 'Upstash Redis (REST protocol)', stores: "a real Redis keyspace served over Upstash's REST envelope" }));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function createUpstashRedisTwinServer(options: UpstashRedisServerOptions = {}): Promise<{ port: number; stop: () => void }> {
|
|
97
|
+
const server = await serveHttp({
|
|
98
|
+
port: options.port ?? 0,
|
|
99
|
+
idleTimeout: 60,
|
|
100
|
+
fetch: createUpstashRedisTwinFetch(options),
|
|
101
|
+
});
|
|
102
|
+
return { port: server.port ?? options.port ?? 0, stop: () => server.stop(true) };
|
|
103
|
+
}
|