Skip to content

feat(kit): add conditional entitlement refresh - #258

Merged
hyochan merged 4 commits into
mainfrom
feat/kit-conditional-entitlement-refresh
Jul 27, 2026
Merged

hyochan merged 4 commits into
mainfrom
feat/kit-conditional-entitlement-refresh

Conversation

@hyochan

@hyochan hyochan commented Jul 27, 2026 •

Copy link
Copy Markdown
Member

Summary

  • add bounded, user-scoped subscription evaluation snapshots for hosted IAPKit status and entitlement reads
  • support API-key-, route-, user-, and content-scoped weak ETags with conditional 304 Not Modified responses
  • document persistent local caching and cold-start, stale-foreground, or explicit refresh as the safe replacement for removed outbound event streams

Changes

Hosted subscription refresh

  • add an indexed (projectId, userId, updatedAt) lookup with a 201-row overflow probe and a fail-closed 200-row public contract
  • return only stored-state Active/InGracePeriod candidates plus one latest fallback for Fly-side evaluation
  • evaluate expiresAt against the Fly server clock on every HTTP request
  • keep polling read-only and preserve existing status and entitlement response bodies for unconditional clients

HTTP and cache safety

  • isolate weak ETags by API key, route, user, and evaluated response content
  • return Vary: Authorization and private cache directives for publishable-key responses
  • keep secret/admin responses private, no-store without ETags
  • preserve bounded rate limiting and Retry-After behavior

Documentation

  • add persistent project-and-user cache examples that coalesce refreshes and reject invalid or future cache timestamps
  • recommend direct cold-start, stale-foreground, and explicit refreshes instead of foreground-wide polling
  • document rollout ordering, cost characteristics, the legacy direct-Convex residual, and the permanent prohibition on IAPKit-to-SDK SSE/WebSockets/push relays/long polling

Rollout

Deploy the additive Convex schema/query before the Fly server. Existing SDK helpers remain unconditional body-only reads, so this hosted-only change does not require a native or framework package version bump.

Test plan

  • Kit Prettier, TypeScript, Convex typecheck, and ESLint
  • Kit test suite: 72 files / 858 tests
  • Kit production build, compiled server, and browser/API smoke probes
  • Docs Prettier, typecheck, lint, and production build
  • Documentation, SDK parity, deprecation, and release-state audits
  • Removed outbound stream routes and legacy key-in-path variants return 404
  • git diff --check

Preview

A visual recording is not applicable because this is a hosted HTTP/Convex contract and documentation change with no new interactive UI. The compiled-server smoke probes and regression tests above cover the changed surface.

Summary by CodeRabbit

  • New Features
    • Added user-scoped subscription status and entitlements endpoints with conditional snapshot refresh via ETag/If-None-Match and 304 reuse.
    • Implemented bounded subscription snapshot evaluation with “fail closed” on overflow.
    • Added a hosted refresh flow replacement that uses polling (not app-facing event streams), including 429 Retry-After guidance and stale-age/offline behavior.
  • Documentation
    • Expanded SSE replacement and refresh/caching guidance, plus updated API reference and quickstart/release notes.
    • Updated integration version snippets and timestamps.
  • Tests
    • Extended API and snapshot evaluation tests, including limit, ETag, and caching edge cases.

Expose bounded user-scoped subscription snapshots with conditional ETag responses for cold-start, stale-foreground, and explicit refresh flows.\n\nDocument persistent client caching, rate-limit handling, expiry evaluation, and the hosted-only rollout while preserving inbound store webhooks and keeping outbound event streams removed.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@hyochan hyochan added openiap-kit packages/kit (IAPKit SaaS) 🎯 feature New feature 📖 documentation Improvements or additions to documentation labels Jul 27, 2026
@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@hyochan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dadbf5bf-3238-4c91-8fab-f19628c04ee8

📥 Commits

Reviewing files that changed from the base of the PR and between 1413729 and 8dcb05f.

📒 Files selected for processing (2)
  • packages/docs/src/pages/docs/kit-backend.tsx
  • packages/kit/src/pages/docs/sections/api.tsx
📝 Walkthrough

Walkthrough

The change adds bounded subscription evaluation snapshots, conditional status and entitlement responses with ETags, updated subscription API routes, and documentation for polling-based refresh without outbound event streams.

Changes

Subscription snapshots

