Skip to content

Make Stored in-memory-only and add GraphUserDefault#110

Draft
muukii wants to merge 4 commits into
mainfrom
codex/fix-user-defaults-storage
Draft

Make Stored in-memory-only and add GraphUserDefault#110
muukii wants to merge 4 commits into
mainfrom
codex/fix-user-defaults-storage

Conversation

@muukii

@muukii muukii commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • make Stored a final, in-memory-only graph primitive
  • remove the generic Storage abstraction and the @GraphStored(backed:) syntax
  • add @GraphUserDefault as a reference-identity UserDefaults synchronization owner composed around an internal Stored
  • serialize Foundation access and publish graph updates synchronously in access order after releasing coordinator and node locks
  • split the primitive, UserDefaults support, macro, tests, and documentation into focused files

Motivation

Stored is the graph's primitive mutation boundary. Making external persistence replace its value storage mixed graph behavior with Foundation cache, observer, and lifecycle concerns, and required generic lock hand-offs inside the source node.

This change inverts that dependency: Stored always owns its value in memory, while GraphUserDefault is a separate model that owns UserDefaults synchronization and delegates graph behavior to an internal Stored node.

GraphUserDefault is a final class, and its projected value returns the same instance. Retaining $property therefore retains the observer and synchronization lifecycle, while writes through the projection follow the same persistence path as writes through the wrapped property.

Breaking changes

  • remove Storage, InMemoryStorage, UserDefaultsStorage, _Stored, UserDefaultsStored, and GraphStorageBacking
  • remove the @GraphStored(backed:) overload; @GraphStored is now always in-memory
  • replace UserDefaults-backed declarations with @GraphUserDefault("key")
  • change a projected UserDefaults value from Stored<Value> to GraphUserDefault<Value>

No compatibility layer is included intentionally.

Concurrency and lifecycle

  • UserDefaultsAccessCoordinator serializes UserDefaults reads and writes across value types and store instances
  • reentrant notifications enqueue publications into the current access batch
  • graph publications run synchronously in access order after releasing the Foundation access lock
  • the internal Stored publishes dependency invalidation and callbacks only after releasing its node lock
  • GraphUserDefault owns its observer token and removes it when the final projected or wrapper reference is released

Validation

  • swift test --filter GraphUserDefaultTests (16 tests passed)
  • swift test (17 XCTest tests and 147 Swift Testing tests passed)
  • git diff --check
  • CI build-development passed
  • CI package-test passed the complete @GraphUserDefault suite, but the job remains red after one rerun because four unchanged tests time out with the same eight issues already present on current main

The matching failures are concurrentInvocationsAreSerializedAndCoalesced, readWaitsForCoordinatedMutation, sharedCoordinatorBlocksReadsAcrossStores, and stuckMain. They occur in both the PR run and the current main run.

@muukii muukii changed the title Synchronize UserDefaults-backed storage Make Stored in-memory-only and add GraphUserDefault Jul 18, 2026
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