What you will learn
Build a small learning record, save it with Gammal Tech and explain exactly what changes when a new object is saved. This lab teaches data boundaries, defensive type handling and the difference between personal preferences and trusted business authority. Your deliverable is a tested fictional record and a short evidence worksheet, not a production access-control system.
Prepare an authorized learning environment
Use a project you control, its registered HTTPS domain and your own verified account. Complete the first-project and login exercises before attempting live storage. If verification is pending, complete the schema and expected-result worksheet offline. Do not create a second personal account or borrow a client’s credentials.
Load the documented SDK before your own exercise code:
<script src="https://api.gammal.tech/sdk-web.js"></script>After completing the supported login flow, add these elements and place your exercise JavaScript below them:
<button id="run-data-lab" type="button">Run fictional data lab</button>
<pre id="lab-result"></pre>
<p id="lab-status" role="status"></p>Use fictional values in a learningLab field without existing work. The exercises replace this field. Requests run only after a deliberate button click.
Design a small record before writing requests
The published API contract describes one JSON object per user per project, up to 1 MB. A first user.get() returns an empty object when nothing has been stored. Keys beginning with _gt_ are reserved and rejected.
Start with this fictional application shape:
{
"learningLab": {
"language": "en",
"progress": { "lesson": 1, "completed": false }
},
"learningMarker": "round-1"
}language is an approved choice, lesson is an integer from 1 to 20, and completed is a Boolean. The marker helps demonstrate top-level behavior. None of these values grants access to a paid course. Write each field’s meaning and permitted type on your worksheet before touching the network.
Reconstruct known fields instead of trusting every key
For missing or older records, reconstruct only the fields this exercise understands:
function normalizeLab(value) {
const object = x => x !== null && typeof x === "object"
&& !Array.isArray(x);
const item = object(value) ? value : {};
const progress = object(item.progress) ? item.progress : {};
return {
language: ["en", "ar"].includes(item.language)
? item.language : "en",
progress: {
lesson: Number.isInteger(progress.lesson)
&& progress.lesson >= 1 && progress.lesson <= 20
? progress.lesson : 1,
completed: typeof progress.completed === "boolean"
? progress.completed : false
}
};
}Test it locally with null, an array, a missing progress object, a string lesson number and completed: "false". The string is not the Boolean false; treating any nonempty string as true would produce the wrong result. Unknown keys are not copied.
Normalization supplies safe application defaults. It is not evidence that the original record was valid or that a user has permission to perform an action. If a user is filling a form, show a validation message for invalid input instead of silently claiming the requested value was saved.
Predict top-level merge and nested replacement
Predict the result first. Gammal Tech merges supplied top-level keys but replaces a nested object supplied at a key. The second save therefore replaces the whole learningLab object.
const dataLabButton = document.getElementById("run-data-lab");
const dataLabResult = document.getElementById("lab-result");
const dataLabStatus = document.getElementById("lab-status");
dataLabButton.addEventListener("click", async () => {
if (dataLabButton.disabled) return;
dataLabButton.disabled = true;
try {
await GammalTech.user.save({
learningLab: {
language: "en", progress: { lesson: 1, completed: false }
},
learningMarker: "round-1"
});
await GammalTech.user.save({ learningLab: { language: "ar" } });
const saved = await GammalTech.user.get();
dataLabResult.textContent = JSON.stringify({
learningLab: saved.learningLab,
learningMarker: saved.learningMarker
}, null, 2);
dataLabStatus.textContent = "Read-back complete.";
} catch {
dataLabStatus.textContent = "Result not confirmed. Check before retrying.";
} finally {
dataLabButton.disabled = false;
}
});The expected read-back contains learningMarker: "round-1" and learningLab: {"language":"ar"}. The earlier progress fields are absent. A common mistake is expecting a deep merge that preserves them. Read only your fictional exercise fields into the output; do not display unrelated user data. A rejected or interrupted request requires checking the state, not assuming nothing happened.
Make a deliberate replacement and review concurrency
To change the lesson while retaining this exercise’s known preferences, read the existing record, normalize the learning object and save the complete replacement. Replace the previous handler’s try body with this fragment; retain its error and button handling:
// Same handler context; normalizeLab is defined above.
const current = await GammalTech.user.get();
const next = normalizeLab(current.learningLab);
next.progress.lesson = 2;
await GammalTech.user.save({ learningLab: next });
const checked = await GammalTech.user.get();
dataLabResult.textContent = JSON.stringify(
normalizeLab(checked.learningLab), null, 2
);The expected result is Arabic language, lesson 2 and a default false completion value because the previous exercise removed progress. Explain why that default appears. The awaited save does not supply a documented read-back object; retrieve the data explicitly.
This read/modify/save sequence is not an atomic transaction. Two tabs can read old state and later overwrite one another. For this beginner lab, edit from one tab. A client feature needing concurrent changes must define its conflict requirements and verify that the architecture can support them.
Check separation with two consenting people
- Ask a second authorized learner to use their own verified account on the same approved project. Each person controls their own login.
- Each saves a different fictional marker and records the expected value privately.
- Each reads through the normal application flow and checks that their own value appears.
- After a supported sign-out and sign-in, repeat the read. Never exchange tokens or credentials to perform the comparison.
If you are learning alone, leave this test marked “not executed” rather than inventing a second account or claiming a pass. This is a functional acceptance check, not a penetration test or proof of all isolation controls.
Submit a record and evidence worksheet
Your deliverable contains the schema, the normalizer, predicted and observed merge results, and the separation check’s actual status. Include the expected default for each malformed input and one sentence about concurrent edits. Use screenshots showing only fictional application output, without developer tools, identity information or session values.
Acceptance requires correct types, deliberate nested replacement, a neutral error state and no privileged decision based on the record. Also identify who controls the domain and publishing account. Frontend, hosting and broader business security remain responsibilities.
Common questions
Can I use a second account just for the lab?
Use one verified individual account per person. For the two-person check, work with another consenting authorized learner using their own account. Otherwise record that check as not executed.
Does saving a partial nested object preserve its other fields?
No. Supplied top-level keys merge, but a nested object at a supplied key is replaced. Reconstruct the intended full object and consider concurrent updates before saving.
Lesson references
Documentation checked on 12 October 2026. Recheck current documentation before implementing or quoting a client project.