Skip to main content

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​

PropertyTypeDescription
storedataStorageLazy-initialized two-tiered object storage
alivesaliveItemsWeakRef-based ViewObject cache with automatic GC cleanup
eventseventsNotifierEvent logging system for drawer, login, and guest actions
sessionSession | nullThe current active session
userUser | nullThe currently authenticated user
placePlace | nullThe currently active place/business
qrcodeQrCode | nullThe currently active QR code (guest mode)
serialnumberCRC32-based serial from place.objid + device for ticket numbering
injectorInjectorAngular injector exposed for ViewObjects to resolve services lazily

Observables​

ObservableTypeDescription
OnSessionChangedSubject<void>Emitted when the session is established or changed
OnSessionReleasedSubject<boolean>Emitted when the session ends; boolean indicates whether it expired
OnReloadRequestedSubject<void>Emitted when the server requests a full reload
OnUserChangedSubject<void>Emitted when the user entity is set or changed
OnPlaceChangedSubject<void>Emitted when the place entity is set or changed
OnQrCodeChangedSubject<void>Emitted when the QR code entity is set or changed
OnRefreshPercentObservable<number>Proxied from syncService — sync progress (0–100)
OnRefreshCompletedObservable<void>Proxied from syncService — sync cycle completed
OnCommitCompletedObservable<void>Proxied from syncService — commit acknowledged by server

Session Lifecycle​

Start(session, access)​

Begins a data session:

  1. Creates a Session object in the store.
  2. Starts the sync service: sync.Start(session).
  3. After Session.OnUpdated, both access modes bind from the hydrated session:
    • LOGIN: SetUser(session.user), then view USER if a user is set.
    • GUEST: SetQrScan(session.qrscan), then view PLACE if a qrcode is set. SetQrScan binds /sync ?table= and applies SetQrCode(qrscan.qrcode).
  4. Emits OnSessionChanged.

Stop(expired)​

Ends the data session:

  1. Releases the current session.
  2. Stops the sync service: sync.Stop().
  3. Clears the object storage: store.Clear().
  4. 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:

  1. Wait for the entity to be fully loaded (its OnInitialize event).
  2. Update the internal reference (this._user, this._place, this._qrcode).
  3. Add the entity to the store.
  4. Reset the sync stage: sync.ResetStage().
  5. Emit the corresponding change observable.
  6. SetPlace additionally: logs a LOGIN event, 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:

  1. For each CommitChange, checks that all requires (dependencies) are safe:
    • The dependency has an objid (already resolved), or
    • The dependency is already in the ToCommit map, or
    • The dependency is in the store and currently InCommit.
  2. Adds validated changes to the internal _tocommit map (keyed by table@objid or table@uuid).

Flush(force: boolean): Promise<boolean>​

Sends accumulated changes to the server:

  1. Collects all entries from the _tocommit map.
  2. Delegates to sync.Flush(changes, force).
  3. Returns true on 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​

MethodSignatureDescription
GetByRef(table, objid, uuid) → BaseObject | nullSearches by uuid in awaiting first, then by objid in resolved
GetObject(dataobject) → BaseObject | nullSearches by objref in both tiers
AddObject(dataobject) → booleanRoutes to resolved (if numeric objid) or awaiting (otherwise). Skips copies (CopyOf is set)
DelObject(dataobject) → voidRemoves from the appropriate tier based on objid
SetObject(dataobject) → voidDelete + Add (update in place)
Resolve(dataobject) → voidMoves 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) → voidReplaces an existing entry in resolved with a new version. Handles uuid-based lookups in awaiting
Clear() → voidEmpties both maps completely

Resolution Flow​

When a client-created object (e.g., a new Ticket) is committed to the server:

  1. The object starts in awaiting with a temporary _uuid.
  2. The server responds with a permanent objid.
  3. Resolve() is called:
    • Removes the object from awaiting.
    • Sets objid from the server response.
    • If an object with the same objid already exists in resolved (a stub created by the factory because a child row, e.g. a PRESELECT echo, was synchronized before its awaiting parent), it is removed and its OnResolve() 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:

  1. If force is true, calls item.ForceUpdate() to mark it for update.
  2. Collects all CommitChange objects from item.Commit (includes children and dependencies).
  3. Adds each change to the transaction's _tocommit map.
  4. 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.
  5. Throws if any dependency is unresolved.

flush(force?: boolean): Promise<boolean>​

Commits all accumulated changes:

  1. Merges the transaction's _tocommit into dataService.ToCommit.
  2. Calls dataService.Flush(force).
  3. Returns true on 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​

MethodDescription
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