This is Part 2 of the series. If you have not read it yet, start with Part 1 — Specification: README-first workflows, MVP contracts, and layered packages (@slothy/core, @slothy/angular).
Here we focus on what keeps that contract honest over time: versioning, changelog discipline, and testing.
Versioning — SemVer with intent
Semantic versioning (MAJOR.MINOR.PATCH) is a communication tool. Consumers read a version bump as a signal:
| Bump | Typical meaning |
|---|---|
| MAJOR | Breaking change to the supported public API (or behavior users relied on). |
| MINOR | New backward-compatible features (new optional config, new export). |
| PATCH | Bug fixes that preserve the documented contract. |
What counts as “breaking”?
Be stricter than “the build still passes.” Breaking changes include:
- Renaming or removing exported symbols.
- Tightening types in a way that rejects previously valid inputs.
- Changing default behavior when the old behavior was observable and undocumented—but consumers will still depend on it.
If you must change behavior that people rely on, either document the old behavior as unsupported in a MINOR with a deprecation path, or ship a MAJOR with a migration note.
The 0.x era
While the API is still fluid, 0.y.z signals instability: MINOR may include breaking changes until you commit to 1.0.0. After 1.0.0, treat MAJOR bumps as a big deal: migration guide, codemods if possible, and a clear story in the changelog.
Aligning multiple packages (@slothy/*)
If you run a monorepo with @slothy/core and @slothy/angular:
- Option A — independent versions: each package has its own semver. Document which adapter versions are compatible with which core range (peer dependency + release notes).
- Option B — lockstep: one version number for all packages (simpler for consumers, heavier for maintainers).
Pick one strategy and document it in the root README. Avoid “silent” peer drift.
Changelog — one source of truth
Automated git logs are noisy. A human changelog (see Keep a Changelog) helps consumers answer: Should I upgrade today?
Recommended sections:
- Added — new APIs, features.
- Changed — behavior changes that are not breaking.
- Deprecated — what will be removed, and when.
- Removed — actually deleted in this release.
- Fixed — bug fixes.
- Security — advisories.
Link changes to the public API
Instead of “fixed internal refactors,” prefer:
Fixed:
createSlothyClientno longer throws whenstorageisindexed_dband the store is empty (issue #42).
That ties the note to what users do, not how you refactored folders.
Migration guides for MAJOR
For every MAJOR, add MIGRATION.md (or a section in the changelog) with:
- Before / after code snippets.
- Why the change happened (one paragraph).
- Mechanical steps (search-replace, renamed exports).
Releases — predictable rhythm
Whether you publish to npm (public or private) or to an internal registry:
- Tag in git (
v2.0.0) matching the published version. - CI runs on the tag: tests, build, optional bundle-size check.
- Publish from a clean tree (many teams use
npm publish/pnpm publishin CI only).
For organizations, pin who can publish and which branch may release (e.g. main only).
Deprecations
Use TypeScript @deprecated JSDoc, runtime console.warn (sparingly), and changelog entries with a removal target (“removed in v3”).
Testing — protect the contract, not the folders
1. Test the public API first
Your tests should import @slothy/core the same way consumers do (from the built entry, not deep paths into src/lib/domain/... unless those paths are officially supported).
// slothy-core.public-api.spec.ts
import { createSlothyClient } from '@slothy/core';
it('creates a client with in_memory storage', async () => {
const client = await createSlothyClient({ storage: 'in_memory', debug: false });
expect(client).toBeDefined();
});
If refactoring internals breaks only internal tests, you moved too much logic away from the supported surface.
2. Contract tests for adapters
For @slothy/angular, assert that forRoot provides a client that behaves like core:
- Same task operations succeed/fail under the same config.
- Tokens resolve without duplicate
SlothyClientinstances in typical setups.
3. Avoid testing implementation details
Do not assert private method names or internal module graphs unless they are part of your stability promise.
4. Matrix what matters
At minimum:
- Unit tests for pure domain logic.
- Integration tests for storage backends (
in_memoryvsindexed_dbin a headless environment when feasible).
For Angular adapters, use TestBed smoke tests that imports compile and forRoot registers providers.
CI as a gate
A practical baseline:
| Check | Why |
|---|---|
| Lint + format | Keeps API exports and docs consistent. |
tsc --noEmit / build |
Catches broken types before publish. |
| Tests | See above. |
| Pack dry-run | Ensures package.json files / exports ship what you think. |
Optional but valuable: bundle size limits on @slothy/core to catch accidental heavy dependencies.
How Part 2 connects to Part 1
| Part 1 | Part 2 |
|---|---|
| README as contract | Changelog explains how that contract evolves |
| MVP boundaries | SemVer encodes what “safe upgrade” means |
| Layered packages | Version alignment + adapter testing |
What’s next?
Continue with Part 3 — Contributors & documentation for CONTRIBUTING.md, issue templates, RFCs, and API docs (TSDoc / generated reference).
Tags: open source, library, TypeScript, Angular, semver, testing