@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
package/src/index.ts
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// @volter/twin-upstash — the Upstash Redis REST API twin, built on the shared @volter/world-core
|
|
2
|
+
// kernel. Per-database host transport (`https://<slug>.upstash.io`), five request forms
|
|
3
|
+
// (`POST /`, path-style `GET|POST /CMD/args`, raw-body `POST /set/k`, `POST /pipeline`,
|
|
4
|
+
// `POST /multi-exec`), Bearer-token auth with the vendor's literal `WRONGPASS` 401, the
|
|
5
|
+
// `{result}`/`{error}` envelope, and `Upstash-Encoding: base64`.
|
|
6
|
+
//
|
|
7
|
+
// Behind that protocol is a REAL Redis: a stateful keyspace (strings, counters, hashes, sets,
|
|
8
|
+
// sorted sets, lists, streams) with real type errors (`WRONGTYPE`), real integer semantics
|
|
9
|
+
// (`ERR value is not an integer or out of range`), real lazy TTL expiry — and a Lua-subset
|
|
10
|
+
// INTERPRETER behind `EVAL`/`EVALSHA`, which is what lets `@upstash/ratelimit` run its own
|
|
11
|
+
// unmodified fixed-window / sliding-window / token-bucket scripts against this twin and actually
|
|
12
|
+
// enforce a limit. Nothing about those scripts is special-cased: they run because the interpreter
|
|
13
|
+
// runs Lua, and their SHA1s are only ever used the way real Redis uses them (the script cache and
|
|
14
|
+
// the `NOSCRIPT` fallback contract).
|
|
15
|
+
//
|
|
16
|
+
// TIME IS INJECTED: TTL expiry, rate-limit windows and stream ids are all measured against an
|
|
17
|
+
// INJECTED clock — the request's own `occurredAt`, or `createUpstashRedisTwinServer({ now })` — so
|
|
18
|
+
// every time-dependent behaviour is reproducible rather than raced. See README ## Coverage.
|
|
19
|
+
//
|
|
20
|
+
// State lives ENTIRELY in the kernel action log (no side-store): every Redis key is one kernel
|
|
21
|
+
// subject, so a restart against the same root answers the same `GET`.
|
|
22
|
+
export {
|
|
23
|
+
handleUpstashRedisTwinRequest,
|
|
24
|
+
routeUpstashRedisSurface,
|
|
25
|
+
upstashTwinSnapshot,
|
|
26
|
+
extractUpstashCredential,
|
|
27
|
+
encodeResult,
|
|
28
|
+
nextSyncToken,
|
|
29
|
+
UPSTASH_RESOURCE_TYPES,
|
|
30
|
+
UPSTASH_UNAUTHORIZED_ERROR,
|
|
31
|
+
} from './upstash-twin.ts';
|
|
32
|
+
export type {
|
|
33
|
+
UpstashRedisRequest,
|
|
34
|
+
UpstashRedisResponse,
|
|
35
|
+
UpstashRedisResourceType,
|
|
36
|
+
UpstashRedisSurface,
|
|
37
|
+
UpstashRedisTwinSnapshot,
|
|
38
|
+
} from './upstash-twin.ts';
|
|
39
|
+
|
|
40
|
+
// The command core — exported so a caller can drive Redis semantics in-process, without HTTP.
|
|
41
|
+
export {
|
|
42
|
+
execRedisRun,
|
|
43
|
+
commandShapeError,
|
|
44
|
+
globMatch,
|
|
45
|
+
isWriteCommand,
|
|
46
|
+
unavailableCommand,
|
|
47
|
+
restRestricted,
|
|
48
|
+
wrongArity,
|
|
49
|
+
RedisCommandError,
|
|
50
|
+
RedisStatus,
|
|
51
|
+
ReadOnlyError,
|
|
52
|
+
SERVED_COMMANDS,
|
|
53
|
+
commandId,
|
|
54
|
+
upstashRedisOwners,
|
|
55
|
+
KEY_TYPES,
|
|
56
|
+
REST_RESTRICTED_COMMANDS,
|
|
57
|
+
SERVICE,
|
|
58
|
+
} from './upstash-store.ts';
|
|
59
|
+
export type { RedisContext, RedisValue, RunItem, KeyType } from './upstash-store.ts';
|
|
60
|
+
|
|
61
|
+
// The Lua-subset interpreter. Exported because `EVAL` fidelity is a headline claim of this pack and
|
|
62
|
+
// a consumer (or a reviewer) must be able to run a script against it directly.
|
|
63
|
+
export { runLua, parseLua, tokenize, luaScriptSha1, luaTruthy, luaTypeName, luaToNumber, luaEquals, numberToLuaString, LuaError, LuaTable } from './upstash-lua.ts';
|
|
64
|
+
export type { LuaHost, LuaValue, LuaFunction } from './upstash-lua.ts';
|
|
65
|
+
|
|
66
|
+
export { createUpstashRedisTwinFetch, createUpstashRedisTwinServer } from './upstash-server.ts';
|
|
67
|
+
export type { UpstashRedisServerOptions, UpstashRedisTwinFetchOptions } from './upstash-server.ts';
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
mapKey,
|
|
71
|
+
pullUpstashRedisKeys,
|
|
72
|
+
pullUpstashRedisScan,
|
|
73
|
+
syncUpstashRedisFromReal,
|
|
74
|
+
} from './upstash-connector.ts';
|
|
75
|
+
export type {
|
|
76
|
+
UpstashRedisLikeClient,
|
|
77
|
+
UpstashRedisKeyHandle,
|
|
78
|
+
UpstashRedisRealKey,
|
|
79
|
+
UpstashRedisBudgetedOptions,
|
|
80
|
+
} from './upstash-connector.ts';
|
|
81
|
+
|
|
82
|
+
// The client-side rate budget — the fail-closed backstop every live call goes through. The
|
|
83
|
+
// MECHANISM is the kernel's shared, vendor-agnostic `RateBudget`; what lives here is this vendor's
|
|
84
|
+
// DECLARATION (window/ceiling/per-method weights) plus `guardUpstashRedisClient`, the choke point
|
|
85
|
+
// the connector entrypoints apply unconditionally. Exported so an operator can inspect spend
|
|
86
|
+
// (`snapshot`) and a caller can catch `UpstashRedisBudgetError` by type; there is deliberately no
|
|
87
|
+
// export that disables the guard.
|
|
88
|
+
export {
|
|
89
|
+
UPSTASH_BUDGETED_METHODS,
|
|
90
|
+
UPSTASH_BUDGET_CEILING,
|
|
91
|
+
UPSTASH_BUDGET_MAX_RETRY_AFTER_S,
|
|
92
|
+
UPSTASH_BUDGET_WINDOW_MS,
|
|
93
|
+
UPSTASH_CALL_WEIGHTS,
|
|
94
|
+
UPSTASH_RATE_BUDGET,
|
|
95
|
+
UpstashRedisBudget,
|
|
96
|
+
UpstashRedisBudgetError,
|
|
97
|
+
upstashBudgetPath,
|
|
98
|
+
upstashCallWeight,
|
|
99
|
+
upstashClientBudget,
|
|
100
|
+
guardUpstashRedisClient,
|
|
101
|
+
} from './upstash-budget.ts';
|
|
102
|
+
export type {
|
|
103
|
+
UpstashRedisBudgetErrorKind,
|
|
104
|
+
UpstashRedisBudgetOptions,
|
|
105
|
+
UpstashRedisBudgetReservation,
|
|
106
|
+
UpstashRedisBudgetSnapshot,
|
|
107
|
+
} from './upstash-budget.ts';
|
|
108
|
+
|
|
109
|
+
// Registry descriptor: the pack self-describes so tooling can discover it.
|
|
110
|
+
import { registerPack, type TwinPack } from '@volter/world-core';
|
|
111
|
+
import { performUpstashRedisAction, syncUpstashRedisFromRemote } from './upstash-connector.ts';
|
|
112
|
+
import { UPSTASH_RATE_BUDGET as RATE_BUDGET } from './upstash-budget.ts';
|
|
113
|
+
export const pack: TwinPack = {
|
|
114
|
+
// PROTOCOL 2 (docs/contributing/architecture.md#protocol-2-the-pack-is-a-plugin): the pack is a plugin — its wire, its tree, and its half of
|
|
115
|
+
// the real state system. Moved 2026-09-08.
|
|
116
|
+
protocol: '2',
|
|
117
|
+
// A cache moves constantly and a full SCAN of a real keyspace is expensive, so the poll is coarse
|
|
118
|
+
// and the on-demand path is what a world uses when it needs to know now.
|
|
119
|
+
refresh: { every: '15m', webhook: false, onDemand: { atMost: '30s' } },
|
|
120
|
+
stateSystem: { perform: performUpstashRedisAction, refresh: syncUpstashRedisFromRemote },
|
|
121
|
+
// The round trip is a SET — Redis's own write, idempotent by nature, so a second send on a branch
|
|
122
|
+
// sets the key again rather than being refused.
|
|
123
|
+
roundTrip: { method: 'POST', path: '/', body: ['SET', 'round-trip', 'round-trip'], headers: { authorization: 'Bearer round-trip' } },
|
|
124
|
+
parityOrigin: 'http://twin',
|
|
125
|
+
shapeParity: 'held',
|
|
126
|
+
|
|
127
|
+
// The SAME object upstash-budget.ts declares at module load — one source of truth, so
|
|
128
|
+
// registering the pack and importing the connector can never arm two different ceilings.
|
|
129
|
+
rateBudget: RATE_BUDGET,
|
|
130
|
+
vendor: 'upstash',
|
|
131
|
+
// INTERCEPTION — this pack's hosts as DATA (packRegistry HostRule), moved from the injector's
|
|
132
|
+
// hand table with its reasoning verbatim:
|
|
133
|
+
// (TOMBSTONE) `azureformrecognizer` used to live here as a bare
|
|
134
|
+
// `h.endsWith('.cognitiveservices.azure.com')` suffix matcher, with a note that a second Azure
|
|
135
|
+
// AI twin on that shared domain would need path-prefix disambiguation. That second twin is here
|
|
136
|
+
// — the `azure` pack's service area 2 serves Azure OpenAI on `/openai/*` — so BOTH claims moved
|
|
137
|
+
// onto their pack descriptors WITH `pathPattern`s and this hand entry was deleted in the same
|
|
138
|
+
// change (one home per fact; keeping it would throw at require time). See
|
|
139
|
+
// packages/twin/azureformrecognizer/src/index.ts and packages/twin/azure/src/index.ts.
|
|
140
|
+
// Upstash Redis. Every database gets its OWN host under one shared apex —
|
|
141
|
+
// https://<slug>.upstash.io, where the slug is per-database (both naming generations exist in
|
|
142
|
+
// the wild: the legacy regional `us1-merry-cat-32748` form and the current
|
|
143
|
+
// `striking-osprey-20681` form) — so there is no fixed host to match and this needs a SUFFIX
|
|
144
|
+
// matcher, exactly like azureformrecognizer's per-resource Azure hosts above. Confirmed against
|
|
145
|
+
// the pack's own connector/README and the vendor's REST docs; global databases use ONE hostname
|
|
146
|
+
// and Upstash routes internally, so there is no region-suffixed variant to add.
|
|
147
|
+
//
|
|
148
|
+
// QStash and Upstash Workflow share the apex too (qstash.upstash.io, qstash-<region>.upstash.io): they are this pack's
|
|
149
|
+
// `qstash` lane (../qstash/src/), so the suffix takes their hosts and the pack's fetch sends them to the lane. The one
|
|
150
|
+
// exclusion is Upstash VECTOR (`*-vector.upstash.io`, packages/twin/upstashvector), claimed by that pack's own entry: a
|
|
151
|
+
// bare suffix would swallow it into this twin. Upstash Search (`*-search.upstash.io`) also lives here and has no twin
|
|
152
|
+
// yet; when one lands it needs the same treatment, and this comment is the note that says so.
|
|
153
|
+
hosts: [
|
|
154
|
+
{ suffix: '.upstash.io' },
|
|
155
|
+
{ suffix: '-vector.upstash.io', exclude: true },
|
|
156
|
+
// the Developer API and the console's pages, the pack's `api` lane (../api/src/index.ts)
|
|
157
|
+
{ host: 'api.upstash.com', pathPattern: '^/v2/(redis|team|teams)(/|$)' },
|
|
158
|
+
{ host: 'console.upstash.com', pathPattern: '^/(login|account/(api|teams)|qstash|invite)(/|$)' },
|
|
159
|
+
],
|
|
160
|
+
// QStash's clients read their base URL from QSTASH_URL (@upstash/qstash's documented override; @upstash/workflow's
|
|
161
|
+
// Client is a QStash client and reads the same), so the World points it at this twin as well as redirecting the hosts.
|
|
162
|
+
endpointEnv: {
|
|
163
|
+
name: 'UPSTASH_TWIN_URL',
|
|
164
|
+
templates: { QSTASH_URL: '${url}' },
|
|
165
|
+
note: '@upstash/qstash and @upstash/workflow read QSTASH_URL; the injector also redirects *.upstash.io (Redis databases, QStash) to this twin.',
|
|
166
|
+
},
|
|
167
|
+
transport: 'rest',
|
|
168
|
+
archetype: 'crud',
|
|
169
|
+
bin: 'world-upstash',
|
|
170
|
+
resources: ['key'],
|
|
171
|
+
specSource:
|
|
172
|
+
'upstash.com/docs/redis/features/restapi + the installed @upstash/redis@1.35.7 compiled source + LIVE PROBES of a real ephemeral Upstash database (every error string, HTTP status and base64 rule); see spec-sources.json.',
|
|
173
|
+
description:
|
|
174
|
+
'Upstash Redis REST twin — a real stateful Redis keyspace (strings/counters/TTL/hashes/sets/sorted-sets/lists/streams, WRONGTYPE and integer errors, lazy expiry) served over Upstash\'s REST protocol: POST /, path-style GET /get/key, POST /pipeline, POST /multi-exec, Bearer auth with the vendor\'s WRONGPASS 401, and Upstash-Encoding: base64. EVAL/EVALSHA run a Lua-subset INTERPRETER over that same command core, so @upstash/ratelimit\'s own unmodified scripts execute and a limiter genuinely enforces. Clock is injected (no wall-clock reads). Kernel-backed. Its lanes: the Developer API and console (api.upstash.com, console.upstash.com: sign-in, API keys, the QStash page), and QStash with Upstash Workflow (qstash.upstash.io and QSTASH_URL: publish, enqueue, batch, schedules, queues, the DLQs, signing keys, and workflow runs delivered step by step to the app, with waits, notify, cancel and resume).',
|
|
175
|
+
// Adoption, all in the pack's one home. The `scopes` entries are exact PACKAGE names, not an apex: the `@upstash/` scope
|
|
176
|
+
// spans several Upstash products, and Vector has its own pack, so it is claimed package by package: the Redis client and
|
|
177
|
+
// the ratelimiter built on it, and QStash's and Workflow's clients (the qstash lane). Stems: UPSTASH_REDIS_* and KV_REST_*
|
|
178
|
+
// (the env names Vercel KV, this vendor's Redis under another label, writes into a project), and QSTASH_*.
|
|
179
|
+
adoption: {
|
|
180
|
+
pypi: ['upstash-redis', 'upstash-ratelimit', 'qstash', 'upstash-workflow'],
|
|
181
|
+
sdks: ['@upstash/redis', '@upstash/ratelimit', '@upstash/qstash', '@upstash/workflow'],
|
|
182
|
+
scopes: ['@upstash/redis', '@upstash/ratelimit', '@upstash/qstash', '@upstash/workflow'],
|
|
183
|
+
envStems: ['UPSTASHREDIS', 'KVREST', 'QSTASH'],
|
|
184
|
+
},
|
|
185
|
+
browserRouting: { apiPathPrefix: '/', loaderHost: 'https://twin.upstash.io' },
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
registerPack(pack);
|
|
189
|
+
|
|
190
|
+
/** A string of a `Upstash-Encoding: base64` answer as @upstash/redis decodes it: base64 to UTF-8, the bare status `OK`
|
|
191
|
+
* excepted, which Upstash leaves raw (upstash-twin.ts, `encodeResult`). */
|
|
192
|
+
const decodeResult = (v: unknown): unknown =>
|
|
193
|
+
typeof v === 'string' ? (v === 'OK' ? v : Buffer.from(v, 'base64').toString('utf8')) : Array.isArray(v) ? v.map(decodeResult) : v;
|
|
194
|
+
|
|
195
|
+
/** The pack's views of its answers, for a journey step's `view` (scripts/journey-kit.ts). `decoded`: an answer to a
|
|
196
|
+
* request sent with `Upstash-Encoding: base64`, as @upstash/redis reads it: each `result` decoded, each `error` as it is,
|
|
197
|
+
* for a single command's `{result}` or `{error}` and for a pipeline's or transaction's list of them. */
|
|
198
|
+
export const answerViews: Record<string, (body: unknown) => unknown> = {
|
|
199
|
+
decoded: (body) => {
|
|
200
|
+
const one = (item: unknown): unknown => (item !== null && typeof item === 'object' && 'result' in item ? { ...item, result: decodeResult((item as { result: unknown }).result) } : item);
|
|
201
|
+
return Array.isArray(body) ? body.map(one) : one(body);
|
|
202
|
+
},
|
|
203
|
+
};
|
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Upstash Redis's manifest: the vendor facts Redis's command table does not carry (docs/contributing/architecture.md,
|
|
2
|
+
// "Protocol 3"). The surface is generated (./generated/surface.gen.json, from ../spec/commands by scripts/derive-pack.ts):
|
|
3
|
+
// Redis's command table, one entry per command id with its arity, dispatched by upstash-store.ts. The wire is
|
|
4
|
+
// Upstash's REST envelope (upstash.com/docs/redis/features/restapi), served by the pack's own fetch
|
|
5
|
+
// (upstash-server.ts over upstash-twin.ts), each form it serves cited to that page there.
|
|
6
|
+
//
|
|
7
|
+
// WHY NO `auth` AND NO ERROR TEMPLATE. The envelope's answers are Upstash's own: `{result}` or `{error}` with the Redis
|
|
8
|
+
// error text, a 401 `{error: "WRONGPASS …"}` for a missing or wrong token, a 405 with no body for a method it does not
|
|
9
|
+
// take. The pack's fetch keeps them in that order (upstash-twin.ts, `handleUpstashRedisTwinRequest`); nothing in the
|
|
10
|
+
// kernel would enforce a declared `auth`, so none is declared, as psdb's manifest declares none.
|
|
11
|
+
//
|
|
12
|
+
// THE STATE. Keys and their values are the World's state: each key one world-store subject `key:<name>` (its kind,
|
|
13
|
+
// value, deadline and a write ordinal), each cached script one subject `script:<sha1>` (upstash-store.ts, the
|
|
14
|
+
// KeySpace). No command moves a declared state: a key's TTL is a deadline its reads compare with the World clock, and a
|
|
15
|
+
// key's kind is fixed by the command that creates it (a command against another kind answers WRONGTYPE). So no state
|
|
16
|
+
// machine is declared.
|
|
17
|
+
import type { DerivedManifest } from '@volter/world-core';
|
|
18
|
+
|
|
19
|
+
export const manifest = {
|
|
20
|
+
vendor: 'upstash',
|
|
21
|
+
service: 'upstash',
|
|
22
|
+
resources: {
|
|
23
|
+
Key: { storedAs: 'key', idPrefix: 'key' },
|
|
24
|
+
Script: { storedAs: 'script', idPrefix: 'script' },
|
|
25
|
+
},
|
|
26
|
+
} satisfies Pick<DerivedManifest, 'vendor' | 'service' | 'resources'>;
|