* feat(sync-core): drop shared-schema vector-clock compat re-export and app enum - Remove the shared-schema → sync-core compatibility re-export of vector-clock types/functions; retarget app files (vector-clock.ts,operation-log.const.ts) and the server (sync.types.ts) to import from @sp/sync-core directly - Add @sp/sync-core to super-sync-server/package.json deps (it was load-bearing through the re-export) - Convert VectorClockComparison from a bare type to an as const object + derived type in sync-core,drop the app-side enum copy and the as cast - Update spec imports and pa ckage-boundaries.md * docs: remove stale shared-schema vector-clock references - Update comments in vector-clocks.md, client vector-clock.ts, and server sync.types.ts to reference @sp/sync-core directly - Remove @sp/sync-core dependency from shared-schema/package.json - Regenerate package-lock.json to reflect the removed edge * docs(sync): fix orphaned VectorClockComparison comment The comment block describing VectorClockComparison was left dangling above no declaration after the app-side enum was removed, and still claimed 'Uses enum for client-side ergonomics'. Move it above the re-export it documents and correct the wording. --------- Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
6.8 KiB
Sync Package Boundaries
Status: Active Last Updated: May 13, 2026
This note documents the package split used by the operation-log sync stack. The goal is to keep reusable sync logic framework-agnostic while leaving Super Productivity domain wiring in the app.
Dependency Direction
Allowed direction:
src/app
-> @sp/sync-providers
-> @sp/sync-core
src/app
-> @sp/sync-core
packages/super-sync-server
-> @sp/sync-core
-> @sp/shared-schema
Rules:
@sp/sync-coremust not import Angular, NgRx,src/app,@sp/shared-schema,@sp/sync-providers, or provider-specific code.@sp/sync-providersmay import only public@sp/sync-coreexports. It must not deep-import@sp/sync-core/*, Angular, NgRx,src/app, or@sp/shared-schema.- The app may import both packages and is responsible for Angular dependency injection, NgRx, Electron/Capacitor bridges, config UI, OAuth routing, and Super Productivity-specific model wiring.
packages/shared-schemaowns schema contracts and validators shared between app and server. It has no dependency on@sp/sync-core.packages/super-sync-serverdepends on both@sp/shared-schema(HTTP contract types and validation schemas) and@sp/sync-core(vector-clock algorithms).
Ownership
@sp/sync-core owns reusable sync-engine primitives:
- generic operation and apply types;
- vector-clock compare, merge, and prune algorithms;
- pure conflict, import-filter, upload/download, replay, compression, and prefix helpers;
- structural entity-registry contracts;
- app-facing port contracts and the privacy-aware
SyncLoggerinterface.
@sp/sync-providers owns bundled provider implementations and provider-neutral
contracts:
- Dropbox, WebDAV, Nextcloud, SuperSync, and LocalFile provider classes;
- file-based sync envelope types and provider response contracts;
- provider-owned file envelope constants such as
sync-data.jsonand file-sync version keys; - credential, file-adapter, platform-info, web-fetch, native-HTTP, storage, and response-validator ports;
- provider-shared error classes, PKCE helpers, retry helpers, and safe logging metadata helpers.
Cross-provider utilities belong in @sp/sync-providers when they are reusable
by provider implementations but not by the generic engine. Existing examples are
provider-shared error classes, PKCE, retry predicates, native-HTTP retry, and
safe log-metadata helpers.
New bundled providers should follow the Dropbox/WebDAV/SuperSync/LocalFile
pattern: put provider-owned protocol logic and provider-neutral contracts in
@sp/sync-providers, then compose app-only credentials, platform bridges,
validators, OAuth routing, and UI config in thin app-side factories. If a
provider is app-specific or plugin-provided rather than bundled, implement it
app-side against the provider contracts instead of widening the package surface.
src/app owns host-specific configuration and choreography:
ActionType,ENTITY_TYPES,SyncProviderId, provider lists, and storage prefixes such asREMOTE_FILE_CONTENT_PREFIXandPRIVATE_CFG_PREFIX;- entity registry construction from feature reducers/selectors;
- wrapped full-state payload shape, import reasons, repair payloads, and
validation against
@sp/shared-schema; - Angular services, NgRx dispatch/replay conversion, local-action filtering, hydration windows, archive side effects, provider factories, OAuth callbacks, config dialogs, and platform bridge implementations.
packages/shared-schema owns Super Productivity schema contracts and validators
that are shared between app and server. In this boundary it should stay
SP-coupled and should not become a dependency of @sp/sync-core or
@sp/sync-providers.
Public Exports
Package consumers should import from package barrels only:
import { compareVectorClocks } from '@sp/sync-core';
import { Dropbox, PROVIDER_ID_DROPBOX } from '@sp/sync-providers/dropbox';
Do not import from package internals such as @sp/sync-core/src/*,
@sp/sync-providers/src/*, or dist/*. If a host needs a symbol, promote it to
the package barrel deliberately and check that it is not app-owned.
The root @sp/sync-providers barrel has been removed; consumers MUST import
from focused subpath barrels: @sp/sync-providers/dropbox, /webdav,
/super-sync, /local-file, /http, /errors, /file-based, /pkce,
/platform, /provider-types, /credential-store, and /log. Provider
classes, provider-owned string constants, and shared privacy-boundary logging
helpers are exported there, but app enums such as SyncProviderId are not.
Internal helpers such as WebDAV API/adapter classes stay unexported unless a
second host needs them.
@sp/sync-core still exports deprecated full-state op compatibility defaults
and host-defined OpType.SyncImport / BackupImport / Repair strings for
existing consumers. New reusable hosts should provide their own full-state
operation strings through createFullStateOpTypeHelpers().
Privacy Boundary
Package logging must use SyncLogger and safe structured metadata only.
SyncLogger is a privacy-aware port shape; it does not sanitize arbitrary
metadata. Call sites are responsible for passing already-scrubbed values, and
enforcement is currently code review plus focused tests.
IDs, counts, action strings, entity types, provider IDs, and error names/codes are acceptable. URL metadata is acceptable only after the caller strips query strings, fragments, credentials, tokens, raw response bodies, and user-provided path segments such as file names, emails, share IDs, or folder names. Prefer coarse path templates, provider operation names, host-only values, or a provider-owned relative path category over raw URL paths.
Full entities, operation payloads, task titles, note text, raw provider
responses, credentials, headers, and encryption material must stay out of
exportable logs. A lint rule for unsafe direct logging remains a possible
follow-up; until then, new movable/provider code should use SyncLogger and
tests should assert privacy-sensitive catch paths.
Tests
The ESLint package-boundary overrides apply to all TypeScript files under
packages/sync-core/** and packages/sync-providers/**, including tests. Tests
may import their own package internals through relative paths for white-box
coverage. @sp/sync-providers tests may import public @sp/sync-core exports,
but should not import @sp/sync-core internals or sync-core test helpers.
Verification
Before moving code across these boundaries, run:
npm run lint
npm run sync-core:build
npm run sync-providers:build
npm run packages:test
For a quick boundary spot-check, use:
rg -n "from ['\"](@angular|@ngrx|@sp/shared-schema|src/app|@sp/sync-core/src|@sp/sync-core/)|import\(['\"](@angular|@ngrx|@sp/shared-schema|src/app|@sp/sync-core/src|@sp/sync-core/)" packages/sync-core/src packages/sync-providers/src