timsoftdz_core 1.0.1 copy "timsoftdz_core: ^1.0.1" to clipboard
timsoftdz_core: ^1.0.1 copied to clipboard

Enterprise-grade core library for Dart & Flutter — Result pattern, unified exception hierarchy, structured logging with pipelines, interceptor-based HTTP client, contract-driven key-value storage, Uni [...]

timsoftdz_core #

pub.dev Dart SDK License: MIT style: lint

Enterprise-grade core library for Dart & Flutter.

Production-ready utilities following Clean Architecture principles — zero heavy framework dependencies, full null safety, >90 % test coverage.


✨ Feature Overview #

Module What you get
Result<T> Success / Failure discriminated union — no exceptions in your control flow
Exceptions Typed hierarchy — ValidationException, NetworkException, StorageException, etc.
Logger 7 levels, ANSI console, JSON pipeline, filter DSL, lazy evaluation
HTTP Client Interceptor chain, jitter-backoff retry, CancelToken, full testability
Storage Async key-value, read-through cache, optional encryption plugin
Strings Unicode-safe, camelCase/snake/kebab/pascal, masking, Levenshtein
Validation Email, phone, password, IBAN, IPv4/6, NIN, Luhn, hex colour
DateTime Zero-intl formatting, relative time (AR / EN / FR)
DzPhone Algerian phone normaliser, carrier detection

📐 Architecture #

timsoftdz_core
├── core/
│   ├── result.dart           ← Result<T>, Success<T>, Failure<T>
│   ├── app_exception.dart    ← AppException hierarchy (10 types)
│   └── error_codes.dart      ← Machine-readable string constants
│
├── logger/
│   ├── logger.dart           ← TimLogger contract + SilentLogger + CompositeLogger + TaggedLogger
│   ├── log_level.dart        ← LogLevel enum (trace…fatal) with ordering operators
│   ├── log_entry.dart        ← Immutable structured log record
│   ├── log_filter.dart       ← LogFilter DSL (&, |, ~)
│   ├── console_logger.dart   ← ANSI-coloured terminal output, lazy evaluation
│   ├── json_logger.dart      ← NDJSON output for log pipelines
│   └── filtered_logger.dart  ← Decorator applying LogFilter
│
├── network/
│   ├── http_client_contract.dart ← TimHttpClientContract (abstract interface)
│   ├── http_client.dart          ← TimHttpClient (package:http impl)
│   ├── http_response.dart        ← TimHttpResponse model
│   ├── retry_policy.dart         ← RetryPolicy + ConstantBackoff / Exponential / JitteredExponential
│   └── cancel_token.dart         ← Cooperative request cancellation
│
├── storage/
│   ├── storage_contract.dart      ← TimStorage abstract key-value contract
│   ├── memory_storage.dart        ← In-memory (tests, session cache)
│   ├── file_storage.dart          ← JSON file (atomic write-then-rename)
│   ├── cached_storage.dart        ← Read-through / write-through cache + TTL
│   └── storage_encryption_codec.dart ← Plugin interface + Base64 + NoOp codecs
│
├── strings/
│   └── string_helpers.dart  ← TimStringX, TimNullableStringX, TimStringUtils
│
├── validation/
│   ├── validator.dart        ← Validator (static helpers)
│   └── dz_phone.dart         ← DzPhone (Algerian-specific)
│
└── datetime/
    ├── date_formatter.dart   ← Pattern-based formatting (no intl dependency)
    └── relative_time.dart    ← RelativeTime + RelativeLocale + DateTimeRelativeX

🚀 Quick Start #

# pubspec.yaml
dependencies:
  timsoftdz_core: ^1.0.0
import 'package:timsoftdz_core/timsoftdz_core.dart';

📖 Core API #

Result<T> #

Every operation that can fail returns Result<T> — never throw, never catch in your business logic.

// Construction
final Result<int> ok   = const Success(42);
final Result<int> fail = Failure(const AppException('oops'));

// Pattern matching
final label = ok.fold(
  onSuccess: (v) => 'Value: $v',
  onFailure: (e) => 'Error: ${e.message}',
);

// Transformation
final doubled = ok.map((v) => v * 2);              // Success(84)
final chained = ok.flatMap((v) => Success(v + 1)); // Success(43)

// Async
final asyncResult = await Result.runAsync(() async => fetchData());

