@vercel/flags
Version:
Flags SDK by Vercel - The feature flags toolkit for Next.js and SvelteKit
173 lines (172 loc) • 5.26 kB
JavaScript
import {
normalizeOptions
} from "./chunk-ZNKTXALY.js";
import {
HeadersAdapter,
RequestCookiesAdapter,
decrypt,
encrypt,
reportValue,
verifyAccess
} from "./chunk-43J6AE7X.js";
import {
safeJsonStringify
} from "./chunk-2UDLZC33.js";
// src/sveltekit/index.ts
import { AsyncLocalStorage } from "node:async_hooks";
import { RequestCookies } from "@edge-runtime/cookies";
function hasOwnProperty(obj, prop) {
return obj.hasOwnProperty(prop);
}
var headersMap = /* @__PURE__ */ new WeakMap();
var cookiesMap = /* @__PURE__ */ new WeakMap();
function sealHeaders(headers) {
const cached = headersMap.get(headers);
if (cached !== void 0)
return cached;
const sealed = HeadersAdapter.seal(headers);
headersMap.set(headers, sealed);
return sealed;
}
function sealCookies(headers) {
const cached = cookiesMap.get(headers);
if (cached !== void 0)
return cached;
const sealed = RequestCookiesAdapter.seal(new RequestCookies(headers));
cookiesMap.set(headers, sealed);
return sealed;
}
async function resolveObjectPromises(obj) {
const entries = Object.entries(obj);
const resolvedEntries = await Promise.all(
entries.map(async ([key, promise]) => {
const value = await promise;
return [key, value];
})
);
return Object.fromEntries(resolvedEntries);
}
function getDecide(definition) {
return function decide(params) {
if (typeof definition.decide === "function") {
return definition.decide(params);
}
if (typeof definition.adapter?.decide === "function") {
return definition.adapter.decide({ key: definition.key, ...params });
}
throw new Error(
`@vercel/flags: No decide function provided for ${definition.key}`
);
};
}
function flag(definition) {
const decide = getDecide(definition);
const flagImpl = async function flagImpl2() {
const store = flagStorage.getStore();
if (!store) {
throw new Error("@vercel/flags: context not found");
}
if (hasOwnProperty(store.usedFlags, definition.key)) {
const valuePromise2 = store.usedFlags[definition.key];
if (typeof valuePromise2 !== "undefined") {
return valuePromise2;
}
}
const overridesCookie = store.event.cookies.get("vercel-flag-overrides");
const overrides = overridesCookie ? await decrypt(overridesCookie, store.secret) : void 0;
if (overrides && hasOwnProperty(overrides, definition.key)) {
const value2 = overrides[definition.key];
if (typeof value2 !== "undefined") {
reportValue(definition.key, value2);
store.usedFlags[definition.key] = Promise.resolve(value2);
return value2;
}
}
const valuePromise = decide(
{
headers: sealHeaders(store.event.request.headers),
cookies: sealCookies(store.event.request.headers)
},
// @ts-expect-error not part of the type, but we supply it for convenience
{ event: store.event }
);
store.usedFlags[definition.key] = valuePromise;
const value = await valuePromise;
reportValue(definition.key, value);
return value;
};
flagImpl.key = definition.key;
flagImpl.defaultValue = definition.defaultValue;
flagImpl.origin = definition.origin;
flagImpl.description = definition.description;
flagImpl.options = definition.options;
flagImpl.decide = decide;
return flagImpl;
}
function getProviderData(flags) {
const definitions = Object.values(flags).reduce(
(acc, d) => {
acc[d.key] = {
options: normalizeOptions(d.options),
origin: d.origin,
description: d.description
};
return acc;
},
{}
);
return { definitions, hints: [] };
}
function createContext(event, secret) {
return {
event,
secret,
usedFlags: {}
};
}
var flagStorage = new AsyncLocalStorage();
function createHandle({
secret,
flags
}) {
return function handle({ event, resolve }) {
if (flags && // avoid creating the URL object for every request by checking with includes() first
event.request.url.includes("/.well-known/") && new URL(event.request.url).pathname === "/.well-known/vercel/flags") {
return handleWellKnownFlagsRoute(event, secret, flags);
}
const flagContext = createContext(event, secret);
return flagStorage.run(
flagContext,
() => resolve(event, {
transformPageChunk: async ({ html }) => {
const store = flagStorage.getStore();
if (!store || Object.keys(store.usedFlags).length === 0)
return html;
const encryptedFlagValues = await encrypt(
await resolveObjectPromises(store.usedFlags),
secret
);
return html.replace(
"</body>",
`<script type="application/json" data-flag-values>${safeJsonStringify(encryptedFlagValues)}</script></body>`
);
}
})
);
};
}
async function handleWellKnownFlagsRoute(event, secret, flags) {
const access = await verifyAccess(
event.request.headers.get("Authorization"),
secret
);
if (!access)
return new Response(null, { status: 401 });
return Response.json(getProviderData(flags));
}
export {
createHandle,
flag,
getProviderData
};
//# sourceMappingURL=sveltekit.js.map