1-
21// Copyright 2025 Google LLC
32//
43// Licensed under the Apache License, Version 2.0 (the "License");
1312// See the License for the specific language governing permissions and
1413// limitations under the License.
1514
15+ import 'dart:convert' ;
16+
17+ import 'package:firebase_data_connect/src/cache/cache_provider.dart' ;
18+ import 'package:flutter/foundation.dart' show kIsWeb;
19+
1620/// Type of storage to use for the cache
17- enum CacheStorage {
18- persistent,
19- memory
20- }
21+ enum CacheStorage { persistent, memory }
2122
2223const String GlobalIDKey = 'cacheId' ;
2324
2425/// Configuration for the cache
2526class CacheSettings {
26- /// The type of storage to use (e.g., "persistent", "ephemeral ")
27+ /// The type of storage to use (e.g., "persistent", "memory ")
2728 final CacheStorage storage;
2829
2930 /// The maximum size of the cache in bytes
3031 final int maxSizeBytes;
3132
32- const CacheSettings ({this .storage = CacheStorage .memory, this .maxSizeBytes = 100000000 });
33+ /// Duration for which cache is used before revalidation with server
34+ final Duration maxAge;
35+
36+ const CacheSettings (
37+ {this .storage = kIsWeb ? CacheStorage .memory : CacheStorage .persistent,
38+ this .maxSizeBytes = kIsWeb ? 40000000 : 100000000 ,
39+ this .maxAge = Duration .zero});
3340}
3441
3542/// Enum to control the fetch policy for a query
@@ -59,31 +66,45 @@ class ResultTree {
5966 DateTime lastAccessed;
6067
6168 /// A reference to the root `EntityNode` of the dehydrated tree.
62- final EntityNode rootObject;
69+ // final EntityNode rootObject;
6370
6471 /// Checks if cached data is stale
6572 bool isStale () {
6673 if (DateTime .now ().difference (cachedAt) > ttl) {
6774 return true ; // stale
6875 } else {
6976 return false ;
70- }
77+ }
7178 }
7279
73-
7480 ResultTree (
7581 {required this .data,
7682 required this .ttl,
7783 required this .cachedAt,
78- required this .lastAccessed,
79- required this .rootObject});
84+ required this .lastAccessed});
85+
86+ factory ResultTree .fromJson (Map <String , dynamic > json) => ResultTree (
87+ data: Map <String , dynamic >.from (json['data' ] as Map ),
88+ ttl: Duration (microseconds: json['ttl' ] as int ),
89+ cachedAt: DateTime .parse (json['cachedAt' ] as String ),
90+ lastAccessed: DateTime .parse (json['lastAccessed' ] as String ),
91+ );
92+
93+ Map <String , dynamic > toJson () => {
94+ 'data' : data,
95+ 'ttl' : ttl.inMicroseconds,
96+ 'cachedAt' : cachedAt.toIso8601String (),
97+ 'lastAccessed' : lastAccessed.toIso8601String (),
98+ };
99+
100+ factory ResultTree .fromRawJson (String source) =>
101+ ResultTree .fromJson (json.decode (source) as Map <String , dynamic >);
102+
103+ String toRawJson () => json.encode (toJson ());
80104}
81105
82106/// Target encoding mode
83- enum EncodingMode {
84- hydrated,
85- dehydrated
86- }
107+ enum EncodingMode { hydrated, dehydrated }
87108
88109/// Represents a normalized data entity.
89110class EntityDataObject {
@@ -109,8 +130,23 @@ class EntityDataObject {
109130 return _serverValues;
110131 }
111132
112- EntityDataObject (
113- {required this .guid});
133+ EntityDataObject ({required this .guid});
134+
135+ factory EntityDataObject .fromRawJson (String source) =>
136+ EntityDataObject .fromJson (json.decode (source) as Map <String , dynamic >);
137+
138+ String toRawJson () => json.encode (toJson ());
139+
140+ factory EntityDataObject .fromJson (Map <String , dynamic > json) =>
141+ EntityDataObject (
142+ guid: json[GlobalIDKey ] as String ,
143+ ).._serverValues =
144+ Map <String , dynamic >.from (json['_serverValues' ] as Map );
145+
146+ Map <String , dynamic > toJson () => {
147+ GlobalIDKey : guid,
148+ '_serverValues' : _serverValues,
149+ };
114150}
115151
116152/// A tree-like data structure that represents the dehydrated or hydrated query result.
@@ -120,19 +156,90 @@ class EntityNode {
120156
121157 /// A dictionary of scalar values (if the node does not represent a normalized entity).
122158 final Map <String , dynamic >? scalarValues;
159+ static const String scalarsKey = 'scalars' ;
123160
124161 /// A dictionary of references to other `EntityNode` s (for nested objects).
125162 final Map <String , EntityNode >? nestedObjects;
163+ static const String objectsKey = 'objects' ;
126164
127165 /// A dictionary of lists of other `EntityNode` s (for arrays of objects).
128166 final Map <String , List <EntityNode >>? nestedObjectLists;
167+ static const String listsKey = 'lists' ;
129168
130169 EntityNode (
131170 {this .entity,
132171 this .scalarValues,
133172 this .nestedObjects,
134173 this .nestedObjectLists});
135174
175+ factory EntityNode .fromJson (
176+ Map <String , dynamic > json, CacheProvider cacheProvider) {
177+ EntityDataObject ? entity = null ;
178+ if (json[GlobalIDKey ] != null ) {
179+ entity = cacheProvider.getEntityDataObject (json[GlobalIDKey ]);
180+ }
181+
182+ Map <String , dynamic >? scalars = null ;
183+ if (json[scalarsKey] != null ) {
184+ scalars = json[scalarsKey];
185+ }
186+
187+ Map <String , EntityNode >? objects;
188+ if (json[objectsKey] != null ) {
189+ Map <String , dynamic > srcObjMap = json[objectsKey] as Map <String , dynamic >;
190+ objects = {};
191+ srcObjMap.forEach ((key, value) {
192+ Map <String , dynamic > objValue = value as Map <String , dynamic >;
193+ EntityNode node = EntityNode .fromJson (objValue, cacheProvider);
194+ objects? [key] = node;
195+ });
196+ }
197+
198+ Map <String , List <EntityNode >>? objLists;
199+ if (json[listsKey] != null ) {
200+ Map <String , dynamic > srcListMap = json[listsKey] as Map <String , dynamic >;
201+ objLists = {};
202+ srcListMap.forEach ((key, value) {
203+ List <EntityNode > enodeList = [];
204+ List <dynamic > jsonList = value as List <dynamic >;
205+ jsonList.forEach ((jsonObj) {
206+ Map <String , dynamic > jmap = jsonObj as Map <String , dynamic >;
207+ EntityNode en = EntityNode .fromJson (jmap, cacheProvider);
208+ enodeList.add (en);
209+ });
210+ objLists? [key] = enodeList;
211+ });
212+ }
213+ return EntityNode (
214+ entity: entity,
215+ scalarValues: scalars,
216+ nestedObjects: objects,
217+ nestedObjectLists: objLists);
218+ }
219+
220+ /*
221+ factory EntityNode.fromJson(Map<String, dynamic> json, CacheProvider cacheProvider) => EntityNode(
222+ entity: json[GlobalIDKey] == null
223+ ? null
224+ : cacheProvider.getEntityDataObject(json[GlobalIDKey]),
225+ scalarValues: json['scalars'] == null
226+ ? null
227+ : Map<String, dynamic>.from(json['scalars'] as Map),
228+ nestedObjects: json['objects'] == null
229+ ? null
230+ : (json['objects'] as Map<String, dynamic>).map(
231+ (k, e) => MapEntry(
232+ k, EntityNode.fromJson(e as Map<String, dynamic>, cacheProvider))),
233+ nestedObjectLists: json['lists'] == null
234+ ? null
235+ : (json['lists'] as Map<String, dynamic>).map((k, e) =>
236+ MapEntry(
237+ k,
238+ List<EntityNode>.from((e as List<dynamic>).map((x) =>
239+ EntityNode.fromJson(x as Map<String, dynamic>, cacheProvider))))),
240+ );
241+ */
242+
136243 Map <String , dynamic > toJson ({EncodingMode mode = EncodingMode .hydrated}) {
137244 Map <String , dynamic > jsonData = {};
138245 if (mode == EncodingMode .hydrated) {
@@ -153,46 +260,42 @@ class EntityNode {
153260 if (nestedObjectLists != null ) {
154261 nestedObjectLists! .forEach ((key, edoList) {
155262 List <Map <String , dynamic >> jsonList = [];
156- edoList.forEach ((edo){
263+ edoList.forEach ((edo) {
157264 jsonList.add (edo.toJson (mode: mode));
158265 });
159266 jsonData[key] = jsonList;
160267 });
161268 }
162-
163- } // if hydrated
269+ } // if hydrated
164270 else if (mode == EncodingMode .dehydrated) {
165271 // encode the guid so we can extract the EntityDataObject
166272 if (entity != null ) {
167273 jsonData[GlobalIDKey ] = entity! .guid;
168274 }
169275
170276 if (scalarValues != null ) {
171- jsonData['scalars' ] = scalarValues;
277+ jsonData[scalarsKey ] = scalarValues;
172278 }
173279
174280 if (nestedObjects != null ) {
175- List <Map <String , dynamic >> nestedObjectsJson = [];
176- nestedObjects! .forEach ((key, edo){
177- Map <String , dynamic > obj = {};
178- obj[key] = edo.toJson (mode: mode);
179- nestedObjectsJson.add (obj);
180- });
181- jsonData['objects' ] = nestedObjectsJson;
281+ Map <String , dynamic > nestedObjectsJson = {};
282+ nestedObjects! .forEach ((key, edo) {
283+ nestedObjectsJson[key] = edo.toJson (mode: mode);
284+ });
285+ jsonData[objectsKey] = nestedObjectsJson;
182286 }
183287
184288 if (nestedObjectLists != null ) {
185- List < Map <String , dynamic >> nestedObjectListsJson = [] ;
186- nestedObjectLists! .forEach ((key, edoList){
289+ Map <String , dynamic > nestedObjectListsJson = {} ;
290+ nestedObjectLists! .forEach ((key, edoList) {
187291 List <Map <String , dynamic >> jsonList = [];
188- edoList.forEach ((edo){
292+ edoList.forEach ((edo) {
189293 jsonList.add (edo.toJson (mode: mode));
190294 });
191- nestedObjectListsJson. add ({ key: jsonList}) ;
295+ nestedObjectListsJson[ key] = jsonList;
192296 });
193- jsonData['lists' ] = nestedObjectListsJson;
297+ jsonData[listsKey ] = nestedObjectListsJson;
194298 }
195-
196299 }
197300 return jsonData;
198301 }
0 commit comments