From a17bdf061c2da8a927f727694f121fccfc50068c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Sep 2025 13:17:26 +0000 Subject: [PATCH] feat: Add @reflag/svelte-sdk package Co-authored-by: lasse --- packages/svelte-sdk/README.md | 293 ++++++++++++++++ packages/svelte-sdk/dev/plain/App.svelte | 182 ++++++++++ packages/svelte-sdk/dev/plain/index.html | 13 + packages/svelte-sdk/dev/plain/main.ts | 7 + packages/svelte-sdk/eslint.config.js | 26 ++ packages/svelte-sdk/package.json | 65 ++++ packages/svelte-sdk/src/index.d.ts | 6 + packages/svelte-sdk/src/index.js | 8 + packages/svelte-sdk/src/index.js.map | 1 + packages/svelte-sdk/src/index.ts | 39 +++ packages/svelte-sdk/src/provider.d.ts | 15 + packages/svelte-sdk/src/provider.js | 98 ++++++ packages/svelte-sdk/src/provider.js.map | 1 + packages/svelte-sdk/src/provider.ts | 121 +++++++ packages/svelte-sdk/src/stores.d.ts | 67 ++++ packages/svelte-sdk/src/stores.js | 147 ++++++++ packages/svelte-sdk/src/stores.js.map | 1 + packages/svelte-sdk/src/stores.ts | 177 ++++++++++ packages/svelte-sdk/src/types.d.ts | 90 +++++ packages/svelte-sdk/src/types.js | 2 + packages/svelte-sdk/src/types.js.map | 1 + packages/svelte-sdk/src/types.ts | 120 +++++++ packages/svelte-sdk/src/version.d.ts | 1 + packages/svelte-sdk/src/version.js | 3 + packages/svelte-sdk/src/version.js.map | 1 + packages/svelte-sdk/src/version.ts | 3 + packages/svelte-sdk/test/usage.test.ts | 108 ++++++ packages/svelte-sdk/tsconfig.build.json | 4 + packages/svelte-sdk/tsconfig.eslint.json | 4 + packages/svelte-sdk/tsconfig.json | 12 + packages/svelte-sdk/typedoc.json | 6 + packages/svelte-sdk/vite.config.mjs | 41 +++ yarn.lock | 417 ++++++++++++++++++++++- 33 files changed, 2076 insertions(+), 4 deletions(-) create mode 100644 packages/svelte-sdk/README.md create mode 100644 packages/svelte-sdk/dev/plain/App.svelte create mode 100644 packages/svelte-sdk/dev/plain/index.html create mode 100644 packages/svelte-sdk/dev/plain/main.ts create mode 100644 packages/svelte-sdk/eslint.config.js create mode 100644 packages/svelte-sdk/package.json create mode 100644 packages/svelte-sdk/src/index.d.ts create mode 100644 packages/svelte-sdk/src/index.js create mode 100644 packages/svelte-sdk/src/index.js.map create mode 100644 packages/svelte-sdk/src/index.ts create mode 100644 packages/svelte-sdk/src/provider.d.ts create mode 100644 packages/svelte-sdk/src/provider.js create mode 100644 packages/svelte-sdk/src/provider.js.map create mode 100644 packages/svelte-sdk/src/provider.ts create mode 100644 packages/svelte-sdk/src/stores.d.ts create mode 100644 packages/svelte-sdk/src/stores.js create mode 100644 packages/svelte-sdk/src/stores.js.map create mode 100644 packages/svelte-sdk/src/stores.ts create mode 100644 packages/svelte-sdk/src/types.d.ts create mode 100644 packages/svelte-sdk/src/types.js create mode 100644 packages/svelte-sdk/src/types.js.map create mode 100644 packages/svelte-sdk/src/types.ts create mode 100644 packages/svelte-sdk/src/version.d.ts create mode 100644 packages/svelte-sdk/src/version.js create mode 100644 packages/svelte-sdk/src/version.js.map create mode 100644 packages/svelte-sdk/src/version.ts create mode 100644 packages/svelte-sdk/test/usage.test.ts create mode 100644 packages/svelte-sdk/tsconfig.build.json create mode 100644 packages/svelte-sdk/tsconfig.eslint.json create mode 100644 packages/svelte-sdk/tsconfig.json create mode 100644 packages/svelte-sdk/typedoc.json create mode 100644 packages/svelte-sdk/vite.config.mjs diff --git a/packages/svelte-sdk/README.md b/packages/svelte-sdk/README.md new file mode 100644 index 00000000..41777d4a --- /dev/null +++ b/packages/svelte-sdk/README.md @@ -0,0 +1,293 @@ +# Reflag Svelte SDK + +The Reflag Svelte SDK provides a simple and intuitive way to integrate feature flags into your Svelte applications. Built on top of the Reflag browser SDK, it offers reactive stores and utilities that work seamlessly with Svelte's reactivity system. + +## Installation + +```bash +npm install @reflag/svelte-sdk +# or +yarn add @reflag/svelte-sdk +# or +pnpm add @reflag/svelte-sdk +``` + +## Quick Start + +### 1. Set up the Provider + +First, initialize the Reflag provider at the root of your application: + +```javascript +// main.ts or App.svelte +import { createReflagProvider } from '@reflag/svelte-sdk'; + +// Initialize the provider +createReflagProvider({ + apiKey: 'your-api-key', + user: { + id: 'user-123', + email: 'user@example.com', + name: 'John Doe' + }, + company: { + id: 'company-456', + name: 'Acme Inc' + } +}); +``` + +### 2. Use Feature Flags in Components + +```svelte + + +{#if $huddleFlag.isEnabled} + +{:else} +

Huddle feature is not available

