@volter/twin-upstash 0.1.1 → 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.
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// the SERVER is one line of `Bun.serve` around it. This is a CUSTOM fetch, not the kernel adapter
|
|
14
14
|
// (`createTwinFetchFromHandler`): replies carry ONLY the handler's own headers (the REST envelope
|
|
15
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';
|
|
16
|
+
import { isReadOnlyRequest, serveHttp, withRequestScopes } from '@volter/world-core';
|
|
17
17
|
import { handleUpstashRedisTwinRequest } from "./upstash-twin.js";
|
|
18
18
|
import { upstashRedisOwners } from "./upstash-store.js";
|
|
19
19
|
import { API_HOST, CONSOLE_HOST, createUpstashApiLaneFetch } from "../api/src/fetch.js";
|
|
@@ -26,12 +26,16 @@ const QSTASH_HOST = /^qstash(-[a-z0-9-]+)?\.upstash\.io$/;
|
|
|
26
26
|
const QSTASH_PATH = /^\/(v2\/|_twin\/(deliveries|destinations)(\/|$))/;
|
|
27
27
|
/** The pack's fetch, with `owners()`: per id of Redis's command table, the command core (`handler`) or the gap. */
|
|
28
28
|
export function createUpstashRedisTwinFetch(options = {}) {
|
|
29
|
-
const readOnly = options.readOnly ?? false;
|
|
30
29
|
const now = options.now ?? (() => worldNow());
|
|
31
30
|
// api.upstash.com's Developer API and console.upstash.com's pages are the pack's `api` lane (../api/src/fetch.ts)
|
|
31
|
+
// the lanes' own read-only twin; a read-only REQUEST is refused by the kernel's scope around them (withRequestScopes)
|
|
32
|
+
const readOnly = options.readOnly ?? false;
|
|
32
33
|
const api = createUpstashApiLaneFetch({ ...(options.root !== undefined ? { root: options.root } : {}), readOnly, clock: now });
|
|
33
34
|
const qstash = options.qstash ?? createQstashLaneFetch({ ...(options.root !== undefined ? { root: options.root } : {}), readOnly, clock: now });
|
|
34
|
-
|
|
35
|
+
// a read-only request (x-volter-read-only) is a request to a read-only twin: a command that writes
|
|
36
|
+
// (by GET too: Upstash runs any command from the path) is refused, and the kernel refuses any that slip
|
|
37
|
+
const fetch = withRequestScopes(async function upstashRedisTwinFetch(request) {
|
|
38
|
+
const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
|
|
35
39
|
const url = new URL(request.url);
|
|
36
40
|
// the vendor host a redirected request names in x-volter-twin-original-host (the injector's fetch path, a hosted
|
|
37
41
|
// World), else its Host, else its URL's (vercel-server.ts's precedent): the lane's two hosts go to the lane
|
|
@@ -64,7 +68,7 @@ export function createUpstashRedisTwinFetch(options = {}) {
|
|
|
64
68
|
if (out === null)
|
|
65
69
|
return new Response(null, { status, headers: { ...(outHeaders ?? {}) } });
|
|
66
70
|
return new Response(JSON.stringify(out), { status, headers: { ...(outHeaders ?? {}) } });
|
|
67
|
-
};
|
|
71
|
+
}, { refuse: () => Response.json({ error: 'NOPERM this request is read-only' }, { status: 403 }) });
|
|
68
72
|
return Object.assign(fetch, { owners: upstashRedisOwners });
|
|
69
73
|
}
|
|
70
74
|
/** `GET /twin`, the discovery manifest. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/twin-upstash",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Local Upstash Redis (REST API) twin: a real stateful Redis command core (strings/counters/TTL/hashes/sets/sorted-sets/keyspace) served over Upstash's per-database REST protocol — single-command POST /, path-style GET /get/key, POST /pipeline, POST /multi-exec, Bearer-token auth, the {result}/{error} envelope, Upstash-Encoding: base64 — plus an EVAL/EVALSHA Lua-subset interpreter that runs @upstash/ratelimit's real fixed-window/sliding-window/token-bucket scripts. Built on @volter/world-core.",
|
|
5
5
|
"author": "Volter (https://github.com/volter-ai)",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -37,17 +37,17 @@
|
|
|
37
37
|
"postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@volter/world-ui": "0.1.
|
|
40
|
+
"@volter/world-ui": "0.1.3",
|
|
41
41
|
"react": "^19.2.7",
|
|
42
42
|
"react-dom": "^19.2.7"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@volter/world-core": "2.0.
|
|
45
|
+
"@volter/world-core": "2.0.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/bun": "^1.2.20",
|
|
49
49
|
"@types/node": "^24.0.0",
|
|
50
|
-
"@volter/world-core": "2.0.
|
|
50
|
+
"@volter/world-core": "2.0.3",
|
|
51
51
|
"@volter/world-tooling": "0.1.0",
|
|
52
52
|
"@upstash/redis": "1.35.7",
|
|
53
53
|
"@upstash/ratelimit": "2.0.8",
|
package/src/upstash-server.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// the SERVER is one line of `Bun.serve` around it. This is a CUSTOM fetch, not the kernel adapter
|
|
14
14
|
// (`createTwinFetchFromHandler`): replies carry ONLY the handler's own headers (the REST envelope
|
|
15
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';
|
|
16
|
+
import { isReadOnlyRequest, serveHttp, withRequestScopes } from '@volter/world-core';
|
|
17
17
|
import { handleUpstashRedisTwinRequest } from './upstash-twin.ts';
|
|
18
18
|
import { upstashRedisOwners } from './upstash-store.ts';
|
|
19
19
|
import { API_HOST, CONSOLE_HOST, createUpstashApiLaneFetch } from '../api/src/fetch.ts';
|
|
@@ -50,12 +50,16 @@ const QSTASH_PATH = /^\/(v2\/|_twin\/(deliveries|destinations)(\/|$))/;
|
|
|
50
50
|
|
|
51
51
|
/** The pack's fetch, with `owners()`: per id of Redis's command table, the command core (`handler`) or the gap. */
|
|
52
52
|
export function createUpstashRedisTwinFetch(options: UpstashRedisTwinFetchOptions = {}): ((request: Request) => Promise<Response>) & { owners: typeof upstashRedisOwners } {
|
|
53
|
-
const readOnly = options.readOnly ?? false;
|
|
54
53
|
const now = options.now ?? (() => worldNow());
|
|
55
54
|
// api.upstash.com's Developer API and console.upstash.com's pages are the pack's `api` lane (../api/src/fetch.ts)
|
|
55
|
+
// the lanes' own read-only twin; a read-only REQUEST is refused by the kernel's scope around them (withRequestScopes)
|
|
56
|
+
const readOnly = options.readOnly ?? false;
|
|
56
57
|
const api = createUpstashApiLaneFetch({ ...(options.root !== undefined ? { root: options.root } : {}), readOnly, clock: now });
|
|
57
58
|
const qstash = options.qstash ?? createQstashLaneFetch({ ...(options.root !== undefined ? { root: options.root } : {}), readOnly, clock: now });
|
|
58
|
-
|
|
59
|
+
// a read-only request (x-volter-read-only) is a request to a read-only twin: a command that writes
|
|
60
|
+
// (by GET too: Upstash runs any command from the path) is refused, and the kernel refuses any that slip
|
|
61
|
+
const fetch = withRequestScopes(async function upstashRedisTwinFetch(request: Request): Promise<Response> {
|
|
62
|
+
const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
|
|
59
63
|
const url = new URL(request.url);
|
|
60
64
|
// the vendor host a redirected request names in x-volter-twin-original-host (the injector's fetch path, a hosted
|
|
61
65
|
// World), else its Host, else its URL's (vercel-server.ts's precedent): the lane's two hosts go to the lane
|
|
@@ -84,7 +88,7 @@ export function createUpstashRedisTwinFetch(options: UpstashRedisTwinFetchOption
|
|
|
84
88
|
// the real service answers those with zero bytes and, for 405, no content-type either.
|
|
85
89
|
if (out === null) return new Response(null, { status, headers: { ...(outHeaders ?? {}) } });
|
|
86
90
|
return new Response(JSON.stringify(out), { status, headers: { ...(outHeaders ?? {}) } });
|
|
87
|
-
};
|
|
91
|
+
}, { refuse: () => Response.json({ error: 'NOPERM this request is read-only' }, { status: 403 }) });
|
|
88
92
|
return Object.assign(fetch, { owners: upstashRedisOwners });
|
|
89
93
|
}
|
|
90
94
|
|