// Guard (sync try/catch)
final parsed = Result.guard(() => int.parse(input));

// Recovery
final safe = fail.recover((_) => 0);          // Success(0)
final safeWith = fail.recoverWith((_) => ok); // Success(42)

// Side effects
asyncResult
  .onSuccess((v) => log.info('Got $v'))
  .onFailure((e) => log.error(e.message));

// Async chaining
final result = await fetchUser(id)
    .thenMap((u) => u.displayName)
    .thenFlatMap((name) => fetchAvatar(name));

Exception Hierarchy #

AppException
├── ValidationException   // invalid user input
├── NetworkException      // HTTP / connectivity
│   ├── TimeoutException
│   ├── CancelledException
│   ├── UnauthorizedException  (401)
│   ├── ForbiddenException     (403)
│   ├── NotFoundException      (404)
│   ├── ConflictException      (409)
│   └── RateLimitedException   (429)
├── StorageException      // read/write failures
└── ParseException        // JSON / format errors
// Catch any library exception safely
try {
  final r = await client.get('/users');
} on AppException catch (e) {
  print('${e.code}: ${e.message}');
  if (e is NetworkException) print('status: ${e.statusCode}');
}

// Machine-readable codes (never hardcode strings)
throw ValidationException('Email invalid', code: ErrorCode.invalidEmail);

🪵 Logger #

// Console logger with ANSI colours
final log = ConsoleLogger(
  minLevel: LogLevel.debug,
  showTimestamp: true,
);

log.trace('Entering method X', tag: 'Repo');
log.debug('Query executed', tag: 'DB');
log.info('Server started', tag: 'App');
log.success('Payment processed', tag: 'Billing');
log.warning('Rate limit 80 %', tag: 'Monitor');
log.error('Request failed', error: e, stackTrace: s);
log.fatal('DB unreachable — terminating');

// Tagged logger — auto-attaches tag
class AuthService {
  final _log = TaggedLogger(ConsoleLogger(), 'AuthService');

  void signIn(String email) {
    _log.info('Signing in $email');  // → [AuthService] Signing in …
  }
}

// Filtered logger — only forward warning and above
final filtered = FilteredLogger(
  delegate: ConsoleLogger(),
  filter: const MinLevelFilter(LogLevel.warning),
);

// Compose filters
final f = const MinLevelFilter(LogLevel.error) | const TagFilter('Payment');

// JSON logger — NDJSON for ELK / Loki / Datadog
final file = File('app.log').openWrite(mode: FileMode.append);
final jsonLog = JsonLogger(sink: file, minLevel: LogLevel.info);
jsonLog.info('Order placed', tag: 'Shop');  // {"timestamp":"…","level":"INFO","tag":"Shop","message":"Order placed"}
await jsonLog.close();

// Fan-out to multiple sinks
final multi = CompositeLogger([ConsoleLogger(), jsonLog]);

🌐 HTTP Client #

// Create once, inject everywhere (program against the interface)
final TimHttpClientContract client = TimHttpClient(
  baseUrl: 'https://api.timsoft.dz',
  defaultHeaders: {'Authorization': 'Bearer $token'},
  timeout: const Duration(seconds: 15),
  retryPolicy: RetryPolicy.standard,   // 3 attempts, jitter backoff
  onRequest: (method, url, headers) async {
    headers['X-Request-Id'] = TimStringUtils.randomAlphanumeric(16);
  },
  onError: (e) async {
    if (e is UnauthorizedException) await refreshToken();
    return e;
  },
);

// All methods return Result<TimHttpResponse>
final result = await client.get('/users', query: {'page': '1'});
result
  .onSuccess((r) => print(r.bodyAsMap()))
  .onFailure((e) => print('${e.code}: ${e.message}'));

// Cancellation
final token = CancelToken();
unawaited(client.post('/orders', body: order, cancelToken: token));
token.cancel('User pressed back');

// Retry policies
final aggressive = RetryPolicy(
  maxAttempts: 5,
  backoff: const JitteredExponentialBackoff(
    base: Duration(seconds: 1),
    maxDelay: Duration(seconds: 60),
  ),
);

client.close(); // release socket connections

📦 Storage #

// Contract-driven — swap implementations without changing business logic
final TimStorage store = CachedStorage(
  FileStorage('./config/app.json'),
  ttl: const Duration(minutes: 30),
);

