Skip to content

Commit c0025bf

Browse files
feat: impl localstroage and filystem module
other modules still WIP Co-authored-by: MasterMarcoHD <MasterMarcoHD@users.noreply.github.com>
1 parent cbf30ec commit c0025bf

88 files changed

Lines changed: 3465 additions & 1457 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 112 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,114 @@
11
# Grumpy IO
22

3-
Full IO mapping for Grumpy applications, providing a unified API for file system access, networking, and local storage across all platforms.
4-
5-
## Modules
6-
7-
- [ ] File System Module
8-
- [ ] File System CRUD operations
9-
- [ ] File Picker
10-
- [ ] Filesystem Watcher
11-
- [ ] File Metadata Extraction
12-
- [ ] Networking Module
13-
- [x] Network Requests
14-
- [ ] Typed Response parsing via datasources
15-
- [ ] WebSocket Support
16-
- [ ] Network Connectivity Monitoring
17-
- [ ] Data Streaming
18-
- [ ] Downloading, with progress tracking
19-
- [ ] Uploading, with progress tracking
20-
- [ ] Local Storage
21-
- [ ] App Directories
22-
- [ ] Temporary Directory/temp files
23-
- [ ] Persistent Directory
24-
- [ ] Cache Directory
25-
- [ ] Cookies
26-
- [ ] Process management (desktop only, noop for web and mobile)
27-
- [ ] Spawn processes
28-
- [ ] Inter-process communication (IPC)
29-
- [ ] Process monitoring and control
30-
- [ ] Force single app instance
31-
- [ ] Run shell commands
32-
- [ ] Command line argument parsing (via config)
33-
- [ ] System information
34-
- [ ] OS details
35-
- [ ] Hardware specifications
36-
- [ ] Environment variables
3+
`grumpy_io` provides cross-platform IO services and typed datasource adapters for Grumpy apps.
4+
5+
It covers:
6+
- raw networking
7+
- raw filesystem
8+
- persistent local storage
9+
- typed datasource wrappers in each owning module
10+
- optional Grumpy cache/persistence compatibility adapters
11+
12+
## Design Goals
13+
14+
- Keep public IO contracts backend-agnostic.
15+
- Return typed `IoResult<T>` instead of leaking backend exceptions.
16+
- Keep typed datasources close to their owning module.
17+
- Use compile-time platform selection for platform-specific services.
18+
19+
## Module Layout
20+
21+
### Networking Module
22+
23+
Exports:
24+
- `NetworkService`
25+
- `FileTransferService`
26+
- `TypedNetworkDatasource`
27+
28+
Default implementations:
29+
- `DioNetworkService`
30+
- `DefaultFileTransferService`
31+
- `DefaultTypedNetworkDatasource`
32+
33+
### File System Module
34+
35+
Exports:
36+
- `FileSystemService`
37+
- `TypedFileSystemDatasource`
38+
- `IoPath`, `FsMetadata`, `FsEntityType`
39+
40+
Default implementations:
41+
- `DefaultFileSystemService` (platform-selected)
42+
- `DefaultTypedFileSystemDatasource`
43+
44+
### Local Storage Module
45+
46+
Exports:
47+
- `LocalStorageService`
48+
- `TypedLocalStorageDatasource`
49+
- `LocalStorageKey`, `LocalStorageValue`
50+
51+
Default implementations:
52+
- `DefaultLocalStorageService` (platform-selected)
53+
- `DefaultTypedLocalStorageDatasource`
54+
55+
Persistence behavior:
56+
- Native (`io`): persisted via `FileSystemService` in a JSON file.
57+
- Web: persisted in browser `localStorage` with cookie fallback.
58+
59+
## Typed Datasources
60+
61+
Typed datasources are intentionally colocated with their modules:
62+
- networking typed datasource lives in networking module
63+
- filesystem typed datasource lives in filesystem module
64+
- local-storage typed datasource lives in local-storage module
65+
66+
Shared typed datasource concerns are in `shared_module`.
67+
68+
## Platform-Specific Service Convention
69+
70+
For services with platform-specific implementations:
71+
- platform files live under `<module>/infra/services/<service>/...`
72+
- an entrypoint file performs conditional export
73+
- concrete platform implementations expose the same symbols
74+
75+
Example pattern:
76+
77+
```dart
78+
export 'service_stub.dart'
79+
if (dart.library.io) 'service_io.dart'
80+
if (dart.library.js_interop) 'service_web.dart';
81+
```
82+
83+
## Error Model
84+
85+
All public IO surfaces use:
86+
- `IoResult<T>`
87+
- `IoOk<T>`
88+
- `IoErr<T>`
89+
- `IoFailure` with `IoFailureCode`
90+
91+
This keeps consumers on one stable error contract across backends/platforms.
92+
93+
## Grumpy Compatibility Layer
94+
95+
`grumpy_compat` provides optional adapters for:
96+
- memory cache layer
97+
- persistent cache layer
98+
- cache pipeline
99+
- repo snapshot persistence
100+
- root module mixin wiring
101+
102+
## Public Exports
103+
104+
Package root exports:
105+
- `core`
106+
- `networking_module`
107+
- `file_system_module`
108+
- `local_storage_module`
109+
- `grumpy_compat`
110+
- `grumpy_io_module`
111+
112+
## Status
113+
114+
Current focus is V1 core IO and persistence surfaces. Process-management and system-information features remain out of scope for V1.

