diff --git a/.changeset/thirty-pants-sell.md b/.changeset/thirty-pants-sell.md new file mode 100644 index 00000000..d6c8de9c --- /dev/null +++ b/.changeset/thirty-pants-sell.md @@ -0,0 +1,5 @@ +--- +'flags': minor +--- + +feat: provide precompute patterns for SvelteKit diff --git a/examples/sveltekit-example/middleware.ts b/examples/sveltekit-example/middleware.ts new file mode 100644 index 00000000..602057c1 --- /dev/null +++ b/examples/sveltekit-example/middleware.ts @@ -0,0 +1,65 @@ +import { rewrite } from '@vercel/edge'; +import { parse } from 'cookie'; +import { normalizeUrl } from '@sveltejs/kit'; +import { computeInternalRoute, createVisitorId } from './src/lib/precomputed-flags'; +import { marketingABTestManualApproach } from './src/lib/flags'; + +export const config = { + // Either run middleware on all but the internal asset paths ... + // matcher: '/((?!_app/|favicon.ico|favicon.png).*)' + // ... or only run it where you actually need it (more performant). + matcher: [ + '/examples/marketing-pages-manual-approach', + '/examples/marketing-pages' + // add more paths here if you want to run A/B tests on other pages, e.g. + // '/something-else' + ] +}; + +export default async function middleware(request: Request) { + const { url, denormalize } = normalizeUrl(request.url); + + if (url.pathname === '/examples/marketing-pages-manual-approach') { + // Retrieve cookies which contain the feature flags. + let flag = parse(request.headers.get('cookie') ?? '').marketingManual || ''; + + if (!flag) { + flag = String(Math.random() < 0.5); + request.headers.set('x-marketingManual', flag); // cookie is not available on the initial request + } + + return rewrite( + // Get destination URL based on the feature flag + denormalize( + (await marketingABTestManualApproach(request)) + ? '/examples/marketing-pages-variant-a' + : '/examples/marketing-pages-variant-b' + ), + { + headers: { + 'Set-Cookie': `marketingManual=${flag}; Path=/` + } + } + ); + } + + if (url.pathname === '/examples/marketing-pages') { + // Retrieve cookies which contain the feature flags. + let visitorId = parse(request.headers.get('cookie') ?? '').visitorId || ''; + + if (!visitorId) { + visitorId = createVisitorId(); + request.headers.set('x-visitorId', visitorId); // cookie is not available on the initial request + } + + return rewrite( + // Get destination URL based on the feature flag + denormalize(await computeInternalRoute(url.pathname, request)), + { + headers: { + 'Set-Cookie': `visitorId=${visitorId}; Path=/` + } + } + ); + } +} diff --git a/examples/sveltekit-example/package.json b/examples/sveltekit-example/package.json index d23da5e8..49d4d3ce 100644 --- a/examples/sveltekit-example/package.json +++ b/examples/sveltekit-example/package.json @@ -12,20 +12,21 @@ "format": "prettier --write ." }, "dependencies": { + "@vercel/edge": "^1.2.1", + "@vercel/toolbar": "0.1.15", "flags": "workspace:*", - "@vercel/toolbar": "0.1.15" + "cookie": "^0.6.0" }, "devDependencies": { - "@sveltejs/adapter-auto": "^3.0.0", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@sveltejs/adapter-vercel": "^5.6.0", + "@sveltejs/kit": "^2.20.2", + "@sveltejs/vite-plugin-svelte": "^4.0.0", "prettier": "^3.1.1", - "prettier-plugin-svelte": "^3.1.2", - "svelte": "^4.2.7", - "svelte-check": "^3.6.0", - "tslib": "^2.4.1", - "typescript": "^5.0.0", - "vite": "^5.0.3" + "prettier-plugin-svelte": "^3.2.6", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.5.0", + "vite": "^5.4.4" }, "type": "module" } diff --git a/examples/sveltekit-example/src/hooks.ts b/examples/sveltekit-example/src/hooks.ts new file mode 100644 index 00000000..1e1be3a2 --- /dev/null +++ b/examples/sveltekit-example/src/hooks.ts @@ -0,0 +1,25 @@ +// `reroute` is called on both the server and client during dev, because `middleware.ts` is unknown to SvelteKit. +// In production it's called on the client only because `middleware.ts` will handle the first page visit. +// As a result, when visiting a page you'll get rerouted accordingly in all situations in both dev and prod. +export async function reroute({ url, fetch }) { + if (url.pathname === '/examples/marketing-pages-manual-approach') { + const destination = new URL('/api/reroute-manual', url); + + // Since `reroute` runs on the client and the cookie with the flag info is not available to it, + // we do a server request to get the internal route. + return fetch(destination).then((response) => response.text()); + } + + if ( + url.pathname === '/examples/marketing-pages' + // add more paths here if you want to run A/B tests on other pages, e.g. + // || url.pathname === '/something-else' + ) { + const destination = new URL('/api/reroute', url); + destination.searchParams.set('pathname', url.pathname); + + // Since `reroute` runs on the client and the cookie with the flag info is not available to it, + // we do a server request to get the internal route. + return fetch(destination).then((response) => response.text()); + } +} diff --git a/examples/sveltekit-example/src/lib/flags.ts b/examples/sveltekit-example/src/lib/flags.ts index 7c0a87c5..b98c5802 100644 --- a/examples/sveltekit-example/src/lib/flags.ts +++ b/examples/sveltekit-example/src/lib/flags.ts @@ -1,12 +1,67 @@ +import type { ReadonlyHeaders, ReadonlyRequestCookies } from 'flags'; import { flag } from 'flags/sveltekit'; -export const showDashboard = flag({ - key: 'showDashboard', - description: 'Show the dashboard', // optional - origin: 'https://example.com/#showdashbord', // optional +export const showNewDashboard = flag({ + key: 'showNewDashboard', + description: 'Show the new dashboard', // optional + origin: 'https://example.com/#shownewdashbord', // optional options: [{ value: true }, { value: false }], // optional - // can be async and has access to the event - decide(_event) { - return false; + // can be async and has access to entities (see below for an example), headers and cookies + decide({ cookies }) { + return cookies.get('showNewDashboard')?.value === 'true'; + } +}); + +export const marketingABTestManualApproach = flag({ + key: 'marketingABTestManualApproach', + description: 'Marketing AB Test Manual Approach', + decide({ cookies, headers }) { + return (cookies.get('marketingManual')?.value ?? headers.get('x-marketingManual')) === 'true'; + } +}); + +interface Entities { + visitorId?: string; +} + +function identify({ + cookies, + headers +}: { + cookies: ReadonlyRequestCookies; + headers: ReadonlyHeaders; +}): Entities { + const visitorId = cookies.get('visitorId')?.value ?? headers.get('x-visitorId'); + + if (!visitorId) { + throw new Error( + 'Visitor ID not found - should have been set by middleware or within api/reroute' + ); + } + + return { visitorId }; +} + +export const firstMarketingABTest = flag({ + key: 'firstMarketingABTest', + description: 'Example of a precomputed flag', + identify, + decide({ entities }) { + if (!entities?.visitorId) return false; + + // Use any kind of deterministic method that runs on the visitorId + return /^[a-n0-5]/i.test(entities?.visitorId); + } +}); + +export const secondMarketingABTest = flag({ + key: 'secondMarketingABTest', + description: 'Example of a precomputed flag', + identify, + decide({ entities }) { + if (!entities?.visitorId) return false; + + // Use any kind of deterministic method that runs on the visitorId + return /[a-n0-5]$/i.test(entities.visitorId); } }); diff --git a/examples/sveltekit-example/src/lib/precomputed-flags.ts b/examples/sveltekit-example/src/lib/precomputed-flags.ts new file mode 100644 index 00000000..ffd95c4f --- /dev/null +++ b/examples/sveltekit-example/src/lib/precomputed-flags.ts @@ -0,0 +1,21 @@ +import { precompute } from 'flags/sveltekit'; +import { firstMarketingABTest, secondMarketingABTest } from './flags'; + +export const marketingFlags = [firstMarketingABTest, secondMarketingABTest]; + +/** + * Given a user-visible pathname, precompute the internal route using the flags used on that page + * + * e.g. /marketing -> /marketing/asd-qwe-123 + */ +export async function computeInternalRoute(pathname: string, request: Request) { + if (pathname === '/examples/marketing-pages') { + return '/examples/marketing-pages/' + (await precompute(marketingFlags, request)); + } + + return pathname; +} + +export function createVisitorId() { + return crypto.randomUUID().replace(/-/g, ''); +} diff --git a/examples/sveltekit-example/src/routes/+layout.server.ts b/examples/sveltekit-example/src/routes/+layout.server.ts deleted file mode 100644 index 91ff1f9d..00000000 --- a/examples/sveltekit-example/src/routes/+layout.server.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { LayoutServerLoad } from './$types'; -import { showDashboard } from '$lib/flags'; - -export const load: LayoutServerLoad = async () => { - const dashboard = await showDashboard(); - return { title: dashboard ? 'new dashboard' : 'old dashboard' }; -}; diff --git a/examples/sveltekit-example/src/routes/+layout.svelte b/examples/sveltekit-example/src/routes/+layout.svelte index 7b9f1d8c..d5ab53fe 100644 --- a/examples/sveltekit-example/src/routes/+layout.svelte +++ b/examples/sveltekit-example/src/routes/+layout.svelte @@ -1,15 +1,22 @@ +{#if page.url.pathname !== '/'} +
+ +
+{/if} +
- {data.title} - - + {@render children()}
diff --git a/examples/sveltekit-example/src/routes/+page.server.ts b/examples/sveltekit-example/src/routes/+page.server.ts deleted file mode 100644 index 492256bc..00000000 --- a/examples/sveltekit-example/src/routes/+page.server.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { showDashboard } from '$lib/flags'; - -export const load: PageServerLoad = async () => { - const dashboard = await showDashboard(); - - return { - post: { - title: dashboard ? 'New Dashboard' : `Old Dashboard`, - content: `Content for page goes here` - } - }; -}; diff --git a/examples/sveltekit-example/src/routes/+page.svelte b/examples/sveltekit-example/src/routes/+page.svelte index cf3e86db..77ea28ba 100644 --- a/examples/sveltekit-example/src/routes/+page.svelte +++ b/examples/sveltekit-example/src/routes/+page.svelte @@ -1,8 +1,37 @@ - +

This page contains example snippets for the Flags SDK using SvelteKit

-

{data.post.title}

-
{@html data.post.content}
+

+ See flags-sdk.dev for the full documentation, or + GitHub for the source + code. +

+ + +

Dashboard Pages

+

Using feature flags on dynamic pages

+
+ + +

Marketing Pages (manual approach)

+

Simple but not scalable approach to feature flags on static pages

+
+ + +

Marketing Pages

+

Using feature flags on static pages

+
+ + diff --git a/examples/sveltekit-example/src/routes/api/reroute-manual/+server.ts b/examples/sveltekit-example/src/routes/api/reroute-manual/+server.ts new file mode 100644 index 00000000..8d87f72e --- /dev/null +++ b/examples/sveltekit-example/src/routes/api/reroute-manual/+server.ts @@ -0,0 +1,21 @@ +import { marketingABTestManualApproach } from '$lib/flags.js'; +import { text } from '@sveltejs/kit'; + +export async function GET({ request, cookies }) { + let flag = cookies.get('marketingManual'); + + if (!flag) { + flag = String(Math.random() < 0.5); + cookies.set('marketingManual', flag, { + path: '/', + httpOnly: false // So that we can reset the visitor Id on the client in the examples + }); + request.headers.set('x-marketingManual', flag); // cookie is not available on the initial request + } + + return text( + (await marketingABTestManualApproach()) + ? '/examples/marketing-pages-variant-a' + : '/examples/marketing-pages-variant-b' + ); +} diff --git a/examples/sveltekit-example/src/routes/api/reroute/+server.ts b/examples/sveltekit-example/src/routes/api/reroute/+server.ts new file mode 100644 index 00000000..fec1687e --- /dev/null +++ b/examples/sveltekit-example/src/routes/api/reroute/+server.ts @@ -0,0 +1,20 @@ +import { text } from '@sveltejs/kit'; +import { computeInternalRoute, createVisitorId } from '$lib/precomputed-flags'; + +export async function GET({ url, request, cookies, setHeaders }) { + let visitorId = cookies.get('visitorId'); + + if (!visitorId) { + visitorId = createVisitorId(); + cookies.set('visitorId', visitorId, { + path: '/', + httpOnly: false // So that we can reset the visitor Id on the client in the examples + }); + request.headers.set('x-visitorId', visitorId); // cookie is not available on the initial request + } + + // Add cache headers to not request the API as much (as the visitor id is not changing) + setHeaders({ 'Cache-Control': 'private, max-age=300, stale-while-revalidate=600' }); + + return text(await computeInternalRoute(url.searchParams.get('pathname')!, request)); +} diff --git a/examples/sveltekit-example/src/routes/examples/(marketing-pages-manual-approach)/marketing-pages-variant-a/+page.svelte b/examples/sveltekit-example/src/routes/examples/(marketing-pages-manual-approach)/marketing-pages-variant-a/+page.svelte new file mode 100644 index 00000000..95e5b061 --- /dev/null +++ b/examples/sveltekit-example/src/routes/examples/(marketing-pages-manual-approach)/marketing-pages-variant-a/+page.svelte @@ -0,0 +1,20 @@ +

Marketing page (manual approach) variant A

+ +
+ + (will automatically assign a new visitor id, which depending on the value will opt you into one + of two variants) +
+ + diff --git a/examples/sveltekit-example/src/routes/examples/(marketing-pages-manual-approach)/marketing-pages-variant-b/+page.svelte b/examples/sveltekit-example/src/routes/examples/(marketing-pages-manual-approach)/marketing-pages-variant-b/+page.svelte new file mode 100644 index 00000000..d5e258fa --- /dev/null +++ b/examples/sveltekit-example/src/routes/examples/(marketing-pages-manual-approach)/marketing-pages-variant-b/+page.svelte @@ -0,0 +1,20 @@ +

Marketing page (manual approach) variant B

+ +
+ + (will automatically assign a new visitor id, which depending on the value will opt you into one + of two variants) +
+ + diff --git a/examples/sveltekit-example/src/routes/examples/dashboard-pages/+page.server.ts b/examples/sveltekit-example/src/routes/examples/dashboard-pages/+page.server.ts new file mode 100644 index 00000000..45345f12 --- /dev/null +++ b/examples/sveltekit-example/src/routes/examples/dashboard-pages/+page.server.ts @@ -0,0 +1,10 @@ +import type { PageServerLoad } from './$types'; +import { showNewDashboard } from '$lib/flags'; + +export const load: PageServerLoad = async () => { + const dashboard = await showNewDashboard(); + + return { + title: dashboard ? 'New Dashboard' : `Old Dashboard` + }; +}; diff --git a/examples/sveltekit-example/src/routes/examples/dashboard-pages/+page.svelte b/examples/sveltekit-example/src/routes/examples/dashboard-pages/+page.svelte new file mode 100644 index 00000000..e42bbde8 --- /dev/null +++ b/examples/sveltekit-example/src/routes/examples/dashboard-pages/+page.svelte @@ -0,0 +1,26 @@ + + +

{data.title}

+ + + + diff --git a/examples/sveltekit-example/src/routes/examples/marketing-pages/[code]/+page.server.ts b/examples/sveltekit-example/src/routes/examples/marketing-pages/[code]/+page.server.ts new file mode 100644 index 00000000..966c4145 --- /dev/null +++ b/examples/sveltekit-example/src/routes/examples/marketing-pages/[code]/+page.server.ts @@ -0,0 +1,29 @@ +import type { PageServerLoad } from './$types'; +import { firstMarketingABTest, secondMarketingABTest } from '$lib/flags'; +import { marketingFlags } from '$lib/precomputed-flags'; +import { generatePermutations } from 'flags/sveltekit'; + +// Use Vercel ISR: +export const config = { + isr: { + expiration: false + } +}; + +// You could also prerender at build time by doing: +// +// export const prerender = true; +// +// export async function entries() { +// return (await generatePermutations(marketingFlags)).map((code) => ({ code })); +// } + +export const load: PageServerLoad = async ({ params }) => { + const flag1 = await firstMarketingABTest(params.code, marketingFlags); + const flag2 = await secondMarketingABTest(params.code, marketingFlags); + + return { + first: `First flag evaluated to ${flag1}`, + second: `Second flag evaluated to ${flag2}` + }; +}; diff --git a/examples/sveltekit-example/src/routes/examples/marketing-pages/[code]/+page.svelte b/examples/sveltekit-example/src/routes/examples/marketing-pages/[code]/+page.svelte new file mode 100644 index 00000000..4a632331 --- /dev/null +++ b/examples/sveltekit-example/src/routes/examples/marketing-pages/[code]/+page.svelte @@ -0,0 +1,27 @@ + + +

{data.first}

+

{data.second}

+ +
+ + (will automatically assign a new visitor id, which depending on the value will opt you into the + new marketing page) +
+ + diff --git a/examples/sveltekit-example/svelte.config.js b/examples/sveltekit-example/svelte.config.js index 4a82086e..9b85c7f1 100644 --- a/examples/sveltekit-example/svelte.config.js +++ b/examples/sveltekit-example/svelte.config.js @@ -1,4 +1,4 @@ -import adapter from '@sveltejs/adapter-auto'; +import adapter from '@sveltejs/adapter-vercel'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ @@ -8,9 +8,8 @@ const config = { preprocess: vitePreprocess(), kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. + // You can switch out the Vercel adapter with another one, though keep in mind that the precompute + // approach needs another approach then, as middleware.ts is Vercel concept. adapter: adapter() } }; diff --git a/packages/flags/package.json b/packages/flags/package.json index 7736d999..128f1cc7 100644 --- a/packages/flags/package.json +++ b/packages/flags/package.json @@ -39,6 +39,7 @@ "require": "./dist/react.cjs" }, "./sveltekit": { + "svelte": "./dist/sveltekit.js", "import": "./dist/sveltekit.js", "require": "./dist/sveltekit.cjs" } diff --git a/packages/flags/src/next/async-memoize-one.ts b/packages/flags/src/lib/async-memoize-one.ts similarity index 100% rename from packages/flags/src/next/async-memoize-one.ts rename to packages/flags/src/lib/async-memoize-one.ts diff --git a/packages/flags/src/next/serialization.ts b/packages/flags/src/lib/serialization.ts similarity index 96% rename from packages/flags/src/next/serialization.ts rename to packages/flags/src/lib/serialization.ts index f402697b..71a845f3 100644 --- a/packages/flags/src/next/serialization.ts +++ b/packages/flags/src/lib/serialization.ts @@ -1,6 +1,5 @@ import type { JsonValue } from '..'; import { memoizeOne } from './async-memoize-one'; -import type { Flag } from './types'; import type { FlagOption } from '../types'; import { CompactSign, base64url, compactVerify } from 'jose'; @@ -48,9 +47,17 @@ function splitUint8Array( return [firstHalf, secondHalf]; } +/** + * Common subset of the flag type used in here + */ +type Flag = { + key: string; + options?: FlagOption[]; +}; + export async function deserialize( code: string, - flags: readonly Flag[], + flags: readonly Flag[], secret: string, ): Promise> { // TODO what happens when verification fails? @@ -146,8 +153,8 @@ function joinUint8Arrays(array1: Uint8Array, array2: Uint8Array): Uint8Array { return combined; } export async function serialize( - flagSet: Record['key'], JsonValue>, - flags: readonly Flag[], + flagSet: Record, + flags: readonly Flag[], secret: string, ) { const unlistedValues: JsonValue[] = []; diff --git a/packages/flags/src/next/overrides.ts b/packages/flags/src/next/overrides.ts index 123ab983..0e43f247 100644 --- a/packages/flags/src/next/overrides.ts +++ b/packages/flags/src/next/overrides.ts @@ -1,5 +1,5 @@ import { type FlagOverridesType, decrypt } from '..'; -import { memoizeOne } from './async-memoize-one'; +import { memoizeOne } from '../lib/async-memoize-one'; const memoizedDecrypt = memoizeOne( (text: string) => decrypt(text), diff --git a/packages/flags/src/next/precompute.ts b/packages/flags/src/next/precompute.ts index 237d9b4b..c5a8b6aa 100644 --- a/packages/flags/src/next/precompute.ts +++ b/packages/flags/src/next/precompute.ts @@ -1,6 +1,6 @@ import { Flag } from './types'; import type { JsonValue } from '..'; -import * as s from './serialization'; +import * as s from '../lib/serialization'; type FlagsArray = readonly Flag[]; type ValuesArray = readonly any[]; diff --git a/packages/flags/src/sveltekit/env.ts b/packages/flags/src/sveltekit/env.ts new file mode 100644 index 00000000..fdaf0c78 --- /dev/null +++ b/packages/flags/src/sveltekit/env.ts @@ -0,0 +1,27 @@ +// We're doing this dance so that the flags package is usable both in the SvelteKit environment +// as well as other environments that don't know about '$env/dynamic/private', such as Edge Middleware +let default_secret: string | undefined = process.env.FLAGS_SECRET; + +export async function tryGetSecret(secret?: string): Promise { + if (!default_secret) { + try { + // @ts-expect-error SvelteKit will know about this + // use static instead of dynamic because only the latter is available during prerendering, + // and in case it's not available through that it should be via process.env above. + const env = await import('$env/static/private'); + default_secret = env.FLAGS_SECRET; + } catch (e) { + // ignore, could happen when importing from an environment that doesn't know this import + } + } + + secret = secret || default_secret; + + if (!secret) { + throw new Error( + 'flags: No secret provided. Set an environment variable FLAGS_SECRET or provide a secret to the function.', + ); + } + + return secret; +} diff --git a/packages/flags/src/sveltekit/index.test.ts b/packages/flags/src/sveltekit/index.test.ts index 0ae76b6b..451081d7 100644 --- a/packages/flags/src/sveltekit/index.test.ts +++ b/packages/flags/src/sveltekit/index.test.ts @@ -1,6 +1,8 @@ -import { expect, it, describe } from 'vitest'; +import { expect, it, describe, vi } from 'vitest'; import { getProviderData, flag } from '.'; +vi.mock('$env/dynamic/private', () => ({ FLAGS_SECRET: 'secret' })); + describe('getProviderData', () => { it('is a function', () => { expect(typeof getProviderData).toBe('function'); diff --git a/packages/flags/src/sveltekit/index.ts b/packages/flags/src/sveltekit/index.ts index 592fb322..1d301aa2 100644 --- a/packages/flags/src/sveltekit/index.ts +++ b/packages/flags/src/sveltekit/index.ts @@ -2,15 +2,15 @@ import type { Handle, RequestEvent } from '@sveltejs/kit'; import { AsyncLocalStorage } from 'node:async_hooks'; import { type ApiData, - decrypt, - encrypt, + decrypt as _decrypt, + encrypt as _encrypt, reportValue, safeJsonStringify, verifyAccess, type JsonValue, type FlagDefinitionsType, } from '..'; -import { Decide, FlagDeclaration, GenerousOption } from '../types'; +import { Decide, FlagDeclaration, Identify } from '../types'; import { type ReadonlyHeaders, HeadersAdapter, @@ -21,6 +21,13 @@ import { } from '../spec-extension/adapters/request-cookies'; import { normalizeOptions } from '../lib/normalize-options'; import { RequestCookies } from '@edge-runtime/cookies'; +import { Flag, FlagsArray } from './types'; +import { + generatePermutations as _generatePermutations, + getPrecomputed, + precompute as _precompute, +} from './precompute'; +import { tryGetSecret } from './env'; function hasOwnProperty( obj: X, @@ -50,13 +57,6 @@ function sealCookies(headers: Headers): ReadonlyRequestCookies { return sealed; } -type Flag = (() => ReturnValue | Promise) & { - key: string; - description?: string; - origin?: string | Record; - options?: GenerousOption[]; -}; - type PromisesMap = { [K in keyof T]: Promise; }; @@ -91,29 +91,79 @@ function getDecide( }; } +function getIdentify( + definition: FlagDeclaration, +): Identify | undefined { + if (typeof definition.identify === 'function') { + return definition.identify; + } + if (typeof definition.adapter?.identify === 'function') { + return definition.adapter.identify; + } +} + /** - * Declares a feature flag + * Used when a flag is called outside of a request context, i.e. outside of the lifecycle of the `handle` hook. + * This could be the case when the flag is called from edge middleware. */ -export function flag(definition: FlagDeclaration): Flag { - const decide = getDecide(definition); +const requestMap = new WeakMap(); - const flagImpl = async function flagImpl(): Promise { - const store = flagStorage.getStore(); +/** + * Declares a feature flag + */ +export function flag< + ValueType extends JsonValue = boolean | string | number, + EntitiesType = any, +>(definition: FlagDeclaration): Flag { + const decide = getDecide(definition); + const identify = getIdentify(definition); + + const flagImpl = async function flagImpl( + requestOrCode?: string | Request, + flagsArrayOrSecret?: string | Flag[], + ): Promise { + let store = flagStorage.getStore(); if (!store) { - throw new Error('flags: context not found'); + if (requestOrCode instanceof Request) { + store = requestMap.get(requestOrCode); + if (!store) { + store = createContext( + requestOrCode, + (flagsArrayOrSecret as string) ?? (await tryGetSecret()), + ); + requestMap.set(requestOrCode, store); + } + } else { + throw new Error('flags: Neither context found nor Request provided'); + } + } + + if ( + typeof requestOrCode === 'string' && + Array.isArray(flagsArrayOrSecret) + ) { + return getPrecomputed( + definition.key, + flagsArrayOrSecret, + requestOrCode, + store.secret, + ); } if (hasOwnProperty(store.usedFlags, definition.key)) { const valuePromise = store.usedFlags[definition.key]; if (typeof valuePromise !== 'undefined') { - return valuePromise as Promise; + return valuePromise as Promise; } } - const overridesCookie = store.event.cookies.get('vercel-flag-overrides'); + const headers = sealHeaders(store.request.headers); + const cookies = sealCookies(store.request.headers); + + const overridesCookie = cookies.get('vercel-flag-overrides')?.value; const overrides = overridesCookie - ? await decrypt>(overridesCookie, store.secret) + ? await decrypt>(overridesCookie, store.secret) : undefined; if (overrides && hasOwnProperty(overrides, definition.key)) { @@ -125,14 +175,25 @@ export function flag(definition: FlagDeclaration): Flag { } } - 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 }, - ); + let entities: EntitiesType | undefined; + if (identify) { + // Deduplicate calls to identify, key being the function itself + if (!store.identifiers.has(identify)) { + const entities = identify({ + headers, + cookies, + }); + store.identifiers.set(identify, entities); + } + + entities = (await store.identifiers.get(identify)) as EntitiesType; + } + + const valuePromise = decide({ + headers, + cookies, + entities, + }); store.usedFlags[definition.key] = valuePromise as Promise; const value = await valuePromise; @@ -144,16 +205,14 @@ export function flag(definition: FlagDeclaration): Flag { flagImpl.defaultValue = definition.defaultValue; flagImpl.origin = definition.origin; flagImpl.description = definition.description; - flagImpl.options = definition.options; + flagImpl.options = normalizeOptions(definition.options); flagImpl.decide = decide; - // flagImpl.identify = definition.identify; + flagImpl.identify = identify; return flagImpl; } -export function getProviderData( - flags: Record>, -): ApiData { +export function getProviderData(flags: Record>): ApiData { const definitions = Object.values(flags).reduce( (acc, d) => { acc[d.key] = { @@ -170,19 +229,24 @@ export function getProviderData( } interface AsyncLocalContext { - event: RequestEvent>, string | null>; + request: Request; secret: string; + params: Record; usedFlags: Record>; + identifiers: Map, ReturnType>>; } function createContext( - event: RequestEvent>, string | null>, + request: Request, secret: string, + params?: Record, ): AsyncLocalContext { return { - event, + request, secret, + params: params ?? {}, usedFlags: {}, + identifiers: new Map(), }; } @@ -198,10 +262,9 @@ const flagStorage = new AsyncLocalStorage(); * * ```ts * import { createHandle } from 'flags/sveltekit'; - * import { FLAGS_SECRET } from '$env/static/private'; * import * as flags from '$lib/flags'; * - * export const handle = createHandle({ secret: FLAGS_SECRET, flags }); + * export const handle = createHandle({ flags }); * ``` * * @example Usage example in src/hooks.server.ts with other handlers @@ -212,10 +275,12 @@ export function createHandle({ secret, flags, }: { - secret: string; - flags?: Record>; + secret?: string; + flags?: Record>; }): Handle { - return function handle({ event, resolve }) { + return async function handle({ event, resolve }) { + secret ??= await tryGetSecret(secret); + if ( flags && // avoid creating the URL object for every request by checking with includes() first @@ -225,7 +290,11 @@ export function createHandle({ return handleWellKnownFlagsRoute(event, secret, flags); } - const flagContext = createContext(event, secret); + const flagContext = createContext( + event.request, + secret, + event.params as Record, + ); return flagStorage.run(flagContext, () => resolve(event, { transformPageChunk: async ({ html }) => { @@ -253,7 +322,7 @@ export function createHandle({ async function handleWellKnownFlagsRoute( event: RequestEvent>, string | null>, secret: string, - flags: Record>, + flags: Record>, ) { const access = await verifyAccess( event.request.headers.get('Authorization'), @@ -262,3 +331,62 @@ async function handleWellKnownFlagsRoute( if (!access) return new Response(null, { status: 401 }); return Response.json(getProviderData(flags)); } + +/** + * Function to encrypt overrides, values, definitions, and API data. + * + * Convenience wrapper around `encrypt` from `@vercel/flags` for not + * having to provide a secret - it will be read from the environment + * variable `FLAGS_SECRET` via `$env/dynamic/private` if not provided. + */ +export async function encrypt( + value: T, + secret?: string, +): Promise { + return _encrypt(value, await tryGetSecret(secret)); +} + +/** + * Function to decrypt overrides, values, definitions, and API data. + * + * Convenience wrapper around `deencrypt` from `@vercel/flags` for not + * having to provide a secret - it will be read from the environment + * variable `FLAGS_SECRET` via `$env/dynamic/private` if not provided. + */ +export async function decrypt( + encryptedData: string, + secret?: string, +): Promise { + return _decrypt(encryptedData, await tryGetSecret(secret)); +} + +/** + * Evaluate a list of feature flags and generate a signed string representing their values. + * + * This convenience function call combines `evaluate` and `serialize`. + * + * @param flags - list of flags + * @returns - a string representing evaluated flags + */ +export async function precompute( + flags: T, + request: Request, + secret?: string, +): Promise { + return _precompute(flags, request, await tryGetSecret(secret)); +} + +/** + * Generates all permutations given a list of feature flags based on the options declared on each flag. + * @param flags - The list of feature flags + * @param filter - An optional filter function which gets called with each permutation. + * @param secret - The secret sign the generated permutation with + * @returns An array of strings representing each permutation + */ +export async function generatePermutations( + flags: FlagsArray, + filter: ((permutation: Record) => boolean) | null = null, + secret?: string, +): Promise { + return _generatePermutations(flags, filter, await tryGetSecret(secret)); +} diff --git a/packages/flags/src/sveltekit/precompute.ts b/packages/flags/src/sveltekit/precompute.ts new file mode 100644 index 00000000..74a11df7 --- /dev/null +++ b/packages/flags/src/sveltekit/precompute.ts @@ -0,0 +1,138 @@ +import type { Flag, FlagsArray } from './types'; +import type { JsonValue } from '..'; +import * as s from '../lib/serialization'; + +type ValuesArray = readonly any[]; + +/** + * Resolves a list of flags + * @param flags - list of flags + * @returns - an array of evaluated flag values with one entry per flag + */ +async function evaluate( + flags: T, + request: Request, +): Promise<{ [K in keyof T]: Awaited> }> { + return Promise.all(flags.map((flag) => flag(request))) as Promise<{ + [K in keyof T]: Awaited>; + }>; +} + +/** + * Evaluate a list of feature flags and generate a signed string representing their values. + * + * This convenience function call combines `evaluate` and `serialize`. + * + * @param flags - list of flags + * @returns - a string representing evaluated flags + */ +export async function precompute( + flags: T, + request: Request, + secret: string, +): Promise { + const values = await evaluate(flags, request); + return serialize(flags, values, secret); +} + +/** + * Combines flag declarations with values. + * @param flags - flag declarations + * @param values - flag values + * @returns - A record where the keys are flag keys and the values are flag values. + */ +function combine(flags: FlagsArray, values: ValuesArray) { + return Object.fromEntries(flags.map((flag, i) => [flag.key, values[i]])); +} + +/** + * Takes a list of feature flag declarations and their values and turns them into a short, signed string. + * + * The returned string is signed to avoid enumeration attacks. + * + * When a feature flag's `options` contains the value the flag resolved to, then the encoding will store it's index only, leading to better compression. Boolean values and null are compressed even when the options are not declared on the flag. + * + * @param flags - A list of feature flags + * @param values - A list of the values of the flags declared in ´flags` + * @param secret - The secret to use for signing the result + * @returns - A short string representing the values. + */ +async function serialize( + flags: FlagsArray, + values: ValuesArray, + secret: string, +) { + return s.serialize(combine(flags, values), flags, secret); +} + +/** + * Decodes all flags given the list of flags used to encode. Returns an object consisting of each flag's key and its resolved value. + * @param flags - Flags used when `code` was generated by `precompute` or `serialize`. + * @param code - The code returned from `serialize` + * @param secret - The secret to use for signing the result + * @returns - An object consisting of each flag's key and its resolved value. + */ +async function deserialize(flags: FlagsArray, code: string, secret: string) { + return s.deserialize(code, flags, secret); +} + +/** + * Decodes the value of one or multiple flags given the list of flags used to encode and the code. + * + * @param flagKey - Flag or list of flags to decode + * @param precomputeFlags - Flags used when `code` was generated by `serialize` + * @param code - The code returned from `serialize` + * @param secret - The secret to use for verifying the signature + */ +export async function getPrecomputed( + flagKey: Flag['key'], + precomputeFlags: FlagsArray, + code: string, + secret: string, +): Promise { + const flagSet = await deserialize(precomputeFlags, code, secret); + + return flagSet[flagKey]; +} + +// see https://stackoverflow.com/a/44344803 +function* cartesianIterator(items: T[][]): Generator { + const remainder = items.length > 1 ? cartesianIterator(items.slice(1)) : [[]]; + for (let r of remainder) for (let h of items.at(0)!) yield [h, ...r]; +} + +/** + * Generates all permutations given a list of feature flags based on the options declared on each flag. + * @param flags - The list of feature flags + * @param filter - An optional filter function which gets called with each permutation. + * @param secret - The secret sign the generated permutation with + * @returns An array of strings representing each permutation + */ +export async function generatePermutations( + flags: FlagsArray, + filter: ((permutation: Record) => boolean) | null = null, + secret: string, +): Promise { + const options = flags.map((flag) => { + // infer boolean permutations if you don't declare any options. + // + // to explicitly opt out you need to use "filter" + if (!flag.options) return [false, true]; + return flag.options.map((option) => option.value); + }); + + const list: Record[] = []; + + for (const permutation of cartesianIterator(options)) { + const permObject = permutation.reduce>( + (acc, value, index) => { + acc[flags[index]!.key] = value; + return acc; + }, + {}, + ); + if (!filter || filter(permObject)) list.push(permObject); + } + + return Promise.all(list.map((values) => s.serialize(values, flags, secret))); +} diff --git a/packages/flags/src/sveltekit/types.ts b/packages/flags/src/sveltekit/types.ts new file mode 100644 index 00000000..599cc8ea --- /dev/null +++ b/packages/flags/src/sveltekit/types.ts @@ -0,0 +1,33 @@ +import { FlagOption } from '../types'; + +type FlagsMeta = { + key: string; + description?: string; + origin?: string | Record; + options?: FlagOption[]; +}; + +type RegularFlag = { + (): ReturnValue | Promise; + ( + /** Only provide this if you're retrieving the flag value outside of the lifecycle of the `handle` hook, e.g. when calling it inside edge middleware. */ + request?: Request, + secret?: string, + ): ReturnValue | Promise; +} & FlagsMeta; + +type PrecomputedFlag = { + (): never; + ( + /** The route parameter that contains the precomputed flag values */ + code: string, + /** The flags which were used to create the code (i.e. the same array you passed to `precompute(...)`) */ + flagsArray: FlagsArray, + ): ReturnValue | Promise; +} & FlagsMeta; + +export type Flag = + | RegularFlag + | PrecomputedFlag; + +export type FlagsArray = readonly Flag[]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a9f679a..2025ee6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -266,43 +266,46 @@ importers: examples/sveltekit-example: dependencies: + '@vercel/edge': + specifier: ^1.2.1 + version: 1.2.1 '@vercel/toolbar': specifier: 0.1.15 - version: 0.1.15(vite@5.1.1) + version: 0.1.15(vite@5.4.14) + cookie: + specifier: ^0.6.0 + version: 0.6.0 flags: specifier: workspace:* version: link:../../packages/flags devDependencies: - '@sveltejs/adapter-auto': - specifier: ^3.0.0 - version: 3.3.1(@sveltejs/kit@2.19.0) + '@sveltejs/adapter-vercel': + specifier: ^5.6.0 + version: 5.6.3(@sveltejs/kit@2.20.2) '@sveltejs/kit': - specifier: ^2.0.0 - version: 2.19.0(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@4.2.19)(vite@5.1.1) + specifier: ^2.20.2 + version: 2.20.2(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.23.0)(vite@5.4.14) '@sveltejs/vite-plugin-svelte': - specifier: ^3.0.0 - version: 3.1.2(svelte@4.2.19)(vite@5.1.1) + specifier: ^4.0.0 + version: 4.0.4(svelte@5.23.0)(vite@5.4.14) prettier: specifier: ^3.1.1 version: 3.2.5 prettier-plugin-svelte: - specifier: ^3.1.2 - version: 3.3.3(prettier@3.2.5)(svelte@4.2.19) + specifier: ^3.2.6 + version: 3.3.3(prettier@3.2.5)(svelte@5.23.0) svelte: - specifier: ^4.2.7 - version: 4.2.19 + specifier: ^5.0.0 + version: 5.23.0 svelte-check: - specifier: ^3.6.0 - version: 3.8.6(@babel/core@7.26.10)(svelte@4.2.19) - tslib: - specifier: ^2.4.1 - version: 2.8.1 + specifier: ^4.0.0 + version: 4.1.5(svelte@5.23.0)(typescript@5.8.2) typescript: - specifier: ^5.0.0 + specifier: ^5.5.0 version: 5.8.2 vite: - specifier: ^5.0.3 - version: 5.1.1(@types/node@22.9.0) + specifier: ^5.4.4 + version: 5.4.14(@types/node@22.9.0) packages/adapter-edge-config: dependencies: @@ -609,13 +612,13 @@ importers: version: 1.9.0 '@sveltejs/kit': specifier: '*' - version: 2.19.0(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@4.2.19)(vite@5.1.1) + version: 2.19.0(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.23.0)(vite@5.1.1) jose: specifier: ^5.2.1 version: 5.10.0 react-dom: specifier: '*' - version: 19.0.0(react@19.1.0-canary-6aa8254b-20250312) + version: 19.0.0(react@19.1.0-canary-a4f9bd58-20250319) devDependencies: '@arethetypeswrong/cli': specifier: 0.17.3 @@ -637,10 +640,10 @@ importers: version: 2.6.4(@types/node@20.11.17)(typescript@5.6.3) next: specifier: 15.1.4 - version: 15.1.4(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@19.0.0)(react@19.1.0-canary-6aa8254b-20250312) + version: 15.1.4(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@19.0.0)(react@19.1.0-canary-a4f9bd58-20250319) react: specifier: canary - version: 19.1.0-canary-6aa8254b-20250312 + version: 19.1.0-canary-a4f9bd58-20250319 tsconfig: specifier: workspace:* version: link:../../tooling/tsconfig @@ -775,7 +778,7 @@ importers: dependencies: '@vercel/toolbar': specifier: 0.1.15 - version: 0.1.15(vite@5.1.1) + version: 0.1.15(vite@5.4.14) flags: specifier: workspace:* version: link:../../packages/flags @@ -783,36 +786,33 @@ importers: '@playwright/test': specifier: 1.48.2 version: 1.48.2 - '@sveltejs/adapter-auto': - specifier: ^3.0.0 - version: 3.3.1(@sveltejs/kit@2.19.0) + '@sveltejs/adapter-vercel': + specifier: ^5.6.0 + version: 5.6.3(@sveltejs/kit@2.20.2) '@sveltejs/kit': - specifier: ^2.0.0 - version: 2.19.0(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@4.2.19)(vite@5.1.1) + specifier: ^2.20.2 + version: 2.20.2(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.23.0)(vite@5.4.14) '@sveltejs/vite-plugin-svelte': - specifier: ^3.0.0 - version: 3.1.2(svelte@4.2.19)(vite@5.1.1) + specifier: ^4.0.0 + version: 4.0.4(svelte@5.23.0)(vite@5.4.14) prettier: specifier: ^3.1.1 version: 3.2.5 prettier-plugin-svelte: - specifier: ^3.1.2 - version: 3.3.3(prettier@3.2.5)(svelte@4.2.19) + specifier: ^3.2.6 + version: 3.3.3(prettier@3.2.5)(svelte@5.23.0) svelte: - specifier: ^4.2.7 - version: 4.2.19 + specifier: ^5.0.0 + version: 5.23.0 svelte-check: - specifier: ^3.6.0 - version: 3.8.6(@babel/core@7.26.10)(svelte@4.2.19) - tslib: - specifier: ^2.4.1 - version: 2.8.1 + specifier: ^4.0.0 + version: 4.1.5(svelte@5.23.0)(typescript@5.8.2) typescript: - specifier: ^5.0.0 + specifier: ^5.5.0 version: 5.8.2 vite: - specifier: ^5.0.3 - version: 5.1.1(@types/node@22.9.0) + specifier: ^5.4.4 + version: 5.4.14(@types/node@22.9.0) tooling/eslint-config-custom: dependencies: @@ -1451,6 +1451,23 @@ packages: requiresBuild: true optional: true + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + optional: true + + /@esbuild/aix-ppc64@0.24.2: + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + /@esbuild/android-arm64@0.19.12: resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} @@ -1459,6 +1476,23 @@ packages: requiresBuild: true optional: true + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/android-arm64@0.24.2: + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/android-arm@0.19.12: resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} @@ -1467,6 +1501,23 @@ packages: requiresBuild: true optional: true + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/android-arm@0.24.2: + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/android-x64@0.19.12: resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} @@ -1475,6 +1526,23 @@ packages: requiresBuild: true optional: true + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/android-x64@0.24.2: + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/darwin-arm64@0.19.12: resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} @@ -1483,6 +1551,23 @@ packages: requiresBuild: true optional: true + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /@esbuild/darwin-arm64@0.24.2: + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@esbuild/darwin-x64@0.19.12: resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} @@ -1491,6 +1576,23 @@ packages: requiresBuild: true optional: true + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /@esbuild/darwin-x64@0.24.2: + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@esbuild/freebsd-arm64@0.19.12: resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} @@ -1499,6 +1601,23 @@ packages: requiresBuild: true optional: true + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + + /@esbuild/freebsd-arm64@0.24.2: + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/freebsd-x64@0.19.12: resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} @@ -1507,6 +1626,23 @@ packages: requiresBuild: true optional: true + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + + /@esbuild/freebsd-x64@0.24.2: + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-arm64@0.19.12: resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} @@ -1515,6 +1651,23 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-arm64@0.24.2: + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-arm@0.19.12: resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} @@ -1523,6 +1676,23 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-arm@0.24.2: + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-ia32@0.19.12: resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} @@ -1531,6 +1701,23 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-ia32@0.24.2: + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-loong64@0.19.12: resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} @@ -1539,6 +1726,23 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-loong64@0.24.2: + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-mips64el@0.19.12: resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} @@ -1547,6 +1751,23 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-mips64el@0.24.2: + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-ppc64@0.19.12: resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} @@ -1555,6 +1776,23 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-ppc64@0.24.2: + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-riscv64@0.19.12: resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} @@ -1563,6 +1801,23 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-riscv64@0.24.2: + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-s390x@0.19.12: resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} @@ -1571,6 +1826,23 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-s390x@0.24.2: + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-x64@0.19.12: resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} @@ -1579,6 +1851,32 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-x64@0.24.2: + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-arm64@0.24.2: + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/netbsd-x64@0.19.12: resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} @@ -1587,6 +1885,32 @@ packages: requiresBuild: true optional: true + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + optional: true + + /@esbuild/netbsd-x64@0.24.2: + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-arm64@0.24.2: + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/openbsd-x64@0.19.12: resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} @@ -1595,6 +1919,23 @@ packages: requiresBuild: true optional: true + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + optional: true + + /@esbuild/openbsd-x64@0.24.2: + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/sunos-x64@0.19.12: resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} @@ -1603,6 +1944,23 @@ packages: requiresBuild: true optional: true + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + optional: true + + /@esbuild/sunos-x64@0.24.2: + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-arm64@0.19.12: resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} @@ -1611,6 +1969,23 @@ packages: requiresBuild: true optional: true + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /@esbuild/win32-arm64@0.24.2: + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-ia32@0.19.12: resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} @@ -1619,6 +1994,23 @@ packages: requiresBuild: true optional: true + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + + /@esbuild/win32-ia32@0.24.2: + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-x64@0.19.12: resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} @@ -1627,6 +2019,23 @@ packages: requiresBuild: true optional: true + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + + /@esbuild/win32-x64@0.24.2: + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@eslint-community/eslint-utils@4.5.0(eslint@8.48.0): resolution: {integrity: sha512-RoV8Xs9eNwiDvhv7M+xcL4PWyRyIXRY/FLp3buU4h1EYfdF7unWUy3dOjPqb3C7rMUewIcqwW850PgS8h1o1yg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2094,6 +2503,13 @@ packages: wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 + /@isaacs/fs-minipass@4.0.1: + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + dependencies: + minipass: 7.1.2 + dev: true + /@istanbuljs/load-nyc-config@1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -2409,6 +2825,23 @@ packages: read-yaml-file: 1.1.0 dev: true + /@mapbox/node-pre-gyp@2.0.0: + resolution: {integrity: sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==} + engines: {node: '>=18'} + hasBin: true + dependencies: + consola: 3.4.2 + detect-libc: 2.0.3 + https-proxy-agent: 7.0.6 + node-fetch: 2.7.0 + nopt: 8.1.0 + semver: 7.7.1 + tar: 7.4.3 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + /@microsoft/tsdoc-config@0.16.2: resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==} dependencies: @@ -3431,6 +3864,20 @@ packages: react: 19.0.0 dev: false + /@rollup/pluginutils@5.1.4: + resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.6 + estree-walker: 2.0.2 + picomatch: 4.0.2 + dev: true + /@rollup/rollup-android-arm-eabi@4.35.0: resolution: {integrity: sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==} cpu: [arm] @@ -3625,16 +4072,28 @@ packages: dependencies: '@sinonjs/commons': 3.0.1 - /@sveltejs/adapter-auto@3.3.1(@sveltejs/kit@2.19.0): - resolution: {integrity: sha512-5Sc7WAxYdL6q9j/+D0jJKjGREGlfIevDyHSQ2eNETHcB1TKlQWHcAo8AS8H1QdjNvSXpvOwNjykDUHPEAyGgdQ==} + /@sveltejs/acorn-typescript@1.0.5(acorn@8.14.1): + resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} peerDependencies: - '@sveltejs/kit': ^2.0.0 + acorn: ^8.9.0 dependencies: - '@sveltejs/kit': 2.19.0(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@4.2.19)(vite@5.1.1) - import-meta-resolve: 4.1.0 + acorn: 8.14.1 + + /@sveltejs/adapter-vercel@5.6.3(@sveltejs/kit@2.20.2): + resolution: {integrity: sha512-V5ow05ziJ1LVzGthulVwmS1KfGY0Grxa/8PwhrQk+Iu2VThhrkC/5ZBagI0MmkJIsb25xT3JV9Px4GlWM2pCVg==} + peerDependencies: + '@sveltejs/kit': ^2.4.0 + dependencies: + '@sveltejs/kit': 2.20.2(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.23.0)(vite@5.4.14) + '@vercel/nft': 0.29.2 + esbuild: 0.24.2 + transitivePeerDependencies: + - encoding + - rollup + - supports-color dev: true - /@sveltejs/kit@2.19.0(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@4.2.19)(vite@5.1.1): + /@sveltejs/kit@2.19.0(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.23.0)(vite@5.1.1): resolution: {integrity: sha512-UTx28Ad4sYsLU//gqkEo5aFOPFBRT2uXCmXTsURqhurDCvzkVwXruJgBcHDaMiK6RKKpYRteDUaXYqZyGPgCXQ==} engines: {node: '>=18.13'} hasBin: true @@ -3643,7 +4102,7 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.3 || ^6.0.0 dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.1.1) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.23.0)(vite@5.1.1) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.1.1 @@ -3655,42 +4114,104 @@ packages: sade: 1.8.1 set-cookie-parser: 2.7.1 sirv: 3.0.1 - svelte: 4.2.19 + svelte: 5.23.0 vite: 5.1.1(@types/node@20.11.17) + dev: false - /@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@4.2.19)(vite@5.1.1): - resolution: {integrity: sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==} - engines: {node: ^18.0.0 || >=20} + /@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.23.0)(vite@5.4.14): + resolution: {integrity: sha512-Dv8TOAZC9vyfcAB9TMsvUEJsRbklRTeNfcYBPaeH6KnABJ99i3CvCB2eNx8fiiliIqe+9GIchBg4RodRH5p1BQ==} + engines: {node: '>=18.13'} + hasBin: true peerDependencies: - '@sveltejs/vite-plugin-svelte': ^3.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 || ^6.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.23.0)(vite@5.4.14) + '@types/cookie': 0.6.0 + cookie: 0.6.0 + devalue: 5.1.1 + esm-env: 1.2.2 + import-meta-resolve: 4.1.0 + kleur: 4.1.5 + magic-string: 0.30.17 + mrmime: 2.0.1 + sade: 1.8.1 + set-cookie-parser: 2.7.1 + sirv: 3.0.1 + svelte: 5.23.0 + vite: 5.4.14(@types/node@22.9.0) + dev: true + + /@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.23.0)(vite@5.1.1): + resolution: {integrity: sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^4.0.0-next.0||^4.0.0 + svelte: ^5.0.0-next.96 || ^5.0.0 + vite: ^5.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.23.0)(vite@5.1.1) + debug: 4.4.0 + svelte: 5.23.0 + vite: 5.1.1(@types/node@20.11.17) + transitivePeerDependencies: + - supports-color + dev: false + + /@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.23.0)(vite@5.4.14): + resolution: {integrity: sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^4.0.0-next.0||^4.0.0 + svelte: ^5.0.0-next.96 || ^5.0.0 + vite: ^5.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.23.0)(vite@5.4.14) + debug: 4.4.0 + svelte: 5.23.0 + vite: 5.4.14(@types/node@22.9.0) + transitivePeerDependencies: + - supports-color + dev: true + + /@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.1.1): + resolution: {integrity: sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + svelte: ^5.0.0-next.96 || ^5.0.0 vite: ^5.0.0 dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.1.1) + '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.23.0)(vite@5.1.1) debug: 4.4.0 - svelte: 4.2.19 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.17 + svelte: 5.23.0 vite: 5.1.1(@types/node@20.11.17) + vitefu: 1.0.6(vite@5.1.1) transitivePeerDependencies: - supports-color + dev: false - /@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.1.1): - resolution: {integrity: sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==} - engines: {node: ^18.0.0 || >=20} + /@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.23.0)(vite@5.4.14): + resolution: {integrity: sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} peerDependencies: - svelte: ^4.0.0 || ^5.0.0-next.0 + svelte: ^5.0.0-next.96 || ^5.0.0 vite: ^5.0.0 dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@4.2.19)(vite@5.1.1) + '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.23.0)(vite@5.4.14) debug: 4.4.0 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 - svelte: 4.2.19 - svelte-hmr: 0.16.0(svelte@4.2.19) - vite: 5.1.1(@types/node@20.11.17) - vitefu: 0.2.5(vite@5.1.1) + svelte: 5.23.0 + vite: 5.4.14(@types/node@22.9.0) + vitefu: 1.0.6(vite@5.4.14) transitivePeerDependencies: - supports-color + dev: true /@swc/counter@0.1.3: resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -4099,10 +4620,6 @@ packages: resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} dev: true - /@types/pug@2.0.10: - resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} - dev: true - /@types/react-dom@18.3.5(@types/react@18.2.55): resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} peerDependencies: @@ -4188,7 +4705,6 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color - dev: true /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.48.0)(typescript@5.6.3): resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} @@ -4217,6 +4733,7 @@ packages: typescript: 5.6.3 transitivePeerDependencies: - supports-color + dev: false /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.8.2): resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} @@ -4314,7 +4831,6 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color - dev: true /@typescript-eslint/parser@6.21.0(eslint@8.48.0)(typescript@5.6.3): resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} @@ -4335,6 +4851,7 @@ packages: typescript: 5.6.3 transitivePeerDependencies: - supports-color + dev: false /@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.2): resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} @@ -4433,7 +4950,6 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color - dev: true /@typescript-eslint/type-utils@6.21.0(eslint@8.48.0)(typescript@5.6.3): resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} @@ -4453,6 +4969,7 @@ packages: typescript: 5.6.3 transitivePeerDependencies: - supports-color + dev: false /@typescript-eslint/type-utils@6.21.0(eslint@8.56.0)(typescript@5.8.2): resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} @@ -4540,7 +5057,6 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color - dev: true /@typescript-eslint/typescript-estree@5.62.0(typescript@5.6.3): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} @@ -4561,6 +5077,7 @@ packages: typescript: 5.6.3 transitivePeerDependencies: - supports-color + dev: false /@typescript-eslint/typescript-estree@5.62.0(typescript@5.8.2): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} @@ -4603,7 +5120,6 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color - dev: true /@typescript-eslint/typescript-estree@6.21.0(typescript@5.6.3): resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} @@ -4625,6 +5141,7 @@ packages: typescript: 5.6.3 transitivePeerDependencies: - supports-color + dev: false /@typescript-eslint/typescript-estree@6.21.0(typescript@5.8.2): resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} @@ -4685,7 +5202,6 @@ packages: transitivePeerDependencies: - supports-color - typescript - dev: true /@typescript-eslint/utils@5.62.0(eslint@8.48.0)(typescript@5.6.3): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} @@ -4705,6 +5221,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: false /@typescript-eslint/utils@5.62.0(eslint@8.56.0)(typescript@5.8.2): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} @@ -4743,7 +5260,6 @@ packages: transitivePeerDependencies: - supports-color - typescript - dev: true /@typescript-eslint/utils@6.21.0(eslint@8.48.0)(typescript@5.6.3): resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} @@ -4762,6 +5278,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: false /@typescript-eslint/utils@6.21.0(eslint@8.56.0)(typescript@5.8.2): resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} @@ -4954,6 +5471,29 @@ packages: - debug dev: false + /@vercel/nft@0.29.2: + resolution: {integrity: sha512-A/Si4mrTkQqJ6EXJKv5EYCDQ3NL6nJXxG8VGXePsaiQigsomHYQC9xSpX8qGk7AEZk4b1ssbYIqJ0ISQQ7bfcA==} + engines: {node: '>=18'} + hasBin: true + dependencies: + '@mapbox/node-pre-gyp': 2.0.0 + '@rollup/pluginutils': 5.1.4 + acorn: 8.14.1 + acorn-import-attributes: 1.9.5(acorn@8.14.1) + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 10.4.5 + graceful-fs: 4.2.11 + node-gyp-build: 4.8.4 + picomatch: 4.0.2 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + dev: true + /@vercel/style-guide@5.2.0(eslint@8.48.0)(jest@29.7.0)(prettier@3.2.5)(typescript@5.3.3): resolution: {integrity: sha512-fNSKEaZvSkiBoF6XEefs8CcgAV9K9e+MbcsDZjUsktHycKdA0jvjAzQi1W/FzLS+Nr5zZ6oejCwq/97dHUKe0g==} engines: {node: '>=16'} @@ -5095,7 +5635,7 @@ packages: - supports-color dev: true - /@vercel/toolbar@0.1.15(vite@5.1.1): + /@vercel/toolbar@0.1.15(vite@5.4.14): resolution: {integrity: sha512-YDwRdYM6ij5CbkZm9NgoO1H7uXPuoM5nnzoHYMAZtF0sJylhQ7JIgwLniW71V6RNCKCrJGS1B23BvrQQl8WyrQ==} peerDependencies: next: '>=11.0.0' @@ -5115,7 +5655,7 @@ packages: find-up: 5.0.0 get-port: 5.1.1 strip-ansi: 6.0.1 - vite: 5.1.1(@types/node@22.9.0) + vite: 5.4.14(@types/node@22.9.0) dev: false /@vercel/toolbar@0.1.27(next@15.2.2-canary.4)(react@19.0.0-rc.1): @@ -5229,6 +5769,19 @@ packages: pretty-format: 29.7.0 dev: true + /abbrev@3.0.0: + resolution: {integrity: sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==} + engines: {node: ^18.17.0 || >=20.5.0} + dev: true + + /acorn-import-attributes@1.9.5(acorn@8.14.1): + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + dependencies: + acorn: 8.14.1 + dev: true + /acorn-jsx@5.3.2(acorn@8.14.1): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -5248,6 +5801,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + /agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + dev: true + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -5444,6 +6002,10 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + /async-sema@3.1.1: + resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} + dev: true + /autoprefixer@10.4.21(postcss@8.5.3): resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} @@ -5558,6 +6120,12 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + /bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: true + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -5603,11 +6171,6 @@ packages: dependencies: node-int64: 0.4.0 - /buffer-crc32@1.0.0: - resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} - engines: {node: '>=8.0.0'} - dev: true - /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -5766,6 +6329,18 @@ packages: optionalDependencies: fsevents: 2.3.3 + /chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + dependencies: + readdirp: 4.1.2 + dev: true + + /chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + dev: true + /ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} @@ -5867,21 +6442,11 @@ packages: /clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - dev: false /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - /code-red@1.0.4: - resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - '@types/estree': 1.0.6 - acorn: 8.14.1 - estree-walker: 3.0.3 - periscopic: 3.1.0 - /collect-v8-coverage@1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} @@ -5955,6 +6520,11 @@ packages: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} dev: true + /consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + dev: true + /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -6010,13 +6580,6 @@ packages: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} dev: false - /css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - dependencies: - mdn-data: 2.0.30 - source-map-js: 1.2.1 - /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -6433,10 +6996,6 @@ packages: engines: {node: '>=12.x'} dev: false - /es6-promise@3.3.1: - resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} - dev: true - /esbuild@0.19.12: resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} engines: {node: '>=12'} @@ -6467,6 +7026,69 @@ packages: '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + /esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + dev: true + /escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -6682,7 +7304,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.48.0)(typescript@5.6.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.48.0)(typescript@5.3.3) debug: 3.2.7 eslint: 8.48.0 eslint-import-resolver-node: 0.3.9 @@ -6812,7 +7434,7 @@ packages: optional: true dependencies: '@rtsao/scc': 1.1.0 - '@typescript-eslint/parser': 6.21.0(eslint@8.48.0)(typescript@5.6.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.48.0)(typescript@5.3.3) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.3 @@ -6968,7 +7590,6 @@ packages: transitivePeerDependencies: - supports-color - typescript - dev: true /eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.48.0)(jest@29.7.0)(typescript@5.6.3): resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} @@ -6990,6 +7611,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: false /eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.56.0)(jest@29.7.0)(typescript@5.8.2): resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} @@ -7094,7 +7716,7 @@ packages: optional: true dependencies: eslint: 8.48.0 - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.48.0)(jest@29.7.0)(typescript@5.6.3) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.48.0)(jest@29.7.0)(typescript@5.3.3) /eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.56.0): resolution: {integrity: sha512-DcHpF0SLbNeh9MT4pMzUGuUSnJ7q5MWbP8sSEFIMS6j7Ggnduq8ghNlfhURgty4c1YFny7Ge9xYTO1FSAoV2Vw==} @@ -7534,6 +8156,11 @@ packages: dependencies: estraverse: 5.3.0 + /esrap@1.4.5: + resolution: {integrity: sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + /esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -7548,10 +8175,15 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true + /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: '@types/estree': 1.0.6 + dev: true /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} @@ -7692,6 +8324,10 @@ packages: flat-cache: 4.0.1 dev: true + /file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: true + /fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -8131,6 +8767,16 @@ packages: - debug dev: false + /https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + dev: true + /human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} dev: true @@ -9425,9 +10071,6 @@ packages: vfile: 6.0.3 dev: true - /mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - /meow@6.1.1: resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} engines: {node: '>=8'} @@ -9569,16 +10212,23 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + /minizlib@3.0.1: + resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + engines: {node: '>= 18'} + dependencies: + minipass: 7.1.2 + rimraf: 5.0.10 + dev: true + /mixme@0.5.10: resolution: {integrity: sha512-5H76ANWinB1H3twpJ6JY8uvAtpmFvHNArpilJAjXRKXSDDLPIMoZArw5SH0q9z+lLs8IrMw7Q2VWpWimFKFT1Q==} engines: {node: '>= 8.0.0'} dev: true - /mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + /mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} hasBin: true - dependencies: - minimist: 1.2.8 dev: true /mlly@1.7.4: @@ -9784,7 +10434,7 @@ packages: - babel-plugin-macros dev: false - /next@15.1.4(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@19.0.0)(react@19.1.0-canary-6aa8254b-20250312): + /next@15.1.4(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@19.0.0)(react@19.1.0-canary-a4f9bd58-20250319): resolution: {integrity: sha512-mTaq9dwaSuwwOrcu3ebjDYObekkxRnXpuVL21zotM8qE2W0HBOdVIdg2Li9QjMEZrj73LN96LcWcz62V19FjAg==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true @@ -9812,9 +10462,9 @@ packages: busboy: 1.6.0 caniuse-lite: 1.0.30001704 postcss: 8.4.31 - react: 19.1.0-canary-6aa8254b-20250312 - react-dom: 19.0.0(react@19.1.0-canary-6aa8254b-20250312) - styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.1.0-canary-6aa8254b-20250312) + react: 19.1.0-canary-a4f9bd58-20250319 + react-dom: 19.0.0(react@19.1.0-canary-a4f9bd58-20250319) + styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.1.0-canary-a4f9bd58-20250319) optionalDependencies: '@next/swc-darwin-arm64': 15.1.4 '@next/swc-darwin-x64': 15.1.4 @@ -10031,7 +10681,11 @@ packages: optional: true dependencies: whatwg-url: 5.0.0 - dev: false + + /node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + dev: true /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -10039,6 +10693,14 @@ packages: /node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + /nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + dependencies: + abbrev: 3.0.0 + dev: true + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: @@ -10356,13 +11018,6 @@ packages: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true - /periscopic@3.1.0: - resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} - dependencies: - '@types/estree': 1.0.6 - estree-walker: 3.0.3 - is-reference: 3.0.3 - /picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -10533,14 +11188,14 @@ packages: sort-package-json: 2.15.1 synckit: 0.9.2 - /prettier-plugin-svelte@3.3.3(prettier@3.2.5)(svelte@4.2.19): + /prettier-plugin-svelte@3.3.3(prettier@3.2.5)(svelte@5.23.0): resolution: {integrity: sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==} peerDependencies: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 dependencies: prettier: 3.2.5 - svelte: 4.2.19 + svelte: 5.23.0 dev: true /prettier@2.8.8: @@ -10643,12 +11298,12 @@ packages: scheduler: 0.25.0 dev: false - /react-dom@19.0.0(react@19.1.0-canary-6aa8254b-20250312): + /react-dom@19.0.0(react@19.1.0-canary-a4f9bd58-20250319): resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: react: ^19.0.0 dependencies: - react: 19.1.0-canary-6aa8254b-20250312 + react: 19.1.0-canary-a4f9bd58-20250319 scheduler: 0.25.0 /react-dom@19.0.0-rc-02c0e824-20241028(react@19.0.0-rc-02c0e824-20241028): @@ -10753,8 +11408,8 @@ packages: engines: {node: '>=0.10.0'} dev: false - /react@19.1.0-canary-6aa8254b-20250312: - resolution: {integrity: sha512-8OeMqhjusFQuIUxkyGYI0Gp5D9fNeQzfWLaxIuW/j0/XZQFGK+HeGS6M0ST5ur/MtaA0PY8kZJb+Ay/1sjNTAw==} + /react@19.1.0-canary-a4f9bd58-20250319: + resolution: {integrity: sha512-wRC4V2P/2yecBs/676cNhcQHv1UGfmNxizrqvEX6OjqNne79lzhe5oPbQ06JxNkzew5+2B2EtnTBEjs6M7vuKQ==} engines: {node: '>=0.10.0'} /read-cache@1.0.0: @@ -10795,6 +11450,11 @@ packages: dependencies: picomatch: 2.3.1 + /readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + dev: true + /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -10938,20 +11598,19 @@ packages: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} dev: true - /rimraf@2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true dependencies: glob: 7.2.3 - dev: true - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported + /rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true dependencies: - glob: 7.2.3 + glob: 10.4.5 + dev: true /rimraf@6.0.1: resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} @@ -11030,15 +11689,6 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true - /sander@0.5.1: - resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} - dependencies: - es6-promise: 3.3.1 - graceful-fs: 4.2.11 - mkdirp: 0.5.6 - rimraf: 2.7.1 - dev: true - /scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} dependencies: @@ -11290,16 +11940,6 @@ packages: react-dom: 19.0.0(react@19.0.0) dev: false - /sorcery@0.11.1: - resolution: {integrity: sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ==} - hasBin: true - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - buffer-crc32: 1.0.0 - minimist: 1.2.8 - sander: 0.5.1 - dev: true - /sort-object-keys@1.1.3: resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==} @@ -11655,7 +12295,7 @@ packages: react: 19.0.0-rc.1 dev: false - /styled-jsx@5.1.6(@babel/core@7.26.10)(react@19.1.0-canary-6aa8254b-20250312): + /styled-jsx@5.1.6(@babel/core@7.26.10)(react@19.1.0-canary-a4f9bd58-20250319): resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} peerDependencies: @@ -11670,7 +12310,7 @@ packages: dependencies: '@babel/core': 7.26.10 client-only: 0.0.1 - react: 19.1.0-canary-6aa8254b-20250312 + react: 19.1.0-canary-a4f9bd58-20250319 dev: true /sucrase@3.35.0: @@ -11717,105 +12357,43 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - /svelte-check@3.8.6(@babel/core@7.26.10)(svelte@4.2.19): - resolution: {integrity: sha512-ij0u4Lw/sOTREP13BdWZjiXD/BlHE6/e2e34XzmVmsp5IN4kVa3PWP65NM32JAgwjZlwBg/+JtiNV1MM8khu0Q==} + /svelte-check@4.1.5(svelte@5.23.0)(typescript@5.8.2): + resolution: {integrity: sha512-Gb0T2IqBNe1tLB9EB1Qh+LOe+JB8wt2/rNBDGvkxQVvk8vNeAoG+vZgFB/3P5+zC7RWlyBlzm9dVjZFph/maIg==} + engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: - svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' dependencies: '@jridgewell/trace-mapping': 0.3.25 - chokidar: 3.6.0 + chokidar: 4.0.3 + fdir: 6.4.3(picomatch@4.0.2) picocolors: 1.1.1 sade: 1.8.1 - svelte: 4.2.19 - svelte-preprocess: 5.1.4(@babel/core@7.26.10)(svelte@4.2.19)(typescript@5.8.2) + svelte: 5.23.0 typescript: 5.8.2 transitivePeerDependencies: - - '@babel/core' - - coffeescript - - less - - postcss - - postcss-load-config - - pug - - sass - - stylus - - sugarss - dev: true - - /svelte-hmr@0.16.0(svelte@4.2.19): - resolution: {integrity: sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==} - engines: {node: ^12.20 || ^14.13.1 || >= 16} - peerDependencies: - svelte: ^3.19.0 || ^4.0.0 - dependencies: - svelte: 4.2.19 - - /svelte-preprocess@5.1.4(@babel/core@7.26.10)(svelte@4.2.19)(typescript@5.8.2): - resolution: {integrity: sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==} - engines: {node: '>= 16.0.0'} - requiresBuild: true - peerDependencies: - '@babel/core': ^7.10.2 - coffeescript: ^2.5.1 - less: ^3.11.3 || ^4.0.0 - postcss: ^7 || ^8 - postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 - pug: ^3.0.0 - sass: ^1.26.8 - stylus: ^0.55.0 - sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 - svelte: ^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 - typescript: '>=3.9.5 || ^4.0.0 || ^5.0.0' - peerDependenciesMeta: - '@babel/core': - optional: true - coffeescript: - optional: true - less: - optional: true - postcss: - optional: true - postcss-load-config: - optional: true - pug: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - typescript: - optional: true - dependencies: - '@babel/core': 7.26.10 - '@types/pug': 2.0.10 - detect-indent: 6.1.0 - magic-string: 0.30.17 - sorcery: 0.11.1 - strip-indent: 3.0.0 - svelte: 4.2.19 - typescript: 5.8.2 + - picomatch dev: true - /svelte@4.2.19: - resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==} - engines: {node: '>=16'} + /svelte@5.23.0: + resolution: {integrity: sha512-v0lL3NuKontiCxholEiAXCB+BYbndlKbwlDMK0DS86WgGELMJSpyqCSbJeMEMBDwOglnS7Ar2Rq0wwa/z2L8Vg==} + engines: {node: '>=18'} dependencies: '@ampproject/remapping': 2.3.0 '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1) '@types/estree': 1.0.6 acorn: 8.14.1 aria-query: 5.3.2 axobject-query: 4.1.0 - code-red: 1.0.4 - css-tree: 2.3.1 - estree-walker: 3.0.3 + clsx: 2.1.1 + esm-env: 1.2.2 + esrap: 1.4.5 is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.17 - periscopic: 3.1.0 + zimmerframe: 1.1.2 /synckit@0.9.2: resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} @@ -11877,6 +12455,18 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + /tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.1 + mkdirp: 3.0.1 + yallist: 5.0.0 + dev: true + /term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -11957,7 +12547,6 @@ packages: /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: false /tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} @@ -11986,7 +12575,6 @@ packages: typescript: '>=4.2.0' dependencies: typescript: 5.3.3 - dev: true /ts-api-utils@1.4.3(typescript@5.6.3): resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} @@ -11995,6 +12583,7 @@ packages: typescript: '>=4.2.0' dependencies: typescript: 5.6.3 + dev: false /ts-api-utils@1.4.3(typescript@5.8.2): resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} @@ -12112,7 +12701,6 @@ packages: dependencies: tslib: 1.14.1 typescript: 5.3.3 - dev: true /tsutils@3.21.0(typescript@5.6.3): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -12122,6 +12710,7 @@ packages: dependencies: tslib: 1.14.1 typescript: 5.6.3 + dev: false /tsutils@3.21.0(typescript@5.8.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -12293,7 +12882,6 @@ packages: resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} engines: {node: '>=14.17'} hasBin: true - dev: true /typescript@5.6.1-rc: resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} @@ -12491,12 +13079,13 @@ packages: debug: 4.4.0 pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.1.1(@types/node@20.11.17) + vite: 5.4.14(@types/node@20.11.17) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color @@ -12538,8 +13127,47 @@ packages: optionalDependencies: fsevents: 2.3.3 - /vite@5.1.1(@types/node@22.9.0): - resolution: {integrity: sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==} + /vite@5.4.14(@types/node@20.11.17): + resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.11.17 + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.35.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vite@5.4.14(@types/node@22.9.0): + resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -12547,6 +13175,7 @@ packages: less: '*' lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' terser: ^5.4.0 @@ -12559,6 +13188,8 @@ packages: optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: @@ -12567,21 +13198,33 @@ packages: optional: true dependencies: '@types/node': 22.9.0 - esbuild: 0.19.12 + esbuild: 0.21.5 postcss: 8.5.3 rollup: 4.35.0 optionalDependencies: fsevents: 2.3.3 - /vitefu@0.2.5(vite@5.1.1): - resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} + /vitefu@1.0.6(vite@5.1.1): + resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==} peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 peerDependenciesMeta: vite: optional: true dependencies: vite: 5.1.1(@types/node@20.11.17) + dev: false + + /vitefu@1.0.6(vite@5.4.14): + resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + vite: + optional: true + dependencies: + vite: 5.4.14(@types/node@22.9.0) + dev: true /vitest@1.4.0(@types/node@20.11.17): resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} @@ -12633,6 +13276,7 @@ packages: - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color @@ -12660,7 +13304,6 @@ packages: /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: false /webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -12671,7 +13314,6 @@ packages: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - dev: false /whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} @@ -12832,6 +13474,11 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: false + /yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + dev: true + /yaml@2.3.4: resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} engines: {node: '>= 14'} @@ -12915,6 +13562,9 @@ packages: engines: {node: '>=18'} dev: true + /zimmerframe@1.1.2: + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: true diff --git a/tests/sveltekit-e2e/package.json b/tests/sveltekit-e2e/package.json index 60994c69..ffdeab01 100644 --- a/tests/sveltekit-e2e/package.json +++ b/tests/sveltekit-e2e/package.json @@ -18,16 +18,15 @@ }, "devDependencies": { "@playwright/test": "1.48.2", - "@sveltejs/adapter-auto": "^3.0.0", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@sveltejs/adapter-vercel": "^5.6.0", + "@sveltejs/kit": "^2.20.2", + "@sveltejs/vite-plugin-svelte": "^4.0.0", "prettier": "^3.1.1", - "prettier-plugin-svelte": "^3.1.2", - "svelte": "^4.2.7", - "svelte-check": "^3.6.0", - "tslib": "^2.4.1", - "typescript": "^5.0.0", - "vite": "^5.0.3" + "prettier-plugin-svelte": "^3.2.6", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.5.0", + "vite": "^5.4.4" }, "type": "module" } diff --git a/tests/sveltekit-e2e/src/hooks.ts b/tests/sveltekit-e2e/src/hooks.ts new file mode 100644 index 00000000..3350d9f6 --- /dev/null +++ b/tests/sveltekit-e2e/src/hooks.ts @@ -0,0 +1,8 @@ +export async function reroute({ url, fetch }) { + if (url.pathname === '/precomputed') { + const destination = new URL('/api/reroute', url); + destination.searchParams.set('pathname', url.pathname); + + return fetch(destination).then((response) => response.text()); + } +} diff --git a/tests/sveltekit-e2e/src/lib/flags.ts b/tests/sveltekit-e2e/src/lib/flags.ts index 309f3a8e..f258a176 100644 --- a/tests/sveltekit-e2e/src/lib/flags.ts +++ b/tests/sveltekit-e2e/src/lib/flags.ts @@ -1,3 +1,4 @@ +import { ReadonlyHeaders, ReadonlyRequestCookies } from 'flags'; import { flag } from 'flags/sveltekit'; export const showDashboard = flag({ @@ -20,3 +21,42 @@ export const cookieFlag = flag({ key: 'cookie', decide: ({ cookies }) => cookies.get('example-cookie')?.value || 'no cookie' }); + +interface Entities { + visitorId?: string; +} + +function identify({ + cookies, + headers +}: { + cookies: ReadonlyRequestCookies; + headers: ReadonlyHeaders; +}): Entities { + const visitorId = cookies.get('visitorId')?.value ?? headers.get('x-visitorId'); + + if (!visitorId) { + throw new Error( + 'Visitor ID not found - should have been set by middleware or within api/reroute' + ); + } + + return { visitorId }; +} + +export const precomputedFlag = flag({ + key: 'precomputedFlag', + description: 'A precomputed flag', + identify, + decide({ entities, cookies, headers }) { + if (!entities?.visitorId) return 'fail'; + + return ( + entities.visitorId + + '|' + + (cookies.get('visitorId')?.value || 'no cookie') + + '|' + + (headers.get('x-visitorId') || 'no header') + ); + } +}); diff --git a/tests/sveltekit-e2e/src/lib/precomputed-flags.ts b/tests/sveltekit-e2e/src/lib/precomputed-flags.ts new file mode 100644 index 00000000..7df4dec7 --- /dev/null +++ b/tests/sveltekit-e2e/src/lib/precomputed-flags.ts @@ -0,0 +1,21 @@ +import { precompute } from 'flags/sveltekit'; +import { precomputedFlag } from './flags'; + +export const precomputedFlags = [precomputedFlag]; + +/** + * Given a user-visible pathname, precompute the internal route using the flags used on that page + * + * e.g. /precomputed -> /precomputed/asd-qwe-123 + */ +export async function computeInternalRoute(pathname: string, request: Request) { + if (pathname === '/precomputed') { + return '/precomputed/' + (await precompute(precomputedFlags, request)); + } + + return pathname; +} + +export function createVisitorId() { + return 'visitorId'; +} diff --git a/tests/sveltekit-e2e/src/routes/+layout.svelte b/tests/sveltekit-e2e/src/routes/+layout.svelte index 7b9f1d8c..66f86a6a 100644 --- a/tests/sveltekit-e2e/src/routes/+layout.svelte +++ b/tests/sveltekit-e2e/src/routes/+layout.svelte @@ -1,15 +1,22 @@ +
+ +
+
{data.title} - + {@render children?.()}
diff --git a/tests/sveltekit-e2e/src/routes/+page.svelte b/tests/sveltekit-e2e/src/routes/+page.svelte index 0904e89d..807a2104 100644 --- a/tests/sveltekit-e2e/src/routes/+page.svelte +++ b/tests/sveltekit-e2e/src/routes/+page.svelte @@ -1,7 +1,7 @@

{data.post.title}

diff --git a/tests/sveltekit-e2e/src/routes/api/reroute/+server.ts b/tests/sveltekit-e2e/src/routes/api/reroute/+server.ts new file mode 100644 index 00000000..e58a994e --- /dev/null +++ b/tests/sveltekit-e2e/src/routes/api/reroute/+server.ts @@ -0,0 +1,14 @@ +import { text } from '@sveltejs/kit'; +import { computeInternalRoute, createVisitorId } from '$lib/precomputed-flags'; + +export async function GET({ url, request, cookies }) { + let visitorId = cookies.get('visitorId'); + + if (!visitorId) { + visitorId = createVisitorId(); + cookies.set('visitorId', visitorId, { path: '/' }); + request.headers.set('x-visitorId', visitorId); // cookie is not available on the initial request + } + + return text(await computeInternalRoute(url.searchParams.get('pathname')!, request)); +} diff --git a/tests/sveltekit-e2e/src/routes/precomputed/[code]/+page.server.ts b/tests/sveltekit-e2e/src/routes/precomputed/[code]/+page.server.ts new file mode 100644 index 00000000..de33df95 --- /dev/null +++ b/tests/sveltekit-e2e/src/routes/precomputed/[code]/+page.server.ts @@ -0,0 +1,16 @@ +import type { PageServerLoad } from './$types'; +import { precomputedFlag } from '$lib/flags'; +import { precomputedFlags } from '$lib/precomputed-flags'; +import { generatePermutations } from 'flags/sveltekit'; + +export const prerender = true; + +export async function entries() { + return (await generatePermutations(precomputedFlags)).map((code) => ({ code })); +} + +export const load: PageServerLoad = async ({ params }) => { + return { + flag: await precomputedFlag(params.code, precomputedFlags) + }; +}; diff --git a/tests/sveltekit-e2e/src/routes/precomputed/[code]/+page.svelte b/tests/sveltekit-e2e/src/routes/precomputed/[code]/+page.svelte new file mode 100644 index 00000000..b28e4879 --- /dev/null +++ b/tests/sveltekit-e2e/src/routes/precomputed/[code]/+page.svelte @@ -0,0 +1,7 @@ + + +

{data.flag}

diff --git a/tests/sveltekit-e2e/src/routes/precomputed/page.spec.ts b/tests/sveltekit-e2e/src/routes/precomputed/page.spec.ts new file mode 100644 index 00000000..8ec4660e --- /dev/null +++ b/tests/sveltekit-e2e/src/routes/precomputed/page.spec.ts @@ -0,0 +1,38 @@ +import { test, expect } from '@playwright/test'; +import { port } from '../../port'; + +test('displays the flag value (full page hit, new visitor)', async ({ page }) => { + await page.goto(`http://localhost:${port}/precomputed`); + await expect(page.getByText('visitorId|no cookie|visitorId')).toBeVisible(); +}); + +test('displays the flag value (full page hit, known visitor)', async ({ page }) => { + await page.context().addCookies([ + { + name: 'visitorId', + value: 'visitorId', + url: `http://localhost:${port}/` + } + ]); + await page.goto(`http://localhost:${port}/precomputed`); + await expect(page.getByText('visitorId|visitorId|no header')).toBeVisible(); +}); + +test('displays the flag value (client-side navigation, new visitor)', async ({ page }) => { + await page.goto(`http://localhost:${port}/`); + await page.click('a[href="/precomputed"]'); + await expect(page.getByText('visitorId|no cookie|visitorId')).toBeVisible(); +}); + +test('displays the flag value (client-side navigation, known visitor)', async ({ page }) => { + await page.context().addCookies([ + { + name: 'visitorId', + value: 'visitorId', + url: `http://localhost:${port}/` + } + ]); + await page.goto(`http://localhost:${port}/`); + await page.click('a[href="/precomputed"]'); + await expect(page.getByText('visitorId|visitorId|no header')).toBeVisible(); +}); diff --git a/tests/sveltekit-e2e/svelte.config.js b/tests/sveltekit-e2e/svelte.config.js index 4a82086e..06e291a2 100644 --- a/tests/sveltekit-e2e/svelte.config.js +++ b/tests/sveltekit-e2e/svelte.config.js @@ -1,4 +1,4 @@ -import adapter from '@sveltejs/adapter-auto'; +import adapter from '@sveltejs/adapter-vercel'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ @@ -8,9 +8,6 @@ const config = { preprocess: vitePreprocess(), kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. adapter: adapter() } };