// Write
await store.set('theme', 'dark');
await store.setAll({'lang': 'ar', 'uid': '42'});
await store.setInt('retries', 3);
await store.setBool('onboarded', value: true);

// Read
final theme  = (await store.getOrDefault('theme', 'light')).getOrElse('light');
final uid    = (await store.get('uid')).valueOrNull;
final count  = (await store.count()).getOrElse(0);

// Cache management
if (store is CachedStorage) {
  store.invalidate('theme');  // evict from cache
  print(store.cacheSize);    // number of live entries
}

// Encryption plugin
final secure = FileStorage('./prefs.json', codec: const Base64StorageCodec());

// Testing helpers
final mock = MemoryStorage({'token': 'test-token'});
final failing = AlwaysFailStorage('disk full');

🔤 String Utilities #

// Case conversions
'helloWorld'.toSnakeCase()    // 'hello_world'
'hello_world'.toCamelCase()   // 'helloWorld'
'hello_world'.toPascalCase()  // 'HelloWorld'
'MyClass'.toKebabCase()       // 'my-class'
'hello world'.titleCase()     // 'Hello World'

// Slugify (URL-safe, Arabic-aware)
'Hello World 2026!'.slugify()  // 'hello-world-2026'
'مرحبا بالعالم'.slugify()       // 'مرحبا-بالعالم'

// Truncation
'Hello World'.truncate(5)                     // 'Hello…'
'Hello beautiful world'.truncateWords(12)     // 'Hello…'

// Masking / privacy
'1234567890'.mask(start: 3, end: 7)           // '123****890'
'user@timsoft.dz'.maskEmail()                 // 'us***@timsoft.dz'
'0551234567'.maskPhone()                      // '055*****67'

// Character predicates
'12345'.isDigitsOnly   // true
'Hello'.isAlpha        // true
'ABC123'.isAlphanumeric // true

// Initials
'John Doe'.initials()                         // 'JD'
TimStringUtils.initials('Ahmed Ben Salah', max: 2)  // 'AB'

// Unicode-safe reverse
'hi 🎉'.unicodeReversed     // '🎉 ih'
'racecar'.isPalindrome       // true

// Static utilities
TimStringUtils.levenshtein('kitten', 'sitting') // 3
TimStringUtils.similarity('dart', 'dart')       // 1.0
TimStringUtils.pluralise(3, 'item')             // '3 items'
TimStringUtils.pluralise(0, 'box', 'boxes')     // '0 boxes'
TimStringUtils.wordWrap('Hello world', 5)       // 'Hello\nworld'
TimStringUtils.randomAlphanumeric(12)           // 'aB3xZ9mQ7Kp1'

// Nullable extensions
String? name;
name.orEmpty       // ''
name.isNullOrBlank // true
name.orElse('N/A') // 'N/A'

✅ Validation #

// Email
Validator.isEmail('user@timsoft.dz')          // true
Validator.validateEmail('user@x.com')         // Result<String>

// Phone
Validator.isPhone('+12025551234')             // true (international)
Validator.isAlgerianPhone('0551234567')       // true

// Password
Validator.passwordStrength('P@ssword1')       // 4 (Strong)
Validator.validatePassword('Weak1', minLength: 8) // Failure(tooShort)

// IBAN (MOD-97 checksum)
Validator.isIBAN('GB29NWBK60161331926819')    // true
Validator.isIBAN('DZ5900600002100111999000')  // true (Algeria)

// IPv4 / IPv6
Validator.isIPv4('192.168.1.1')    // true
Validator.isIPv6('::1')            // true
Validator.isIPv6('2001:db8::1')    // true
Validator.isIP('::1')              // true (either version)

// Credit card — Luhn
Validator.isValidCreditCard('4532015112830366')  // true (test Visa)

// Algerian NIN
Validator.isAlgerianNIN('123456789012345678')     // true (18 digits)

// Helpers
Validator.isSlug('hello-world')   // true
Validator.isHexColor('#1A2B3C')   // true
Validator.isJsonString('{"k":"v"}') // true

📅 DateTime #

final now = DateTime(2026, 3, 15, 14, 30, 5);

