Skip to content

[NO-JIRA] Add App State SDK - #75

Merged
basit3407 merged 14 commits into
mainfrom
codex/app-state-api
Sep 17, 2026
Merged

basit3407 merged 14 commits into
mainfrom
codex/app-state-api

Conversation

@osamasayed

@osamasayed osamasayed commented Aug 30, 2026

Copy link
Copy Markdown
Member

Summary

Adds a typed App State SDK surface for both public and server JavaScript clients, including transport-safe errors and transactional reconciliation.

Key changes

  • Adds typed methods for configuration, bootstrap, changes, collection, and document operations.
  • Supports opaque cursors, ETags, conditional writes, and idempotency keys.
  • Adds immutable App State reconciliation and account-state helpers backed by a shared conformance fixture.
  • Atomically binds switchAccount(accountId, transport) to the target account's credential-bound transport; in-flight work retains its original transport and stale results cannot commit after a switch.
  • Exports the complete App State type surface from both @quranjs/api/public and @quranjs/api/server.
  • Exports QuranHttpError so callers can distinguish 410 recovery from 409 and 412 conflicts while retaining response payloads and headers.
  • Documents the new APIs and records the additive SDK release in a minor changeset.

Validation

At exact head 034f5ac1058d1c9528b3cda24e4f8f65a97d561d:

  • All 25 test files and 208 tests passed.
  • Build, lint, typecheck, operation-catalog, and bundle-size checks passed.
  • Account-switch regression and public/server type-surface tests passed.

Compatibility

  • The SDK additions do not alter previously released client behavior or exports.
  • The unreleased App State reconciler now requires an account-bound transport when switching accounts.
  • This is a library-only change; it adds no deployment configuration.

Environment variables

  • New or changed environment variables: None.
  • Existing consumer-managed credentials and smoke-test variables are unchanged.

Related rollout

NO-LAUNCH: keep this PR in draft and do not publish the App State SDK surface until the coordinated backend rollout, deployed-prelive acceptance, and required lifecycle/security/privacy/trust-and-safety approvals are complete.

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
dist/index.min.js 7.85 KB (+70.52% 🔺)
dist/index.min.mjs 7.85 KB (+68.51% 🔺)
dist/server.min.js 14.79 KB (+31.92% 🔺)
dist/public.min.js 9.79 KB (+57.37% 🔺)

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T06:58:37.652826Z 4bcbdb1 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 19146fcc59

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aeddf0d659

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/runtime/app-state-reconciler.ts

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 498b9c1074

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/runtime/app-state-reconciler.ts Outdated

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80e7d4e97b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/runtime/app-state-reconciler.ts Outdated
Comment thread packages/api/src/runtime/app-state-reconciler.ts Outdated

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bf095d097e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/runtime/app-state-reconciler.ts

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c0aaf0d489

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/runtime/app-state-reconciler.ts Outdated

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 4bcbdb1692

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@basit3407
basit3407 marked this pull request as draft September 16, 2026 07:26
@basit3407
basit3407 requested a lite review from Copilot September 16, 2026 14:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Several moderate reconciliation and error-classification issues remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds a typed App State SDK for public and server clients, including HTTP errors, transactional reconciliation, account isolation, and shared exports.

Changes:

  • Adds App State transport methods, types, and client facades.
  • Adds immutable state management, reconciliation, and account switching.
  • Adds tests, conformance fixtures, documentation, and release metadata.
File summaries
File Description
packages/api/test/smoke/app-state-package.cjs Package entrypoint smoke checks
packages/api/test/http-error.test.ts HTTP error behavior tests
packages/api/test/app-state.test.ts App State operation tests
packages/api/test/app-state-types.test.ts Public and server type-surface tests
packages/api/test/app-state-state.test.ts Immutable state transition tests
packages/api/test/app-state-reconciler.test.ts Reconciliation and account-switching tests
packages/api/test/app-state-conformance.test.ts Shared conformance tests
packages/api/src/types/quran-client.ts Request metadata options
packages/api/src/types/index.ts App State type exports
packages/api/src/types/app-state.ts App State contracts
packages/api/src/server.ts Server API exports
packages/api/src/sdk/public-fetcher.ts Public response and error handling
packages/api/src/sdk/http-error.ts Shared HTTP error implementation
packages/api/src/sdk/fetcher.ts Server response and error handling
packages/api/src/sdk/app-state-errors.ts App State error classification
packages/api/src/runtime/create-public-client.ts Public client facade
packages/api/src/runtime/create-client.ts Server client facade
packages/api/src/runtime/app-state.ts App State transport operations
packages/api/src/runtime/app-state-state.ts Account state and mutation helpers
packages/api/src/runtime/app-state-reconciler.ts Transactional reconciliation
packages/api/src/public.ts Public exports
packages/api/src/index.ts Root exports
packages/api/README.md App State documentation
packages/api/conformance/app-state-reconciliation-v1.sha256 Conformance fixture checksum
packages/api/conformance/app-state-reconciliation-v1.json Conformance scenarios
.changeset/add-app-state-sdk.md Minor release metadata
Review details