Layer / File(s) Summary
Bounded subscription snapshot evaluation
packages/kit/convex/schema.ts, packages/kit/convex/subscriptions/query.ts, packages/kit/convex/subscriptions/query.test.ts
Adds the (projectId, userId, updatedAt) index, caps reads at 200 rows with overflow detection, shapes candidates and fallback records, and tests the new behavior.
Conditional subscription API responses
packages/kit/server/api/v1/subscriptions.ts, packages/kit/server/api/v1/subscriptions.test.ts
Derives status and entitlements from evaluation snapshots, adds weak ETags and 304 handling, separates secret-key caching behavior, and tests isolation, expiry, mutations, and overflow failures.
Snapshot API and refresh documentation
packages/docs/src/pages/docs/kit-backend.tsx, packages/kit/src/pages/docs/sections/api.tsx, packages/kit/public/llms*.txt, packages/docs/public/llms-full.txt
Documents header-authenticated routes, compatibility aliases, conditional refresh, rate limiting, overflow behavior, and the replacement of outbound event streaming.
Operational guidance and release documentation
packages/kit/COST-SAFETY.md, packages/kit/README.md, packages/kit/src/pages/docs/..., packages/docs/src/pages/docs/updates/releases.tsx
Updates cache, cost, refresh, quickstart, navigation, and hosted-release documentation.
Published package reference updates
packages/docs/public/llms*.txt
Refreshes generated timestamps and published Android, Godot, Kotlin Multiplatform, and .NET MAUI versions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant FlyAPI
  participant Convex
  App->>FlyAPI: Request status or entitlements with If-None-Match
  FlyAPI->>Convex: Load subscriptionEvaluationSnapshot
  Convex-->>FlyAPI: Return bounded candidates and fallback
  FlyAPI-->>App: Return 200 snapshot or 304
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.85% 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 accurately captures the main change: adding conditional entitlement refresh for IAPKit hosted status and entitlement reads.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/kit-conditional-entitlement-refresh

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/kit/server/api/v1/subscriptions.test.ts (1)

306-306: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore the Date.now spy in a teardown hook so a failing assertion can't leak it.

dateNow.mockRestore() runs at the end of the test body; if any preceding expect throws, Date.now stays stubbed and later time-dependent tests in this file observe a frozen clock. Move restoration into afterEach (or enable restoreMocks in the Vitest config) so it always runs.

♻️ Sketch
+  afterEach(() => {
+    vi.restoreAllMocks();
+  });

Also applies to: 364-364, 499-499, 543-543

🤖 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/kit/server/api/v1/subscriptions.test.ts` at line 306, Move
restoration of the Date.now spy created by dateNow in the affected tests into an
afterEach teardown hook, ensuring it runs even when assertions fail. Apply the
same cleanup to all listed occurrences and remove the test-body-only
dateNow.mockRestore() calls.
packages/kit/server/api/v1/subscriptions.ts (1)

50-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider deriving these types from the generated Convex API instead of hand-mirroring subscriptionShape.

SubscriptionSnapshotRow duplicates every field of subscriptionShape in packages/kit/convex/subscriptions/query.ts. A future field added to the Convex validator won't surface here as a type error, and the ETag/response payload silently diverges from the Convex contract.

♻️ Sketch
type SubscriptionEvaluationSnapshot = Awaited<
  ReturnType<typeof client.query<typeof api.subscriptions.query.subscriptionEvaluationSnapshot>>
>;
type SubscriptionSnapshotRow = SubscriptionEvaluationSnapshot["candidates"][number];
🤖 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/kit/server/api/v1/subscriptions.ts` around lines 50 - 70, Replace
the hand-maintained SubscriptionSnapshotRow and SubscriptionEvaluationSnapshot
definitions with types derived from the generated Convex API, using the return
type of api.subscriptions.query.subscriptionEvaluationSnapshot and extracting
candidates[number] for each row. Update the surrounding client/query type
references as needed so the ETag and response payload remain aligned with the
Convex contract.
packages/docs/src/pages/docs/kit-backend.tsx (1)

605-696: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Undeclared iapkitPublishableKey (and other free variables) in new conditional-refresh code samples. Both new TypeScript examples reference identifiers that are never declared or explained, breaking from this codebase's existing convention of using explicit placeholders (e.g. '<IAPKIT_PUBLISHABLE_KEY>') or env vars for keys in copy-pasteable snippets.

  • packages/docs/src/pages/docs/kit-backend.tsx#L605-L696: the refreshEntitlements function body uses iapkitPublishableKey (line 644) without declaring it as a parameter or module-level placeholder; add a comment or parameter matching the file's existing '<IAPKIT_PUBLISHABLE_KEY>' / env-var convention.
  • packages/kit/src/pages/docs/sections/api.tsx#L248-L319: the "Conditional entitlement refresh" block additionally leaves cached, maxStaleMs, and userId undeclared with no enclosing function at all; wrap the snippet in a function signature (mirroring kit-backend.tsx's refreshEntitlements) so readers can see where each value originates.
🤖 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/docs/src/pages/docs/kit-backend.tsx` around lines 605 - 696, Update
packages/docs/src/pages/docs/kit-backend.tsx:605-696 by declaring or
parameterizing iapkitPublishableKey in refreshEntitlements using the existing
'<IAPKIT_PUBLISHABLE_KEY>' or environment-variable convention. Update
packages/kit/src/pages/docs/sections/api.tsx:248-319 by wrapping the Conditional
entitlement refresh snippet in a function signature mirroring
refreshEntitlements, with cached, maxStaleMs, and userId explicitly provided;
preserve the existing refresh logic.
🤖 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/kit/server/api/v1/subscriptions.ts`:
- Around line 530-536: Update the active subscription selection in the status
response to use an explicit deterministic comparator instead of
activeSubscriptions[0]. Match the previous selectMostRecentlyUpdatedSubscription
behavior by ordering by updatedAt and applying a stable secondary tie-breaker
such as startedAt or id, while preserving the existing active flag and fallback
behavior.