+{/if} +``` + +## API Reference + +### Provider + +#### `createReflagProvider(props: ReflagProps)` + +Creates and initializes the Reflag provider. This should be called once at the root of your application. + +**Parameters:** +- `props.apiKey` (string): Your Reflag API key +- `props.user` (object, optional): User context information +- `props.company` (object, optional): Company context information +- `props.otherContext` (object, optional): Additional context information +- `props.debug` (boolean, optional): Enable debug logging + +### Stores and Utilities + +#### `useFlag(key: string)` + +Returns a reactive flag object with the following properties: + +- `isEnabled` (Readable): Whether the flag is enabled +- `isLoading` (Readable): Whether the flag is currently loading +- `config` (Readable): The flag's configuration data +- `track()` (function): Track usage of this flag +- `requestFeedback(options)` (function): Request feedback for this flag + +**Example:** +```svelte + + +{#if $myFlag.isLoading} +

Loading...

+{:else if $myFlag.isEnabled} +
Feature is enabled!
+ +{:else} +
Feature is disabled
+{/if} +``` + +#### `useTrack()` + +Returns a function to track custom events. + +**Example:** +```svelte + + + +``` + +#### `useClient()` + +Returns a readable store containing the current ReflagClient instance. + +**Example:** +```svelte + +``` + +#### `useIsLoading()` + +Returns a readable store indicating whether the SDK is currently loading. + +**Example:** +```svelte + + +{#if $isLoading} +
Loading flags...
+{:else} +
Flags loaded!
+{/if} +``` + +#### `useRequestFeedback()` + +Returns a function to request feedback from users. + +**Example:** +```svelte + + + +``` + +#### `useSendFeedback()` + +Returns a function to send feedback programmatically. + +#### `useUpdateUser()`, `useUpdateCompany()`, `useUpdateOtherContext()` + +Return functions to update context information, which will trigger flag re-evaluation. + +**Example:** +```svelte + + + +``` + +## TypeScript Support + +The SDK includes full TypeScript support. You can extend the `Flags` interface to get type-safe flag keys: + +```typescript +// types/reflag.d.ts +declare module '@reflag/svelte-sdk' { + interface Flags { + 'huddle': { + config: { + payload: { + buttonText: string; + maxParticipants: number; + }; + }; + }; + 'file-uploads': { + config: { + payload: { + maxFileSize: number; + allowedTypes: string[]; + }; + }; + }; + } +} +``` + +Now you'll get full type safety: + +```svelte + +``` + +## Error Handling + +The SDK will throw an error if you try to use any of the utilities without first calling `createReflagProvider()`: + +```javascript +// This will throw an error +const flag = useFlag('my-flag'); // Error: ReflagProvider is missing +``` + +Make sure to call `createReflagProvider()` before using any other SDK functions. + +## Development + +To run the development example: + +```bash +cd packages/svelte-sdk +yarn dev +``` + +This will start a development server with a demo application showing the SDK in action. + +## Testing + +```bash +yarn test +``` + +## Building + +```bash +yarn build +``` + +## License + +MIT \ No newline at end of file diff --git a/packages/svelte-sdk/dev/plain/App.svelte b/packages/svelte-sdk/dev/plain/App.svelte new file mode 100644 index 00000000..d1d7513c --- /dev/null +++ b/packages/svelte-sdk/dev/plain/App.svelte @@ -0,0 +1,182 @@ + + +
+

Reflag Svelte SDK Demo

+ + {#if !providerInitialized} +

Initializing Reflag provider...

+ {:else} +
+

SDK Status

+

Loading: {$isLoading ? 'Yes' : 'No'}

+

Client: {$client ? 'Connected' : 'Not connected'}

+
+ +
+

Feature Flags

+ +
+

Huddle Feature

+ {#if huddleFlag} +

Enabled: {$huddleFlag.isEnabled ? 'Yes' : 'No'}

+

Loading: {$huddleFlag.isLoading ? 'Yes' : 'No'}

+

Config: {JSON.stringify($huddleFlag.config)}

+ + {#if $huddleFlag.isEnabled} + + + {/if} + {:else} +

Loading flag...

+ {/if} +
+ +
+

File Uploads Feature

+ {#if fileUploadsFlag} +

Enabled: {$fileUploadsFlag.isEnabled ? 'Yes' : 'No'}

+

Loading: {$fileUploadsFlag.isLoading ? 'Yes' : 'No'}

+

Config: {JSON.stringify($fileUploadsFlag.config)}

+ + {#if $fileUploadsFlag.isEnabled} + + {/if} + {:else} +

Loading flag...

+ {/if} +
+
+ +
+

Actions

+ +
+ {/if} +
+ + \ No newline at end of file diff --git a/packages/svelte-sdk/dev/plain/index.html b/packages/svelte-sdk/dev/plain/index.html new file mode 100644 index 00000000..aa194d3b --- /dev/null +++ b/packages/svelte-sdk/dev/plain/index.html @@ -0,0 +1,13 @@ + + + + + + + Reflag Svelte SDK Demo + + +
+ + + \ No newline at end of file diff --git a/packages/svelte-sdk/dev/plain/main.ts b/packages/svelte-sdk/dev/plain/main.ts new file mode 100644 index 00000000..c5fb20ce --- /dev/null +++ b/packages/svelte-sdk/dev/plain/main.ts @@ -0,0 +1,7 @@ +import App from './App.svelte'; + +const app = new App({ + target: document.getElementById('app')!, +}); + +export default app; \ No newline at end of file diff --git a/packages/svelte-sdk/eslint.config.js b/packages/svelte-sdk/eslint.config.js new file mode 100644 index 00000000..325853d5 --- /dev/null +++ b/packages/svelte-sdk/eslint.config.js @@ -0,0 +1,26 @@ +import eslint from "@reflag/eslint-config/base.js"; +import sveltePlugin from "eslint-plugin-svelte"; + +export default [ + ...eslint, + ...sveltePlugin.configs["flat/recommended"], + { + languageOptions: { + parserOptions: { + parser: "@typescript-eslint/parser", + extraFileExtensions: [".svelte"], + svelteFeatures: { + experimentalGenerics: true, + }, + }, + }, + }, + { + files: ["**/*.svelte"], + languageOptions: { + parserOptions: { + parser: "@typescript-eslint/parser", + }, + }, + }, +]; \ No newline at end of file diff --git a/packages/svelte-sdk/package.json b/packages/svelte-sdk/package.json new file mode 100644 index 00000000..9f611694 --- /dev/null +++ b/packages/svelte-sdk/package.json @@ -0,0 +1,65 @@ +{ + "name": "@reflag/svelte-sdk", + "version": "1.1.0", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/reflagcom/javascript.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "dev": "vite", + "build": "tsc --project tsconfig.build.json && vite build", + "test": "vitest -c vite.config.mjs", + "test:ci": "vitest run -c vite.config.mjs --reporter=default --reporter=junit --outputFile=junit.xml", + "coverage": "vitest run --coverage", + "lint": "eslint .", + "lint:ci": "eslint --output-file eslint-report.json --format json .", + "prettier": "prettier --check .", + "format": "yarn lint --fix && yarn prettier --write", + "preversion": "yarn lint && yarn prettier && yarn vitest run -c vite.config.mjs && yarn build" + }, + "files": [ + "dist" + ], + "main": "./dist/reflag-svelte-sdk.umd.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/reflag-svelte-sdk.mjs", + "require": "./dist/reflag-svelte-sdk.umd.js", + "types": "./dist/index.d.ts" + } + }, + "dependencies": { + "@reflag/browser-sdk": "1.1.0", + "canonical-json": "^0.2.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0" + }, + "devDependencies": { + "@reflag/eslint-config": "^0.0.2", + "@reflag/tsconfig": "^0.0.2", + "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@types/jsdom": "^21.1.6", + "@types/node": "^22.12.0", + "eslint": "^9.21.0", + "eslint-plugin-svelte": "^2.46.0", + "jsdom": "^24.1.0", + "msw": "^2.3.5", + "prettier": "^3.5.2", + "prettier-plugin-svelte": "^3.2.8", + "rollup": "^4.2.0", + "rollup-preserve-directives": "^1.1.2", + "svelte": "^5.16.0", + "svelte-check": "^4.1.1", + "ts-node": "^10.9.2", + "typescript": "^5.7.3", + "vite": "^5.0.13", + "vite-plugin-dts": "^4.5.4", + "vitest": "^2.0.4" + } +} diff --git a/packages/svelte-sdk/src/index.d.ts b/packages/svelte-sdk/src/index.d.ts new file mode 100644 index 00000000..94e28573 --- /dev/null +++ b/packages/svelte-sdk/src/index.d.ts @@ -0,0 +1,6 @@ +export type { CheckEvent, CompanyContext, TrackEvent, UserContext, } from "@reflag/browser-sdk"; +export type { EmptyFlagRemoteConfig, Flag, FlagType, FlagRemoteConfig, TypedFlags, FlagKey, ReflagProps, RequestFlagFeedbackOptions, } from "./types"; +export { createReflagProvider, getReflagContext } from "./provider"; +export { useFlag, useFeature, // deprecated +useTrack, useRequestFeedback, useSendFeedback, useUpdateUser, useUpdateCompany, useUpdateOtherContext, useClient, useIsLoading, } from "./stores"; +export { SDK_VERSION } from "./version"; diff --git a/packages/svelte-sdk/src/index.js b/packages/svelte-sdk/src/index.js new file mode 100644 index 00000000..a788dd58 --- /dev/null +++ b/packages/svelte-sdk/src/index.js @@ -0,0 +1,8 @@ +// Export provider functionality +export { createReflagProvider, getReflagContext } from "./provider"; +// Export all stores and utilities +export { useFlag, useFeature, // deprecated +useTrack, useRequestFeedback, useSendFeedback, useUpdateUser, useUpdateCompany, useUpdateOtherContext, useClient, useIsLoading, } from "./stores"; +// Export version +export { SDK_VERSION } from "./version"; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/svelte-sdk/src/index.js.map b/packages/svelte-sdk/src/index.js.map new file mode 100644 index 00000000..189f52f8 --- /dev/null +++ b/packages/svelte-sdk/src/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAoBA,gCAAgC;AAChC,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEpE,kCAAkC;AAClC,OAAO,EACL,OAAO,EACP,UAAU,EAAE,aAAa;AACzB,QAAQ,EACR,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,SAAS,EACT,YAAY,GACb,MAAM,UAAU,CAAC;AAElB,iBAAiB;AACjB,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"} \ No newline at end of file diff --git a/packages/svelte-sdk/src/index.ts b/packages/svelte-sdk/src/index.ts new file mode 100644 index 00000000..8ac05c75 --- /dev/null +++ b/packages/svelte-sdk/src/index.ts @@ -0,0 +1,39 @@ +// Re-export types from browser-sdk +export type { + CheckEvent, + CompanyContext, + TrackEvent, + UserContext, +} from "@reflag/browser-sdk"; + +// Export our types +export type { + EmptyFlagRemoteConfig, + Flag, + FlagType, + FlagRemoteConfig, + TypedFlags, + FlagKey, + ReflagProps, + RequestFlagFeedbackOptions, +} from "./types"; + +// Export provider functionality +export { createReflagProvider, getReflagContext } from "./provider"; + +// Export all stores and utilities +export { + useFlag, + useFeature, // deprecated + useTrack, + useRequestFeedback, + useSendFeedback, + useUpdateUser, + useUpdateCompany, + useUpdateOtherContext, + useClient, + useIsLoading, +} from "./stores"; + +// Export version +export { SDK_VERSION } from "./version"; \ No newline at end of file diff --git a/packages/svelte-sdk/src/provider.d.ts b/packages/svelte-sdk/src/provider.d.ts new file mode 100644 index 00000000..1e29fb4c --- /dev/null +++ b/packages/svelte-sdk/src/provider.d.ts @@ -0,0 +1,15 @@ +import type { Readable } from "svelte/store"; +import type { ProviderContextType, ReflagProps } from "./types"; +/** + * Creates a Reflag provider context with the given configuration. + * This should be called at the root of your Svelte application. + * + * @param props - The configuration for the Reflag client + * @returns A readable store containing the provider context + */ +export declare function createReflagProvider(props: ReflagProps): Readable; +/** + * Gets the current Reflag provider context. + * Throws an error if no provider has been created. + */ +export declare function getReflagContext(): ProviderContextType; diff --git a/packages/svelte-sdk/src/provider.js b/packages/svelte-sdk/src/provider.js new file mode 100644 index 00000000..e050b6b5 --- /dev/null +++ b/packages/svelte-sdk/src/provider.js @@ -0,0 +1,98 @@ +import canonicalJSON from "canonical-json"; +import { writable, readable, get } from "svelte/store"; +import { ReflagClient } from "@reflag/browser-sdk"; +import { SDK_VERSION } from "./version"; +// Global provider context store +const providerContext = writable(null); +/** + * Creates a Reflag provider context with the given configuration. + * This should be called at the root of your Svelte application. + * + * @param props - The configuration for the Reflag client + * @returns A readable store containing the provider context + */ +export function createReflagProvider(props) { + const clientStore = writable(null); + const isLoadingStore = writable(true); + const rawFlagsStore = writable({}); + let currentClient = null; + let contextKey = null; + // Create the context + const context = { + client: readable(null, (set) => { + return clientStore.subscribe(set); + }), + isLoading: readable(true, (set) => { + return isLoadingStore.subscribe(set); + }), + provider: true, + }; + // Function to update the client when props change + const updateClient = (newProps) => { + const featureContext = { + user: newProps.user || null, + company: newProps.company || null, + otherContext: newProps.otherContext || null + }; + const configForKey = { ...newProps }; + delete configForKey.newReflagClient; // Remove function from serialization + const newContextKey = canonicalJSON({ config: configForKey, featureContext }); + // Only recreate client if context has changed + if (contextKey === newContextKey) { + return; + } + contextKey = newContextKey; + // Stop existing client + if (currentClient) { + void currentClient.stop(); + } + isLoadingStore.set(true); + // Create new client + const client = (newProps.newReflagClient ?? ((...args) => new ReflagClient(...args)))({ + ...newProps, + logger: newProps.debug ? console : undefined, + sdkVersion: SDK_VERSION, + }); + currentClient = client; + clientStore.set(client); + // Set up event listeners + client.on("flagsUpdated", (flags) => { + rawFlagsStore.set(flags); + }); + // Initialize client + client + .initialize() + .catch((e) => { + client.logger.error("failed to initialize client", e); + }) + .finally(() => { + isLoadingStore.set(false); + }); + }; + // Initialize with props + updateClient(props); + // Set the global context + providerContext.set(context); + // Return a store that allows updating the props + return readable(context, (set) => { + set(context); + return () => { + if (currentClient) { + void currentClient.stop(); + } + providerContext.set(null); + }; + }); +} +/** + * Gets the current Reflag provider context. + * Throws an error if no provider has been created. + */ +export function getReflagContext() { + const context = get(providerContext); + if (!context?.provider) { + throw new Error("ReflagProvider is missing. Please ensure createReflagProvider() has been called at the root of your application."); + } + return context; +} +//# sourceMappingURL=provider.js.map \ No newline at end of file diff --git a/packages/svelte-sdk/src/provider.js.map b/packages/svelte-sdk/src/provider.js.map new file mode 100644 index 00000000..401095db --- /dev/null +++ b/packages/svelte-sdk/src/provider.js.map @@ -0,0 +1 @@ +{"version":3,"file":"provider.js","sourceRoot":"","sources":["provider.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/svelte-sdk/src/provider.ts b/packages/svelte-sdk/src/provider.ts new file mode 100644 index 00000000..4f707aa5 --- /dev/null +++ b/packages/svelte-sdk/src/provider.ts @@ -0,0 +1,121 @@ +import canonicalJSON from "canonical-json"; +import { writable, readable, get } from "svelte/store"; +import type { Readable, Writable } from "svelte/store"; + +import { ReflagClient, type RawFlags } from "@reflag/browser-sdk"; + +import type { ProviderContextType, ReflagProps } from "./types"; +import { SDK_VERSION } from "./version"; + +// Global provider context store +const providerContext: Writable = writable(null); + +/** + * Creates a Reflag provider context with the given configuration. + * This should be called at the root of your Svelte application. + * + * @param props - The configuration for the Reflag client + * @returns A readable store containing the provider context + */ +export function createReflagProvider(props: ReflagProps): Readable { + const clientStore = writable(null); + const isLoadingStore = writable(true); + const rawFlagsStore = writable({}); + + let currentClient: ReflagClient | null = null; + let contextKey: string | null = null; + + // Create the context + const context: ProviderContextType = { + client: readable(null, (set) => { + return clientStore.subscribe(set); + }), + isLoading: readable(true, (set) => { + return isLoadingStore.subscribe(set); + }), + provider: true, + }; + + // Function to update the client when props change + const updateClient = (newProps: ReflagProps) => { + const featureContext = { + user: newProps.user || null, + company: newProps.company || null, + otherContext: newProps.otherContext || null + }; + const configForKey = { ...newProps }; + delete configForKey.newReflagClient; // Remove function from serialization + const newContextKey = canonicalJSON({ config: configForKey, featureContext }); + + // Only recreate client if context has changed + if (contextKey === newContextKey) { + return; + } + + contextKey = newContextKey; + + // Stop existing client + if (currentClient) { + void currentClient.stop(); + } + + isLoadingStore.set(true); + + // Create new client + const client = (newProps.newReflagClient ?? ((...args: ConstructorParameters) => new ReflagClient(...args)))({ + ...newProps, + logger: newProps.debug ? console : undefined, + sdkVersion: SDK_VERSION, + }); + + currentClient = client; + clientStore.set(client); + + // Set up event listeners + client.on("flagsUpdated", (flags: RawFlags) => { + rawFlagsStore.set(flags); + }); + + // Initialize client + client + .initialize() + .catch((e: any) => { + client.logger.error("failed to initialize client", e); + }) + .finally(() => { + isLoadingStore.set(false); + }); + }; + + // Initialize with props + updateClient(props); + + // Set the global context + providerContext.set(context); + + // Return a store that allows updating the props + return readable(context, (set) => { + set(context); + + return () => { + if (currentClient) { + void currentClient.stop(); + } + providerContext.set(null); + }; + }); +} + +/** + * Gets the current Reflag provider context. + * Throws an error if no provider has been created. + */ +export function getReflagContext(): ProviderContextType { + const context = get(providerContext); + if (!context?.provider) { + throw new Error( + "ReflagProvider is missing. Please ensure createReflagProvider() has been called at the root of your application." + ); + } + return context; +} \ No newline at end of file diff --git a/packages/svelte-sdk/src/stores.d.ts b/packages/svelte-sdk/src/stores.d.ts new file mode 100644 index 00000000..5ea2b2b1 --- /dev/null +++ b/packages/svelte-sdk/src/stores.d.ts @@ -0,0 +1,67 @@ +import type { RequestFeedbackData, UnassignedFeedback } from "@reflag/browser-sdk"; +import type { FlagKey, TypedFlags } from "./types"; +/** + * Creates a store for a specific feature flag. + * + * @param key - The key of the feature flag + * @returns A Flag object with reactive Svelte stores + */ +export declare function useFlag(key: TKey): TypedFlags[TKey]; +/** + * Creates a store for tracking custom events. + * + * @returns A function that tracks an event + */ +export declare function useTrack(): (eventName: string, attributes?: Record | null) => Promise | undefined; +/** + * Creates a store for requesting user feedback. + * + * @returns A function that requests feedback from the user + */ +export declare function useRequestFeedback(): (options: RequestFeedbackData) => void | undefined; +/** + * Creates a store for sending feedback. + * + * @returns A function that sends feedback to the Reflag SDK + */ +export declare function useSendFeedback(): (opts: UnassignedFeedback) => Promise | undefined; +/** + * Creates a store for updating the user context. + * + * @returns A function that updates the user context + */ +export declare function useUpdateUser(): (opts: { + [key: string]: string | number | undefined; +}) => Promise | undefined; +/** + * Creates a store for updating the company context. + * + * @returns A function that updates the company context + */ +export declare function useUpdateCompany(): (opts: { + [key: string]: string | number | undefined; +}) => Promise | undefined; +/** + * Creates a store for updating the other context. + * + * @returns A function that updates the other context + */ +export declare function useUpdateOtherContext(): (opts: { + [key: string]: string | number | undefined; +}) => Promise | undefined; +/** + * Gets the current Reflag client as a readable store. + * + * @returns A readable store containing the ReflagClient + */ +export declare function useClient(): import("svelte/store").Readable; +/** + * Gets the loading state as a readable store. + * + * @returns A readable store indicating whether the client is loading + */ +export declare function useIsLoading(): import("svelte/store").Readable; +/** + * @deprecated Use `useFlag` instead + */ +export declare function useFeature(key: TKey): import("./types").Flag; diff --git a/packages/svelte-sdk/src/stores.js b/packages/svelte-sdk/src/stores.js new file mode 100644 index 00000000..e7792713 --- /dev/null +++ b/packages/svelte-sdk/src/stores.js @@ -0,0 +1,147 @@ +import { writable, derived, get } from "svelte/store"; +import { getReflagContext } from "./provider"; +/** + * Creates a store for a specific feature flag. + * + * @param key - The key of the feature flag + * @returns A Flag object with reactive Svelte stores + */ +export function useFlag(key) { + const context = getReflagContext(); + // Create reactive stores for the flag properties + const flagStore = writable(null); + const isEnabledStore = derived([context.client, flagStore], ([$client]) => { + if (!$client) + return false; + const flag = $client.getFlag(key); + return flag.isEnabled ?? false; + }); + const configStore = derived([context.client, flagStore], ([$client]) => { + if (!$client) + return { key: undefined, payload: undefined }; + const flag = $client.getFlag(key); + return flag.config; + }); + const track = () => { + const client = get(context.client); + return client?.track(key); + }; + const requestFeedback = (opts) => { + const client = get(context.client); + return client?.requestFeedback({ ...opts, flagKey: key }); + }; + // Set up flag updates + const client = get(context.client); + if (client) { + const updateFlag = () => { + flagStore.set(client.getFlag(key)); + }; + client.on("flagsUpdated", updateFlag); + updateFlag(); // Initial update + } + return { + key, + isEnabled: isEnabledStore, + isLoading: context.isLoading, + config: configStore, + track, + requestFeedback, + }; +} +/** + * Creates a store for tracking custom events. + * + * @returns A function that tracks an event + */ +export function useTrack() { + const context = getReflagContext(); + return (eventName, attributes) => { + const client = get(context.client); + return client?.track(eventName, attributes); + }; +} +/** + * Creates a store for requesting user feedback. + * + * @returns A function that requests feedback from the user + */ +export function useRequestFeedback() { + const context = getReflagContext(); + return (options) => { + const client = get(context.client); + return client?.requestFeedback(options); + }; +} +/** + * Creates a store for sending feedback. + * + * @returns A function that sends feedback to the Reflag SDK + */ +export function useSendFeedback() { + const context = getReflagContext(); + return (opts) => { + const client = get(context.client); + return client?.feedback(opts); + }; +} +/** + * Creates a store for updating the user context. + * + * @returns A function that updates the user context + */ +export function useUpdateUser() { + const context = getReflagContext(); + return (opts) => { + const client = get(context.client); + return client?.updateUser(opts); + }; +} +/** + * Creates a store for updating the company context. + * + * @returns A function that updates the company context + */ +export function useUpdateCompany() { + const context = getReflagContext(); + return (opts) => { + const client = get(context.client); + return client?.updateCompany(opts); + }; +} +/** + * Creates a store for updating the other context. + * + * @returns A function that updates the other context + */ +export function useUpdateOtherContext() { + const context = getReflagContext(); + return (opts) => { + const client = get(context.client); + return client?.updateOtherContext(opts); + }; +} +/** + * Gets the current Reflag client as a readable store. + * + * @returns A readable store containing the ReflagClient + */ +export function useClient() { + const context = getReflagContext(); + return context.client; +} +/** + * Gets the loading state as a readable store. + * + * @returns A readable store indicating whether the client is loading + */ +export function useIsLoading() { + const context = getReflagContext(); + return context.isLoading; +} +/** + * @deprecated Use `useFlag` instead + */ +export function useFeature(key) { + return useFlag(key); +} +//# sourceMappingURL=stores.js.map \ No newline at end of file diff --git a/packages/svelte-sdk/src/stores.js.map b/packages/svelte-sdk/src/stores.js.map new file mode 100644 index 00000000..df6daa3c --- /dev/null +++ b/packages/svelte-sdk/src/stores.js.map @@ -0,0 +1 @@ +{"version":3,"file":"stores.js","sourceRoot":"","sources":["stores.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/svelte-sdk/src/stores.ts b/packages/svelte-sdk/src/stores.ts new file mode 100644 index 00000000..a8165132 --- /dev/null +++ b/packages/svelte-sdk/src/stores.ts @@ -0,0 +1,177 @@ +import { writable, derived, get } from "svelte/store"; + +import type { RequestFeedbackData, UnassignedFeedback } from "@reflag/browser-sdk"; + +import { getReflagContext } from "./provider"; +import type { FlagKey, TypedFlags, RequestFlagFeedbackOptions } from "./types"; + +/** + * Creates a store for a specific feature flag. + * + * @param key - The key of the feature flag + * @returns A Flag object with reactive Svelte stores + */ +export function useFlag(key: TKey): TypedFlags[TKey] { + const context = getReflagContext(); + + // Create reactive stores for the flag properties + const flagStore = writable(null); + const isEnabledStore = derived( + [context.client, flagStore], + ([$client]) => { + if (!$client) return false; + const flag = $client.getFlag(key); + return flag.isEnabled ?? false; + } + ); + + const configStore = derived( + [context.client, flagStore], + ([$client]) => { + if (!$client) return { key: undefined, payload: undefined }; + const flag = $client.getFlag(key); + return flag.config; + } + ); + + const track = () => { + const client = get(context.client); + return client?.track(key); + }; + + const requestFeedback = (opts: RequestFlagFeedbackOptions) => { + const client = get(context.client); + return client?.requestFeedback({ ...opts, flagKey: key }); + }; + + // Set up flag updates + const client = get(context.client); + if (client) { + const updateFlag = () => { + flagStore.set(client.getFlag(key) as any); + }; + + client.on("flagsUpdated", updateFlag); + updateFlag(); // Initial update + } + + return { + key, + isEnabled: isEnabledStore, + isLoading: context.isLoading, + config: configStore, + track, + requestFeedback, + } as TypedFlags[TKey]; +} + +/** + * Creates a store for tracking custom events. + * + * @returns A function that tracks an event + */ +export function useTrack() { + const context = getReflagContext(); + + return (eventName: string, attributes?: Record | null) => { + const client = get(context.client); + return client?.track(eventName, attributes); + }; +} + +/** + * Creates a store for requesting user feedback. + * + * @returns A function that requests feedback from the user + */ +export function useRequestFeedback() { + const context = getReflagContext(); + + return (options: RequestFeedbackData) => { + const client = get(context.client); + return client?.requestFeedback(options); + }; +} + +/** + * Creates a store for sending feedback. + * + * @returns A function that sends feedback to the Reflag SDK + */ +export function useSendFeedback() { + const context = getReflagContext(); + + return (opts: UnassignedFeedback) => { + const client = get(context.client); + return client?.feedback(opts); + }; +} + +/** + * Creates a store for updating the user context. + * + * @returns A function that updates the user context + */ +export function useUpdateUser() { + const context = getReflagContext(); + + return (opts: { [key: string]: string | number | undefined }) => { + const client = get(context.client); + return client?.updateUser(opts); + }; +} + +/** + * Creates a store for updating the company context. + * + * @returns A function that updates the company context + */ +export function useUpdateCompany() { + const context = getReflagContext(); + + return (opts: { [key: string]: string | number | undefined }) => { + const client = get(context.client); + return client?.updateCompany(opts); + }; +} + +/** + * Creates a store for updating the other context. + * + * @returns A function that updates the other context + */ +export function useUpdateOtherContext() { + const context = getReflagContext(); + + return (opts: { [key: string]: string | number | undefined }) => { + const client = get(context.client); + return client?.updateOtherContext(opts); + }; +} + +/** + * Gets the current Reflag client as a readable store. + * + * @returns A readable store containing the ReflagClient + */ +export function useClient() { + const context = getReflagContext(); + return context.client; +} + +/** + * Gets the loading state as a readable store. + * + * @returns A readable store indicating whether the client is loading + */ +export function useIsLoading() { + const context = getReflagContext(); + return context.isLoading; +} + +/** + * @deprecated Use `useFlag` instead + */ +export function useFeature(key: TKey) { + return useFlag(key); +} \ No newline at end of file diff --git a/packages/svelte-sdk/src/types.d.ts b/packages/svelte-sdk/src/types.d.ts new file mode 100644 index 00000000..7c568dcf --- /dev/null +++ b/packages/svelte-sdk/src/types.d.ts @@ -0,0 +1,90 @@ +import type { Readable } from "svelte/store"; +import type { InitOptions, ReflagClient, ReflagContext, RequestFeedbackData } from "@reflag/browser-sdk"; +export type EmptyFlagRemoteConfig = { + key: undefined; + payload: undefined; +}; +export type FlagType = { + config?: { + payload: any; + }; +}; +/** + * A remotely managed configuration value for a feature. + */ +export type FlagRemoteConfig = { + /** + * The key of the matched configuration value. + */ + key: string; + /** + * The optional user-supplied payload data. + */ + payload: any; +} | EmptyFlagRemoteConfig; +/** + * Describes a feature flag in Svelte + */ +export interface Flag { + /** + * The key of the feature. + */ + key: string; + /** + * If the feature is enabled (as a Svelte store). + */ + isEnabled: Readable; + /** + * If the feature is loading (as a Svelte store). + */ + isLoading: Readable; + /** + * Optional user-defined configuration (as a Svelte store). + */ + config: Readable; + /** + * Track feature usage in Reflag. + */ + track(): Promise | undefined; + /** + * Request feedback from the user. + */ + requestFeedback: (opts: RequestFlagFeedbackOptions) => void; +} +export interface Flags { +} +/** + * Describes a collection of evaluated features. + * + * @remarks + * This type falls back to a generic Record if the Flags interface + * has not been extended. + */ +export type TypedFlags = keyof Flags extends never ? Record : { + [TypedFlagKey in keyof Flags]: Flag; +}; +export type FlagKey = keyof TypedFlags; +/** + * Context type for the Reflag provider store + */ +export interface ProviderContextType { + client: Readable; + isLoading: Readable; + provider: boolean; +} +/** + * Props for the Reflag provider + */ +export type ReflagProps = ReflagContext & InitOptions & { + /** + * Whether to enable debug mode (optional). + */ + debug?: boolean; + /** + * New ReflagClient constructor. + * + * @internal + */ + newReflagClient?: (...args: ConstructorParameters) => ReflagClient; +}; +export type RequestFlagFeedbackOptions = Omit; diff --git a/packages/svelte-sdk/src/types.js b/packages/svelte-sdk/src/types.js new file mode 100644 index 00000000..718fd38a --- /dev/null +++ b/packages/svelte-sdk/src/types.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/packages/svelte-sdk/src/types.js.map b/packages/svelte-sdk/src/types.js.map new file mode 100644 index 00000000..8da0887a --- /dev/null +++ b/packages/svelte-sdk/src/types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/svelte-sdk/src/types.ts b/packages/svelte-sdk/src/types.ts new file mode 100644 index 00000000..b3585ee7 --- /dev/null +++ b/packages/svelte-sdk/src/types.ts @@ -0,0 +1,120 @@ +import type { Readable } from "svelte/store"; + +import type { + InitOptions, + ReflagClient, + ReflagContext, + RequestFeedbackData, +} from "@reflag/browser-sdk"; + +export type EmptyFlagRemoteConfig = { key: undefined; payload: undefined }; + +export type FlagType = { + config?: { + payload: any; + }; +}; + +/** + * A remotely managed configuration value for a feature. + */ +export type FlagRemoteConfig = + | { + /** + * The key of the matched configuration value. + */ + key: string; + + /** + * The optional user-supplied payload data. + */ + payload: any; + } + | EmptyFlagRemoteConfig; + +/** + * Describes a feature flag in Svelte + */ +export interface Flag { + /** + * The key of the feature. + */ + key: string; + + /** + * If the feature is enabled (as a Svelte store). + */ + isEnabled: Readable; + + /** + * If the feature is loading (as a Svelte store). + */ + isLoading: Readable; + + /** + * Optional user-defined configuration (as a Svelte store). + */ + config: Readable; + + /** + * Track feature usage in Reflag. + */ + track(): Promise | undefined; + + /** + * Request feedback from the user. + */ + requestFeedback: (opts: RequestFlagFeedbackOptions) => void; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface Flags {} + +/** + * Describes a collection of evaluated features. + * + * @remarks + * This type falls back to a generic Record if the Flags interface + * has not been extended. + */ +export type TypedFlags = keyof Flags extends never + ? Record + : { + [TypedFlagKey in keyof Flags]: Flag; + }; + +export type FlagKey = keyof TypedFlags; + +/** + * Context type for the Reflag provider store + */ +export interface ProviderContextType { + client: Readable; + isLoading: Readable; + provider: boolean; +} + +/** + * Props for the Reflag provider + */ +export type ReflagProps = ReflagContext & + InitOptions & { + /** + * Whether to enable debug mode (optional). + */ + debug?: boolean; + + /** + * New ReflagClient constructor. + * + * @internal + */ + newReflagClient?: ( + ...args: ConstructorParameters + ) => ReflagClient; + }; + +export type RequestFlagFeedbackOptions = Omit< + RequestFeedbackData, + "flagKey" | "featureId" +>; \ No newline at end of file diff --git a/packages/svelte-sdk/src/version.d.ts b/packages/svelte-sdk/src/version.d.ts new file mode 100644 index 00000000..70393501 --- /dev/null +++ b/packages/svelte-sdk/src/version.d.ts @@ -0,0 +1 @@ +export declare const SDK_VERSION: string; diff --git a/packages/svelte-sdk/src/version.js b/packages/svelte-sdk/src/version.js new file mode 100644 index 00000000..14821695 --- /dev/null +++ b/packages/svelte-sdk/src/version.js @@ -0,0 +1,3 @@ +import { version } from "../package.json"; +export const SDK_VERSION = `svelte-sdk/${version}`; +//# sourceMappingURL=version.js.map \ No newline at end of file diff --git a/packages/svelte-sdk/src/version.js.map b/packages/svelte-sdk/src/version.js.map new file mode 100644 index 00000000..84ead6d0 --- /dev/null +++ b/packages/svelte-sdk/src/version.js.map @@ -0,0 +1 @@ +{"version":3,"file":"version.js","sourceRoot":"","sources":["version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,MAAM,CAAC,MAAM,WAAW,GAAG,cAAc,OAAO,EAAE,CAAC"} \ No newline at end of file diff --git a/packages/svelte-sdk/src/version.ts b/packages/svelte-sdk/src/version.ts new file mode 100644 index 00000000..8eb1f6a1 --- /dev/null +++ b/packages/svelte-sdk/src/version.ts @@ -0,0 +1,3 @@ +import { version } from "../package.json"; + +export const SDK_VERSION = `svelte-sdk/${version}`; \ No newline at end of file diff --git a/packages/svelte-sdk/test/usage.test.ts b/packages/svelte-sdk/test/usage.test.ts new file mode 100644 index 00000000..3c5af316 --- /dev/null +++ b/packages/svelte-sdk/test/usage.test.ts @@ -0,0 +1,108 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { get } from "svelte/store"; + +import { ReflagClient } from "@reflag/browser-sdk"; + +import { createReflagProvider, useFlag, useTrack, useClient } from "../src"; + +// Mock the ReflagClient +vi.mock("@reflag/browser-sdk", () => ({ + ReflagClient: vi.fn().mockImplementation(() => ({ + initialize: vi.fn().mockResolvedValue(undefined), + stop: vi.fn().mockResolvedValue(undefined), + getFlag: vi.fn().mockReturnValue({ + isEnabled: true, + config: { key: "test-config", payload: { message: "Hello World" } }, + }), + track: vi.fn().mockResolvedValue(undefined), + on: vi.fn(), + off: vi.fn(), + logger: { error: vi.fn() }, + })), +})); + +describe("Svelte SDK", () => { + let mockClient: any; + + beforeEach(() => { + vi.clearAllMocks(); + mockClient = new ReflagClient({}); + }); + + describe("createReflagProvider", () => { + it("should create a provider context", async () => { + const provider = createReflagProvider({ + apiKey: "test-api-key", + user: { id: "test-user" }, + newReflagClient: () => mockClient, + }); + + const context = get(provider); + expect(context.provider).toBe(true); + expect(mockClient.initialize).toHaveBeenCalled(); + }); + }); + + describe("useFlag", () => { + it("should return flag state", () => { + // Set up provider first + createReflagProvider({ + apiKey: "test-api-key", + user: { id: "test-user" }, + newReflagClient: () => mockClient, + }); + + const flag = useFlag("test-flag"); + + expect(flag.key).toBe("test-flag"); + expect(flag.isEnabled).toBeDefined(); + expect(flag.isLoading).toBeDefined(); + expect(flag.config).toBeDefined(); + expect(typeof flag.track).toBe("function"); + expect(typeof flag.requestFeedback).toBe("function"); + }); + }); + + describe("useTrack", () => { + it("should return a track function", () => { + // Set up provider first + createReflagProvider({ + apiKey: "test-api-key", + user: { id: "test-user" }, + newReflagClient: () => mockClient, + }); + + const track = useTrack(); + expect(typeof track).toBe("function"); + + track("test-event", { key: "value" }); + expect(mockClient.track).toHaveBeenCalledWith("test-event", { key: "value" }); + }); + }); + + describe("useClient", () => { + it("should return the client store", () => { + // Set up provider first + createReflagProvider({ + apiKey: "test-api-key", + user: { id: "test-user" }, + newReflagClient: () => mockClient, + }); + + const clientStore = useClient(); + expect(clientStore).toBeDefined(); + + const client = get(clientStore); + expect(client).toBe(mockClient); + }); + }); + + describe("error handling", () => { + it("should throw error when provider is not set up", () => { + // Clear any existing provider by creating a new one and not using it + expect(() => { + useFlag("test-flag"); + }).toThrow("ReflagProvider is missing"); + }); + }); +}); \ No newline at end of file diff --git a/packages/svelte-sdk/tsconfig.build.json b/packages/svelte-sdk/tsconfig.build.json new file mode 100644 index 00000000..e9041fd6 --- /dev/null +++ b/packages/svelte-sdk/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"] +} \ No newline at end of file diff --git a/packages/svelte-sdk/tsconfig.eslint.json b/packages/svelte-sdk/tsconfig.eslint.json new file mode 100644 index 00000000..14f89161 --- /dev/null +++ b/packages/svelte-sdk/tsconfig.eslint.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src", "test", "dev", "*.js", "*.mjs"] +} \ No newline at end of file diff --git a/packages/svelte-sdk/tsconfig.json b/packages/svelte-sdk/tsconfig.json new file mode 100644 index 00000000..3fd364b8 --- /dev/null +++ b/packages/svelte-sdk/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@reflag/tsconfig/library.json", + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "node", + "lib": ["ES2020", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.js"], + "exclude": ["node_modules/**", "dist/**"] +} \ No newline at end of file diff --git a/packages/svelte-sdk/typedoc.json b/packages/svelte-sdk/typedoc.json new file mode 100644 index 00000000..558b7aec --- /dev/null +++ b/packages/svelte-sdk/typedoc.json @@ -0,0 +1,6 @@ +{ + "extends": "../../typedoc.json", + "entryPoints": ["src/index.ts"], + "out": "docs", + "name": "Reflag Svelte SDK" +} \ No newline at end of file diff --git a/packages/svelte-sdk/vite.config.mjs b/packages/svelte-sdk/vite.config.mjs new file mode 100644 index 00000000..c0c74c56 --- /dev/null +++ b/packages/svelte-sdk/vite.config.mjs @@ -0,0 +1,41 @@ +import { resolve } from "path"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import preserveDirectives from "rollup-preserve-directives"; +import { defineConfig } from "vite"; +import dts from "vite-plugin-dts"; + +export default defineConfig({ + test: { + environment: "jsdom", + }, + optimizeDeps: { + include: ["@reflag/browser-sdk"], + }, + plugins: [ + svelte(), + dts({ insertTypesEntry: true, exclude: ["dev"] }), + preserveDirectives(), + ], + build: { + exclude: ["**/node_modules/**", "test/e2e/**", "dev"], + sourcemap: true, + lib: { + entry: resolve(__dirname, "src/index.ts"), + name: "ReflagSvelteSDK", + fileName: "reflag-svelte-sdk", + formats: ["es", "umd"], + }, + rollupOptions: { + external: ["svelte", "svelte/store"], + output: { + globals: { + svelte: "Svelte", + "svelte/store": "SvelteStore", + }, + }, + }, + }, + server: { + open: "/dev/plain/index.html", + }, +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 015a0514..2cf315b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1943,6 +1943,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/remapping@npm:^2.3.4": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + "@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": version: 3.1.1 resolution: "@jridgewell/resolve-uri@npm:3.1.1" @@ -1988,6 +1998,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -3158,6 +3175,37 @@ __metadata: languageName: unknown linkType: soft +"@reflag/svelte-sdk@workspace:packages/svelte-sdk": + version: 0.0.0-use.local + resolution: "@reflag/svelte-sdk@workspace:packages/svelte-sdk" + dependencies: + "@reflag/browser-sdk": "npm:1.1.0" + "@reflag/eslint-config": "npm:^0.0.2" + "@reflag/tsconfig": "npm:^0.0.2" + "@sveltejs/vite-plugin-svelte": "npm:^4.0.0" + "@types/jsdom": "npm:^21.1.6" + "@types/node": "npm:^22.12.0" + canonical-json: "npm:^0.2.0" + eslint: "npm:^9.21.0" + eslint-plugin-svelte: "npm:^2.46.0" + jsdom: "npm:^24.1.0" + msw: "npm:^2.3.5" + prettier: "npm:^3.5.2" + prettier-plugin-svelte: "npm:^3.2.8" + rollup: "npm:^4.2.0" + rollup-preserve-directives: "npm:^1.1.2" + svelte: "npm:^5.16.0" + svelte-check: "npm:^4.1.1" + ts-node: "npm:^10.9.2" + typescript: "npm:^5.7.3" + vite: "npm:^5.0.13" + vite-plugin-dts: "npm:^4.5.4" + vitest: "npm:^2.0.4" + peerDependencies: + svelte: ^4.0.0 || ^5.0.0 + languageName: unknown + linkType: soft + "@reflag/tsconfig@npm:0.0.2, @reflag/tsconfig@npm:^0.0.2, @reflag/tsconfig@npm:~0.0.2, @reflag/tsconfig@workspace:packages/tsconfig": version: 0.0.0-use.local resolution: "@reflag/tsconfig@workspace:packages/tsconfig" @@ -3917,6 +3965,45 @@ __metadata: languageName: node linkType: hard +"@sveltejs/acorn-typescript@npm:^1.0.5": + version: 1.0.5 + resolution: "@sveltejs/acorn-typescript@npm:1.0.5" + peerDependencies: + acorn: ^8.9.0 + checksum: 10c0/5f5393ca3afc3d532baa3d418b51972ad26966ff352e13a46c6baa6d7099655acf2668be1d693e9daba2d3994a40b4c9a6b3157340e9cdfe2ffb52e4334630fd + languageName: node + linkType: hard + +"@sveltejs/vite-plugin-svelte-inspector@npm:^3.0.0-next.0||^3.0.0": + version: 3.0.1 + resolution: "@sveltejs/vite-plugin-svelte-inspector@npm:3.0.1" + dependencies: + debug: "npm:^4.3.7" + 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 + checksum: 10c0/0b70d6c78c198ffdc49a3edd0aa4649f7c56a0affbc67cd85db03aeb9d9ee709d0f61ff98accab3c308cae768ee8c9fd7666c8cd826ac467b3595fbe45381952 + languageName: node + linkType: hard + +"@sveltejs/vite-plugin-svelte@npm:^4.0.0": + version: 4.0.4 + resolution: "@sveltejs/vite-plugin-svelte@npm:4.0.4" + dependencies: + "@sveltejs/vite-plugin-svelte-inspector": "npm:^3.0.0-next.0||^3.0.0" + debug: "npm:^4.3.7" + deepmerge: "npm:^4.3.1" + kleur: "npm:^4.1.5" + magic-string: "npm:^0.30.12" + vitefu: "npm:^1.0.3" + peerDependencies: + svelte: ^5.0.0-next.96 || ^5.0.0 + vite: ^5.0.0 + checksum: 10c0/aa1f1d1db490f0c517511cc17e6628b592f35a18663077464936e74c3b09426a970a3330a2dc620446fca851a6d87f45f41c9476f6634b8019717d455759fdec + languageName: node + linkType: hard + "@swc/counter@npm:^0.1.3": version: 0.1.3 resolution: "@swc/counter@npm:0.1.3" @@ -5496,6 +5583,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.12.1": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" + bin: + acorn: bin/acorn + checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec + languageName: node + linkType: hard + "acorn@npm:^8.14.0": version: 8.14.0 resolution: "acorn@npm:8.14.0" @@ -5801,6 +5897,13 @@ __metadata: languageName: node linkType: hard +"aria-query@npm:^5.3.1": + version: 5.3.2 + resolution: "aria-query@npm:5.3.2" + checksum: 10c0/003c7e3e2cff5540bf7a7893775fc614de82b0c5dde8ae823d47b7a28a9d4da1f7ed85f340bdb93d5649caa927755f0e31ecc7ab63edfdfc00c8ef07e505e03e + languageName: node + linkType: hard + "aria-query@npm:~5.1.3": version: 5.1.3 resolution: "aria-query@npm:5.1.3" @@ -6169,6 +6272,13 @@ __metadata: languageName: node linkType: hard +"axobject-query@npm:^4.1.0": + version: 4.1.0 + resolution: "axobject-query@npm:4.1.0" + checksum: 10c0/c470e4f95008f232eadd755b018cb55f16c03ccf39c027b941cd8820ac6b68707ce5d7368a46756db4256fbc91bb4ead368f84f7fb034b2b7932f082f6dc0775 + languageName: node + linkType: hard + "axobject-query@npm:~3.1.1": version: 3.1.1 resolution: "axobject-query@npm:3.1.1" @@ -6741,6 +6851,15 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^4.0.1": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad + languageName: node + linkType: hard + "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -6862,6 +6981,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + "cmd-shim@npm:6.0.1": version: 6.0.1 resolution: "cmd-shim@npm:6.0.1" @@ -7568,6 +7694,13 @@ __metadata: languageName: node linkType: hard +"deepmerge@npm:^4.3.1": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 + languageName: node + linkType: hard + "default-browser-id@npm:^5.0.0": version: 5.0.0 resolution: "default-browser-id@npm:5.0.0" @@ -8547,6 +8680,17 @@ __metadata: languageName: node linkType: hard +"eslint-compat-utils@npm:^0.5.1": + version: 0.5.1 + resolution: "eslint-compat-utils@npm:0.5.1" + dependencies: + semver: "npm:^7.5.4" + peerDependencies: + eslint: ">=6.0.0" + checksum: 10c0/325e815205fab70ebcd379f6d4b5d44c7d791bb8dfe0c9888233f30ebabd9418422595b53a781b946c768d9244d858540e5e6129a6b3dd6d606f467d599edc6c + languageName: node + linkType: hard + "eslint-config-next@npm:14.2.5": version: 14.2.5 resolution: "eslint-config-next@npm:14.2.5" @@ -8895,6 +9039,31 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-svelte@npm:^2.46.0": + version: 2.46.1 + resolution: "eslint-plugin-svelte@npm:2.46.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.4.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.15" + eslint-compat-utils: "npm:^0.5.1" + esutils: "npm:^2.0.3" + known-css-properties: "npm:^0.35.0" + postcss: "npm:^8.4.38" + postcss-load-config: "npm:^3.1.4" + postcss-safe-parser: "npm:^6.0.0" + postcss-selector-parser: "npm:^6.1.0" + semver: "npm:^7.6.2" + svelte-eslint-parser: "npm:^0.43.0" + peerDependencies: + eslint: ^7.0.0 || ^8.0.0-0 || ^9.0.0-0 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + checksum: 10c0/48fd6c792615c534b505e5ef4616034da71af3892e5895de0b3802cdef03819e43716f36442137233a734726385a66ed8a01c08ff4c0496eaab1bb8bfbbc07c0 + languageName: node + linkType: hard + "eslint-plugin-unused-imports@npm:^4.1.4": version: 4.1.4 resolution: "eslint-plugin-unused-imports@npm:4.1.4" @@ -9074,6 +9243,13 @@ __metadata: languageName: node linkType: hard +"esm-env@npm:^1.2.1": + version: 1.2.2 + resolution: "esm-env@npm:1.2.2" + checksum: 10c0/3d25c973f2fd69c25ffff29c964399cea573fe10795ecc1d26f6f957ce0483d3254e1cceddb34bf3296a0d7b0f1d53a28992f064ba509dfe6366751e752c4166 + languageName: node + linkType: hard + "espree@npm:^10.0.1, espree@npm:^10.3.0": version: 10.3.0 resolution: "espree@npm:10.3.0" @@ -9124,6 +9300,15 @@ __metadata: languageName: node linkType: hard +"esrap@npm:^2.1.0": + version: 2.1.0 + resolution: "esrap@npm:2.1.0" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.4.15" + checksum: 10c0/42f9f8b49972989a58082dda58c3862689c9c45f3245fd9bfa7e84a00de9cdc422d73621fad1c5d4872c12875869bd770cda80be20ffb1244e6c27b192a3f7b0 + languageName: node + linkType: hard + "esrecurse@npm:^4.3.0": version: 4.3.0 resolution: "esrecurse@npm:4.3.0" @@ -9163,7 +9348,7 @@ __metadata: languageName: node linkType: hard -"esutils@npm:^2.0.2": +"esutils@npm:^2.0.2, esutils@npm:^2.0.3": version: 2.0.3 resolution: "esutils@npm:2.0.3" checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 @@ -9379,6 +9564,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.2.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + "fdir@npm:^6.4.3": version: 6.4.3 resolution: "fdir@npm:6.4.3" @@ -11290,6 +11487,15 @@ __metadata: languageName: node linkType: hard +"is-reference@npm:^3.0.3": + version: 3.0.3 + resolution: "is-reference@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.6" + checksum: 10c0/35edd284cfb4cd9e9f08973f20e276ec517eaca31f5f049598e97dbb2d05544973dde212dac30fddee5b420930bff365e2e67dcd1293d0866c6720377382e3e5 + languageName: node + linkType: hard + "is-regex@npm:^1.1.4": version: 1.1.4 resolution: "is-regex@npm:1.1.4" @@ -12010,6 +12216,20 @@ __metadata: languageName: node linkType: hard +"kleur@npm:^4.1.5": + version: 4.1.5 + resolution: "kleur@npm:4.1.5" + checksum: 10c0/e9de6cb49657b6fa70ba2d1448fd3d691a5c4370d8f7bbf1c2f64c24d461270f2117e1b0afe8cb3114f13bbd8e51de158c2a224953960331904e636a5e4c0f2a + languageName: node + linkType: hard + +"known-css-properties@npm:^0.35.0": + version: 0.35.0 + resolution: "known-css-properties@npm:0.35.0" + checksum: 10c0/04a4a2859d62670bb25b5b28091a1f03f6f0d3298a5ed3e7476397c5287b98c434f6dd9c004a0c67a53b7f21acc93f83c972e98c122f568d4d0bd21fd2b90fb6 + languageName: node + linkType: hard + "kolorist@npm:^1.8.0": version: 1.8.0 resolution: "kolorist@npm:1.8.0" @@ -12153,7 +12373,7 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:^2.1.0": +"lilconfig@npm:^2.0.5, lilconfig@npm:^2.1.0": version: 2.1.0 resolution: "lilconfig@npm:2.1.0" checksum: 10c0/64645641aa8d274c99338e130554abd6a0190533c0d9eb2ce7ebfaf2e05c7d9961f3ffe2bfa39efd3b60c521ba3dd24fa236fe2775fc38501bf82bf49d4678b8 @@ -12242,6 +12462,13 @@ __metadata: languageName: node linkType: hard +"locate-character@npm:^3.0.0": + version: 3.0.0 + resolution: "locate-character@npm:3.0.0" + checksum: 10c0/9da917622395002eb1336fca8cbef1c19904e3dc0b3b8078abe8ff390106d947a86feccecd0346f0e0e19fa017623fb4ccb65263d72a76dfa36e20cc18766b6c + languageName: node + linkType: hard + "locate-path@npm:^2.0.0": version: 2.0.0 resolution: "locate-path@npm:2.0.0" @@ -12434,6 +12661,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.11": + version: 0.30.19 + resolution: "magic-string@npm:0.30.19" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/db23fd2e2ee98a1aeb88a4cdb2353137fcf05819b883c856dd79e4c7dfb25151e2a5a4d5dbd88add5e30ed8ae5c51bcf4accbc6becb75249d924ec7b4fbcae27 + languageName: node + linkType: hard + "magic-string@npm:^0.30.12, magic-string@npm:^0.30.17": version: 0.30.17 resolution: "magic-string@npm:0.30.17" @@ -12985,6 +13221,13 @@ __metadata: languageName: node linkType: hard +"mri@npm:^1.1.0": + version: 1.2.0 + resolution: "mri@npm:1.2.0" + checksum: 10c0/a3d32379c2554cf7351db6237ddc18dc9e54e4214953f3da105b97dc3babe0deb3ffe99cf409b38ea47cc29f9430561ba6b53b24ab8f9ce97a4b50409e4a50e7 + languageName: node + linkType: hard + "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -13122,6 +13365,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + "nanoid@npm:^3.3.6, nanoid@npm:^3.3.7": version: 3.3.8 resolution: "nanoid@npm:3.3.8" @@ -14876,6 +15128,24 @@ __metadata: languageName: node linkType: hard +"postcss-load-config@npm:^3.1.4": + version: 3.1.4 + resolution: "postcss-load-config@npm:3.1.4" + dependencies: + lilconfig: "npm:^2.0.5" + yaml: "npm:^1.10.2" + peerDependencies: + postcss: ">=8.0.9" + ts-node: ">=9.0.0" + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + checksum: 10c0/7d2cc6695c2fc063e4538316d651a687fdb55e48db453ff699de916a6ee55ab68eac2b120c28a6b8ca7aa746a588888351b810a215b5cd090eabea62c5762ede + languageName: node + linkType: hard + "postcss-load-config@npm:^4.0.1": version: 4.0.2 resolution: "postcss-load-config@npm:4.0.2" @@ -15070,6 +15340,24 @@ __metadata: languageName: node linkType: hard +"postcss-safe-parser@npm:^6.0.0": + version: 6.0.0 + resolution: "postcss-safe-parser@npm:6.0.0" + peerDependencies: + postcss: ^8.3.3 + checksum: 10c0/5b0997b63de6ab4afb4b718a52dd7902e465c21d1f2e516762bcb59047787459b4dc5713132f6a19c9c8c483043b20b8a380a55fb61152ee66cbffcddf3b57f0 + languageName: node + linkType: hard + +"postcss-scss@npm:^4.0.9": + version: 4.0.9 + resolution: "postcss-scss@npm:4.0.9" + peerDependencies: + postcss: ^8.4.29 + checksum: 10c0/f917ecfd4b9113a6648e966a41f027ff7e14238393914978d44596e227a50f084667dc8818742348dc7d8b20130b30d4259aca1d4db86754a9c141202ae03714 + languageName: node + linkType: hard + "postcss-selector-not@npm:^7.0.1": version: 7.0.1 resolution: "postcss-selector-not@npm:7.0.1" @@ -15101,7 +15389,7 @@ __metadata: languageName: node linkType: hard -"postcss-selector-parser@npm:^6.0.15": +"postcss-selector-parser@npm:^6.0.15, postcss-selector-parser@npm:^6.1.0": version: 6.1.2 resolution: "postcss-selector-parser@npm:6.1.2" dependencies: @@ -15151,6 +15439,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.38, postcss@npm:^8.4.39": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 + languageName: node + linkType: hard + "postcss@npm:^8.4.43": version: 8.4.47 resolution: "postcss@npm:8.4.47" @@ -15187,6 +15486,16 @@ __metadata: languageName: node linkType: hard +"prettier-plugin-svelte@npm:^3.2.8": + version: 3.4.0 + resolution: "prettier-plugin-svelte@npm:3.4.0" + peerDependencies: + prettier: ^3.0.0 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + checksum: 10c0/caf11188adf6c5ce86189c98de5d0a9371c72bba64fc9b714a9b074f63677a3dead6c3cf3c2912a9aa63b1a2235e6c562b4f7c8f812631c69d3cc1ca07dc1a9c + languageName: node + linkType: hard + "prettier@npm:^3.5.2": version: 3.5.2 resolution: "prettier@npm:3.5.2" @@ -15603,6 +15912,13 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 + languageName: node + linkType: hard + "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -16220,6 +16536,15 @@ __metadata: languageName: node linkType: hard +"sade@npm:^1.7.4": + version: 1.8.1 + resolution: "sade@npm:1.8.1" + dependencies: + mri: "npm:^1.1.0" + checksum: 10c0/da8a3a5d667ad5ce3bf6d4f054bbb9f711103e5df21003c5a5c1a8a77ce12b640ed4017dd423b13c2307ea7e645adee7c2ae3afe8051b9db16a6f6d3da3f90b1 + languageName: node + linkType: hard + "safe-array-concat@npm:^1.0.1": version: 1.0.1 resolution: "safe-array-concat@npm:1.0.1" @@ -16406,7 +16731,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.6, semver@npm:^7.7.2": +"semver@npm:^7.3.6, semver@npm:^7.6.2, semver@npm:^7.7.2": version: 7.7.2 resolution: "semver@npm:7.7.2" bin: @@ -17327,6 +17652,64 @@ __metadata: languageName: node linkType: hard +"svelte-check@npm:^4.1.1": + version: 4.3.2 + resolution: "svelte-check@npm:4.3.2" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.25" + chokidar: "npm:^4.0.1" + fdir: "npm:^6.2.0" + picocolors: "npm:^1.0.0" + sade: "npm:^1.7.4" + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: ">=5.0.0" + bin: + svelte-check: bin/svelte-check + checksum: 10c0/a3b35da017ae5f24b6594f713147e559c5339d9b2b3924ac53d59e07cff3ffdb11767b9aab65a9b3453c4b8f34fa7ed3045b249d7a2961fa484ce70145aa3f2b + languageName: node + linkType: hard + +"svelte-eslint-parser@npm:^0.43.0": + version: 0.43.0 + resolution: "svelte-eslint-parser@npm:0.43.0" + dependencies: + eslint-scope: "npm:^7.2.2" + eslint-visitor-keys: "npm:^3.4.3" + espree: "npm:^9.6.1" + postcss: "npm:^8.4.39" + postcss-scss: "npm:^4.0.9" + peerDependencies: + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + checksum: 10c0/52bbbf5d266fc36f64626e38d8de4354d78a5490fa60b61b1cd127c1c661d94f0c671a220e3ed7dd92b144a3c99f517d5205c218f78feea0603132f24eeb4c9d + languageName: node + linkType: hard + +"svelte@npm:^5.16.0": + version: 5.39.6 + resolution: "svelte@npm:5.39.6" + dependencies: + "@jridgewell/remapping": "npm:^2.3.4" + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@sveltejs/acorn-typescript": "npm:^1.0.5" + "@types/estree": "npm:^1.0.5" + acorn: "npm:^8.12.1" + aria-query: "npm:^5.3.1" + axobject-query: "npm:^4.1.0" + clsx: "npm:^2.1.1" + esm-env: "npm:^1.2.1" + esrap: "npm:^2.1.0" + is-reference: "npm:^3.0.3" + locate-character: "npm:^3.0.0" + magic-string: "npm:^0.30.11" + zimmerframe: "npm:^1.1.2" + checksum: 10c0/9d3b6ffe4a90600cbf3e03ad33bd3d80c968f18f94f2c96768029cae820fcff5b9a307fa3280848c168d486e4c43547b1661e4a7949ee768ca7f8b907d07956e + languageName: node + linkType: hard + "symbol-tree@npm:^3.2.4": version: 3.2.4 resolution: "symbol-tree@npm:3.2.4" @@ -18694,6 +19077,18 @@ __metadata: languageName: node linkType: hard +"vitefu@npm:^1.0.3": + version: 1.1.1 + resolution: "vitefu@npm:1.1.1" + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + checksum: 10c0/7e0d0dd6fb73bd80cb56a3f47ccc44159e0330c3e94b2951648079b35711226f9088dbe616d910b931740b92259230b874fbe351108b49f5c11b629b641292a5 + languageName: node + linkType: hard + "vitest@npm:^2.0.4, vitest@npm:^2.0.5": version: 2.1.9 resolution: "vitest@npm:2.1.9" @@ -19461,6 +19856,13 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^1.10.2": + version: 1.10.2 + resolution: "yaml@npm:1.10.2" + checksum: 10c0/5c28b9eb7adc46544f28d9a8d20c5b3cb1215a886609a2fd41f51628d8aaa5878ccd628b755dbcd29f6bb4921bd04ffbc6dcc370689bb96e594e2f9813d2605f + languageName: node + linkType: hard + "yaml@npm:^2.3.4": version: 2.4.5 resolution: "yaml@npm:2.4.5" @@ -19568,6 +19970,13 @@ __metadata: languageName: node linkType: hard +"zimmerframe@npm:^1.1.2": + version: 1.1.4 + resolution: "zimmerframe@npm:1.1.4" + checksum: 10c0/9470cbf22cefae975ab413c7158a119d082b354ddcf0da48a842f2f42246fa15943cd9b92c047de39db38015e3b866e32f383bc217e8e4f4192945c7d425536b + languageName: node + linkType: hard + "zod@npm:^3.24.2": version: 3.24.2 resolution: "zod@npm:3.24.2"