Skip to content

Move implementation details into separate package#993

Open
simolus3 wants to merge 12 commits into
v2from
internals-package
Open

Move implementation details into separate package#993
simolus3 wants to merge 12 commits into
v2from
internals-package

Conversation

@simolus3

@simolus3 simolus3 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

This moves @internal members from @powersync/common into the new @powersync/shared-internals package. The idea is that @powersync/shared-internals would never be imported by users, so we can change it freely. @powersync/common still contains and exports common interfaces relevant across multiple SDKs.

This PR effectively solves two long-term goals:

  1. We want to be explicit about our public API, and make it harder for users to accidentally rely on implementation details. Exporting stuff from @powersync/common (which is then re-exported from our SDKs) made it very easy to get this wrong. By moving things to @powersync/shared-internals (which we obviously wouldn't re-export), we're able to encapsulate implementation details much better.
  2. Adding new members to abstract classes shouldn't be a breaking change. Previously, AbstractPowerSyncDatabase was both a class and effectively a public interface. We often forgot about the second part, and added new methods without realizing that this is a breaking change! Since implementation details are now part of @powersync/shared-internals, interfaces are extracted explicitly.

For the most part, this PR just moves code around: Identify stuff marked as @internal in @powersync/common, move it into the internals package, update imports, repeat. There are two exceptions, which I'll outline below.

AbstractPowerSyncDatabase

This class is both an interface (users imported it from @powersync/common as a type) and an implementation detail (it's an abstract class only meant to be extended from our SDKs). This makes it very easy to leak implementation details into the interface, so this introduces a split:

  1. In @powersync/common, this introduces CommonPowerSyncDatabase: A TypeScript interface containing public members from the old AbstractPowerSyncDatabase class. When writing APIs that need to work across SDKs (attachments, React hooks, ...), this is the type to use. I've kept AbstractPowerSyncDatabase around as a type alias for compatibility.
  2. In @powersync/shared-internals, BasePowerSyncDatabase implements that interface, and our existing SDKs continue to extend that class.

There is a crucial detail here: We can't have an export class PowerSyncDatabase extends BasePowerSyncDatabase anymore, as that would leak everything defined on BasePowerSyncDatabase into the implicit interface for PowerSyncDatabase. So, we make the actual implementation class private and only export the constructor, which is declared to return a CommonPowerSyncDatabase. There's an exception for the web, where we export WebPowerSyncDatabase to be able to extend it in Capacitor / Nuxt SDKs.

SyncStatus

The sync status was a mix of public and private fields, with e.g. sync streams being derived from core extension data structures but mentioned in DataFlowStatus. I don't want to keep those structures in @powersync/common because they're an implementation detail, so this restructures SyncStatus into a public interface and an internal implementation (SyncStatusSnapshot in @powersync/shared-internals). A similar transformation is applied to some CRUD classes to reduce the public API surface.

Remaining work

There are five remaining warnings about @internal members being exported in @powersync/common:

  • DBAdapterDefaultMixin and DBGetUtilsDefaultMixin: I'll open a follow-up PR to clean up the interface of DB adapters further, that should make these mixins unecessary.
  • extractTableUpdates and isBatchedUpdateNotification: In the same follow-up PR, I'll normalize how we represent table updates to make this unecessary.
  • Mutex (the type) is used internally in the attachments implementation, but the implementation is in shared-internals. So common exports the interface for the implementation package. This is probably fine.

@changeset-bot

changeset-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 98c6f74

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@powersync/react-native Major
@powersync/web Major
@powersync/capacitor Major
@powersync/common Minor
@powersync/tauri-plugin Minor
@powersync/node Minor
@powersync/nuxt Major
@powersync/vue Minor
@powersync/tanstack-react-query Patch
@powersync/diagnostics-app Patch
@powersync/op-sqlite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@simolus3 simolus3 marked this pull request as ready for review June 16, 2026 07:27
@simolus3 simolus3 requested a review from Chriztiaan June 16, 2026 14:17
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.

1 participant