---

Nitpick comments:
In `@packages/docs/src/pages/docs/kit-backend.tsx`:
- Around line 605-696: Update
packages/docs/src/pages/docs/kit-backend.tsx:605-696 by declaring or
parameterizing iapkitPublishableKey in refreshEntitlements using the existing
'<IAPKIT_PUBLISHABLE_KEY>' or environment-variable convention. Update
packages/kit/src/pages/docs/sections/api.tsx:248-319 by wrapping the Conditional
entitlement refresh snippet in a function signature mirroring
refreshEntitlements, with cached, maxStaleMs, and userId explicitly provided;
preserve the existing refresh logic.

In `@packages/kit/server/api/v1/subscriptions.test.ts`:
- Line 306: Move restoration of the Date.now spy created by dateNow in the
affected tests into an afterEach teardown hook, ensuring it runs even when
assertions fail. Apply the same cleanup to all listed occurrences and remove the
test-body-only dateNow.mockRestore() calls.

In `@packages/kit/server/api/v1/subscriptions.ts`:
- Around line 50-70: Replace the hand-maintained SubscriptionSnapshotRow and
SubscriptionEvaluationSnapshot definitions with types derived from the generated
Convex API, using the return type of
api.subscriptions.query.subscriptionEvaluationSnapshot and extracting
candidates[number] for each row. Update the surrounding client/query type
references as needed so the ETag and response payload remain aligned with the
Convex contract.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e99caf8-d54f-463d-baa4-bdef5f006400

📥 Commits

Reviewing files that changed from the base of the PR and between 05a04bf and a8b4e7a.

📒 Files selected for processing (17)
  • packages/docs/public/llms-full.txt
  • packages/docs/public/llms.txt
  • packages/docs/src/pages/docs/kit-backend.tsx
  • packages/docs/src/pages/docs/updates/releases.tsx
  • packages/kit/COST-SAFETY.md
  • packages/kit/README.md
  • packages/kit/convex/schema.ts
  • packages/kit/convex/subscriptions/query.test.ts
  • packages/kit/convex/subscriptions/query.ts
  • packages/kit/public/llms-full.txt
  • packages/kit/public/llms.txt
  • packages/kit/server/api/v1/subscriptions.test.ts
  • packages/kit/server/api/v1/subscriptions.ts
  • packages/kit/src/pages/docs/nav.ts
  • packages/kit/src/pages/docs/sections/api.tsx
  • packages/kit/src/pages/docs/sections/quickstart.tsx
  • packages/kit/src/pages/docs/sections/release-notes.tsx

Comment thread packages/kit/server/api/v1/subscriptions.ts
@hyochan

hyochan commented Jul 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 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/kit/src/pages/docs/sections/api.tsx`:
- Around line 271-306: Update canUseCachedSnapshot and the request-header logic
in the entitlement-fetch flow to reuse cached data and send cached.etag only
when cached.snapshot.userId === userId. Ensure both offline error fallback and
429 handling reject snapshots belonging to another user while preserving valid
same-user caching behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 00456367-71e0-4e7e-a24d-61b3d585d18b

📥 Commits

Reviewing files that changed from the base of the PR and between a8b4e7a and 1413729.

📒 Files selected for processing (6)
  • packages/docs/src/pages/docs/kit-backend.tsx
  • packages/kit/convex/subscriptions/query.test.ts
  • packages/kit/convex/subscriptions/query.ts
  • packages/kit/server/api/v1/subscriptions.test.ts
  • packages/kit/server/api/v1/subscriptions.ts
  • packages/kit/src/pages/docs/sections/api.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/docs/src/pages/docs/kit-backend.tsx
  • packages/kit/convex/subscriptions/query.test.ts
  • packages/kit/server/api/v1/subscriptions.ts
  • packages/kit/server/api/v1/subscriptions.test.ts
  • packages/kit/convex/subscriptions/query.ts

Comment thread packages/kit/src/pages/docs/sections/api.tsx Outdated
@hyochan

hyochan commented Jul 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@hyochan

hyochan commented Jul 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@hyochan
hyochan merged commit cb3e8b9 into main Jul 27, 2026
13 checks passed
@hyochan
hyochan deleted the feat/kit-conditional-entitlement-refresh branch July 27, 2026 23:57
@coderabbitai coderabbitai Bot mentioned this pull request Aug 1, 2026
6 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📖 documentation Improvements or additions to documentation 🎯 feature New feature openiap-kit packages/kit (IAPKit SaaS)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant