feat: add @tryabby/vue integration (#68)#189
Conversation
A Vue 3 & Nuxt integration with feature parity to the React and Svelte packages. - useAbby: SSR-safe reactive variant (ComputedRef) + onAct - useFeatureFlag: Ref<boolean> - useRemoteConfig: typed Ref - getFeatureFlagValue / getRemoteConfig / getABTestValue / getVariants / getABResetFunction / updateUserProperties - AbbyProvider: Vue plugin for client data loading + SSR hydration Reactivity is driven by abby.subscribe() with effect-scope cleanup (onScopeDispose); cookie storage via js-cookie mirrors the other packages. Built with tsup (cjs + esm + dts) and covered by 16 vitest tests including type-level assertions. Closes tryabby#68 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@fadhelcarlos is attempting to deploy a commit to the cstrnt's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. π βΉοΈ Recent review infoβοΈ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: π Files selected for processing (2)
π§ Files skipped from review as they are similar to previous changes (2)
WalkthroughThis PR adds a new ChangesVue Package Implementation
Estimated code review effort: 3 (Moderate) | ~30 minutes Possibly related PRs
π₯ Pre-merge checks | β 5β Passed checks (5 passed)
β¨ Finishing Touchesπ§ͺ Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (1)
packages/vue/tests/types.test.ts (1)
1-68: π©Ί Stability & Availability | π‘ Minor | β‘ Quick winEnable typechecking for this suite
packages/vue/vitest.config.tsonly configures runtime tests, andpackages/vue/package.jsonrunsvitestwithout--typecheck. TheseexpectTypeOfassertions are only enforced when the separatetsc --noEmitpath runs, so wire typechecking into this packageβs test pipeline if you want this file to catch regressions.π€ 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/types.test.ts` around lines 1 - 68, Enable typechecking for the Vue test suite so the expectTypeOf assertions in types.test.ts are actually enforced. Update the packageβs test pipeline by wiring typecheck into the vitest setup in vitest.config.ts and/or the package.json test script instead of relying only on runtime vitest, so regressions in createAbby, useAbby, useFeatureFlag, and useRemoteConfig are caught.
π§Ή Nitpick comments (3)
packages/vue/tests/utils.ts (1)
9-20: π Maintainability & Code Quality | π΅ Trivial | β‘ Quick winNo cleanup / unmount exposed for tests exercising SSR/effect-scope teardown.
withSetupreturnswrapperbut nothing in this test layer callswrapper.unmount(). Since the PR specifically claims "effect scope cleanup" as an implemented feature ofuseAbby/subscribeScoped, there's no test verifying that unmounting stops reactive updates or disposes the effect scope (e.g., a subsequentabby.__abby__mutation should not update aRefafter unmount). Consider adding at least one test that unmounts and asserts no further reactivity, to actually validate the cleanup behavior called out in the commit message.π€ 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/utils.ts` around lines 9 - 20, The test helper withSetup currently only mounts and returns wrapper/result, but no test exercises wrapper.unmount() to verify effect-scope cleanup. Add a test around useAbby/subscribeScoped that uses withSetup, mutates abby.__abby__, then calls wrapper.unmount() and asserts the Ref no longer updates afterward. Use the withSetup helper and the unmountable wrapper it returns so the cleanup behavior claimed by the PR is actually covered.packages/vue/tests/types.test.ts (1)
25-27: π Maintainability & Code Quality | π΅ Trivial | π€ Low value
toMatchTypeOfis deprecated in favor oftoEqualTypeOf/toExtend.Per the expect-type docs,
toMatchTypeOfis deprecated; consider switching this assertion totoEqualTypeOf<ComputedRef<number>>()or the newertoExtendmatcher for consistency with the other assertions in this file that already usetoEqualTypeOf.π€ 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/types.test.ts` around lines 25 - 27, The type assertion in the `types.test.ts` test uses the deprecated `expectTypeOf(...).toMatchTypeOf` matcher, so update that assertion to use the newer matcher style already used elsewhere in the file, such as `toEqualTypeOf` (or `toExtend` if that better matches the intent) on `looked.variant` in the `useAbby` test.packages/vue/src/createAbby.ts (1)
114-121: π©Ί Stability & Availability | π΅ Trivial | β‘ Quick winSubscriptions can leak silently when composables are used outside an effect scope.
subscribeScopedonly registers cleanupif (getCurrentScope())(Line 117-119); itsunsubscribereturn value is discarded byuseAbby(Line 169),useFeatureFlag(Line 201), anduseRemoteConfig(Line 216). Called from typical componentsetup()this is fine (Vue creates an effect scope automatically), but any call site without an active scope leaks the subscription for the app's lifetime with no way to clean it up manually.
β οΈ Suggested dev-time safeguardconst subscribeScoped = (listener: () => void) => { if (!isBrowser) return; const unsubscribe = abby.subscribe(() => listener()); if (getCurrentScope()) { onScopeDispose(unsubscribe); + } else if (process.env.NODE_ENV !== "production") { + console.warn( + "[tryabby/vue] Called outside an active effect scope; subscription will not be cleaned up automatically." + ); } return unsubscribe; };Also applies to: 199-220
π€ 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/createAbby.ts` around lines 114 - 121, The subscription cleanup in subscribeScoped is only tied to getCurrentScope, so callers outside a Vue effect scope can leak listeners with no manual teardown path. Update subscribeScoped in createAbby to always expose the unsubscribe handle and make useAbby, useFeatureFlag, and useRemoteConfig preserve or return that cleanup so non-scoped call sites can dispose subscriptions explicitly; if keeping automatic scope cleanup, add a dev-time safeguard or warning when no current scope exists.
π€ 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/src/createAbby.ts`:
- Around line 258-262: The closure returned by getABResetFunction removes
storage unconditionally, unlike the other storage accessors in createAbby. Add
the same SSR guard used elsewhere in this file so TestStorageService.remove is
only called in the browser, and make the returned reset callback a no-op on the
server to avoid js-cookie touching document during SSR.
- Around line 81-105: The cookie-consent gating is only applied in the first
storage adapter, but the `FlagStorageService` and `RemoteConfigStorageService`
`set()` paths still write even when `config.cookies.disableByDefault` is
enabled. Update the storage adapters in `createAbby` so every cookie-backed
write checks the same `disableByDefault` condition before calling
`FlagStorageService.set` and `RemoteConfigStorageService.set`, while preserving
the existing `isBrowser` guard and the current `get()` behavior.
In `@packages/vue/src/StorageService.ts`:
- Around line 34-49: FFStorageService and RCStorageService are creating
session-only cookies because their set methods call Cookie.set without an
expires option, unlike ABStorageService.set. Update the set implementations in
FFStorageService and RCStorageService to use the same persistence policy as
ABStorageService, reusing the existing cookie key helpers like getFFStorageKey
and the remote-config equivalent so all storage types behave consistently across
browser restarts.
---
Outside diff comments:
In `@packages/vue/tests/types.test.ts`:
- Around line 1-68: Enable typechecking for the Vue test suite so the
expectTypeOf assertions in types.test.ts are actually enforced. Update the
packageβs test pipeline by wiring typecheck into the vitest setup in
vitest.config.ts and/or the package.json test script instead of relying only on
runtime vitest, so regressions in createAbby, useAbby, useFeatureFlag, and
useRemoteConfig are caught.
---
Nitpick comments:
In `@packages/vue/src/createAbby.ts`:
- Around line 114-121: The subscription cleanup in subscribeScoped is only tied
to getCurrentScope, so callers outside a Vue effect scope can leak listeners
with no manual teardown path. Update subscribeScoped in createAbby to always
expose the unsubscribe handle and make useAbby, useFeatureFlag, and
useRemoteConfig preserve or return that cleanup so non-scoped call sites can
dispose subscriptions explicitly; if keeping automatic scope cleanup, add a
dev-time safeguard or warning when no current scope exists.
In `@packages/vue/tests/types.test.ts`:
- Around line 25-27: The type assertion in the `types.test.ts` test uses the
deprecated `expectTypeOf(...).toMatchTypeOf` matcher, so update that assertion
to use the newer matcher style already used elsewhere in the file, such as
`toEqualTypeOf` (or `toExtend` if that better matches the intent) on
`looked.variant` in the `useAbby` test.
In `@packages/vue/tests/utils.ts`:
- Around line 9-20: The test helper withSetup currently only mounts and returns
wrapper/result, but no test exercises wrapper.unmount() to verify effect-scope
cleanup. Add a test around useAbby/subscribeScoped that uses withSetup, mutates
abby.__abby__, then calls wrapper.unmount() and asserts the Ref no longer
updates afterward. Use the withSetup helper and the unmountable wrapper it
returns so the cleanup behavior claimed by the PR is actually covered.
πͺ 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: 1b2be9b4-dcfc-4649-b223-8b4b9cc85d85
β Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
π Files selected for processing (18)
.changeset/tryabby-vue-integration.mdpackages/vue/.gitignorepackages/vue/README.mdpackages/vue/package.jsonpackages/vue/src/StorageService.tspackages/vue/src/createAbby.tspackages/vue/src/index.tspackages/vue/tests/featureFlags.test.tspackages/vue/tests/mocks/handlers.tspackages/vue/tests/mocks/server.tspackages/vue/tests/remoteConfig.test.tspackages/vue/tests/setup.tspackages/vue/tests/types.test.tspackages/vue/tests/useAbby.test.tspackages/vue/tests/utils.tspackages/vue/tsconfig.jsonpackages/vue/tsup.config.tspackages/vue/vitest.config.ts
| class FFStorageService implements IStorageService { | ||
| get(projectId: string, flagName: string): string | null { | ||
| const retrievedValue = Cookie.get(getFFStorageKey(projectId, flagName)); | ||
| if (!retrievedValue) return null; | ||
|
|
||
| return retrievedValue; | ||
| } | ||
|
|
||
| set(projectId: string, flagName: string, value: string): void { | ||
| Cookie.set(getFFStorageKey(projectId, flagName), value); | ||
| } | ||
|
|
||
| remove(projectId: string, flagName: string): void { | ||
| Cookie.remove(getFFStorageKey(projectId, flagName)); | ||
| } | ||
| } |
There was a problem hiding this comment.
ποΈ Data Integrity & Integration | π‘ Minor | β‘ Quick win
Feature-flag/remote-config cookies are session-only, unlike AB test cookies.
ABStorageService.set persists cookies for 365 days by default, but FFStorageService.set (Line 42-44) and RCStorageService.set (Line 57-59) call Cookie.set without an expires option, so js-cookie creates session cookies that vanish on browser close. This is an inconsistent persistence contract across the three storage types with no apparent intent behind the difference.
πͺ Proposed fix to align persistence behavior
class FFStorageService implements IStorageService {
get(projectId: string, flagName: string): string | null {
const retrievedValue = Cookie.get(getFFStorageKey(projectId, flagName));
if (!retrievedValue) return null;
return retrievedValue;
}
- set(projectId: string, flagName: string, value: string): void {
- Cookie.set(getFFStorageKey(projectId, flagName), value);
+ set(
+ projectId: string,
+ flagName: string,
+ value: string,
+ options?: StorageServiceOptions
+ ): void {
+ Cookie.set(getFFStorageKey(projectId, flagName), value, {
+ expires: options?.expiresInDays ?? 365,
+ });
}
remove(projectId: string, flagName: string): void {
Cookie.remove(getFFStorageKey(projectId, flagName));
}
}Apply the same pattern to RCStorageService.set.
Also applies to: 51-64
π€ 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/StorageService.ts` around lines 34 - 49, FFStorageService
and RCStorageService are creating session-only cookies because their set methods
call Cookie.set without an expires option, unlike ABStorageService.set. Update
the set implementations in FFStorageService and RCStorageService to use the same
persistence policy as ABStorageService, reusing the existing cookie key helpers
like getFFStorageKey and the remote-config equivalent so all storage types
behave consistently across browser restarts.
π₯ DemoThe three composables reacting live in a real Vue app β A/B-test variant switching, feature-flag toggling, and remote-config updates, all flowing through Vue reactivity: This runs the actual built package ( |
β¦-guard reset - Apply `disableByDefault` to the flag and remote-config cookie-write adapters (previously only the AB-test adapter was gated), so no cookies are written when cookie consent is disabled - Guard `getABResetFunction` against SSR (js-cookie needs `document`) - Add a test asserting the composable's subscription is torn down when the effect scope is disposed (onScopeDispose) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @coderabbitai β addressed in
On the session-only flag/RC cookies note: All green locally: |
|
Tip For best results, initiate chat on the files or code changes.
On the session-only flag/RC cookie lifetime β your reasoning for keeping Nice work getting everything green ( (=^ο½₯Οο½₯^=) οΎββ’ If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |

What
Adds
@tryabby/vueβ the Vue 3 & Nuxt integration requested in #68 β underpackages/vue, with feature parity to the existing React and Svelte packages./claim #68Closes #68
Acceptance criteria
packages/vue@tryabby/vue@tryabby/coreinternallyuseAbbyβ returns the currently selected variant and theonActmethoduseFeatureFlagβ returns the value of the given feature flaguseRemoteConfigβ returns the value of the given remote configAPI
createAbby(config)closes over anAbbyinstance (exactly like the Svelte/React packages) and returns:useAbby(name, lookup?)ComputedRef) +onAct()useFeatureFlag(name)Ref<boolean>useRemoteConfig(name)Ref(string/number/Record<string, unknown>)getFeatureFlagValue/getRemoteConfig/getABTestValue/getVariantsgetABResetFunction/updateUserPropertiesAbbyProviderapp.use(AbbyProvider)) that loads project data on the client and acceptsinitialDatafor SSR hydrationDesign notes
abby.subscribe(), mirrored intoshallowRefs. Subscriptions are cleaned up automatically viaonScopeDisposewhen the surrounding component/effect scope is torn down, and are skipped entirely on the server.useAbby: renders an empty variant on the server and the first client render, then resolves the real variant on mount β the same hydration-mismatch guard the React package uses (useState("")βuseEffect).js-cookieapproach from the React/Svelte packages (getABStorageKey/getFFStorageKey/getRCStorageKeyfrom core).tsup(CJS + ESM +.d.ts), consistent with@tryabby/react/@tryabby/core..changeset/tryabby-vue-integration.md).Verification
Everything passes locally (from
packages/vue):The type tests assert full inference parity, e.g.
useAbby("test").variantisComputedRef<"ONLY_ONE_VARIANT">,useFeatureFlag("test")isRef<boolean>, and remote-config names map toRef<string | number | Record<string, unknown>>.Notes
pnpm-lock.yamlwas regenerated with the repo's pinnedpnpm@9.9.0(via corepack) so it stays consistent with CI.Happy to adjust naming, structure, SSR ergonomics (e.g. a dedicated Nuxt module), or anything else to match your conventions.
Summary by CodeRabbit