Releases: ihusnainalii/SwiftLocalStorage
Releases · ihusnainalii/SwiftLocalStorage
Release list
v1.1.2: Swift Package Index docs and a CI fix
Swift Package Index documentation and a CI fix; the library is unchanged from 1.1.1.
Added
.spi.ymlso the Swift Package Index builds and hosts the DocC documentation (built on macOS, since SwiftData is Apple-only).
Fixed
- CI no longer cancels runs on
mainwhen a newer commit is pushed; only superseded pull-request runs are cancelled, so every commit onmaingets a complete result instead of a cancelled (red) check.
v1.1.1: Docs, data flow diagrams and a simpler release workflow
Documentation and CI only; the library is unchanged from 1.1.0.
Changed
- Docs and doc comments use plain punctuation: no em dashes or bullet separators; the README tagline reads as a sentence.
Added
- README "How data flows": Mermaid diagrams of the save path, the read path (expiry, DTO migration, write-back) and live queries; also published to the wiki as "How It Works".
Removed
- release-please (workflow job, config and manifest). Releases are cut by hand; publishing a GitHub Release now runs a workflow that re-checks the tag and attaches coverage and the static DocC site. It never commits or opens pull requests, so the Release check no longer fails on every push to
main.
v1.1.0: Indexed live queries and string filters
Added
- Design spec for 1.1, the query track (
docs/specs/2026-09-25-swiftlocalstorage-v1.1-queries-design.md). StorageFilter.hasPrefix(_:_:)andStorageFilter.oneOf(_:_:)for string indexes, evaluated in the store. String conditions on one index intersect; a contradictory set (such as two differentequals) now matches nothing instead of failing a precondition.updates(of:matching:orderedBy:options:): live queries over indexed filters (emit now, then one in-store refetch per burst of writes), andLocalRepository.updates(matching:orderedBy:options:).- Benchmarks: "filter 1,000 by prefix" and "filter 1,000 by any-of" rows;
docs/benchmarks.mdrefreshed for 1.1.
Changed
- The SwiftData engine composes indexed queries from only the active conditions (built with
PredicateExpressions) instead of one#Predicatewith switched-off terms. fetch(_:where:options:)andmigrateAll(_:)walk the type 500 records at a time instead of decoding every record at once;where:stops as soon asoffset + limitmatches are found. Results are unchanged. With the update-ordered sorts, a value updated during the walk can be seen twice or missed. Observation APIs moved toLocalStorage+Observation.swift(no API change).- CI: GitHub Actions moved to their Node 24 majors (
checkout@v7,cache@v6,upload-artifact@v7,codecov-action@v7,release-please-action@v5). - README (filters table, live indexed queries, complete example now uses
updates(matching:orderedBy:), performance and limitations, upgrading row), DocC (Indexed Fields, Observing Changes, Querying) and ROADMAP updated for 1.1; release-please now also bumps the version shown in the README.
Fixed
- Opening several stores at the same time could crash on macOS 15 (SwiftData raced while building the versioned schemas; seen as "model is still editable" followed by SIGABRT/SEGV under parallel tests). Container creation is now serialised process-wide.
v1.0.0: Stable API
The API is now stable: every 1.x release stays source-compatible with this one.
Added
- README: "API at a glance" reference tables, a compiled-and-run "Complete example" (versioned + indexed DTO, migration, cache refresh, live indexed SwiftUI list, launch maintenance), "Performance" cost table and "Upgrading" guide; refreshed architecture (schema V3, index slots, two kinds of versioning), core concepts, metadata
version, configurationmigrations:, testing clock injection, known limitations and FAQ. - Design spec for 1.0, the API freeze (
docs/specs/2026-09-25-swiftlocalstorage-v1.0-design.md). - DocC catalog: landing page with every public type grouped by topic, and articles on getting started, querying, indexed fields, observing changes, migrating stored DTOs, testing and API stability.
SwiftLocalStorageBenchmarksexecutable target (not a product;swift run -c release SwiftLocalStorageBenchmarks): save, fetch and delete at 1, 100 and 1,000 records, 1 MB and 10 MB payloads, and index versus closure filtering. Results are indocs/benchmarks.md.- CI: an API breakage check (
swift package diagnose-api-breaking-changesagainst the latest release) on every pull request, and a release build of the benchmarks target.
Fixed
- CI: the package builds with Xcode 16.4 / Swift 6.1 again (SwiftData
MigrationStageis notSendable, so the schema migration stages are computed instead of stored statics); sources and tests pass SwiftLint and swift-format (6.1 and later) in strict mode. - Batch saves are linear again: the SwiftData engine looks up every key of a batch in one fetch (chunked under SQLite's variable limit) instead of one fetch per record, which rescanned the pending inserts. Saving 1,000 records in one batch drops from ~1.5 s to ~150 ms.
Changed
- README (1.0 stability promise, DocC link, benchmark figures, upgrading row), ROADMAP (1.0 shipped; a public engine moves to 2.0 candidates) and CONTRIBUTING (API breakage check, benchmarks); SECURITY supports 1.x.
v0.6.0: Indexed fields
Added
- Design spec for v0.6 indexed fields (
docs/specs/2026-09-25-swiftlocalstorage-v0.6-indexes-design.md). LocalStorageIndexed: declare up to three indexed fields per type (StorageIndex.string/.numberoverInt,Double,Float,Date,Bool, …), stored next to each record.fetch(_:matching:orderedBy:options:),count(_:matching:)andpage(_:matching:orderedBy:page:pageSize:)withStorageFilter(equals,atLeast,atMost,between; ANDed) andStorageIndexOrder, all evaluated inside the store; repository equivalents.- Automatic re-indexing: records saved before a type was indexed, or under a different declaration, are re-indexed on the next indexed query (tracked by a per-record index signature); DTO migration write-backs refresh index values.
- Index tests on both engines: string/number/Bool/Date/Double equality, ranges, ANDed and narrowed conditions, missing values, ordering with ties and nils, limit/offset/count/page, expiry, updates, re-indexing (unindexed records, changed and alternating declarations), migration write-back, repository; plus a live-query failure test.
- README "Indexed fields" section (declaration, filters table, missing values, re-indexing); roadmap, security policy and demo README updated for 0.6.
Changed
- Internal SwiftData schema V3 (nullable index slots + signature) with a lightweight V2 → V3 stage; containers now open with a single
CurrentStorageSchemaalias shared withStoredRecord, so the two cannot drift. - Schema migration tests now open real V1 and V2 store files (committed fixtures written by the old schemas) instead of creating old-schema containers in the test process.
- Demo app:
Productdeclarescategoryandpriceindexes; the Catalog filters and sorts by price in the store viapage(_:matching:orderedBy:page:pageSize:), so price order now holds across pages.
v0.5.0: DTO migrations
Added
- Design spec for v0.5 DTO migrations (
docs/specs/2026-09-25-swiftlocalstorage-v0.5-migrations-design.md). LocalStorageVersionedto declare a DTO's current version (unversioned types are version 1) andStorageMigrationsteps (typedOld → New, or raw payload) registered inLocalStorageConfiguration.migrations.- Reads upgrade older records through the step chain and write the upgraded payload back once, keeping timestamps and emitting no change events;
migrateAll(_:)upgrades a whole type eagerly. StorageMetadata.versionandStorageMigrationError(missingStep,storedVersionNewer).- Migration tests on both engines: versions on save, typed + raw chains, once-only write-back with preserved timestamps, list/page/filter reads, no events, missing step, newer record, throwing step, wrong shape, key-value,
migrateAll, legacy records. - README "Migrating stored DTOs" section and
migrationFailedin the error table; roadmap, security policy and demo README updated for 0.5.
Changed
- Breaking (pre-1.0):
LocalStorageErrorgainsmigrationFailed(key:underlying:)(andCode.migrationFailed); exhaustive switches need the new case. - Internal: records now store their DTO version (the existing
schemaVersioncolumn) on insert and update; new enginerewrite(key:payload:schemaVersion:)for migration write-backs. - Demo app:
Notegains a requiredpriority(stored version 2) with a v1 → v2StorageMigration, so notes saved by earlier demo builds upgrade on first read; priority shows as a badge and is editable.
v0.4.0: Change feeds and live queries
Added
- Design spec for v0.4 observation (
docs/specs/2026-09-25-swiftlocalstorage-v0.4-observation-design.md). changes(of:)→AsyncStream<StorageChange<T>>with.inserted,.updated,.deleted(the stored value),.clearedand.expired, delivered after each write commits.updates(of:options:)→AsyncThrowingStream<[T], Error>: a live query that emits current results, then refetches after each change (bursts coalesced), built for SwiftUI.taskloops.all(_:batchSize:)→StorageSequence<T>: iterate a large type oldest first, loadingbatchSizerecords per step.- Repository equivalents:
changes(),updates(options:),all(batchSize:). - Observation tests on both engines: event kinds and order, stored values on delete, type isolation, no events on failure, unsubscribe on cancel/release, live-query re-emit and coalescing, batched iteration.
- README "Observation and SwiftUI" section (live query in
.task, change-feed event table, batched iteration); roadmap, security policy and demo README updated for 0.4.
Changed
- Internal:
StorageEngine.upsertreports inserted keys so saves are classified as inserted/updated without extra reads; deletes read the stored value only when the type is observed. - Demo app: Notes renders from a live query (
updates()) with no manual reloads; the Inspector shows an app-lifetime live change feed merged fromchanges(of:)and refreshes counts on every change.
v0.3.0: Sorting, paging and filtering
Added
- Design spec for v0.3 queries (
docs/specs/2026-09-25-swiftlocalstorage-v0.3-queries-design.md). FetchOptions(sort,limit,offset) andStorageSort(oldestFirst,newestFirst,recentlyUpdated,leastRecentlyUpdated, ties broken by insertion order) viafetch(_:options:); sorting and slicing run inside the store so only requested rows are decoded.page(_:page:pageSize:sort:)returningStoragePage(1-basedpage,pageSize,totalCount,totalPages,hasNextPage).fetch(_:where:options:)to filter on any DTO field with a Swift closure (in memory, after decoding), then sort and slice.- Repository equivalents:
fetchAll(options:),fetch(where:options:),page(_:pageSize:sort:). - Query tests on both engines (every sort, ties, limit/offset edges, expired exclusion, page math, filters, repository) and query logging tests.
- README "Queries: sorting, paging, filtering" section; roadmap, security policy and demo README updated for 0.3.
Fixed
- SwiftData engine answers
limit: 0with no rows (SwiftData treatsfetchLimit == 0as unlimited).
Changed
- Demo app: Catalog pages the cache 4 at a time with Load more (
page(_:page:pageSize:)), filters by category (fetch(_:where:)), shows the empty state inside the list, and defaults the cache lifetime to 1 minute.
v0.2.3: Batch insertion order fix
Fixed
- SwiftData engine:
fetch(_:)returned records saved in the same batch (samecreatedAt) in random order. Records now carry an insertionsequenceused as a tiebreaker, matching the in-memory engine and the documented insertion order.
Changed
- Internal SwiftData schema V2 (adds
StoredRecord.sequence); existing V1 stores upgrade automatically through a lightweight migration stage. No public API change.
Added
- Regression tests: 50-record batch ordering on both engines, and a migration test that writes a real V1 store to disk and reopens it with the current schema.
v0.2.2: Clean-architecture demo app
Added
- Demo app
Examples/SwiftLocalStorageDemo: a SwiftUI iOS app built with Clean Architecture + MVVM (Domain / Data / Presentation,AppContainercomposition root) with Catalog (cache-first with expiry countdown), Notes (repository CRUD), Settings (key-value) and Inspector (counts,removeExpired, live storage log) tabs. - Demo unit tests (Swift Testing) running the real repositories and view models on an in-memory store with an injected clock: cache-first, expiry, sorting, notes, settings, maintenance.
- CI job that builds and tests the demo app on the iOS Simulator.
- Demo app README (tabs, architecture, tests) and a Demo app section in the main README.