Skip to content

feat: add Vue integration package#182

Closed
austin33133-maker wants to merge 1 commit into
tryabby:mainfrom
austin33133-maker:feat-vue-integration
Closed

feat: add Vue integration package#182
austin33133-maker wants to merge 1 commit into
tryabby:mainfrom
austin33133-maker:feat-vue-integration

Conversation

@austin33133-maker
Copy link
Copy Markdown

@austin33133-maker austin33133-maker commented May 13, 2026

Summary

  • Add a new @tryabby/vue package that consumes @tryabby/core.
  • Provide Vue composables for A/B tests, feature flags, and remote config.
  • Add storage helpers, README usage docs, tests, and a changeset.

Testing

  • biome ci packages/vue .changeset/fresh-vans-dream.md
  • tsc --noEmit from packages/vue
  • vitest --run --config vite.config.ts from packages/vue
  • tsup src/ from packages/vue

Closes #68

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced @tryabby/vue package with Vue composables for A/B testing, feature flags, and remote configuration
    • Added createAbby factory function to initialize Abby instances
    • AbbyPlugin enables seamless integration with Vue applications
    • Cookie-based storage management for persisting user choices
  • Documentation

    • Added comprehensive README with setup and usage examples

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 13, 2026

@austin33133-maker is attempting to deploy a commit to the cstrnt's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Walkthrough

This PR introduces the @tryabby/vue package, a Vue integration for Abby providing composable hooks (useAbby, useFeatureFlag, useRemoteConfig) and a Vue plugin for A/B testing, feature flags, and remote configuration management. The implementation includes cookie-backed storage services, typed composition APIs with reactive data injection, imperative helper functions, comprehensive type tests, and integration tests validating hook behavior and HTTP event dispatch.

Changes

Vue Integration Package

Layer / File(s) Summary
Package metadata and build configuration
.changeset/fresh-vans-dream.md, packages/vue/package.json, packages/vue/tsconfig.json, packages/vue/tsup.config.ts, packages/vue/vite.config.ts, packages/vue/README.md
Package version bumped to minor, npm scripts for build/dev/test added, TypeScript and Vitest configured, ESM/CJS dual format enabled with type declarations, and README documents usage of createAbby, AbbyPlugin, and composition hooks.
Browser storage service implementations
packages/vue/src/StorageService.ts
Three IStorageService implementations (ABStorageService, FFStorageService, RCStorageService) persist state to cookies with 365-day default expiry; exported as singleton instances for reuse across the Vue app.
Core Vue integration API (createAbby, plugin, hooks)
packages/vue/src/index.ts
Factory createAbby instantiates Abby core with browser storage, exports Vue plugin that provides reactive context and syncs project data, composition hooks (useAbby, useFeatureFlag, useRemoteConfig, useAbbySync) backed by reactive injected state, and imperative helpers (getABTestValue, getFeatureFlagValue, getRemoteConfig, getVariants, updateUserProperties, getABResetFunction) for direct access outside components.
Test environment and type validation
packages/vue/tests/mocks/handlers.ts, packages/vue/tests/mocks/server.ts, packages/vue/tests/setup.ts, packages/vue/tests/types.test.ts
MSW mock server configured with Abby API response payload; test environment wired with Jest DOM matchers, node-fetch, and MSW lifecycle hooks; type test suites assert correct return types for useAbby variants (literal and union), useFeatureFlag (boolean), and useRemoteConfig (string/number/object).
Integration tests for hooks and functionality
packages/vue/tests/useAbby.test.ts
Tests verify useAbby variant selection and optional lookup mapping, HTTP event dispatch (PING on mount and ACT on action via HttpService), useFeatureFlag returning boolean flag values, and useRemoteConfig returning configured default values.

Sequence Diagram

sequenceDiagram
  participant Component as Vue Component
  participant Plugin as AbbyPlugin.install()
  participant Context as InjectionContext
  participant Abby as Abby Core Instance
  participant Storage as StorageService (Cookies)
  participant Server as Abby API Server

  Component->>Plugin: app created with createAbby(config)
  Plugin->>Abby: instantiate with config & storage handlers
  Abby->>Storage: get(testKey) on init
  Storage-->>Abby: null or persisted variant
  Plugin->>Server: loadProjectData(projectId)
  Server-->>Plugin: tests, flags, remoteConfig
  Plugin->>Context: app.provide(AbbyContextKey, reactive data)
  
  Component->>Component: mount with useAbby('test')
  Component->>Context: inject(AbbyContextKey)
  Context-->>Component: computed data & selected variant
  Component->>Server: sendData(PING event)
  
  Component->>Component: user triggers onAct()
  Component->>Storage: set(testKey, variant)
  Component->>Server: sendData(ACT event)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add Vue integration package' clearly and concisely summarizes the main change, which is the addition of a new Vue integration package.
