Data Service
The dataService is the central injectable service in upp-data, managing the data lifecycle, object storage, view caching, synchronization, and session state. It acts as the hub connecting all other components.
dataService
Injectable: providedIn: 'root'
Constructor Dependencies
constructor(
public injector: Injector,
public clock: clockService,
private lang: languageService,
private adhoc: adhocService,
private logs: logsService,
private state: stateService,
private view: viewService,
private sync: syncService
)
Properties
| Property | Type | Description |
|---|---|---|
store | dataStorage | Lazy-initialized two-tiered object storage |
alives | aliveItems | WeakRef-based ViewObject cache with automatic GC cleanup |
events | eventsNotifier | Event logging system for drawer, login, and guest actions |
session | Session | null | The current active session |
user | User | null | The currently authenticated user |
place | Place | null | The currently active place/business |
qrcode | QrCode | null | The currently active QR code (guest mode) |
serial | number | CRC32-based serial from place.objid + device for ticket numbering |
injector | Injector | Angular injector exposed for ViewObjects to resolve services lazily |
Observables
| Observable | Type | Description |
|---|---|---|
OnSessionChanged | Subject<void> | Emitted when the session is established or changed |
OnSessionReleased | Subject<boolean> | Emitted when the session ends; boolean indicates whether it expired |
OnReloadRequested | Subject<void> | Emitted when the server requests a full reload |
OnUserChanged | Subject<void> | Emitted when the user entity is set or changed |
OnPlaceChanged | Subject<void> | Emitted when the place entity is set or changed |
OnQrCodeChanged | Subject<void> | Emitted when the QR code entity is set or changed |
OnRefreshPercent | Observable<number> | Proxied from syncService — sync progress (0–100) |
OnRefreshCompleted | Observable<void> | Proxied from syncService — sync cycle completed |
OnCommitCompleted | Observable<void> | Proxied from syncService — commit acknowledged by server |
Session Lifecycle
Start(session, access)
Begins a data session:
- Creates a
Sessionobject in thestore. - Starts the sync service:
sync.Start(session). - After
Session.OnUpdated, both access modes bind from the hydrated session:LOGIN:SetUser(session.user), then viewUSERif a user is set.GUEST:SetQrScan(session.qrscan), then viewPLACEif a qrcode is set.SetQrScanbinds/sync ?table=and appliesSetQrCode(qrscan.qrcode).
- Emits
OnSessionChanged.
Stop(expired)
Ends the data session:
- Releases the current session.
- Stops the sync service:
sync.Stop(). - Clears the object storage:
store.Clear(). - Emits
OnSessionReleased(expired).
Ready(): Promise<void>
Waits until the sync service signals readiness (sync.IsReady), using firstValueFrom with take(1).
Entity Management
Each setter follows the same pattern:
async SetUser(user: User): Promise<void>
async SetPlace(place: Place): Promise<void>
async SetQrCode(qrcode: QrCode): Promise<void>
The flow:
- Wait for the entity to be fully loaded (its
OnInitializeevent). - Update the internal reference (
this._user,this._place,this._qrcode). - Add the entity to the store.
- Reset the sync stage:
sync.ResetStage(). - Emit the corresponding change observable.
- SetPlace additionally: logs a
LOGINevent, preloads the place's language pack.
Commit Flow
The commit flow is the path data changes take from a DataObject modification to the server:
Commit(changes: CommitChange[])
Validates and queues changes for submission:
- For each
CommitChange, checks that allrequires(dependencies) are safe:- The dependency has an
objid(already resolved), or - The dependency is already in the
ToCommitmap, or - The dependency is in the store and currently
InCommit.
- The dependency has an
- Adds validated changes to the internal
_tocommitmap (keyed bytable@objidortable@uuid).
Flush(force: boolean): Promise<boolean>
Sends accumulated changes to the server:
- Collects all entries from the
_tocommitmap. - Delegates to
sync.Flush(changes, force). - Returns
trueon success.
FetchByObjid(table, objid): Promise<BaseObject>
Ad-hoc server request to fetch a single object by table and objid, independent of the sync cycle.
Transaction Support
get transaction(): dataTransaction
Creates a new dataTransaction for batching multiple changes atomically.
dataStorage
A two-tiered object store that separates objects by resolution state:
Internal Structure
type StorageMap = { [table: string]: Map<string, BaseObject> };
private _objects: {
resolved: StorageMap; // objects with numeric objid from server
awaiting: StorageMap; // objects with temporary _uuid (client-created)
};
Methods
| Method | Signature | Description |
|---|---|---|
GetByRef | (table, objid, uuid) → BaseObject | null | Searches by uuid in awaiting first, then by objid in resolved |
GetObject | (dataobject) → BaseObject | null | Searches by objref in both tiers |
AddObject | (dataobject) → boolean | Routes to resolved (if numeric objid) or awaiting (otherwise). Skips copies (CopyOf is set) |
DelObject | (dataobject) → void | Removes from the appropriate tier based on objid |
SetObject | (dataobject) → void | Delete + Add (update in place) |
Resolve | (dataobject) → void | Moves an object from awaiting to resolved after the server assigns an objid. OnResolve() is called on any displaced object (stub): it hands the objects still referencing it over to the resolved one |
Replace | (dataobject, uuid) → void | Replaces an existing entry in resolved with a new version. Handles uuid-based lookups in awaiting |
Clear | () → void | Empties both maps completely |
Resolution Flow
When a client-created object (e.g., a new Ticket) is committed to the server:
- The object starts in awaiting with a temporary
_uuid. - The server responds with a permanent
objid. Resolve()is called:- Removes the object from awaiting.
- Sets
objidfrom the server response. - If an object with the same
objidalready exists in resolved (a stub created by the factory because a child row, e.g. aPRESELECTecho, was synchronized before its awaiting parent), it is removed and itsOnResolve()is called. Since the storage no longer returns that instance,OnResolve()moves every object still referencing the stub to the surviving one, so a row never lives in two instances. - The object is added to resolved.
dataTransaction
Manages atomic groups of changes to be committed together:
class dataTransaction {
private _trstatus: 'OPEN' | 'CLOSED' = 'OPEN';
private _tocommit = new Map<string, CommitChange>();
}
Methods
push(item: BaseObject, force?: boolean): Promise<boolean>
Adds an object and its dependent changes to the transaction:
- If
forceis true, callsitem.ForceUpdate()to mark it for update. - Collects all
CommitChangeobjects fromitem.Commit(includes children and dependencies). - Adds each change to the transaction's
_tocommitmap. - Validates that all dependencies are safe:
- The dependency has an
objid, or - The dependency is in this transaction's
_tocommit, or - The dependency is in
dataService.ToCommit, or - The dependency is in the store and
InCommit.
- The dependency has an
- Throws if any dependency is unresolved.
flush(force?: boolean): Promise<boolean>
Commits all accumulated changes:
- Merges the transaction's
_tocommitintodataService.ToCommit. - Calls
dataService.Flush(force). - Returns
trueon success.
Usage Example
const tx = this.data.transaction;
const ticket = new Ticket(null, this.data);
ticket.place = this.data.place;
ticket.status = 'AC';
await tx.push(ticket);
const product = new TicketProduct(null, this.data);
product.ticket = ticket;
product.product = someProduct;
product.amount = 2;
await tx.push(product);
await tx.flush();
aliveItems
Manages ViewObject instances using weak references to allow garbage collection when views are no longer needed by the UI.
Internal Structure
class aliveItems {
private _alivemap = new WeakMap<DataObject, WeakRef<ViewObject<DataObject>>>();
private finalizationRegistry = new FinalizationRegistry<DataObject>(...);
}
Methods
| Method | Description |
|---|---|
get(object) | Returns the existing ViewObject if still alive (via WeakRef.deref()), or creates a new one by calling object.View, stores it as a WeakRef, registers it with FinalizationRegistry, and calls doRegister(). Returns null if object is null. |
has(object) | Returns true if a WeakRef exists and the referenced ViewObject is still alive. Logs a warning if the ref exists but the object has been GC'd. |
set(object) | (Private) Creates and stores a new ViewObject via object.View, registers it for finalization tracking. |
Lifecycle
Debug Information
get itemssize(): number // _register_count - _released_count
logstatus(): void // Logs total registered and released counts