@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.
Files changed (162) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +202 -0
  3. package/api/src/fetch.ts +54 -0
  4. package/api/src/generated/surface.gen.json +1 -0
  5. package/api/src/generated/ui.gen.json +1 -0
  6. package/api/src/index.ts +19 -0
  7. package/api/src/key-gate.ts +30 -0
  8. package/api/src/manifest.ts +103 -0
  9. package/api/src/screens/developer-api.tsx +106 -0
  10. package/api/src/screens/qstash.tsx +99 -0
  11. package/api/src/screens/session.tsx +125 -0
  12. package/api/src/screens/teams.tsx +114 -0
  13. package/api/src/semantics/backups.ts +90 -0
  14. package/api/src/semantics/index.ts +191 -0
  15. package/api/src/semantics/shared.ts +42 -0
  16. package/api/src/semantics/teams.ts +108 -0
  17. package/api/src/semantics/time.ts +40 -0
  18. package/dist/api/src/fetch.d.ts +15 -0
  19. package/dist/api/src/fetch.js +44 -0
  20. package/dist/api/src/fetch.ts +54 -0
  21. package/dist/api/src/generated/surface.gen.json +1 -0
  22. package/dist/api/src/generated/ui.gen.json +1 -0
  23. package/dist/api/src/index.ts +19 -0
  24. package/dist/api/src/key-gate.d.ts +3 -0
  25. package/dist/api/src/key-gate.js +30 -0
  26. package/dist/api/src/key-gate.ts +30 -0
  27. package/dist/api/src/manifest.d.ts +2 -0
  28. package/dist/api/src/manifest.js +81 -0
  29. package/dist/api/src/manifest.ts +103 -0
  30. package/dist/api/src/screens/developer-api.d.ts +3 -0
  31. package/dist/api/src/screens/developer-api.js +101 -0
  32. package/dist/api/src/screens/developer-api.tsx +106 -0
  33. package/dist/api/src/screens/qstash.d.ts +3 -0
  34. package/dist/api/src/screens/qstash.js +92 -0
  35. package/dist/api/src/screens/qstash.tsx +99 -0
  36. package/dist/api/src/screens/session.d.ts +9 -0
  37. package/dist/api/src/screens/session.js +118 -0
  38. package/dist/api/src/screens/session.tsx +125 -0
  39. package/dist/api/src/screens/teams.d.ts +3 -0
  40. package/dist/api/src/screens/teams.js +99 -0
  41. package/dist/api/src/screens/teams.tsx +114 -0
  42. package/dist/api/src/semantics/backups.d.ts +7 -0
  43. package/dist/api/src/semantics/backups.js +75 -0
  44. package/dist/api/src/semantics/backups.ts +90 -0
  45. package/dist/api/src/semantics/index.d.ts +10 -0
  46. package/dist/api/src/semantics/index.js +191 -0
  47. package/dist/api/src/semantics/index.ts +191 -0
  48. package/dist/api/src/semantics/shared.d.ts +21 -0
  49. package/dist/api/src/semantics/shared.js +34 -0
  50. package/dist/api/src/semantics/shared.ts +42 -0
  51. package/dist/api/src/semantics/teams.d.ts +13 -0
  52. package/dist/api/src/semantics/teams.js +100 -0
  53. package/dist/api/src/semantics/teams.ts +108 -0
  54. package/dist/api/src/semantics/time.d.ts +2 -0
  55. package/dist/api/src/semantics/time.js +34 -0
  56. package/dist/api/src/semantics/time.ts +40 -0
  57. package/dist/qstash/src/doors.d.ts +6 -0
  58. package/dist/qstash/src/doors.js +33 -0
  59. package/dist/qstash/src/doors.ts +51 -0
  60. package/dist/qstash/src/egress.d.ts +7 -0
  61. package/dist/qstash/src/egress.js +66 -0
  62. package/dist/qstash/src/egress.ts +58 -0
  63. package/dist/qstash/src/fetch.d.ts +7 -0
  64. package/dist/qstash/src/fetch.js +48 -0
  65. package/dist/qstash/src/fetch.ts +46 -0
  66. package/dist/qstash/src/generated/surface.gen.json +1 -0
  67. package/dist/qstash/src/generated/ui.gen.json +1 -0
  68. package/dist/qstash/src/index.ts +35 -0
  69. package/dist/qstash/src/manifest.d.ts +10 -0
  70. package/dist/qstash/src/manifest.js +105 -0
  71. package/dist/qstash/src/manifest.ts +134 -0
  72. package/dist/qstash/src/semantics/account.d.ts +29 -0
  73. package/dist/qstash/src/semantics/account.js +91 -0
  74. package/dist/qstash/src/semantics/account.ts +98 -0
  75. package/dist/qstash/src/semantics/delivery.d.ts +17 -0
  76. package/dist/qstash/src/semantics/delivery.js +274 -0
  77. package/dist/qstash/src/semantics/delivery.ts +264 -0
  78. package/dist/qstash/src/semantics/dlq.d.ts +4 -0
  79. package/dist/qstash/src/semantics/dlq.js +51 -0
  80. package/dist/qstash/src/semantics/dlq.ts +61 -0
  81. package/dist/qstash/src/semantics/index.d.ts +2 -0
  82. package/dist/qstash/src/semantics/index.js +10 -0
  83. package/dist/qstash/src/semantics/index.ts +13 -0
  84. package/dist/qstash/src/semantics/keys.d.ts +2 -0
  85. package/dist/qstash/src/semantics/keys.js +9 -0
  86. package/dist/qstash/src/semantics/keys.ts +14 -0
  87. package/dist/qstash/src/semantics/messages.d.ts +74 -0
  88. package/dist/qstash/src/semantics/messages.js +233 -0
  89. package/dist/qstash/src/semantics/messages.ts +249 -0
  90. package/dist/qstash/src/semantics/queues.d.ts +2 -0
  91. package/dist/qstash/src/semantics/queues.js +60 -0
  92. package/dist/qstash/src/semantics/queues.ts +66 -0
  93. package/dist/qstash/src/semantics/schedules.d.ts +19 -0
  94. package/dist/qstash/src/semantics/schedules.js +125 -0
  95. package/dist/qstash/src/semantics/schedules.ts +132 -0
  96. package/dist/qstash/src/semantics/shared.d.ts +45 -0
  97. package/dist/qstash/src/semantics/shared.js +115 -0
  98. package/dist/qstash/src/semantics/shared.ts +121 -0
  99. package/dist/qstash/src/semantics/urlgroups.d.ts +2 -0
  100. package/dist/qstash/src/semantics/urlgroups.js +58 -0
  101. package/dist/qstash/src/semantics/urlgroups.ts +69 -0
  102. package/dist/qstash/src/semantics/workflows.d.ts +44 -0
  103. package/dist/qstash/src/semantics/workflows.js +379 -0
  104. package/dist/qstash/src/semantics/workflows.ts +401 -0
  105. package/dist/qstash/src/signing.d.ts +4 -0
  106. package/dist/qstash/src/signing.js +16 -0
  107. package/dist/qstash/src/signing.ts +19 -0
  108. package/dist/src/cli.d.ts +2 -0
  109. package/dist/src/cli.js +35 -0
  110. package/dist/src/generated/surface.gen.json +1 -0
  111. package/dist/src/index.d.ts +18 -0
  112. package/dist/src/index.js +124 -0
  113. package/dist/src/manifest.d.ts +14 -0
  114. package/dist/src/manifest.js +8 -0
  115. package/dist/src/upstash-budget.d.ts +85 -0
  116. package/dist/src/upstash-budget.js +440 -0
  117. package/dist/src/upstash-capabilities.d.ts +4 -0
  118. package/dist/src/upstash-capabilities.js +1286 -0
  119. package/dist/src/upstash-conformance.d.ts +7 -0
  120. package/dist/src/upstash-conformance.js +119 -0
  121. package/dist/src/upstash-connector.d.ts +115 -0
  122. package/dist/src/upstash-connector.js +309 -0
  123. package/dist/src/upstash-lua.d.ts +140 -0
  124. package/dist/src/upstash-lua.js +1229 -0
  125. package/dist/src/upstash-server.d.ts +29 -0
  126. package/dist/src/upstash-server.js +81 -0
  127. package/dist/src/upstash-store.d.ts +114 -0
  128. package/dist/src/upstash-store.js +1663 -0
  129. package/dist/src/upstash-twin.d.ts +73 -0
  130. package/dist/src/upstash-twin.js +437 -0
  131. package/package.json +59 -0
  132. package/qstash/src/doors.ts +51 -0
  133. package/qstash/src/egress.ts +58 -0
  134. package/qstash/src/fetch.ts +46 -0
  135. package/qstash/src/generated/surface.gen.json +1 -0
  136. package/qstash/src/generated/ui.gen.json +1 -0
  137. package/qstash/src/index.ts +35 -0
  138. package/qstash/src/manifest.ts +134 -0
  139. package/qstash/src/semantics/account.ts +98 -0
  140. package/qstash/src/semantics/delivery.ts +264 -0
  141. package/qstash/src/semantics/dlq.ts +61 -0
  142. package/qstash/src/semantics/index.ts +13 -0
  143. package/qstash/src/semantics/keys.ts +14 -0
  144. package/qstash/src/semantics/messages.ts +249 -0
  145. package/qstash/src/semantics/queues.ts +66 -0
  146. package/qstash/src/semantics/schedules.ts +132 -0
  147. package/qstash/src/semantics/shared.ts +121 -0
  148. package/qstash/src/semantics/urlgroups.ts +69 -0
  149. package/qstash/src/semantics/workflows.ts +401 -0
  150. package/qstash/src/signing.ts +19 -0
  151. package/src/cli.ts +36 -0
  152. package/src/generated/surface.gen.json +1 -0
  153. package/src/index.ts +203 -0
  154. package/src/manifest.ts +26 -0
  155. package/src/upstash-budget.ts +486 -0
  156. package/src/upstash-capabilities.ts +1418 -0
  157. package/src/upstash-conformance.ts +131 -0
  158. package/src/upstash-connector.ts +340 -0
  159. package/src/upstash-lua.ts +1120 -0
  160. package/src/upstash-server.ts +103 -0
  161. package/src/upstash-store.ts +1437 -0
  162. package/src/upstash-twin.ts +465 -0
