Skip to content

Releases: ihusnainalii/SwiftLocalStorage

v1.1.2: Swift Package Index docs and a CI fix

Choose a tag to compare

@ihusnainalii ihusnainalii released this 27 Sep 15:56
d6e5d31

Swift Package Index documentation and a CI fix; the library is unchanged from 1.1.1.

Added

  • .spi.yml so 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 main when a newer commit is pushed; only superseded pull-request runs are cancelled, so every commit on main gets a complete result instead of a cancelled (red) check.

v1.1.1: Docs, data flow diagrams and a simpler release workflow

Choose a tag to compare

@ihusnainalii ihusnainalii released this 27 Sep 15:32

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

Choose a tag to compare

@ihusnainalii ihusnainalii released this 25 Sep 19:54

Added

  • Design spec for 1.1, the query track (docs/specs/2026-09-25-swiftlocalstorage-v1.1-queries-design.md).
  • StorageFilter.hasPrefix(_:_:) and StorageFilter.oneOf(_:_:) for string indexes, evaluated in the store. String conditions on one index intersect; a contradictory set (such as two different equals) 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), and LocalRepository.updates(matching:orderedBy:options:).
  • Benchmarks: "filter 1,000 by prefix" and "filter 1,000 by any-of" rows; docs/benchmarks.md refreshed for 1.1.

Changed

  • The SwiftData engine composes indexed queries from only the active conditions (built with PredicateExpressions) instead of one #Predicate with switched-off terms.
  • fetch(_:where:options:) and migrateAll(_:) walk the type 500 records at a time instead of decoding every record at once; where: stops as soon as offset + limit matches 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 to LocalStorage+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

Choose a tag to compare

@ihusnainalii ihusnainalii released this 25 Sep 18:52

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, configuration migrations:, 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.
  • SwiftLocalStorageBenchmarks executable 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 in docs/benchmarks.md.
  • CI: an API breakage check (swift package diagnose-api-breaking-changes against 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 MigrationStage is not Sendable, 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

Choose a tag to compare

@ihusnainalii ihusnainalii released this 25 Sep 15:49

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 / .number over Int, Double, Float, Date, Bool, …), stored next to each record.
  • fetch(_:matching:orderedBy:options:), count(_:matching:) and page(_:matching:orderedBy:page:pageSize:) with StorageFilter (equals, atLeast, atMost, between; ANDed) and StorageIndexOrder, 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 CurrentStorageSchema alias shared with StoredRecord, 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: Product declares category and price indexes; the Catalog filters and sorts by price in the store via page(_:matching:orderedBy:page:pageSize:), so price order now holds across pages.

v0.5.0: DTO migrations

Choose a tag to compare

@ihusnainalii ihusnainalii released this 25 Sep 13:04

Added

  • Design spec for v0.5 DTO migrations (docs/specs/2026-09-25-swiftlocalstorage-v0.5-migrations-design.md).
  • LocalStorageVersioned to declare a DTO's current version (unversioned types are version 1) and StorageMigration steps (typed Old → New, or raw payload) registered in LocalStorageConfiguration.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.version and StorageMigrationError (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 migrationFailed in the error table; roadmap, security policy and demo README updated for 0.5.

Changed

  • Breaking (pre-1.0): LocalStorageError gains migrationFailed(key:underlying:) (and Code.migrationFailed); exhaustive switches need the new case.
  • Internal: records now store their DTO version (the existing schemaVersion column) on insert and update; new engine rewrite(key:payload:schemaVersion:) for migration write-backs.
  • Demo app: Note gains a required priority (stored version 2) with a v1 → v2 StorageMigration, 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

Choose a tag to compare

@ihusnainalii ihusnainalii released this 25 Sep 04:43

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), .cleared and .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 .task loops.
  • all(_:batchSize:) → StorageSequence<T>: iterate a large type oldest first, loading batchSize records 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.upsert reports 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 from changes(of:) and refreshes counts on every change.

v0.3.0: Sorting, paging and filtering

Choose a tag to compare

@ihusnainalii ihusnainalii released this 25 Sep 04:25

Added

  • Design spec for v0.3 queries (docs/specs/2026-09-25-swiftlocalstorage-v0.3-queries-design.md).
  • FetchOptions (sort, limit, offset) and StorageSort (oldestFirst, newestFirst, recentlyUpdated, leastRecentlyUpdated, ties broken by insertion order) via fetch(_:options:); sorting and slicing run inside the store so only requested rows are decoded.
  • page(_:page:pageSize:sort:) returning StoragePage (1-based page, 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: 0 with no rows (SwiftData treats fetchLimit == 0 as 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

Choose a tag to compare

@ihusnainalii ihusnainalii released this 25 Sep 03:59

Fixed

  • SwiftData engine: fetch(_:) returned records saved in the same batch (same createdAt) in random order. Records now carry an insertion sequence used 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

Choose a tag to compare

@ihusnainalii ihusnainalii released this 25 Sep 03:56

Added

  • Demo app Examples/SwiftLocalStorageDemo: a SwiftUI iOS app built with Clean Architecture + MVVM (Domain / Data / Presentation, AppContainer composition 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.