Linked Issues check ✅ Passed The PR successfully implements all required functionality: @tryabby/vue package in packages/vue, TypeScript implementation, composables (useAbby, useFeatureFlag, useRemoteConfig), storage services, comprehensive tests for both types and runtime behavior, and internal consumption of @tryabby/core.
Out of Scope Changes check ✅ Passed All changes are directly related to creating the Vue integration package. No out-of-scope modifications detected; includes only package setup, composables, storage helpers, documentation, and tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/vue/tests/setup.ts (1)

6-6: 💤 Low value

Consider a more specific type assertion.

The as any cast bypasses all type checking. While the type mismatch between node-fetch and the native fetch API can make precise typing difficult, consider using as unknown as typeof fetch for a slightly safer assertion path, or accepting the type incompatibility if the current approach works reliably.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/tests/setup.ts` at line 6, Replace the broad "as any" cast on
the global fetch assignment to preserve more type safety: change the assignment
of globalThis.fetch (where you currently do "globalThis.fetch = fetch as any;")
to use a safer assertion like "as unknown as typeof fetch" so TypeScript treats
the value as the native fetch signature instead of bypassing type checks
entirely.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/vue/README.md`:
- Around line 40-43: The README example uses useAbby, useFeatureFlag, and
useRemoteConfig which return ComputedRef objects; update the snippet to show
accessing their .value in script context so consumers don’t misuse them.
Specifically, show that variant and onAct (from useAbby), newCheckout (from
useFeatureFlag), and ctaText (from useRemoteConfig) must be read as .value (or
assign .value to local variables) when used in plain script code and update the
example text accordingly.

In `@packages/vue/src/index.ts`:
- Around line 101-113: The FlagStorageService.set and
RemoteConfigStorageService.set handlers currently write cookies unconditionally;
update both set handlers to honor the cookie opt-out by checking
config.cookies?.disableByDefault (same check used by the AB test cookie logic
around line 91) and skip calling FlagStorageService.set or
RemoteConfigStorageService.set when disableByDefault is true, leaving get
behavior unchanged.

---

Nitpick comments:
In `@packages/vue/tests/setup.ts`:
- Line 6: Replace the broad "as any" cast on the global fetch assignment to
preserve more type safety: change the assignment of globalThis.fetch (where you
currently do "globalThis.fetch = fetch as any;") to use a safer assertion like
"as unknown as typeof fetch" so TypeScript treats the value as the native fetch
signature instead of bypassing type checks entirely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f41a6488-09b3-4702-b10f-0e945b484c69

📥 Commits

Reviewing files that changed from the base of the PR and between e763d1a and 76d91d8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • .changeset/fresh-vans-dream.md
  • packages/vue/README.md
  • packages/vue/package.json
  • packages/vue/src/StorageService.ts
  • packages/vue/src/index.ts
  • packages/vue/tests/mocks/handlers.ts
  • packages/vue/tests/mocks/server.ts
  • packages/vue/tests/setup.ts
  • packages/vue/tests/types.test.ts
  • packages/vue/tests/useAbby.test.ts
  • packages/vue/tsconfig.json
  • packages/vue/tsup.config.ts
  • packages/vue/vite.config.ts

Comment thread packages/vue/README.md
Comment on lines +40 to +43
const { variant, onAct } = useAbby("headline");
const newCheckout = useFeatureFlag("newCheckout");
const ctaText = useRemoteConfig("ctaText");
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Vue 3 Composition API, do ref/computedreturn values require.value access in script code (outside templates)?

💡 Result:

No—inside normal script code (Composition API, i.e., within setup / <script setup>), refs and computed refs require.value access to read the underlying value. Vue’s reactivity APIs define that a ref object has a single.value property that points to the inner value [1]. Vue’s computed returns a (readonly) reactive ref object, and the docs show using plusOne.value in script code (e.g., console.log(plusOne.value)) [1]. By contrast, in templates Vue auto-un-wraps refs/computed refs, so you can use them without.value [2]. Rule of thumb: - Script code (outside templates): refVar.value / computedVar.value - Template expressions: refVar / computedVar (no.value) Caveat: unwrapping isn’t universal (for example, refs inside arrays/collection elements don’t get unwrapped), where you may still need.value [3].

Citations:


🏁 Script executed:

# Check the README file at lines 40-43
sed -n '30,55p' packages/vue/README.md

Repository: tryabby/abby

Length of output: 401


🏁 Script executed:

# Find where these composables are defined
find packages/vue -type f -name "*.ts" -o -name "*.js" | head -20

Repository: tryabby/abby

Length of output: 341


🏁 Script executed:

# Search for the composable definitions
rg -l "useAbby|useFeatureFlag|useRemoteConfig" packages/vue --type ts --type js