@@ -0,0 +1,18 @@
1
+ export { handleUpstashRedisTwinRequest, routeUpstashRedisSurface, upstashTwinSnapshot, extractUpstashCredential, encodeResult, nextSyncToken, UPSTASH_RESOURCE_TYPES, UPSTASH_UNAUTHORIZED_ERROR, } from './upstash-twin.js';
2
+ export type { UpstashRedisRequest, UpstashRedisResponse, UpstashRedisResourceType, UpstashRedisSurface, UpstashRedisTwinSnapshot, } from './upstash-twin.js';
3
+ export { execRedisRun, commandShapeError, globMatch, isWriteCommand, unavailableCommand, restRestricted, wrongArity, RedisCommandError, RedisStatus, ReadOnlyError, SERVED_COMMANDS, commandId, upstashRedisOwners, KEY_TYPES, REST_RESTRICTED_COMMANDS, SERVICE, } from './upstash-store.js';
4
+ export type { RedisContext, RedisValue, RunItem, KeyType } from './upstash-store.js';
5
+ export { runLua, parseLua, tokenize, luaScriptSha1, luaTruthy, luaTypeName, luaToNumber, luaEquals, numberToLuaString, LuaError, LuaTable } from './upstash-lua.js';
6
+ export type { LuaHost, LuaValue, LuaFunction } from './upstash-lua.js';
7
+ export { createUpstashRedisTwinFetch, createUpstashRedisTwinServer } from './upstash-server.js';
8
+ export type { UpstashRedisServerOptions, UpstashRedisTwinFetchOptions } from './upstash-server.js';
9
+ export { mapKey, pullUpstashRedisKeys, pullUpstashRedisScan, syncUpstashRedisFromReal, } from './upstash-connector.js';
10
+ export type { UpstashRedisLikeClient, UpstashRedisKeyHandle, UpstashRedisRealKey, UpstashRedisBudgetedOptions, } from './upstash-connector.js';
11
+ export { UPSTASH_BUDGETED_METHODS, UPSTASH_BUDGET_CEILING, UPSTASH_BUDGET_MAX_RETRY_AFTER_S, UPSTASH_BUDGET_WINDOW_MS, UPSTASH_CALL_WEIGHTS, UPSTASH_RATE_BUDGET, UpstashRedisBudget, UpstashRedisBudgetError, upstashBudgetPath, upstashCallWeight, upstashClientBudget, guardUpstashRedisClient, } from './upstash-budget.js';
12
+ export type { UpstashRedisBudgetErrorKind, UpstashRedisBudgetOptions, UpstashRedisBudgetReservation, UpstashRedisBudgetSnapshot, } from './upstash-budget.js';
13
+ import { type TwinPack } from '@volter/world-core';
14
+ export declare const pack: TwinPack;
15
+ /** The pack's views of its answers, for a journey step's `view` (scripts/journey-kit.ts). `decoded`: an answer to a
16
+ * request sent with `Upstash-Encoding: base64`, as @upstash/redis reads it: each `result` decoded, each `error` as it is,
17
+ * for a single command's `{result}` or `{error}` and for a pipeline's or transaction's list of them. */
18
+ export declare const answerViews: Record<string, (body: unknown) => unknown>;
@@ -0,0 +1,124 @@
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 { handleUpstashRedisTwinRequest, routeUpstashRedisSurface, upstashTwinSnapshot, extractUpstashCredential, encodeResult, nextSyncToken, UPSTASH_RESOURCE_TYPES, UPSTASH_UNAUTHORIZED_ERROR, } from "./upstash-twin.js";
23
+ // The command core — exported so a caller can drive Redis semantics in-process, without HTTP.
24
+ export { execRedisRun, commandShapeError, globMatch, isWriteCommand, unavailableCommand, restRestricted, wrongArity, RedisCommandError, RedisStatus, ReadOnlyError, SERVED_COMMANDS, commandId, upstashRedisOwners, KEY_TYPES, REST_RESTRICTED_COMMANDS, SERVICE, } from "./upstash-store.js";
25
+ // The Lua-subset interpreter. Exported because `EVAL` fidelity is a headline claim of this pack and
26
+ // a consumer (or a reviewer) must be able to run a script against it directly.
27
+ export { runLua, parseLua, tokenize, luaScriptSha1, luaTruthy, luaTypeName, luaToNumber, luaEquals, numberToLuaString, LuaError, LuaTable } from "./upstash-lua.js";
28
+ export { createUpstashRedisTwinFetch, createUpstashRedisTwinServer } from "./upstash-server.js";
29
+ export { mapKey, pullUpstashRedisKeys, pullUpstashRedisScan, syncUpstashRedisFromReal, } from "./upstash-connector.js";
30
+ // The client-side rate budget — the fail-closed backstop every live call goes through. The
31
+ // MECHANISM is the kernel's shared, vendor-agnostic `RateBudget`; what lives here is this vendor's
32
+ // DECLARATION (window/ceiling/per-method weights) plus `guardUpstashRedisClient`, the choke point
33
+ // the connector entrypoints apply unconditionally. Exported so an operator can inspect spend
34
+ // (`snapshot`) and a caller can catch `UpstashRedisBudgetError` by type; there is deliberately no
35
+ // export that disables the guard.
36
+ export { UPSTASH_BUDGETED_METHODS, UPSTASH_BUDGET_CEILING, UPSTASH_BUDGET_MAX_RETRY_AFTER_S, UPSTASH_BUDGET_WINDOW_MS, UPSTASH_CALL_WEIGHTS, UPSTASH_RATE_BUDGET, UpstashRedisBudget, UpstashRedisBudgetError, upstashBudgetPath, upstashCallWeight, upstashClientBudget, guardUpstashRedisClient, } from "./upstash-budget.js";
37
+ // Registry descriptor: the pack self-describes so tooling can discover it.
38
+ import { registerPack } from '@volter/world-core';
39
+ import { performUpstashRedisAction, syncUpstashRedisFromRemote } from "./upstash-connector.js";
40
+ import { UPSTASH_RATE_BUDGET as RATE_BUDGET } from "./upstash-budget.js";
41
+ export const pack = {
42
+ // 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
43
+ // the real state system. Moved 2026-09-08.
44
+ protocol: '2',
45
+ // A cache moves constantly and a full SCAN of a real keyspace is expensive, so the poll is coarse
46
+ // and the on-demand path is what a world uses when it needs to know now.
47
+ refresh: { every: '15m', webhook: false, onDemand: { atMost: '30s' } },
48
+ stateSystem: { perform: performUpstashRedisAction, refresh: syncUpstashRedisFromRemote },
49
+ // The round trip is a SET — Redis's own write, idempotent by nature, so a second send on a branch
50
+ // sets the key again rather than being refused.
51
+ roundTrip: { method: 'POST', path: '/', body: ['SET', 'round-trip', 'round-trip'], headers: { authorization: 'Bearer round-trip' } },
52
+ parityOrigin: 'http://twin',
53
+ shapeParity: 'held',
54
+ // The SAME object upstash-budget.ts declares at module load — one source of truth, so
55
+ // registering the pack and importing the connector can never arm two different ceilings.
56
+ rateBudget: RATE_BUDGET,
57
+ vendor: 'upstash',
58
+ // INTERCEPTION — this pack's hosts as DATA (packRegistry HostRule), moved from the injector's
59
+ // hand table with its reasoning verbatim:
60
+ // (TOMBSTONE) `azureformrecognizer` used to live here as a bare
61
+ // `h.endsWith('.cognitiveservices.azure.com')` suffix matcher, with a note that a second Azure
62
+ // AI twin on that shared domain would need path-prefix disambiguation. That second twin is here
63
+ // — the `azure` pack's service area 2 serves Azure OpenAI on `/openai/*` — so BOTH claims moved
64
+ // onto their pack descriptors WITH `pathPattern`s and this hand entry was deleted in the same
65
+ // change (one home per fact; keeping it would throw at require time). See
66
+ // packages/twin/azureformrecognizer/src/index.ts and packages/twin/azure/src/index.ts.
67
+ // Upstash Redis. Every database gets its OWN host under one shared apex —
68
+ // https://<slug>.upstash.io, where the slug is per-database (both naming generations exist in
69
+ // the wild: the legacy regional `us1-merry-cat-32748` form and the current
70
+ // `striking-osprey-20681` form) — so there is no fixed host to match and this needs a SUFFIX
71
+ // matcher, exactly like azureformrecognizer's per-resource Azure hosts above. Confirmed against
72
+ // the pack's own connector/README and the vendor's REST docs; global databases use ONE hostname
73
+ // and Upstash routes internally, so there is no region-suffixed variant to add.
74
+ //
75
+ // QStash and Upstash Workflow share the apex too (qstash.upstash.io, qstash-<region>.upstash.io): they are this pack's
76
+ // `qstash` lane (../qstash/src/), so the suffix takes their hosts and the pack's fetch sends them to the lane. The one
77
+ // exclusion is Upstash VECTOR (`*-vector.upstash.io`, packages/twin/upstashvector), claimed by that pack's own entry: a
78
+ // bare suffix would swallow it into this twin. Upstash Search (`*-search.upstash.io`) also lives here and has no twin
79
+ // yet; when one lands it needs the same treatment, and this comment is the note that says so.
80
+ hosts: [
81
+ { suffix: '.upstash.io' },
82
+ { suffix: '-vector.upstash.io', exclude: true },
83
+ // the Developer API and the console's pages, the pack's `api` lane (../api/src/index.ts)
84
+ { host: 'api.upstash.com', pathPattern: '^/v2/(redis|team|teams)(/|$)' },
85
+ { host: 'console.upstash.com', pathPattern: '^/(login|account/(api|teams)|qstash|invite)(/|$)' },
86
+ ],
87
+ // QStash's clients read their base URL from QSTASH_URL (@upstash/qstash's documented override; @upstash/workflow's
88
+ // Client is a QStash client and reads the same), so the World points it at this twin as well as redirecting the hosts.
89
+ endpointEnv: {
90
+ name: 'UPSTASH_TWIN_URL',
91
+ templates: { QSTASH_URL: '${url}' },
92
+ note: '@upstash/qstash and @upstash/workflow read QSTASH_URL; the injector also redirects *.upstash.io (Redis databases, QStash) to this twin.',
93
+ },
94
+ transport: 'rest',
95
+ archetype: 'crud',
96
+ bin: 'world-upstash',
97
+ resources: ['key'],
98
+ specSource: '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.',
99
+ description: '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).',
100
+ // Adoption, all in the pack's one home. The `scopes` entries are exact PACKAGE names, not an apex: the `@upstash/` scope
101
+ // spans several Upstash products, and Vector has its own pack, so it is claimed package by package: the Redis client and
102
+ // the ratelimiter built on it, and QStash's and Workflow's clients (the qstash lane). Stems: UPSTASH_REDIS_* and KV_REST_*
103
+ // (the env names Vercel KV, this vendor's Redis under another label, writes into a project), and QSTASH_*.
104
+ adoption: {
105
+ pypi: ['upstash-redis', 'upstash-ratelimit', 'qstash', 'upstash-workflow'],
106
+ sdks: ['@upstash/redis', '@upstash/ratelimit', '@upstash/qstash', '@upstash/workflow'],
107
+ scopes: ['@upstash/redis', '@upstash/ratelimit', '@upstash/qstash', '@upstash/workflow'],
108
+ envStems: ['UPSTASHREDIS', 'KVREST', 'QSTASH'],
109
+ },
110
+ browserRouting: { apiPathPrefix: '/', loaderHost: 'https://twin.upstash.io' },
111
+ };
112
+ registerPack(pack);
113
+ /** A string of a `Upstash-Encoding: base64` answer as @upstash/redis decodes it: base64 to UTF-8, the bare status `OK`
114
+ * excepted, which Upstash leaves raw (upstash-twin.ts, `encodeResult`). */
115
+ const decodeResult = (v) => typeof v === 'string' ? (v === 'OK' ? v : Buffer.from(v, 'base64').toString('utf8')) : Array.isArray(v) ? v.map(decodeResult) : v;
116
+ /** The pack's views of its answers, for a journey step's `view` (scripts/journey-kit.ts). `decoded`: an answer to a
117
+ * request sent with `Upstash-Encoding: base64`, as @upstash/redis reads it: each `result` decoded, each `error` as it is,
118
+ * for a single command's `{result}` or `{error}` and for a pipeline's or transaction's list of them. */
119
+ export const answerViews = {
120
+ decoded: (body) => {
121
+ const one = (item) => (item !== null && typeof item === 'object' && 'result' in item ? { ...item, result: decodeResult(item.result) } : item);
122
+ return Array.isArray(body) ? body.map(one) : one(body);
123
+ },
124
+ };
@@ -0,0 +1,14 @@
1
+ export declare const manifest: {
2
+ vendor: string;
3
+ service: string;
4
+ resources: {
5
+ Key: {
6
+ storedAs: string;
7
+ idPrefix: string;
8
+ };
9
+ Script: {
10
+ storedAs: string;
11
+ idPrefix: string;
12
+ };
13
+ };
14
+ };
@@ -0,0 +1,8 @@
1
+ export const manifest = {
2
+ vendor: 'upstash',
3
+ service: 'upstash',
4
+ resources: {
5
+ Key: { storedAs: 'key', idPrefix: 'key' },
6
+ Script: { storedAs: 'script', idPrefix: 'script' },
7
+ },
8
+ };
@@ -0,0 +1,85 @@
1
+ import { RateBudget, type RateBudgetDeclaration, type RateBudgetOptions, type RateBudgetReservation, type RateBudgetSnapshot } from '@volter/world-core';
2
+ import type { UpstashRedisLikeClient } from './upstash-connector.js';
3
+ /** Rolling window, in ms. Spend older than this is pruned. */
4
+ export declare const UPSTASH_BUDGET_WINDOW_MS = 60000;
5
+ /** Weighted units allowed inside one window. See the header for where this number comes from. */
6
+ export declare const UPSTASH_BUDGET_CEILING = 600;
7
+ /** Seconds. A `Retry-After` above this means the credential is throttled hard — fail loudly. */
8
+ export declare const UPSTASH_BUDGET_MAX_RETRY_AFTER_S = 300;
9
+ /** Per-call cost, keyed by the client method the guard is about to invoke. See the header. */
10
+ export declare const UPSTASH_CALL_WEIGHTS: {
11
+ /** `scan` — a keyspace WALK, whose cost scales with the customer's database, not the request. */
12
+ readonly scan: 8;
13
+ /** Every other modeled call (get/type/ttl/hgetall/smembers/lrange/zrange, and anything unclassified). */
14
+ readonly other: 2;
15
+ };
16
+ /**
17
+ * The client methods this pack PRICES BY NAME, as dotted paths into the injected client.
18
+ *
19
+ * Two kinds of entry: (a) every method this connector actually calls, and (b) an endpoint the
20
+ * VENDOR documents in a distinct tier which a consumer can reach through the guarded client even
21
+ * though this connector never calls it (see the weights section of the header). Used to build the
22
+ * guarded surface — and, in the pack's own suite, the counting fake.
23
+ *
24
+ * NOT a closed list, and not a claim about the injected client's shape. A path the real client does
25
+ * not have is SKIPPED (this connector's members are optional; inventing one would turn "observe
26
+ * nothing" into "call something that isn't there"), and a method absent from this list is still
27
+ * PRICED at `defaultWeight` when a caller reaches for it — an unmodeled endpoint must never be
28
+ * free, and dropping one would be worse than free because it would be invisible.
29
+ */
30
+ export declare const UPSTASH_BUDGETED_METHODS: readonly ["get", "type", "ttl", "hgetall", "smembers", "lrange", "zrange", "scan"];
31
+ /** THE PACK'S DECLARATION — pure data, the only Upstash-specific thing in the whole budget. */
32
+ export declare const UPSTASH_RATE_BUDGET: RateBudgetDeclaration;
33
+ /** Price one Upstash Redis call by its client method name (e.g. `get`, `scan`). */
34
+ export declare function upstashCallWeight(method: string): number;
35
+ /** Where UpstashRedis's ledger lives. Token-keyed and cwd-independent by default (the vendor limits per
36
+ * credential, so a cwd-scoped ledger would hand the same credential a fresh allowance in every
37
+ * checkout, worktree and CI matrix leg); pass `root` to opt into world-scoped accounting. */
38
+ export declare function upstashBudgetPath(opts?: {
39
+ root?: string;
40
+ token?: string;
41
+ } | string): string;
42
+ /** Construction options for UpstashRedis's budget. The vendor is fixed; everything else may only TIGHTEN. */
43
+ export type UpstashRedisBudgetOptions = Omit<RateBudgetOptions, 'vendor'>;
44
+ /**
45
+ * UpstashRedis's budget — the shared kernel guard bound to this vendor's declaration. A real subclass,
46
+ * not an alias, so `budget instanceof UpstashRedisBudget` means "a budget that accounts against this
47
+ * vendor's ledger under this vendor's ceiling": another vendor's `RateBudget` (with its own,
48
+ * possibly larger, ceiling) is NOT assignable where one of these is required.
49
+ */
50
+ export declare class UpstashRedisBudget extends RateBudget {
51
+ constructor(opts?: UpstashRedisBudgetOptions);
52
+ }
53
+ export type { RateBudgetErrorKind as UpstashRedisBudgetErrorKind } from '@volter/world-core';
54
+ export { RateBudgetError as UpstashRedisBudgetError } from '@volter/world-core';
55
+ export type UpstashRedisBudgetReservation = RateBudgetReservation;
56
+ export type UpstashRedisBudgetSnapshot = RateBudgetSnapshot;
57
+ /** What every budgeted connector entrypoint accepts. There is deliberately no option that turns the
58
+ * guard OFF — only ones that say WHICH ledger and clock to account against. */
59
+ export type UpstashRedisBudgetedOptions = {
60
+ /** An existing budget to share across calls. Omit and one is constructed. Cannot be null. */
61
+ budget?: UpstashRedisBudget;
62
+ /** Construction options for the default budget (ledger path, clock). Cannot loosen it. */
63
+ budgetOptions?: UpstashRedisBudgetOptions;
64
+ };
65
+ /** Pull the budget wiring out of a caller's opts bag, so an entrypoint can forward it verbatim. */
66
+ export declare function upstashBudgetOf(opts: UpstashRedisBudgetedOptions): UpstashRedisBudgetedOptions;
67
+ /** Is this client already behind a budget? Returns the budget it is behind, if so. */
68
+ export declare function upstashClientBudget(client: unknown): RateBudget | undefined;
69
+ /**
70
+ * Wrap an INJECTED UpstashRedis client so EVERY call it makes is charged against the shared budget
71
+ * BEFORE the request goes out. This pack's connector never constructs the transport itself (the
72
+ * consumer injects a client that satisfies `UpstashRedisLikeClient` structurally), so the guard is a
73
+ * DECORATOR rather than a factory — which is exactly why every connector entrypoint applies it
74
+ * UNCONDITIONALLY instead of trusting the caller to have done it.
75
+ *
76
+ * IDEMPOTENT: wrapping an already-guarded client returns it unchanged, so a caller who forgot is
77
+ * protected and a caller who wrapped deliberately is not double-charged.
78
+ *
79
+ * A method that THROWS is still inspected: an SDK typically RAISES on a 429 rather than returning
80
+ * it, and that error's back-off is exactly the signal that must become a persisted cooldown. Losing
81
+ * it would leave the ledger cheerfully spending into a throttled credential. The original error is
82
+ * always re-raised afterwards — the budget never swallows a vendor failure — EXCEPT when the
83
+ * back-off is beyond the cap, where the budget's own louder "stop calling" error takes precedence.
84
+ */
85
+ export declare function guardUpstashRedisClient(client: UpstashRedisLikeClient, opts?: UpstashRedisBudgetedOptions): UpstashRedisLikeClient;