Skip to content

Feat/v2 query layer - #1

Merged
webcoderspeed merged 4 commits into
masterfrom
feat/v2-query-layer
Aug 5, 2026
Merged

Feat/v2 query layer#1
webcoderspeed merged 4 commits into
masterfrom
feat/v2-query-layer

Conversation

@webcoderspeed

Copy link
Copy Markdown
Owner

No description provided.

…nfrastructure

Turns Exostate into a single package covering both client state and server
state, and fixes several bugs that would have shipped broken.

Query layer (new)
- QueryClient with stale-while-revalidate caching, request deduplication,
  retries with exponential backoff, refetch on focus/reconnect, polling,
  and garbage collection of unobserved entries.
- createMutation with onMutate/onError context for optimistic updates and
  rollback.
- dehydrate/hydrate for SSR; dataUpdatedAt is preserved so staleTime is
  measured from the server's fetch time and the client avoids a refetch
  waterfall on first paint.
- React bindings: QueryClientProvider, useQuery, useMutation.

Core store
- Single commit() write path so plugins and batching cannot be bypassed.
- Plugin pipeline is now actually wired: onBeforeUpdate (can transform the
  committed value), onAfterUpdate, onSubscribe, onUnsubscribe, onDestroy.
  Previously plugins were registered into a WeakMap that nothing ever read.
- Lifecycle hooks with unmountDelay for lazy resource management.
- Microtask notification batching plus flush().
- destroy() tears down plugins and cancels pending notifications.

Bug fixes
- combineStores returned state frozen at construction time when read without
  an active subscription, and did not catch up after detach/re-attach.
- asyncAction shared one AbortController across invocations, so aborting one
  call cancelled a different one; added latestOnly so a slow earlier request
  cannot clobber a newer one.
- useSelector recreated its subscribe callback every render (tearing down and
  re-adding the listener) and returned an uncached getSnapshot, which trips
  React's "getSnapshot should be cached" infinite loop with inline object
  selectors.
- withMiddleware did not proxy destroyed, listeners or current.
- Unsubscribe was not idempotent, so a double call fired lifecycle hooks twice.
- freeze() could recurse forever on self-referential state.
- persistFs could interleave concurrent writes into a torn file.

Packaging (these would have shipped broken)
- src/vue and src/solid were excluded from tsconfig while package.json
  advertised ./vue and ./solid exports, so those entry points were never built.
- persistFs imported node:fs from the core entry, breaking browser bundlers;
  moved to the exostate/node subpath. CI now asserts the core stays free of
  node: builtins.
- Switched to moduleResolution NodeNext, which surfaced extensionless relative
  imports that emitted ESM Node cannot resolve.

Also adds: IndexedDB persistence, shallow/deepEqual comparators, Svelte and
Solid selector adapters, CI with a Node 18/20/22 matrix and published-ESM smoke
test, semantic-release, CodeQL, size budgets, issue/PR templates, dependabot,
CONTRIBUTING, SECURITY, LICENSE, and a rewritten README.

Test suite: 205 tests across 29 files, all passing.
CI installs with `npm ci`, which only resolves what package.json declares.
Verified by running every CI step against a clean lockfile install.

- typescript was never declared, so `npm run build` and `npm run typecheck`
  worked locally only via a hoisted install and would fail in CI.
- @eslint/js is required by eslint.config.cjs but was undeclared. Pinned to
  ^9 to match the installed eslint major (v10 demands eslint 10).
- @vitest/coverage-v8 is needed by the test:coverage / test:ci scripts.
- esbuild is imported directly by scripts/bundle-for-size.mjs; it previously
  resolved only as a transitive dependency of vitest.
- Aligned size-limit and @size-limit/file, which had drifted to mismatched
  majors (12 vs 13). Pinned to ^12: v13 requires fs/promises.glob, which
  needs Node 22 and contradicts the package's Node 18 baseline.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 40 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: 90198567-b9d9-40c9-97d9-66ad28ed3c12

📥 Commits