Repository: tryabby/abby

Length of output: 150


🏁 Script executed:

# Check the index.ts to see what these composables return
cat -n packages/vue/src/index.ts

Repository: tryabby/abby

Length of output: 10391


Clarify .value access requirement for composables in script context.

useAbby, useFeatureFlag, and useRemoteConfig return ComputedRef objects, which require .value access when used in script code. The README example should demonstrate this to prevent misuse.

✏️ Suggested doc fix
const { variant, onAct } = useAbby("headline");
const newCheckout = useFeatureFlag("newCheckout");
const ctaText = useRemoteConfig("ctaText");
+
+// In script context, access the computed values via .value:
+console.log(variant.value, newCheckout.value, ctaText.value);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { variant, onAct } = useAbby("headline");
const newCheckout = useFeatureFlag("newCheckout");
const ctaText = useRemoteConfig("ctaText");
```
const { variant, onAct } = useAbby("headline");
const newCheckout = useFeatureFlag("newCheckout");
const ctaText = useRemoteConfig("ctaText");
// In script context, access the computed values via .value:
console.log(variant.value, newCheckout.value, ctaText.value);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/README.md` around lines 40 - 43, The README example uses
useAbby, useFeatureFlag, and useRemoteConfig which return ComputedRef objects;
update the snippet to show accessing their .value in script context so consumers
don’t misuse them. Specifically, show that variant and onAct (from useAbby),
newCheckout (from useFeatureFlag), and ctaText (from useRemoteConfig) must be
read as .value (or assign .value to local variables) when used in plain script
code and update the example text accordingly.

Comment thread packages/vue/src/index.ts
Comment on lines +101 to +113
set: (key: string, value: string) => {
if (typeof window === "undefined") return;
FlagStorageService.set(config.projectId, key, value);
},
},
{
get: (key: string) => {
if (typeof window === "undefined") return null;
return RemoteConfigStorageService.get(config.projectId, key);
},
set: (key: string, value: string) => {
if (typeof window === "undefined") return;
RemoteConfigStorageService.set(config.projectId, key, value);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all cookie write paths consistently gate on disableByDefault in Vue integration.
rg -n -C2 'disableByDefault|TestStorageService\.set|FlagStorageService\.set|RemoteConfigStorageService\.set' packages/vue/src/index.ts

Repository: tryabby/abby

Length of output: 725


Respect disableByDefault config for flag and remote-config writes.

Flag and remote-config cookies are written regardless of config.cookies?.disableByDefault, while AB test cookies properly check this setting (line 91). This inconsistency breaks cookie opt-out behavior.

🛠️ Suggested fix
       set: (key: string, value: string) => {
-        if (typeof window === "undefined") return;
+        if (typeof window === "undefined" || config.cookies?.disableByDefault)
+          return;
         FlagStorageService.set(config.projectId, key, value);
       },
@@
       set: (key: string, value: string) => {
-        if (typeof window === "undefined") return;
+        if (typeof window === "undefined" || config.cookies?.disableByDefault)
+          return;
         RemoteConfigStorageService.set(config.projectId, key, value);
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
set: (key: string, value: string) => {
if (typeof window === "undefined") return;
FlagStorageService.set(config.projectId, key, value);
},
},
{
get: (key: string) => {
if (typeof window === "undefined") return null;
return RemoteConfigStorageService.get(config.projectId, key);
},
set: (key: string, value: string) => {
if (typeof window === "undefined") return;
RemoteConfigStorageService.set(config.projectId, key, value);
set: (key: string, value: string) => {
if (typeof window === "undefined" || config.cookies?.disableByDefault)
return;
FlagStorageService.set(config.projectId, key, value);
},
},
{
get: (key: string) => {
if (typeof window === "undefined") return null;
return RemoteConfigStorageService.get(config.projectId, key);
},
set: (key: string, value: string) => {
if (typeof window === "undefined" || config.cookies?.disableByDefault)
return;
RemoteConfigStorageService.set(config.projectId, key, value);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/index.ts` around lines 101 - 113, The FlagStorageService.set
and RemoteConfigStorageService.set handlers currently write cookies
unconditionally; update both set handlers to honor the cookie opt-out by
checking config.cookies?.disableByDefault (same check used by the AB test cookie
logic around line 91) and skip calling FlagStorageService.set or
RemoteConfigStorageService.set when disableByDefault is true, leaving get
behavior unchanged.

@austin33133-maker
Copy link
Copy Markdown
Author

Closing this — I should have checked the bounty status before opening it. Looking back at #68, the bounty has already been awarded:

So this PR is redundant. Apologies for the noise on the queue — happy to pull from this branch if any of the unmerged work here is useful in a follow-up, but otherwise this can stay closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vue Integration

1 participant