Suppressed comments (4)

packages/api/src/runtime/app-state-reconciler.ts:338

  • This catch classifies the result of an in-flight getDocument() before checking whether the reconciliation generation is still current. If the account switches while the refresh is pending, a stale non-document_not_found error is rethrown and rejects the old reconciliation instead of being discarded; even recognized stale errors are inspected after the account boundary has changed. Check isCurrent(reconcileContext) first in this catch and return without classifying stale failures.
    } catch (error) {
      if (getAppStateErrorCode(error) !== "document_not_found") throw error;
      current = null;
    }

packages/api/src/runtime/app-state-reconciler.ts:499

  • reconcileQueue is shared across account generations and is not reset by switchAccount. If an old-account transport request hangs, every reconcile started after switching to the new account remains queued behind that request and never uses the new account's transport, despite the generation checks making the old result safe to discard. Serialize within a generation (or otherwise release the queue on switch) so a stalled stale request cannot block synchronization for the active account.
    reconcile: () => {
      const capturedContext = context();
      const operation = reconcileQueue.then(() =>
        reconcileContext(capturedContext),
      );
      reconcileQueue = operation.then(
        () => undefined,
        () => undefined,
      );

packages/api/src/runtime/app-state-reconciler.ts:480

  • This return reads activeAccountId without binding the final state read to mutationContext. If account A's queued delete completes and its getStateFor transaction is still pending when switchAccount changes the active account to B, the delete promise can resolve with A's view after B is active. Guard the returned snapshot by the captured generation (or re-read the current account before resolving) so a late result cannot expose the previous account's state.
      return getStateFor(activeAccountId);

packages/api/test/app-state-conformance.test.ts:83

  • The conformance test only executes duplicate_change_is_idempotent, out_of_order_pages_keep_newest_document, and the same-version conflict case. The fixture also defines strict-conflict rebase, crash-atomicity, account-switch, and tombstone-lineage scenarios, but their expected results are never consumed by the JS conformance suite, so the claimed shared conformance can silently diverge on those cases. Add assertions/runners for the remaining executable scenarios (or narrow the fixture claim).
  it.each([
    "duplicate_change_is_idempotent",
    "out_of_order_pages_keep_newest_document",
  ])("matches the %s state transition", async (scenarioName) => {
  • Files reviewed: 26/26 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/api/src/runtime/app-state-reconciler.ts
Comment thread packages/api/src/sdk/app-state-errors.ts
Comment thread packages/api/test/smoke/app-state-package.cjs
@basit3407
basit3407 marked this pull request as ready for review September 16, 2026 16:37
@basit3407
basit3407 marked this pull request as draft September 16, 2026 16:38
@basit3407
basit3407 marked this pull request as ready for review September 16, 2026 16:41
@basit3407
basit3407 requested a lite review from Copilot September 17, 2026 01:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Bootstrap recovery, stale conflict handling, and transient mutation retry paths do not fully satisfy the documented reconciliation contract.

Review details

Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

packages/api/src/runtime/app-state-reconciler.ts:176

  • A 410 returned by bootstrap() is rethrown here before the staging state is reset or the bounded restart loop is entered. The App State recovery contract allows bootstrap itself to return bootstrap_required/sync_token_expired and requires discarding partial staging and restarting, so an initial or recovery bootstrap can fail permanently instead of recovering. Handle recovery-coded bootstrap failures through the same reset/restart path as the change-drain failures.
    packages/api/src/runtime/app-state-reconciler.ts:337
  • This catch classifies the refresh error before checking whether the reconciliation context is still current. If the account switches while getDocument() is pending and the old transport then rejects with anything other than document_not_found, the stale reconciliation rejects with that old error instead of discarding it. Check isCurrent(reconcileContext) first and return null for stale refresh failures, matching the stale-response boundary used by the other network calls.
    packages/api/src/runtime/app-state-reconciler.ts:432
  • All non-412 replay failures take this immediate throw path, so a transient 500/503, transport failure, or 429 with Retry-After leaves reconciliation rejected without the bounded retry/backoff required for ambiguous mutations. Add a retry path that preserves the captured body, preconditions, and idempotency key and honors Retry-After, otherwise a lost mutation response can force callers to retry manually and may cause unbounded hammering.
    packages/api/src/sdk/app-state-errors.ts:46
  • The App State contract permits JSON error envelopes without a details member, but this exported payload type requires both details and details.error. That makes valid 4xx/5xx payloads unrepresentable and forces consumers to assert a shape the wire contract does not guarantee; model details/its nested error as optional and narrow before reading them.
  • Files reviewed: 27/27 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@basit3407
basit3407 merged commit e49bf47 into main Sep 17, 2026
7 checks passed
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.

3 participants