DateFormatter.isoDate(now)     // '2026-03-15'
DateFormatter.isoTime(now)     // '14:30:05'
DateFormatter.shortDate(now)   // '15/03/2026'
DateFormatter.longDate(now)    // '15 Mar 2026'
DateFormatter.time12(now)      // '02:30 PM'
DateFormatter.iso8601(now)     // '2026-03-15T14:30:05'
DateFormatter.format(now, 'dd-MM-yyyy HH:mm') // '15-03-2026 14:30'

// Relative time
final past = DateTime.now().subtract(const Duration(minutes: 5));
RelativeTime.from(past, locale: RelativeLocale.en) // '5 minutes ago'
RelativeTime.from(past, locale: RelativeLocale.ar) // 'قبل 5 دقائق'
RelativeTime.from(past, locale: RelativeLocale.fr) // 'il y a 5 minutes'

// Extension method
past.relativeTime()  // '5 minutes ago' (default: en)

📱 DzPhone — Algerian Phone #

DzPhone.isValid('0551234567')         // true
DzPhone.isValid('+213771234567')      // true
DzPhone.normalize('0551234567')       // '+213551234567'
DzPhone.carrier('0551234567')         // 'Ooredoo'
DzPhone.carrier('0661234567')         // 'Mobilis'
DzPhone.carrier('0771234567')         // 'Djezzy'
DzPhone.toLocal('+213551234567')      // '0551234567'
DzPhone.toDisplay('0551234567')       // '055 123 45 67'

🧪 Testing #

The package is designed to be fully testable:

// Replace real client with a mock (no network)
class MockHttpClient implements TimHttpClientContract {
  @override
  Future<Result<TimHttpResponse>> get(String path, {/* … */}) async {
    return Success(TimHttpResponse(
      statusCode: 200,
      body: '{"ok":true}',
      headers: {},
    ));
  }
  // …
}

// Silence all logs in tests
final logger = SilentLogger();

// Pre-populate storage for tests
final store = MemoryStorage({'token': 'test-token', 'uid': '1'});

// Simulate storage failures
final failingStore = AlwaysFailStorage('disk full');

📊 Test Coverage #

The package targets >90 % line coverage:

Module Tests
core/result.dart 40+ cases — Success, Failure, async, fold, recover, swap
core/app_exception.dart All 10 exception types + fromError
logger/ LogLevel ordering, LogEntry, LogFilter DSL, all logger types
network/ CancelToken, RetryPolicy, backoff strategies, TimHttpResponse
storage/ MemoryStorage, FileStorage, CachedStorage, codecs, AlwaysFail
strings/ 50+ edge cases across all extension methods and static utils
validation/ All validators including IBAN, IPv6, Luhn, DzPhone
datetime/ DateFormatter, RelativeTime (all 3 locales)

Run tests:

dart test                        # all tests
dart test test/core_result_test.dart  # specific file

📋 pub.dev Scores (targets) #

Dimension Score Details
Likes Community signal
Pub points 130 Null safety, docs, tests, analysis
Popularity Download count
Overall 160+ Target for v1.0

Score improvements over 0.x:

  • ✅ Full API documentation on every public member
  • example/main.dart in example/ folder
  • ✅ No analyzer warnings (dart analyze --fatal-infos)
  • analysis_options.yaml using lints ^4.0.0
  • ✅ Correct CHANGELOG.md format
  • homepage, repository, issue_tracker, documentation in pubspec
  • topics list for discoverability

🗺️ Roadmap — v1.1+ #

Feature Status
SecureStorage adapter (flutter_secure_storage integration) Planned
FileLogger concrete implementation Planned
RemoteLogger (HTTP log sink) Planned
Validator.validateIBAN() returning Result<String> Planned
TimHttpClientDio adapter Planned
Internationalized ValidationException messages Planned

📄 License #

MIT — see LICENSE.


👤 Author #

TIMSoftDZ
🌐 timsoftdz.com · 🐙 github.com/TIMSoftDZ · 📦 pub.dev/publishers/timsoftdz.com

4
likes
145
points
104
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Enterprise-grade core library for Dart & Flutter — Result pattern, unified exception hierarchy, structured logging with pipelines, interceptor-based HTTP client, contract-driven key-value storage, Unicode-safe string utilities, comprehensive validation (Algerian phone, Luhn, IBAN, IPv6), and zero-intl date/time formatting. Pub.dev 160+ score target. v1.0 production-ready.

Topics

#utilities #networking #validation #logging #storage

License

MIT (license)

Dependencies

http

More

Packages that depend on timsoftdz_core