analysis_options.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ plugins:
2121
must_call_in_constructor: true
2222
abstract_classes_should_set_log_group: true
2323
concrete_classes_should_set_log_tag: true
24-
base_class: true
25-
domain_factory_from_di: true
24+
base_class:
25+
enabled: true
26+
ignorable: true
27+
domain_factory_from_di:
28+
enabled: true
29+
ignorable: true
2630
prefer_domain_di_factory: true
2731
lifecycle_mixin_requires_singleton: true
2832

lib/grumpy_io.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1+
export 'src/core/core.dart';
12
export 'src/file_system_module/file_system_module.dart';
3+
export 'src/grumpy_compat/grumpy_compat.dart';
4+
export 'src/grumpy_io_module/grumpy_io_module.dart';
5+
export 'src/local_storage_module/local_storage_module.dart';
26
export 'src/networking_module/networking_module.dart';

lib/src/core/core.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export 'domain/models/models.dart';
2+
export 'domain/datasources/datasources.dart';
3+
export 'domain/codecs/codecs.dart';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export 'json_utf8_codec.dart';
2+
export 'string_utf8_codec.dart';
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import 'dart:convert';
2+
3+
import 'package:grumpy/grumpy.dart';
4+
5+
import '../models/io_result.dart';
6+
7+
/// Convenience base for JSON-object codecs serialized as UTF-8 bytes.
8+
abstract class JsonUtf8Codec<T> implements SerializationCodec<T, Bytes> {
9+
/// Creates a JSON codec.
10+
const JsonUtf8Codec();
11+
12+
/// Converts [value] into a JSON object.
13+
JsonMap toJson(T value);
14+
15+
/// Converts a JSON object into the target type.
16+
T fromJson(JsonMap json);
17+
18+
@override
19+
Bytes encode(T value) {
20+
return Bytes.fromList(utf8.encode(jsonEncode(toJson(value))));
21+
}
22+
23+
@override
24+
T decode(Bytes payload) {
25+
final dynamic decoded = jsonDecode(utf8.decode(payload));
26+
if (decoded is! Map) {
27+
throw const FormatException('Expected JSON object payload.');
28+
}
29+
return fromJson(Map<String, Object?>.from(decoded));
30+
}
31+
32+
/// Creates a JSON codec from [toJson] and [fromJson] functions.
33+
const factory JsonUtf8Codec.from({
34+
required JsonMap Function(T value) toJson,
35+
required T Function(JsonMap json) fromJson,
36+
}) = _JsonUtf8CodecImpl;
37+
}
38+
39+
class _JsonUtf8CodecImpl<T> extends JsonUtf8Codec<T> {
40+
const _JsonUtf8CodecImpl({
41+
required JsonMap Function(T value) toJson,
42+
required T Function(JsonMap json) fromJson,
43+
}) : _toJson = toJson,
44+
_fromJson = fromJson;
45+
46+
final JsonMap Function(T value) _toJson;
47+
48+
final T Function(JsonMap json) _fromJson;
49+
50+
@override
51+
T fromJson(JsonMap json) => _fromJson(json);
52+
53+
@override
54+
JsonMap toJson(T value) => _toJson(value);
55+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'dart:convert';
2+
3+
import 'package:grumpy/grumpy.dart';
4+
5+
import '../models/io_result.dart';
6+
7+
/// UTF-8 codec for plain text payloads.
8+
class StringUtf8Codec implements SerializationCodec<String, Bytes> {
9+
/// Creates a string codec.
10+
const StringUtf8Codec();
11+
12+
@override
13+
Bytes encode(String value) => Bytes.fromList(utf8.encode(value));
14+
15+
@override
16+
String decode(Bytes payload) => utf8.decode(payload);
17+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export 'typed_datasource.dart';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This is a base type for typed datasource abstractions, and is not to be instantiated directly.
2+
// ignore_for_file: domain_factory_from_di_missing_factory
3+
4+
import 'package:grumpy/grumpy.dart';
5+
6+
/// Base type for module-local typed datasource abstractions.
7+
abstract class TypedDatasource extends Datasource {
8+
/// Creates a typed datasource.
9+
const TypedDatasource.internal() : super();
10+
11+
@override
12+
bool get singelton => true;
13+
@override
14+
String get group => '${super.group}.TypedDatasource';
15+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import 'dart:async';
2+
import 'dart:typed_data';
3+
4+
/// Canonical byte payload type used across network/storage/filesystem services.
5+
typedef Bytes = Uint8List;
6+
7+
/// Stable, backend-agnostic failure categories used by `grumpy_io`.
8+
enum IoFailureCode {
9+
/// This operation is not supported by the current platform or configuration.
10+
unsupported,
11+
12+
/// The requested resource was not found.
13+
notFound,
14+
15+
/// The requested resource already exists.
16+
alreadyExists,
17+
18+
/// Insufficient permissions to perform this operation.
19+
permissionDenied,
20+
21+
/// The operation took too long to complete.
22+
timeout,
23+
24+
/// An error in the underlying network stack (e.g. DNS failure, connection reset, etc.).
25+
networkError,
26+
27+
/// The data is malformed or does not conform to expected format/schema.
28+
invalidData,
29+
30+
/// The operation was cancelled by the caller before completion.
31+
cancelled,
32+
33+
/// An unexpected error occurred (e.g. unhandled exception, etc.).
34+
unknown,
35+
}
36+
37+
/// A typed IO failure envelope.
38+
class IoFailure {
39+
/// Creates an IO failure.
40+
const IoFailure({
41+
required this.code,
42+
required this.message,
43+
this.cause,
44+
this.details = const <String, Object?>{},
45+
this.stackTrace,
46+
});
47+
48+
/// Failure category.
49+
final IoFailureCode code;
50+
51+
/// Human-readable failure summary.
52+
final String message;
53+
54+
/// Optional original cause.
55+
final Object? cause;
56+
57+
/// Optional structured context for callers/logging.
58+
final Map<String, Object?> details;
59+
60+
/// Optional stack trace when failure was captured.
61+
final StackTrace? stackTrace;
62+
63+
@override
64+
String toString() => 'IoFailure(code: $code, message: $message)';
65+
}
66+
67+
/// Functional-style result for IO operations.
68+
sealed class IoResult<T> {
69+
/// Creates a result value.
70+
const IoResult();
71+
72+
/// Returns `true` when this result is [IoOk].
73+
bool get isOk => this is IoOk<T>;
74+
75+
/// Returns `true` when this result is [IoErr].
76+
bool get isErr => this is IoErr<T>;
77+
78+
/// Returns [IoOk.value] or `null`.
79+
T? get valueOrNull => switch (this) {
80+
IoOk<T>(value: final value) => value,
81+
IoErr<T>() => null,
82+
};
83+
84+
/// Returns [IoErr.failure] or `null`.
85+
IoFailure? get failureOrNull => switch (this) {
86+
IoOk<T>() => null,
87+
IoErr<T>(failure: final failure) => failure,
88+
};
89+
}
90+
91+
/// Successful IO result.
92+
final class IoOk<T> extends IoResult<T> {
93+
/// Creates a successful result carrying [value].
94+
const IoOk(this.value);
95+
96+
/// Successful payload.
97+
final T value;
98+
}
99+
100+
/// Failed IO result.
101+
final class IoErr<T> extends IoResult<T> {
102+
/// Creates a failed result carrying [failure].
103+
const IoErr(this.failure);
104+
105+
/// Failure payload.
106+
final IoFailure failure;
107+
108+
/// Creates a failed result for an unsupported operation.
109+
const factory IoErr.unsupported([String? operation]) = _UnsupportedIoErr;
110+
}
111+
112+
final class _UnsupportedIoErr<T> extends IoErr<T> {
113+
final String? operation;
114+
115+
const _UnsupportedIoErr([this.operation])
116+
: super(const IoFailure(code: IoFailureCode.unsupported, message: ''));
117+
118+
@override
119+
IoFailure get failure => IoFailure(
120+
code: IoFailureCode.unsupported,
121+
message: operation == null
122+
? 'This operation is not supported by the current platform or configuration.'
123+
: 'The operation "$operation" is not supported by the current platform or configuration.',
124+
);
125+
}

0 commit comments

Comments
 (0)