-
Notifications
You must be signed in to change notification settings - Fork 1
Design Data Management
The database uses numeric ids for indexing/search efficiency. We rely on the numeric ids to automatically assign new ids to created objects. However, the client side's libraries tend to use strings instead of numbers:
- The way we iterate over keys using
Object.keys()
will always return strings instead of numbers. Our fast lookup structures use strings as a result. - The graphing library we use, Graphlib, also returns its list of node ids as strings even though we are writing them as numbers, and the keys themselves are numbers.
- The UI/HTML tends to sometimes return strings (when reading from a field) or was originally written with string ids in mind. Our original test graph uses text ids instead of numeric ids
We have the following convention currently:
- Client side should just assume IDs are strings, even if they are numbers.
- Database always uses IDs that are numbers, though it will happily accept an id in string format and then fail to find it in update and delete ops.
- NUMBER->STRING CONVERSION: The essential
pmc-data.js
routinesInitializeModel()
andBuildModel()
are the only places where numeric ids from the server are converted to string ids. All code that touches data through PMCDATA can safely assume that the data structures are using string ids locally. - STRING->NUMBER CONVERSION: Any communication with the database
server-database.js
viaUR.DBQuery()
needs to convert string ids to numbers. This is done usingNumber(id)
, where id is a string representation of a number.
The database is implemented using lokijs as a set of individual collections via the DATA module. Another module DATAMAP is used to validate the data objects that are sent to the server to update these collections. Hence, to add a new collection, you just need to update the DATAMAP.DBKEYS collection, and the server/client code will automatically know about them.
-
Data is written through
UR.DBQuery(cmd,data)
. Thecmd
parameter is either 'add', 'remove', or 'update', anddata
is an object that contains the database collections to be modified. DATAMAP maintains the list of associated messages in its DBCMDS structure, mapping the short command name with the longer URSYS message name. For example 'add' invokes a NetCall to 'NET:SRV_DBADD'), which is the main add handler. -
Data is not read directly. Instead, browser applications rely on the DATA module receiving 'NET:SYSTEM_DBSYNC' to synchronize changes to the underlying data structure. This mirrors how the React interface and SVG plotting modules work; in general, our dataflow model is to be responsive to change, instead of forcing change with fragile conditional logic. Subscribing to 'DATA_UPDATED' should be sufficient for receiving change notifications.
Each of the commands accept a different data input, and return the changed data.
- Use
UR.DBQuery('add',data).then(results=>{})
to add data, optionally receive objects with added ids. - Use
UR.DBQuery('update',data).then(results=>{})
to update data by id, optionally receive changed data as confirmation. - Use
UR.DBQuery('remove',data).then(results=>{})
to remove data by id, optionally receive the deleted objects
The format of data
are "collection keys" corresponding to DATAMAP.DBKEYS
. Each key contains ONLY a value. The type of value depends on the command:
- 'add' - values are objects WITHOUT an
id
property. The returned objects will have assigned ids if they are needed at the time of creation as they are inserted into the designated collection. - 'update' - values are objects WITH an
id
property that are used to update specific document objects in the designated collection. - 'remove' - values are objects WITH an
id
prropery, which is used to delete the matching id in the designated collection.
If you pass a single value to a collection defined in the data
parameter, UR.DBQuery
will return a single value.
EXAMPLE:
// add a value to the teachers collection
// receives rdata back from server
UR.DBQuery('add',{
teachers: { name: 'Mrs. Smith' }
}).then(rdata=>{
const teacherId = rdata.id; // added by db
const name = rdata.name;
});
// FYI: collections are defined in DATAMAP.DBKEYS
// FYI: More examples in DATA's window.ur test methods
WARNING! While these methods all return the changed objects asynchronously, you should NOT force a database update in your optional then()
handler. Let the DATA module do it for you, or handle it through the appropriate DBSYNC handler that eventually fires a DATA_UPDATED or BuildModel() followed by DATA_UPDATE. You can, however change viewmodel properties as a result of a data update that might affect the view (e.g. selected items).
Currently there is no handler for the DB updates that synch data, but this is coming next. The stub code is in data.js
, which implements the 'NET:SYSTEM_DBSYNC' message handler for:
- sync add - calls
MIR.SyncAddedData(data)
- sync update - calls
MIR.SyncUpdatedData(data)
- sync remove - calls
MIR.SyncRemovedData(data)
The data parameter is the 'NET:SYSTEM_DBSYNC' data packet received from the network, which can be converted into an array of syncdata objects through DATAMAP.ExtractSyncData(data)
as follows:
const syncdata = DATAMA.ExtractSyncData(data);
syncdata.forEach(item=>{
const { colkey, subkey, value } = item;
...
});
-
colkey
is the collection name that was updated, which can be used as a key to update the local data structure and trigger conditional logic. It is always the main collection name. -
subkey
is the property name within the collection. This is used for collections of the formpmcData.entities
, wherepmcData
refers to the pmcData collection, andentities
refers to a prop within a pmcData record. -
value
is the data used by the server to modify the database.
Use DATAMAP.ExtractQueryData(data)
on the server to extract
params, which returns array of objects { colkey, subkey, value }
data.cmd | colkey | subkey | value |
---|---|---|---|
add | pmcData | { ...props } | |
update | pmcData | { id, ...props } | |
remove | pmcData | { id } | |
add | pmcData | entities | { id, entities:{ ...props } } |
update | pmcData | entities | { id, entities:{ id, ...props } } |
remove | pmcData | entities | { id, entities:{ id } } |
You should rely on DBSYNC RECEIVE
to handle data-driven updates,
but a DBQuery()
returns a Promise that also receives the updated
data in DBSYNC
format (see below). This may be useful in some
cases where you need to make a local state update of a component,
but you can not be sure if other changed data available yet.
Use DATAMAP.ExtractSyncData(data)
to parse incoming sync data,
which returns array of objects { colkey, subkey, value } as
follows:
data.cmd | colkey | subkey | value |
---|---|---|---|
add | pmcData | { id, ...props } | |
update | pmcData | { id, ...props } | |
remove | pmcData | { id, ...props } | |
add | pmcData | entities | { id, ...props } |
update | pmcData | entities | { id, ...props } |
remove | pmcData | entities | { id, ...props } |
NOTE: It is possible for a single data input to produce multiple syncdata object in the output array, as the database always sends sync in an array within the data packet.
ISSUES
-
adm-data.Load()
- load loki tables at app initialization
- ViewMain subscribes to load event
- ViewMain et al render on load event
- ViewMain et al update display via data publish
- UR/Server handles everything (save, sync) behind the scenes
- proper UID generator
-
Student login tokens
- GroupsList needs to be able to generate tokens
-
student login/home page
- students need a login
- after they login they see a list of their models
- what should the routes be? \
#/model/ => model URL?
-
model load
- select model from home page
- How do you return to home page?
-
Set resources for classroom via UI.
- Save resource setting on checkbox click.
LOAD_DATA - UR system calls the LOAD_DATA
hook defined in ViewMain (or wherever)
UR.Lifecycle('LOAD_DATA',(data)=> {
// this code will fire during the LOAD_DATA PHASE
// which occurs BEFORE React even starts any rendering of anything
// to ensure data is present
// do initial mode setting that will affect LATER rendering styles
// flags to set
});
UR.OnDataBaseUpdate('ADMIN', (admdata)=> {
// this fires when the ADMIN data set has new data!
// at this point, you can use this to set React stuff
// by parsing data and using setState() to make React
// draw its elements
// optimization: data might have some way of determing
// what changed e.g
if (admdata.teachers) {}
if (admdata.classrooms) {}
if (admdata.groups) {}
// ...
if (admdata.classroomResources) {}
});
NAMING THOUGHTS - parts of things that go into a method name
- action-noun or noun-action, unambiguous which is the noun and which is the action
- state: pending, promise, or request
- code mechanism: listener, handler, pattern
const LoadData = (data) => {
/* current hardcoded data stuff */
};
// FAKE THE CALL
LoadData(); // will become UR.LifeCycle('LOAD_DATA', LoadData);
in constructor:
- what to name all the UR functions, partiular for URDATA SYNCHED LOADS
ADMDATA.Load()
STUDENT LOGIN/HOME PAGE
MODEL LOAD
- copy/paste model URLs for sharing
To LISTEN/RECEIVE ADMIN DATA subscribe to the 'ADMIN:UPDATED' system event
// Admin Data Module
let db_admin = {};
UR.DB_Subscribe('ADMIN:UPDATED', localHandler);
// in class definition
localHandler(data) {
// save a local instance if you need to refer
// to it later
db_admin = data.db_admin;
// UR.Pub('ADM_DATA_UPDATED');
}
ADMData.GetClassroomsByTeacher = teacherId => {
return db_admin.classrooms.filter(
cls => cls.teacherId === teacherId
);
}
// Component
UR.DB_Subscribe('ADMIN:UPDATED', AdmDataUpdated);
// UR.Sub('ADM_DATA_UPDATED', AdmDataUpdated);
// we're going to
AdmDataUpdated() {
classrooms = ADM.GetClassrooms();
this.setState(classrooms: data.classrooms);
}
To USE ADMIN DATA in a component
- Call AdminData methods that are defined to return data structure. They are GUARANTEED to be up-to-date.
- NOTE: Components rerender data only on receive of 'ADMIN:UPDATED'
To CHANGE ADMIN DATA
- Use UR wrapped call UR.DB_GetObject('ADMIN',(data)=>{});
- when the function is executed by UR, it will then check the data object for changes and update itself consistently
- after the object is updated, UR will fire the related 'ADMIN:UPDATED' system event
To RENDER NEW ADMIN DATA
- subscribe to the 'ADMIN:UPDATED' system event
-
setState()
so React renders its view defined inrender()
AdminData is the ONLY module that will use the UR.DATABASE calls and events AdminData will publish its own DERIVED event calls that drive UI changes when the database updates These derived event calls are defined based on the groups that have changed (e.g. the original groups object, maybe DERIVED group objects if there are any components that depend on them)
ADMINDATA EMITTED EVENT TYPE: BASE DATABASE OBJECT has changed ADMINDATA EMITTED EVENT TYPE: DERIVED DB OBJECT has changed (e.g. filtered list of classrooms has changed) ADMINDATA EMITTED EVENT TYPE: DERIVED COMPONENT DISPLAY DATA STRUCTURE has changed (e.g. teacher is selected)
Question: in the case of a teacher selected
- any related display elements in other components need to know to update
- PATTERN NOTE: the source component that receives the SELECT TEACHER events as part of local UI never sets its state directly IF that state impacts other components. In this case, it fires a change to ADMIN DATA (or a VIEWMODEL manager that is tied closely to admindata) and it updates state on RECEVING the appropriate event.ADMDATA
Eg. instead of code like this:
onclick = read a value, setState to update value in control, then pub event 'something happened'
...you write...
onclick = read a value, call VIEWMODEL changeValue(), and let the related EVENT come back to trigger the update of control.
Because this ensures (1) reduces change of async order of operation errors which also (2) reduces the need for component coupling in unsavory ways (props, call chains, event multiplication
User Manual
Developer Manual
Installation
Setting Up End-User Projects
Deploying
- Deploy Electron
- Electron Code Signing README-signing.md
- Digital Ocean Deployment
- Updating MEME for 2021+
--
Coding Practices
Background
Design Notes
- Dev Insights
- Design Data Management
- Student Login
- Reference Build Differences
- Design Settings Manager
- Why Electron?
Deprecated