effect-vercel 0.2.0 → 0.4.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/README.md +29 -23
- package/dist/AiGateway.d.ts +44 -0
- package/dist/{ai-gateway.js → AiGateway.js} +5 -106
- package/dist/AiGateway.js.map +1 -0
- package/dist/AiGatewayCredentials.d.ts +45 -0
- package/dist/AiGatewayCredentials.js +94 -0
- package/dist/AiGatewayCredentials.js.map +1 -0
- package/dist/Oidc.d.ts +23 -0
- package/dist/Oidc.js +59 -0
- package/dist/Oidc.js.map +1 -0
- package/dist/{runtime-cache.d.ts → RuntimeCache.d.ts} +5 -8
- package/dist/{runtime-cache.js → RuntimeCache.js} +5 -11
- package/dist/RuntimeCache.js.map +1 -0
- package/package.json +10 -9
- package/src/AiGateway.ts +1 -1
- package/src/AiGatewayCredentials.ts +53 -55
- package/src/Oidc.ts +75 -0
- package/src/{VercelRuntimeCache.ts → RuntimeCache.ts} +2 -2
- package/dist/VercelOidc-DCkdXW3E.js +0 -70
- package/dist/VercelOidc-DCkdXW3E.js.map +0 -1
- package/dist/ai-gateway.d.ts +0 -87
- package/dist/ai-gateway.js.map +0 -1
- package/dist/oidc.d.ts +0 -27
- package/dist/oidc.js +0 -2
- package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
- package/dist/runtime-cache.js.map +0 -1
- package/src/VercelOidc.ts +0 -86
- package/src/ai-gateway.ts +0 -2
- package/src/oidc.ts +0 -1
- package/src/runtime-cache.ts +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-cache.js","names":[],"sources":["../src/VercelRuntimeCache.ts"],"sourcesContent":["/**\n * Effect `Persistence` backed by the Vercel Runtime Cache, so a\n * `PersistedCache` shares results across Function instances in a region.\n *\n * Outside Vercel, `getCache` falls back to an in-memory cache, so the same\n * layer works in local dev and tests.\n *\n * @example\n * ```ts\n * import { VercelRuntimeCache } from \"effect-vercel/runtime-cache\"\n * import { Effect } from \"effect\"\n * import { PersistedCache } from \"effect/persistence\"\n *\n * const program = Effect.gen(function* () {\n * const users = yield* PersistedCache.make(fetchUser, {\n * storeId: \"users\",\n * timeToLive: () => \"1 hour\",\n * })\n * return yield* users.get(new GetUser({ id: 1 }))\n * }).pipe(Effect.scoped, Effect.provide(VercelRuntimeCache.layer))\n * ```\n */\n\nimport { createHash } from \"node:crypto\"\nimport { getCache, type RuntimeCache } from \"@vercel/functions\"\nimport { Duration, Effect, Layer } from \"effect\"\nimport { Persistence } from \"effect/persistence\"\n\nconst sha256 = (key: string) => createHash(\"sha256\").update(key).digest(\"hex\")\n\n/**\n * A `BackingPersistence` over `cache`.\n *\n * Entries are keyed by the JSON pair `[storeId, key]`, so no two stores share\n * a key. Each entry is tagged with a hash of the store id, since tags are\n * joined with `,` and sent unescaped, and `clear` expires that tag. Entries\n * are sent without a name, so keys don't reach Vercel's o11y or the request\n * headers, where non-Latin-1 characters would make the write fail. TTLs round\n * up to whole seconds, the cache's resolution.\n *\n * `getCache()` hashes keys with a 32-bit hash by default, which can collide.\n * Pass a cache made with a stronger `keyHashFunction`, as `layerBacking` does.\n */\nexport const makeBacking = (cache: RuntimeCache): Persistence.BackingPersistence[\"Service\"] =>\n Persistence.BackingPersistence.of({\n make: (storeId) =>\n Effect.sync(() => {\n const prefixed = (key: string) => JSON.stringify([storeId, key])\n const tag = `effect-persistence:${sha256(storeId)}`\n\n const get = (key: string) =>\n Effect.tryPromise({\n try: () => cache.get(prefixed(key)),\n catch: (cause) =>\n new Persistence.PersistenceError({ message: `Failed to get key ${key}`, cause }),\n }).pipe(\n Effect.map((value) =>\n typeof value === \"object\" && value !== null ? value : undefined,\n ),\n )\n\n const set = (key: string, value: object, ttl: Duration.Duration | undefined) =>\n Effect.tryPromise({\n try: () =>\n cache.set(prefixed(key), value, {\n name: \"\",\n tags: [tag],\n ...(ttl && { ttl: Math.max(1, Math.ceil(Duration.toSeconds(ttl))) }),\n }),\n catch: (cause) =>\n new Persistence.PersistenceError({ message: `Failed to set key ${key}`, cause }),\n })\n\n return {\n get,\n getMany: (keys) => Effect.forEach(keys, get, { concurrency: \"unbounded\" }),\n set,\n // Writes run concurrently, so keep only the last entry for each key.\n setMany: (entries) =>\n Effect.forEach(\n new Map(entries.map(([key, value, ttl]) => [key, [value, ttl] as const])),\n ([key, [value, ttl]]) => set(key, value, ttl),\n { concurrency: \"unbounded\", discard: true },\n ),\n remove: (key) =>\n Effect.tryPromise({\n try: () => cache.delete(prefixed(key)),\n catch: (cause) =>\n new Persistence.PersistenceError({ message: `Failed to remove key ${key}`, cause }),\n }),\n clear: Effect.tryPromise({\n try: () => cache.expireTag(tag),\n catch: (cause) =>\n new Persistence.PersistenceError({ message: `Failed to clear ${storeId}`, cause }),\n }),\n }\n }),\n })\n\n/** `BackingPersistence` over `getCache()`, with SHA-256 key hashing. */\nexport const layerBacking: Layer.Layer<Persistence.BackingPersistence> = Layer.sync(\n Persistence.BackingPersistence,\n)(() => makeBacking(getCache({ keyHashFunction: sha256 })))\n\n/** `Persistence` backed by the Vercel Runtime Cache. */\nexport const layer: Layer.Layer<Persistence.Persistence> = Persistence.layer.pipe(\n Layer.provide(layerBacking),\n)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAM,UAAU,QAAgB,WAAW,QAAQ,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,KAAK;;;;;;;;;;;;;;AAe7E,MAAa,eAAe,UAC1B,YAAY,mBAAmB,GAAG,EAChC,OAAO,YACL,OAAO,WAAW;CAChB,MAAM,YAAY,QAAgB,KAAK,UAAU,CAAC,SAAS,GAAG,CAAC;CAC/D,MAAM,MAAM,sBAAsB,OAAO,OAAO;CAEhD,MAAM,OAAO,QACX,OAAO,WAAW;EAChB,WAAW,MAAM,IAAI,SAAS,GAAG,CAAC;EAClC,QAAQ,UACN,IAAI,YAAY,iBAAiB;GAAE,SAAS,qBAAqB;GAAO;EAAM,CAAC;CACnF,CAAC,CAAC,CAAC,KACD,OAAO,KAAK,UACV,OAAO,UAAU,YAAY,UAAU,OAAO,QAAQ,KAAA,CACxD,CACF;CAEF,MAAM,OAAO,KAAa,OAAe,QACvC,OAAO,WAAW;EAChB,WACE,MAAM,IAAI,SAAS,GAAG,GAAG,OAAO;GAC9B,MAAM;GACN,MAAM,CAAC,GAAG;GACV,GAAI,OAAO,EAAE,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,SAAS,UAAU,GAAG,CAAC,CAAC,EAAE;EACpE,CAAC;EACH,QAAQ,UACN,IAAI,YAAY,iBAAiB;GAAE,SAAS,qBAAqB;GAAO;EAAM,CAAC;CACnF,CAAC;CAEH,OAAO;EACL;EACA,UAAU,SAAS,OAAO,QAAQ,MAAM,KAAK,EAAE,aAAa,YAAY,CAAC;EACzE;EAEA,UAAU,YACR,OAAO,QACL,IAAI,IAAI,QAAQ,KAAK,CAAC,KAAK,OAAO,SAAS,CAAC,KAAK,CAAC,OAAO,GAAG,CAAU,CAAC,CAAC,IACvE,CAAC,KAAK,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,GAAG,GAC5C;GAAE,aAAa;GAAa,SAAS;EAAK,CAC5C;EACF,SAAS,QACP,OAAO,WAAW;GAChB,WAAW,MAAM,OAAO,SAAS,GAAG,CAAC;GACrC,QAAQ,UACN,IAAI,YAAY,iBAAiB;IAAE,SAAS,wBAAwB;IAAO;GAAM,CAAC;EACtF,CAAC;EACH,OAAO,OAAO,WAAW;GACvB,WAAW,MAAM,UAAU,GAAG;GAC9B,QAAQ,UACN,IAAI,YAAY,iBAAiB;IAAE,SAAS,mBAAmB;IAAW;GAAM,CAAC;EACrF,CAAC;CACH;AACF,CAAC,EACL,CAAC;;AAGH,MAAa,eAA4D,MAAM,KAC7E,YAAY,kBACd,CAAC,OAAO,YAAY,SAAS,EAAE,iBAAiB,OAAO,CAAC,CAAC,CAAC;;AAG1D,MAAa,QAA8C,YAAY,MAAM,KAC3E,MAAM,QAAQ,YAAY,CAC5B"}
|
package/src/VercelOidc.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The OIDC token Vercel issues to a deployment.
|
|
3
|
-
*
|
|
4
|
-
* In Functions the token arrives on each request as the `x-vercel-oidc-token`
|
|
5
|
-
* header and rotates, so it is read on every run of `token`/`find`, never
|
|
6
|
-
* cached. In builds and after `vercel env pull` it is `VERCEL_OIDC_TOKEN`.
|
|
7
|
-
* Use it to authenticate to the AI Gateway, or exchange it for cloud
|
|
8
|
-
* credentials (AWS `AssumeRoleWithWebIdentity`, GCP workload identity
|
|
9
|
-
* federation).
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* import { VercelOidc } from "effect-vercel/oidc"
|
|
14
|
-
* import { Effect, Redacted } from "effect"
|
|
15
|
-
*
|
|
16
|
-
* const program = Effect.gen(function* () {
|
|
17
|
-
* const token = yield* VercelOidc.token
|
|
18
|
-
* // pass Redacted.value(token) as the web identity token
|
|
19
|
-
* })
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { Config, Data, Effect, Option, Redacted } from "effect"
|
|
24
|
-
|
|
25
|
-
/** The environment variable read when no request carries a token. */
|
|
26
|
-
export const TOKEN_ENV = "VERCEL_OIDC_TOKEN"
|
|
27
|
-
|
|
28
|
-
/** No usable OIDC token. */
|
|
29
|
-
export class VercelOidcError extends Data.TaggedError("VercelOidcError")<{
|
|
30
|
-
readonly message: string
|
|
31
|
-
readonly hints: ReadonlyArray<string>
|
|
32
|
-
readonly cause?: unknown
|
|
33
|
-
}> {}
|
|
34
|
-
|
|
35
|
-
/** How to make a token available. */
|
|
36
|
-
export const hints: ReadonlyArray<string> = [
|
|
37
|
-
"Run on Vercel, where each request carries an OIDC token.",
|
|
38
|
-
`Or run \`vercel env pull\` locally to populate ${TOKEN_ENV}.`,
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Vercel exposes the current request's context on a well-known global symbol.
|
|
43
|
-
* In Functions, the `x-vercel-oidc-token` header of the request lives there.
|
|
44
|
-
* Mirrors `@vercel/oidc`'s `getVercelOidcTokenSync`, minus the dev-time
|
|
45
|
-
* refresh that package also offers.
|
|
46
|
-
*/
|
|
47
|
-
const REQUEST_CONTEXT = Symbol.for("@vercel/request-context")
|
|
48
|
-
|
|
49
|
-
type RequestContext = { readonly headers?: Record<string, string | undefined> }
|
|
50
|
-
|
|
51
|
-
const fromRequestContext = Effect.sync(() => {
|
|
52
|
-
const holder = globalThis as typeof globalThis & {
|
|
53
|
-
[REQUEST_CONTEXT]?: { get?: () => RequestContext | undefined }
|
|
54
|
-
}
|
|
55
|
-
return Option.fromNullishOr(
|
|
56
|
-
holder[REQUEST_CONTEXT]?.get?.()?.headers?.["x-vercel-oidc-token"],
|
|
57
|
-
).pipe(Option.map(Redacted.make))
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const fromEnv = Config.option(Config.Redacted(TOKEN_ENV)).pipe(
|
|
61
|
-
Effect.catchTag(
|
|
62
|
-
"ConfigError",
|
|
63
|
-
(cause) => new VercelOidcError({ message: `Failed to read ${TOKEN_ENV}.`, hints, cause }),
|
|
64
|
-
),
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* The token if one is available: the request header, else `VERCEL_OIDC_TOKEN`.
|
|
69
|
-
* `None` means neither is set, so a caller can fall back to something else.
|
|
70
|
-
*/
|
|
71
|
-
export const find: Effect.Effect<
|
|
72
|
-
Option.Option<Redacted.Redacted<string>>,
|
|
73
|
-
VercelOidcError
|
|
74
|
-
> = Effect.flatMap(fromRequestContext, (found) =>
|
|
75
|
-
Option.isSome(found) ? Effect.succeed(found) : fromEnv,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
/** Like `find`, but fails when no token is available. */
|
|
79
|
-
export const token: Effect.Effect<Redacted.Redacted<string>, VercelOidcError> = Effect.flatMap(
|
|
80
|
-
find,
|
|
81
|
-
Option.match({
|
|
82
|
-
onNone: () =>
|
|
83
|
-
Effect.fail(new VercelOidcError({ message: "No Vercel OIDC token is available.", hints })),
|
|
84
|
-
onSome: Effect.succeed,
|
|
85
|
-
}),
|
|
86
|
-
)
|
package/src/ai-gateway.ts
DELETED
package/src/oidc.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * as VercelOidc from "./VercelOidc.js"
|
package/src/runtime-cache.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * as VercelRuntimeCache from "./VercelRuntimeCache.js"
|