Reviewing files that changed from the base of the PR and between 768d583 and a20ba31.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (66)
  • .github/ISSUE_TEMPLATE/bug_report.yml
  • .github/ISSUE_TEMPLATE/config.yml
  • .github/ISSUE_TEMPLATE/documentation.yml
  • .github/ISSUE_TEMPLATE/feature_request.yml
  • .github/dependabot.yml
  • .github/pull_request_template.md
  • .github/workflows/ci.yml
  • .github/workflows/codeql.yml
  • .github/workflows/release.yml
  • .github/workflows/size-limit.yml
  • .gitignore
  • .releaserc.json
  • CHANGELOG.md
  • CODE_OF_CONDUCT.md
  • CONTRIBUTING.md
  • LICENSE
  • README.md
  • SECURITY.md
  • eslint.config.cjs
  • package.json
  • scripts/bundle-for-size.mjs
  • src/async-action.ts
  • src/combine.ts
  • src/computed.ts
  • src/define-store.ts
  • src/derived.ts
  • src/devtools-redux.ts
  • src/devtools.ts
  • src/equality.ts
  • src/errors.ts
  • src/event-source.ts
  • src/history.ts
  • src/index.ts
  • src/middleware.ts
  • src/node/index.ts
  • src/persist-idb.ts
  • src/persist.ts
  • src/plugin.ts
  • src/query.ts
  • src/react/index.ts
  • src/react/query.ts
  • src/serialize.ts
  • src/solid/index.ts
  • src/ssr.ts
  • src/state.ts
  • src/store-factory.ts
  • src/store.ts
  • src/svelte/index.ts
  • src/transaction.ts
  • src/types.ts
  • src/vue/index.ts
  • tests/async-action.test.ts
  • tests/computed.test.ts
  • tests/define-store.test.ts
  • tests/destroy.test.ts
  • tests/event-source.test.ts
  • tests/patch.test.ts
  • tests/persist.fs.test.ts
  • tests/plugin.test.ts
  • tests/public-api.test.ts
  • tests/query.test.ts
  • tests/react.query.test.ts
  • tests/react.selector.stability.test.ts
  • tests/regressions.test.ts
  • tests/store-factory.test.ts
  • tsconfig.json
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v2-query-layer

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.

@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

… package

The Node 18 test job failed: vitest 4 declares engines
`^20 || ^22 || >=24`, and its rolldown dependency imports `styleText` from
node:util, which only exists in Node 20.12+. The unit suite genuinely cannot
run on Node 18.

Dropping Node 18 from the matrix alone would have silently weakened the
`engines.node: >=18` claim in package.json, so instead:

- Unit tests now run on Node 20 and 22.
- A new `node18-compat` job downloads the dist artifact and exercises the
  built package on Node 18 with no dependencies installed — covering the
  store, computed, combineStores, the query layer (AbortController),
  microtask batching (queueMicrotask), and the exostate/node entry.
  The published output uses nothing newer than ES2020, so this passes; if
  someone later reaches for a newer runtime API, the job fails and the
  engines field has to be raised deliberately.
- Release now also gates on node18-compat.

Docs corrected: development requires Node 20+, while the published package
still supports Node 18.
…t files

The node18-compat job failed with "Named export 'QueryClient' not found —
the requested module is a CommonJS module". That was a flaw in the test, not
the package: the job downloaded only dist/, so there was no package.json and
therefore no "type": "module", and Node parsed the ESM output as CommonJS.

Rather than just copying package.json alongside dist/, the job now tests what
a consumer actually gets:

- The build job packs the publishable tarball and uploads it as an artifact.
- node18-compat installs that tarball into a clean project with
  "type": "module" and imports by bare specifier.

This is a stronger check than the original — it exercises the exports map,
package resolution, and "type": "module" handling, not just file contents. It
also now asserts the exostate/node subpath resolves.

Verified end to end locally: npm pack, install into a fresh project, and run
the exact compat script against the installed package.

Also fixes `npm pack --pack-destination ./pack`, which fails when the target
directory does not already exist.
@webcoderspeed
webcoderspeed merged commit 64d19c0 into master Aug 5, 2026
10 checks passed
@webcoderspeed
webcoderspeed deleted the feat/v2-query-layer branch August 5, 2026 20:08
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

🎉 This issue has been resolved in version 1.1.0.
Install it: npm install exostate@1.1.0

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants