Frost Web Codegen Example
- Clone the repo
- VanillaJS: Native using Webpack. Link to repo
- React (CRA). Link to repo
npm installthennpx frost generateor the shorthandnpx frost g. you might not need to run the frost generate command depending on your npm configuration and version, you can run it either way just to be safe- use
npm startto launch the website
Please Don't forget to fill the firebaseConfig object in src/database/frost.ts
Also Don't forget to use getIndices function and add the rules to your firebaseDB
The Data Structure
Schema
The schema file in the example is not in default location. The location for the schema is specified inside package.json
{
...
"frost":{
"schema":{
"path": "frostSchema.fsl"
}
}
...
}
For more details on Defining the schema, go to this page
model Student { @@node(path:"/testing/students") name string year SchoolYear birthday Date? email string? courses Course[] @Relation() club Club @Relation()}enum SchoolYear { FRESHMAN = "FRESHMAN" SOPHOMORE = "SOPHOMORE" JUNIOR = "JUNIOR" SENIOR = "SENIOR"}model Professor { @@node(path:"/testing/professors") name string contactInfo ContactInfo? department string email string courses Course[] @Relation() club Club @Relation()}type ContactInfo { phone string email string}model Course { @@node(path:"/testing/courses") name string difficultyLevel DifficultyLevel duration Duration // in weeks department string students Student[] @Relation() professor Professor @Relation()}enum DifficultyLevel { INTRODUCTORY = "INTRODUCTORY" INTERMEDIATE = "INTERMEDIATE" UPPER_INTERMEDIATE = "UPPER_INTERMEDIATE" ADVANCED_PLACEMENT = "ADVANCED_PLACEMENT"}enum Duration { FULL_YEAR = 24 FULL_SEMESTER = 12 HALF_SEMESTER = 6}model Club { @@node(path:"/testing/clubs") name string type ClubType roomId string members Student[] @Relation() supervisor Professor @Relation()}enum ClubType { STEM = "STEM" SPORTS = "SPORTS" CREATIVE = "CREATIVE"}Models
Students
- The node path [
"/testing/students"] is defined using the@@nodepragma. - The Node Model has :
- 4 base properties
- name
- year
- Type
SchoolYearis User defined Enum.
- Type
- birthday
- 2 relational properties
- courses: denotes a relation with the
CourseModel - club: denotes a relation with the
ClubModel
- courses: denotes a relation with the
- 4 base properties
model Student {
@@node(path:"/testing/students")
name string
year SchoolYear
birthday Date?
email string?
courses Course[] @Relation()
club Club @Relation()
}
enum SchoolYear {
FRESHMAN = "FRESHMAN"
SOPHOMORE = "SOPHOMORE"
JUNIOR = "JUNIOR"
SENIOR = "SENIOR"
}
Professors
- The node path [
"/testing/professors"] is defined using the@@nodepragma. - The Node Model has :
- 4 base properties
- name
- contactInfo
- Type
ContactInfois User defined Type.
- Type
- department
- 2 relational properties
- courses: denotes a relation with the
CourseModel - club: denotes a relation with the
ClubModel
- courses: denotes a relation with the
- 4 base properties
model Professor {
@@node(path:"/testing/professors")
name string
contactInfo ContactInfo?
department string
email string
courses Course[] @Relation()
club Club @Relation()
}
type ContactInfo {
phone string
email string
}
Courses
- The node path [
"/testing/courses"] is defined using the@@nodepragma. - The Node Model has :
- 4 base properties
- name
- department
- duration:
- Type
Durationis User defined Enum.
- Type
- difficultyLevel:
- Type
DifficultyLevelis User defined Enum.
- Type
- 2 relational properties
- students: denotes a relation with the
StudentModel - professor: denotes a relation with the
ProfessorModel
- students: denotes a relation with the
- 4 base properties
model Course {
@@node(path:"/testing/courses")
name string
difficultyLevel DifficultyLevel
duration Duration
department string
students Student[] @Relation()
professor Professor @Relation()
}
enum DifficultyLevel {
INTRODUCTORY = "INTRODUCTORY"
INTERMEDIATE = "INTERMEDIATE"
UPPER_INTERMEDIATE = "UPPER_INTERMEDIATE"
ADVANCED_PLACEMENT = "ADVANCED_PLACEMENT"
}
enum Duration { // in weeks
FULL_YEAR = 24
FULL_SEMESTER = 12
HALF_SEMESTER = 6
}
Clubs
- The node path [
"/testing/clubs"] is defined using the@@nodepragma. - The Node Model has :
- 4 base properties
- name
- roomId
- type:
- Type
ClubTypeis User defined Enum.
- Type
- 2 relational properties
- members: denotes a relation with the
StudentModel - supervisor: denotes a relation with the
ProfessorModel
- members: denotes a relation with the
- 4 base properties
model Club {
@@node(path:"/testing/clubs")
name string
type ClubType
roomId string
members Student[] @Relation()
supervisor Professor @Relation()
}
enum ClubType {
STEM = "STEM"
SPORTS = "SPORTS"
CREATIVE = "CREATIVE"
}
Relations
One to One
Club Supervisor (Professor <--> Club)
- Each Club has one supervisor and each professor supervises only one club.
- Relation Type:
RelationTypes.ONE_TO_ONE.ProfessorModel:clubproperty is single instance (ie; not array) and has@Relationmodifier
ClubModel:supervisorproperty is single instance (ie; not array) and has@Relationmodifier
- You can determine that the relation is one-to-one by looking at the relational properties on each side. In this case the properties [
club,supervisor] are both not arrays so it's aOne to OneRelation.
model Professor {
...
club Club @Relation()
...
}
model Club {
...
supervisor Professor @Relation()
...
}
One to Many
Club Members (Club <--> Student)
- Each Club has multiple students (members) and each student is allowed to participate in one club only
- Relation Type:
RelationTypes.ONE_TO_MANYClubModel:membersproperty is an array type and has@Relationmodifier
StudentModel:clubproperty is single instance (ie; not array) and has@Relationmodifier
- You can determine that the relation is One-to-Many by looking at the relational properties on each side. In this case, the
membersproperty on Club Model is any array, and theclubproperty on the Student model is not an array. So it's aOne to ManyRelation.
model Student {
...
club Club @Relation()
...
}
model Club {
...
members Student[] @Relation()
...
}
Professors' Courses (Professor <--> Course)
- Each Course is taught by one professor but one professor teaches multiple courses.
- Relation Type:
RelationTypes.ONE_TO_MANYProfessorModel:coursesproperty is an array type and has@Relationmodifier
CourseModel:professorproperty is single instance (ie; not array) and has@Relationmodifier
- You can determine that the relation is One-to-Many by looking at the relational properties on each side. In this case, the
coursesproperty on Professor Model is any array, and theprofessorproperty on the Course model is not an array. So it's aOne to ManyRelation.
model Course {
...
professor Professor @Relation()
...
}
model Professor {
...
courses Course[] @Relation()
...
}
Many to Many
Students' Courses (Student <--> Course)
- Each Course is audited by multiple students and each student audits multiple courses
- Relation Type:
RelationTypes.MANY_TO_MANY:StudentModel:coursesproperty is an array type and has@Relationmodifier
CourseModel :studentsproperty is an array type and has@Relationmodifier
- You can determine that the relation is Many-to-Many by looking at the relational properties on each side. In this case the properties [
students,courses] are both arrays so it's aMany to ManyRelation.
model Student {
...
courses Course[] @Relation()
...
}
model Course {
...
students Student[] @Relation()
...
}
Brief Explanation on flow
Initialization
- The firebase configuration object is passed as the first argument to the Frost.initialize function.
- The Function Returns a FrostApp Instance containing the Delegates instances for each model, the
firebaseAppinstance, and thefirebaseDBinstance (SDK 9). - The
firebaseDBinstance will be used in the update() function in Mock Data
export const FrostApp = Frost.initialize(firebaseConfig)
Rendering
We have 3 lists:
- Courses List
- Clubs List
- Students List
Each list will contain cards displaying the data for each item.
The students list is empty by default. Each card in the Clubs and Courses List will have
Students ListButton. When this button is clicked; then the students for said course or club will be displayed in the list.The Rendering is managed by native DOM Manipulation and Custom Web Elements (Cards and Buttons from Ionic UI).
We Have an observer for each list
- Clubs and Courses: Their observers are from the observeMany() function in their respective FrostDelegate.
- Students: Their Observer is manually created form a RX Subject. and the Emitting of the data to this observer is managed through the other observers and the
onclickevents.
...
/*
* When Clicked set the data in the selected variable
* and Emit the new students list
*/
function handleStudentsListClick(data: ClubTypes["FullModel"] | CourseTypes["FullModel"]){
selected = { type: ClubPredicate(data) ? "club":"course", id: data.id };
studentsSubject.next(Object.values(( ClubPredicate(data)? data.members : data.students )?? []) as any);
}
/*
* Courses Observer
* (No Constraints Passed , So it listens to all Courses)
* included with each course is the connected students and professor
*/
FrostApp.course.observeMany({
include: {"professor":true, "students":true},
}).subscribe((data) => {
console.log({data})
/*
* When the data changes the coursesList div is modified
*/
coursesList.replaceChildren(...Object.values(data).map((course)=>courseCard(course,handleStudentsListClick)));
/*
* if the selected course changes then emit the new students list
* if empty then emit an empty student list
*/
if (selected && selected.type === "course" ) {
if(!Object.values(data).length) studentsSubject.next([])
else studentsSubject.next(Object.values(data[selected?.id]?.students ?? [] as any));
}
});
/*
* Clubs Observer
* (No Constraints Passed , So it listens to all Clubs)
* included with each club is the connected students and supervisor
*/
FrostApp.club.observeMany({ include: {"supervisor":true, "members":true } }).subscribe(
(data) => {
/*
* When the data changes the clubsList div is modified
*/
clubsList.replaceChildren(...Object.values(data).map((club)=>clubCard(club,handleStudentsListClick)));
/*
* if the selected club changes then emit the new students list
* if empty then emit an empty student list
*/
if (selected && selected.type === "club" ) {
if(!Object.values(data).length) studentsSubject.next([])
else studentsSubject.next(Object.values(data[selected?.id]?.members ?? [] as any));
}
},
);
studentsSubject.subscribe((data) => {
/*
* When the data changes the studentsList div is modified
*/
studentsList.replaceChildren(...data.map(studentCard));
});
...
Mock Data
- Some Initial data is generated and Added to the database. (
src/database/mock-data.tscontains the frost operations to add the data) - Extra Students can be added. (mock data generated using Faker.Js)
/*
* Sets The initial Mock Data (Clubs,Courses,Professors, Some Students)
*/
mockBtn.onclick = () => {
setData();
};
/*
* Adds Extra Mock Students Data
*/
mockStudentsBtn.onclick = () => {
addStudents();
};