capture method

Snapshot capture(
  1. String appVersion, {
  2. Map<String, dynamic>? metadata,
  3. List<String>? keys,
})

Creates a snapshot by collecting state from all registered contributors.

This method:

  1. Gets all registered keys in sorted order (deterministic)
  2. Calls each exporter function to collect state
  3. Creates a Snapshot with timestamp, appVersion, schemaVersion, and states

Parameters:

  • appVersion: Version string of the application (e.g., "1.2.3")
  • metadata: Optional custom metadata to attach to the snapshot
  • keys: Optional list of keys to capture. If provided, only the specified state contributors will be included. If null, all registered contributors are captured.

Returns: A Snapshot containing all captured state from registered contributors.

Throws:

  • StateError if no state contributors are registered
  • ArgumentError if keys contains a key that is not registered
  • Any exception thrown by an exporter function (propagated)

Deterministic Order: State is collected in alphabetical order of keys, ensuring deterministic snapshot creation regardless of registration order.

Implementation

Snapshot capture(
  String appVersion, {
  Map<String, dynamic>? metadata,
  List<String>? keys,
}) {
  final registry = SnapshotRegistry.instance;
  final allKeys = registry.getAllKeys();

  if (allKeys.isEmpty) {
    throw StateError(
      'No state contributors registered. Register at least one state '
      'contributor before capturing a snapshot.',
    );
  }

  // Determine which keys to capture
  final keysToCapture = keys != null
      ? (keys.toList()..sort())
      : allKeys;

  // Validate that all requested keys are registered
  for (final key in keysToCapture) {
    if (!registry.isRegistered(key)) {
      throw ArgumentError.value(
        key,
        'keys',
        'Key "$key" is not registered. Cannot capture unregistered state.',
      );
    }
  }

  if (keysToCapture.isEmpty) {
    throw StateError(
      'No keys to capture. Provide at least one registered key.',
    );
  }

  final states = <String, dynamic>{};

  // Collect state from specified contributors in deterministic order
  for (final key in keysToCapture) {
    final exporter = registry.getExporter(key);
    if (exporter == null) {
      // This shouldn't happen if registry is consistent, but handle it
      continue;
    }

    try {
      final state = exporter();

      // Validate that the exported state is JSON-serializable
      try {
        StateValidator.validate(state, key);
      } catch (validationError) {
        throw StateError(
          'State validation failed for key "$key": $validationError\n'
          'Exported state must be JSON-serializable. '
          'See StateValidator documentation for restrictions.',
        );
      }

      states[key] = state;
    } catch (e) {
      // If it's already a StateError, rethrow as-is
      if (e is StateError) {
        rethrow;
      }
      // Propagate errors from exporter functions
      // This ensures developers know if their exporter has issues
      throw StateError('Failed to capture state for key "$key": $e');
    }
  }

  // Validate metadata if provided
  if (metadata != null) {
    try {
      StateValidator.validate(metadata, 'metadata');
    } catch (validationError) {
      throw StateError(
        'Metadata validation failed: $validationError\n'
        'Metadata must be JSON-serializable.',
      );
    }
  }

  return Snapshot(
    timestamp: DateTime.now().toUtc(),
    appVersion: appVersion,
    schemaVersion: schemaVersion,
    states: states,
    metadata: metadata,
  );
}