diff --git a/paskia/db/jsonl.py b/paskia/db/jsonl.py index 31c5d84..2dd2aa3 100644 --- a/paskia/db/jsonl.py +++ b/paskia/db/jsonl.py @@ -105,7 +105,7 @@ def create_change_record( # Actions that are allowed to create a new database file -_BOOTSTRAP_ACTIONS = frozenset({"bootstrap", "migrate"}) +_BOOTSTRAP_ACTIONS = frozenset({"bootstrap", "migrate:sql"}) async def flush_changes( @@ -187,15 +187,35 @@ class JsonlStore: # Update previous state to migrated data FIRST (to avoid transaction hardening reset) self._previous_builtins = data_dict - # Persist migration by manually computing and queueing the diff + # Persist version migration by manually computing and queueing the diff if migrated: + new_version = data_dict.get("v", 1) diff = compute_diff(original_dict, data_dict) if diff: + action = f"migrate:v{new_version}" self._pending_changes.append( - create_change_record("migrate", diff, user=None) + create_change_record(action, diff, user=None) ) - _logger.info("Queued migration changes for persistence") + diff_json = json.dumps(diff, default=str) + print(f"{action}: {diff_json}", file=sys.stderr) await self.flush() + # Update original_dict to reflect persisted state + original_dict = copy.deepcopy(data_dict) + + # Normalize via msgspec round-trip (handles omit_defaults etc.) + # This ensures _previous_builtins matches what msgspec would produce + normalized_dict = msgspec.to_builtins(self.db) + diff = compute_diff(original_dict, normalized_dict) + if diff: + action = "migrate:msgspec" + self._pending_changes.append( + create_change_record(action, diff, user=None) + ) + diff_json = json.dumps(diff, default=str) + print(f"{action}: {diff_json}", file=sys.stderr) + await self.flush() + # Update _previous_builtins to normalized state + self._previous_builtins = normalized_dict else: # No data loaded - _previous_builtins stays as empty dict pass @@ -252,7 +272,8 @@ class JsonlStore: current_state = msgspec.to_builtins(self.db) if current_state != self._previous_builtins: # Allow bootstrap/migrate to create a new database from empty state - if action in _BOOTSTRAP_ACTIONS and not self._previous_builtins: + is_bootstrap = action in _BOOTSTRAP_ACTIONS or action.startswith("migrate:") + if is_bootstrap and not self._previous_builtins: pass # Expected: creating database from scratch else: diff = compute_diff(self._previous_builtins, current_state) diff --git a/paskia/db/migrations.py b/paskia/db/migrations.py index a08888f..6eddec6 100644 --- a/paskia/db/migrations.py +++ b/paskia/db/migrations.py @@ -5,10 +5,6 @@ Migrations are applied during database load based on the version field. Each migration should be idempotent and only run when needed. """ -import logging - -_logger = logging.getLogger(__name__) - def apply_migrations(data_dict: dict) -> bool: """Apply any pending schema migrations to the database dictionary. @@ -29,6 +25,5 @@ def apply_migrations(data_dict: dict) -> bool: org_data.pop("created_at", None) data_dict["v"] = 1 migrated = True - _logger.info("Applied schema migration: v0 -> v1 (removed org.created_at)") return migrated diff --git a/paskia/migrate/__init__.py b/paskia/migrate/__init__.py index f2adb17..7777a8b 100644 --- a/paskia/migrate/__init__.py +++ b/paskia/migrate/__init__.py @@ -248,7 +248,7 @@ async def migrate_from_sql( print(f" Migrated {len(token_models)} reset tokens") # Queue and flush all changes using the transaction mechanism - with db.transaction("migrate"): + with db.transaction("migrate:sql"): pass # All data already added to _data, transaction commits on exit await store.flush()