@rashidee/co2 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +25449 -0
- package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
- package/drizzle/0001_wise_wolf_cub.sql +13 -0
- package/drizzle/0002_secret_spyke.sql +16 -0
- package/drizzle/0003_workspace_config_singleton.sql +16 -0
- package/drizzle/0004_terminal_claim_v120.sql +11 -0
- package/drizzle/0005_brainstorm_v124.sql +10 -0
- package/drizzle/0006_project_v140.sql +6 -0
- package/drizzle/0007_prd_wizard_v124.sql +14 -0
- package/drizzle/0008_workflow_artifact_v126.sql +24 -0
- package/drizzle/0009_project_import_v150.sql +7 -0
- package/drizzle/0010_quality_v151.sql +3 -0
- package/drizzle/meta/0000_snapshot.json +1335 -0
- package/drizzle/meta/0001_snapshot.json +1416 -0
- package/drizzle/meta/0002_snapshot.json +1510 -0
- package/drizzle/meta/_journal.json +83 -0
- package/package.json +41 -0
- package/plugin/.claude-plugin/marketplace.json +36 -0
- package/plugin/.claude-plugin/plugin.json +22 -0
- package/plugin/README.md +625 -0
- package/plugin/SKILLS.md +350 -0
- package/plugin/co2.svg +5 -0
- package/plugin/commands/brainstorm-loop.md +129 -0
- package/plugin/skills/conductor-defect/SKILL.md +898 -0
- package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
- package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
- package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
- package/plugin/skills/depgen-k8s/SKILL.md +468 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
- package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
- package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
- package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
- package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
- package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
- package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
- package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
- package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
- package/plugin/skills/modelgen-relational/SKILL.md +707 -0
- package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
- package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
- package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
- package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
- package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
- package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
- package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
- package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
- package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
- package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
- package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
- package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
- package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
- package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
- package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
- package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
- package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
- package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
- package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
- package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
- package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
- package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
- package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
- package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
- package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
- package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
- package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
- package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
- package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
- package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
- package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
- package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
- package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
- package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
- package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
- package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
- package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
- package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
- package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
- package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
- package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
- package/plugin/skills/testgen-functional/SKILL.md +1381 -0
- package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
- package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
- package/plugin/skills/util-projectinit/SKILL.md +307 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
- package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
- package/plugin/skills/util-projectsync/SKILL.md +501 -0
- package/plugin/skills/util-updprd/SKILL.md +324 -0
- package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
- package/plugin/skills/util-ustagger/SKILL.md +154 -0
- package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
- package/static/assets/arc-DRKw7sjs.js +1 -0
- package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
- package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
- package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
- package/static/assets/channel-C2nVbuwv.js +1 -0
- package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
- package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
- package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
- package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
- package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
- package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
- package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
- package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
- package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
- package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
- package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
- package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
- package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
- package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
- package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
- package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
- package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
- package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
- package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
- package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
- package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
- package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
- package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
- package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
- package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
- package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
- package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
- package/static/assets/graph-C9eacEi8.js +1 -0
- package/static/assets/index-LZSQkLE2.css +41 -0
- package/static/assets/index-y7XDSgMG.js +495 -0
- package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
- package/static/assets/init-Gi6I4Gst.js +1 -0
- package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
- package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
- package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
- package/static/assets/katex-C5jXJg4s.js +257 -0
- package/static/assets/layout-DEXfKzaS.js +1 -0
- package/static/assets/linear-CLO--ryk.js +1 -0
- package/static/assets/map-Czzmt4hB.js +1 -0
- package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
- package/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
- package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
- package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
- package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
- package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
- package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
- package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
- package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
- package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
- package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
- package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
- package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
- package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
- package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
- package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
- package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
- package/static/index.html +24 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# Storage Patterns — Hive Local Database + Dio HTTP Client
|
|
2
|
+
|
|
3
|
+
This reference describes how Hive (local NoSQL storage) and Dio (HTTP client) are
|
|
4
|
+
configured and used together in the spec. Include this content in Sections 8 and 9 of
|
|
5
|
+
the generated specification.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Hive Setup
|
|
10
|
+
|
|
11
|
+
### Initialization
|
|
12
|
+
|
|
13
|
+
```dart
|
|
14
|
+
// lib/core/local_storage/hive_setup.dart
|
|
15
|
+
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
16
|
+
import 'package:hive/hive.dart';
|
|
17
|
+
import 'package:hive_flutter/hive_flutter.dart';
|
|
18
|
+
|
|
19
|
+
import '../../features/task/domain/task.dart';
|
|
20
|
+
import '../../features/profile/domain/profile.dart';
|
|
21
|
+
|
|
22
|
+
class HiveSetup {
|
|
23
|
+
HiveSetup._();
|
|
24
|
+
|
|
25
|
+
static const _settingsBox = 'settings';
|
|
26
|
+
static const _userProfileBox = 'user_profile';
|
|
27
|
+
static const _taskCacheBox = 'task_cache';
|
|
28
|
+
static const _encryptionKeyName = 'hive_encryption_key';
|
|
29
|
+
|
|
30
|
+
static Future<void> init() async {
|
|
31
|
+
await Hive.initFlutter();
|
|
32
|
+
|
|
33
|
+
// Register type adapters
|
|
34
|
+
Hive.registerAdapter(TaskAdapter());
|
|
35
|
+
Hive.registerAdapter(TaskStatusAdapter());
|
|
36
|
+
Hive.registerAdapter(ProfileAdapter());
|
|
37
|
+
|
|
38
|
+
// Open plain (non-sensitive) boxes
|
|
39
|
+
await Hive.openBox(_settingsBox);
|
|
40
|
+
|
|
41
|
+
// Open encrypted boxes for sensitive cached data
|
|
42
|
+
final cipher = await _resolveCipher();
|
|
43
|
+
await Hive.openBox<Profile>(_userProfileBox, encryptionCipher: cipher);
|
|
44
|
+
await Hive.openBox<Task>(_taskCacheBox, encryptionCipher: cipher);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static Future<HiveAesCipher> _resolveCipher() async {
|
|
48
|
+
const secureStorage = FlutterSecureStorage();
|
|
49
|
+
String? base64Key = await secureStorage.read(key: _encryptionKeyName);
|
|
50
|
+
if (base64Key == null) {
|
|
51
|
+
final key = Hive.generateSecureKey();
|
|
52
|
+
base64Key = base64Encode(key);
|
|
53
|
+
await secureStorage.write(key: _encryptionKeyName, value: base64Key);
|
|
54
|
+
}
|
|
55
|
+
return HiveAesCipher(base64Decode(base64Key));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Clears all cached boxes (call on logout)
|
|
59
|
+
static Future<void> clearUserData() async {
|
|
60
|
+
await Hive.box<Profile>(_userProfileBox).clear();
|
|
61
|
+
await Hive.box<Task>(_taskCacheBox).clear();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Typed Box Providers
|
|
67
|
+
|
|
68
|
+
```dart
|
|
69
|
+
// lib/core/local_storage/box_provider.dart
|
|
70
|
+
import 'package:hive/hive.dart';
|
|
71
|
+
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
72
|
+
|
|
73
|
+
import '../../features/profile/domain/profile.dart';
|
|
74
|
+
import '../../features/task/domain/task.dart';
|
|
75
|
+
|
|
76
|
+
part 'box_provider.g.dart';
|
|
77
|
+
|
|
78
|
+
@Riverpod(keepAlive: true)
|
|
79
|
+
Box settingsBox(SettingsBoxRef ref) => Hive.box('settings');
|
|
80
|
+
|
|
81
|
+
@Riverpod(keepAlive: true)
|
|
82
|
+
Box<Profile> profileBox(ProfileBoxRef ref) => Hive.box<Profile>('user_profile');
|
|
83
|
+
|
|
84
|
+
@Riverpod(keepAlive: true)
|
|
85
|
+
Box<Task> taskCacheBox(TaskCacheBoxRef ref) => Hive.box<Task>('task_cache');
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Secure Storage Provider
|
|
89
|
+
|
|
90
|
+
```dart
|
|
91
|
+
// lib/core/local_storage/secure_storage_provider.dart
|
|
92
|
+
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
93
|
+
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
94
|
+
|
|
95
|
+
part 'secure_storage_provider.g.dart';
|
|
96
|
+
|
|
97
|
+
@Riverpod(keepAlive: true)
|
|
98
|
+
FlutterSecureStorage secureStorage(SecureStorageRef ref) {
|
|
99
|
+
return const FlutterSecureStorage(
|
|
100
|
+
aOptions: AndroidOptions(encryptedSharedPreferences: true),
|
|
101
|
+
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Hive Type Adapter (Co-located With Freezed Model)
|
|
109
|
+
|
|
110
|
+
Annotate freezed model fields with `@HiveField(N)` to generate adapters. The Hive
|
|
111
|
+
generator picks them up via `build_runner`:
|
|
112
|
+
|
|
113
|
+
```dart
|
|
114
|
+
// lib/features/task/domain/task.dart
|
|
115
|
+
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
116
|
+
import 'package:hive/hive.dart';
|
|
117
|
+
|
|
118
|
+
part 'task.freezed.dart';
|
|
119
|
+
part 'task.g.dart';
|
|
120
|
+
|
|
121
|
+
@HiveType(typeId: 1)
|
|
122
|
+
enum TaskStatus {
|
|
123
|
+
@HiveField(0) @JsonValue('DRAFT') draft,
|
|
124
|
+
@HiveField(1) @JsonValue('IN_PROGRESS') inProgress,
|
|
125
|
+
@HiveField(2) @JsonValue('COMPLETED') completed,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@freezed
|
|
129
|
+
class Task with _$Task {
|
|
130
|
+
@HiveType(typeId: 2)
|
|
131
|
+
const factory Task({
|
|
132
|
+
@HiveField(0) required String id,
|
|
133
|
+
@HiveField(1) required String title,
|
|
134
|
+
@HiveField(2) String? description,
|
|
135
|
+
@HiveField(3) required TaskStatus status,
|
|
136
|
+
@HiveField(4) required DateTime dueDate,
|
|
137
|
+
@HiveField(5) required DateTime createdAt,
|
|
138
|
+
@HiveField(6) required DateTime updatedAt,
|
|
139
|
+
}) = _Task;
|
|
140
|
+
|
|
141
|
+
factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
> Each module's `typeId` must be unique across the application — keep a registry in a
|
|
146
|
+
> central `lib/core/local_storage/type_ids.dart` if many modules use Hive.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Cache-Aside Read Pattern
|
|
151
|
+
|
|
152
|
+
```dart
|
|
153
|
+
// lib/features/task/data/task_repository.dart (excerpt)
|
|
154
|
+
class TaskRepository {
|
|
155
|
+
TaskRepository(this._dio, this._cacheBox);
|
|
156
|
+
final Dio _dio;
|
|
157
|
+
final Box<Task> _cacheBox;
|
|
158
|
+
|
|
159
|
+
Future<List<Task>> listCached() async {
|
|
160
|
+
return _cacheBox.values.toList();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Future<List<Task>> refreshList(TaskListParams params) async {
|
|
164
|
+
try {
|
|
165
|
+
final response = await _dio.get<Map<String, dynamic>>(
|
|
166
|
+
'/tasks',
|
|
167
|
+
queryParameters: params.toJson(),
|
|
168
|
+
);
|
|
169
|
+
final page = PaginatedResponse.fromJson(
|
|
170
|
+
response.data!,
|
|
171
|
+
(json) => Task.fromJson(json as Map<String, dynamic>),
|
|
172
|
+
);
|
|
173
|
+
// Replace cache with fresh page
|
|
174
|
+
await _cacheBox.clear();
|
|
175
|
+
await _cacheBox.addAll(page.content);
|
|
176
|
+
return page.content;
|
|
177
|
+
} on DioException catch (e) {
|
|
178
|
+
throw ApiFailure.fromDioException(e);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
Future<void> upsertCached(Task task) async {
|
|
183
|
+
final key = _cacheBox.values.toList().indexWhere((t) => t.id == task.id);
|
|
184
|
+
if (key >= 0) {
|
|
185
|
+
await _cacheBox.putAt(key, task);
|
|
186
|
+
} else {
|
|
187
|
+
await _cacheBox.add(task);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Paginated Response Type
|
|
196
|
+
|
|
197
|
+
```dart
|
|
198
|
+
// lib/core/api/paginated_response.dart
|
|
199
|
+
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
200
|
+
|
|
201
|
+
part 'paginated_response.freezed.dart';
|
|
202
|
+
|
|
203
|
+
@Freezed(genericArgumentFactories: true)
|
|
204
|
+
class PaginatedResponse<T> with _$PaginatedResponse<T> {
|
|
205
|
+
const factory PaginatedResponse({
|
|
206
|
+
required List<T> content,
|
|
207
|
+
required int page,
|
|
208
|
+
required int size,
|
|
209
|
+
required int totalElements,
|
|
210
|
+
required int totalPages,
|
|
211
|
+
}) = _PaginatedResponse<T>;
|
|
212
|
+
|
|
213
|
+
factory PaginatedResponse.fromJson(
|
|
214
|
+
Map<String, dynamic> json,
|
|
215
|
+
T Function(Object?) fromJsonT,
|
|
216
|
+
) {
|
|
217
|
+
return PaginatedResponse<T>(
|
|
218
|
+
content: (json['content'] as List).map((e) => fromJsonT(e)).toList(),
|
|
219
|
+
page: json['page'] as int,
|
|
220
|
+
size: json['size'] as int,
|
|
221
|
+
totalElements: json['totalElements'] as int,
|
|
222
|
+
totalPages: json['totalPages'] as int,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Settings & Preferences
|
|
231
|
+
|
|
232
|
+
Use the plain `settings` box for simple key-value preferences:
|
|
233
|
+
|
|
234
|
+
```dart
|
|
235
|
+
// Theme mode
|
|
236
|
+
ref.read(settingsBoxProvider).put('themeMode', 'dark');
|
|
237
|
+
final mode = ref.read(settingsBoxProvider).get('themeMode') as String?;
|
|
238
|
+
|
|
239
|
+
// Locale
|
|
240
|
+
ref.read(settingsBoxProvider).put('locale', 'ms');
|
|
241
|
+
|
|
242
|
+
// First-launch flag
|
|
243
|
+
final firstLaunch = !(ref.read(settingsBoxProvider).get('hasLaunched') as bool? ?? false);
|
|
244
|
+
await ref.read(settingsBoxProvider).put('hasLaunched', true);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
For complex typed preferences, define a small `@freezed` class with a Hive adapter and
|
|
248
|
+
store it in a dedicated box (e.g., `Box<UserPreferences>`).
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Dio Configuration (Recap)
|
|
253
|
+
|
|
254
|
+
The Dio instance is constructed once in `dioProvider` and shared via Riverpod. Its
|
|
255
|
+
interceptors are layered in this exact order:
|
|
256
|
+
|
|
257
|
+
1. **AuthInterceptor** — injects Bearer token (reads via `AuthRepository.getAccessToken`)
|
|
258
|
+
2. **RetryInterceptor (dio_smart_retry)** — exponential backoff on 5xx and network
|
|
259
|
+
errors; only retries idempotent methods (GET, PUT, DELETE)
|
|
260
|
+
3. **PrettyDioLogger** — verbose request/response logging in `kDebugMode` only
|
|
261
|
+
|
|
262
|
+
```dart
|
|
263
|
+
// (Already shown in spec-template Section 8)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `pretty_dio_logger` Configuration
|
|
267
|
+
|
|
268
|
+
```dart
|
|
269
|
+
PrettyDioLogger(
|
|
270
|
+
requestHeader: true, // log headers
|
|
271
|
+
requestBody: true, // log request body
|
|
272
|
+
responseBody: true, // log response body
|
|
273
|
+
responseHeader: false,
|
|
274
|
+
error: true,
|
|
275
|
+
compact: false,
|
|
276
|
+
maxWidth: 90,
|
|
277
|
+
);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Retry Interceptor
|
|
281
|
+
|
|
282
|
+
```dart
|
|
283
|
+
RetryInterceptor(
|
|
284
|
+
dio: dio,
|
|
285
|
+
retries: 3,
|
|
286
|
+
retryDelays: const [
|
|
287
|
+
Duration(seconds: 1),
|
|
288
|
+
Duration(seconds: 2),
|
|
289
|
+
Duration(seconds: 4),
|
|
290
|
+
],
|
|
291
|
+
retryableExtraStatuses: const {502, 503, 504},
|
|
292
|
+
// By default, dio_smart_retry only retries idempotent methods (GET, HEAD, PUT,
|
|
293
|
+
// DELETE, OPTIONS, TRACE). POST is NOT retried by default — which is correct for
|
|
294
|
+
// most mutations. Override per-request via `RequestOptions.extra['retry'] = true`
|
|
295
|
+
// for explicitly idempotent POSTs.
|
|
296
|
+
);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Per-Request Retry Override
|
|
300
|
+
|
|
301
|
+
To enable retries for a single (idempotent-by-design) POST:
|
|
302
|
+
|
|
303
|
+
```dart
|
|
304
|
+
await dio.post(
|
|
305
|
+
'/audit-logs',
|
|
306
|
+
data: payload,
|
|
307
|
+
options: Options(extra: {RetryInterceptor.kRetryAttemptsKey: 3}),
|
|
308
|
+
);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Error Mapping at the Boundary
|
|
314
|
+
|
|
315
|
+
Repositories convert `DioException` into the sealed `ApiFailure` type so that screens
|
|
316
|
+
deal with a typed failure rather than transport-level details:
|
|
317
|
+
|
|
318
|
+
```dart
|
|
319
|
+
try {
|
|
320
|
+
final response = await _dio.get<Map<String, dynamic>>('/tasks/$id');
|
|
321
|
+
return Task.fromJson(response.data!);
|
|
322
|
+
} on DioException catch (e) {
|
|
323
|
+
throw ApiFailure.fromDioException(e);
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
`ApiFailure.fromDioException` (see `spec-template.md` Section 8) handles:
|
|
328
|
+
- Network timeouts → `ApiFailure.network()`
|
|
329
|
+
- 401 → `ApiFailure.unauthorized()`
|
|
330
|
+
- 403 → `ApiFailure.forbidden()`
|
|
331
|
+
- 404 → `ApiFailure.notFound()`
|
|
332
|
+
- 5xx → `ApiFailure.server(status, message)`
|
|
333
|
+
- Everything else → `ApiFailure.unknown(error)`
|
|
334
|
+
|
|
335
|
+
Screens consume failures via Riverpod `AsyncValue.when(error: ...)` and feed them into
|
|
336
|
+
the `ErrorState` widget for consistent UX.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Migration Strategy
|
|
341
|
+
|
|
342
|
+
When a freezed/Hive model adds a field with a new `@HiveField` index:
|
|
343
|
+
- The new field MUST be nullable or have a default value — Hive treats missing fields
|
|
344
|
+
as `null` for older records.
|
|
345
|
+
- Never reuse a removed `@HiveField` index. Mark the field with `@Deprecated` if you
|
|
346
|
+
need to keep reading it; otherwise remove and never assign that index again.
|
|
347
|
+
- For breaking schema changes (renamed type, restructured fields), bump the box name
|
|
348
|
+
(e.g., `task_cache` → `task_cache_v2`) and run a one-time clear-and-refetch on startup:
|
|
349
|
+
|
|
350
|
+
```dart
|
|
351
|
+
final box = await Hive.openBox<Task>('task_cache_v2');
|
|
352
|
+
if (Hive.boxExists('task_cache')) {
|
|
353
|
+
await Hive.deleteBoxFromDisk('task_cache');
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Logout Hygiene
|
|
360
|
+
|
|
361
|
+
On user logout, clear all caches and secure-storage keys to avoid leaking data to the
|
|
362
|
+
next user on a shared device:
|
|
363
|
+
|
|
364
|
+
```dart
|
|
365
|
+
Future<void> performLogout(WidgetRef ref) async {
|
|
366
|
+
await ref.read(authProvider.notifier).signOut();
|
|
367
|
+
await HiveSetup.clearUserData();
|
|
368
|
+
// Secure storage keys cleared inside AuthRepository.signOutLocal()
|
|
369
|
+
}
|
|
370
|
+
```
|