A saved review note should keep the values you captured, even if the editor's input object changes later. Appending the same mutable form object to a history array is a small shortcut with a large consequence: a later edit can silently rewrite the apparent past.
I maintain AGProLabs, AliGurtuna.com and Babamın Şiir Defteri. This is an independent educational JavaScript example for flat notes, not a production deployment, automatic publishing system or audit service.
Define the record before copying it
The narrow record contains five primitive fields: an ID, a label, a source URL, exact note text and an explicit epoch-millisecond observation time. Extra fields are deliberately excluded. A form may hold private drafts or nested UI state that do not belong in this record.
The text fields must be nonempty strings. The source link must use HTTPS without embedded username or password. Time must be a nonnegative safe integer within the representable Date range. Zero is valid; a numeric string is not. The function does not insert the current time or claim that a supplied time is truthful.
Save as observation-record.mjs:
export function captureObservation(input) {
if (!input || typeof input !== 'object' || Array.isArray(input))
throw new TypeError('A trusted flat input object is required');
const { id, label, sourceUrl, notes, observedAtMs } = input;
for (const value of [id, label, sourceUrl, notes]) {
if (typeof value !== 'string' || !value.trim())
throw new TypeError('Text fields must be nonempty strings');
}
const url = new URL(sourceUrl);
if (url.protocol !== 'https:' || url.username || url.password)
throw new TypeError('Use HTTPS source links without credentials');
if (!Number.isSafeInteger(observedAtMs) || observedAtMs < 0 || observedAtMs > 8640000000000000)
throw new TypeError('A nonnegative representable epoch-millisecond value is required');
return Object.freeze({ id, label, sourceUrl: url.href, notes, observedAtMs });
}
The returned object is new, so later primitive assignments on the input cannot change it. Note wording is preserved, including surrounding whitespace; trimming is used only to reject blank fields. The URL uses the URL parser's serialization rather than an invented replacement expression.
Freeze only the contract you actually built
MDN's Object.freeze documentation describes shallow freezing. Freezing an object does not recursively freeze objects referenced by its properties. This example avoids that claim by permitting only primitive output fields. It is not a generic deep-copy function.
An HTTPS check is also not a privacy scrubber. HTTPS URLs can contain sensitive query values. Review any source URL before public use, apply an appropriate publication allowlist, and never treat this tiny validator as proof of public access or safe content. HTML rendering needs its own output handling. The function accepts trusted plain input; hostile getters and proxies are outside its boundary.
Eight focused scenarios
Save this beside the module as observation-record.test.mjs:
import test from 'node:test';
import assert from 'node:assert/strict';
import { captureObservation } from './observation-record.mjs';
const fixture = () => ({id:'synthetic-01',label:'Fixture reading',sourceUrl:'https://example.org/resource',notes:'Illustrative observation',observedAtMs:1000});
test('capture returns a separate flat record', () => {
const input=fixture(),record=captureObservation(input);
assert.notEqual(record,input);
assert.deepEqual(record,input);
});
test('later input edits cannot rewrite captured primitive values', () => {
const input=fixture(),record=captureObservation(input);
input.notes='Later edit';input.observedAtMs=2000;
assert.equal(record.notes,'Illustrative observation');
assert.equal(record.observedAtMs,1000);
});
test('frozen output rejects direct edits in module strict mode', () => {
const record=captureObservation(fixture());
assert.ok(Object.isFrozen(record));
assert.throws(()=>{record.notes='Rewrite';},TypeError);
});
test('unselected extra fields are excluded from output', () => {
const record=captureObservation({...fixture(),privateDraft:'not for publication',nested:{x:1}});
assert.equal(Object.hasOwn(record,'privateDraft'),false);
assert.equal(Object.hasOwn(record,'nested'),false);
});
test('text field failures reject rather than stringify objects', () => {
for(const field of ['id','label','sourceUrl','notes']) {
assert.throws(()=>captureObservation({...fixture(),[field]:' '}),TypeError);
assert.throws(()=>captureObservation({...fixture(),[field]:{value:'text'}}),TypeError);
}
});
test('unsafe source schemes and embedded credentials reject', () => {
for(const sourceUrl of ['http://example.org','javascript:alert(1)','https://user:secret@example.org'])
assert.throws(()=>captureObservation({...fixture(),sourceUrl}),TypeError);
});
test('time stays explicit, with zero valid and invalid numbers rejected', () => {
assert.equal(captureObservation({...fixture(),observedAtMs:0}).observedAtMs,0);
for(const observedAtMs of [-1,1.5,NaN,Infinity,'1000',8640000000000001])
assert.throws(()=>captureObservation({...fixture(),observedAtMs}),TypeError);
});
test('capture preserves input and normalizes only the source URL', () => {
const input=Object.freeze({...fixture(),sourceUrl:'https://example.org',notes:' Keep exact wording '});
const record=captureObservation(input);
assert.equal(input.sourceUrl,'https://example.org');
assert.equal(record.sourceUrl,'https://example.org/');
assert.equal(record.notes,' Keep exact wording ');
assert.throws(()=>captureObservation(null),TypeError);
assert.throws(()=>captureObservation([]),TypeError);
});
Run node --test observation-record.test.mjs. All eight local scenarios passed. The most useful regression case edits the original notes and time after capture, then checks that the captured fields stayed unchanged. Another case supplies private and nested extra fields and confirms that neither appears in the record. Direct assignment to the frozen result throws in module strict mode.
The tests cover this limited contract. They are not a throughput benchmark, an exhaustive security assessment or verification of any linked website's tools. The public repository includes the independent module and runner.
Preserve a record without overstating its meaning
For AGProLabs, a screened-candidate journal gives review notes their analytical context. The multi-timeframe guide and forward-testing guide explain different observation questions. Copying note text cannot establish source confirmation, market performance or a completed review.
In the author's calculator directory, health-category directory and awareness-resource directory, a saved reading note is still a software record. This example receives no clinical measurements and validates no medical tool or conclusion.
For poets and poems and literary reviews, preserving a note's text is different from establishing attribution, edition accuracy or permission to reproduce a work. Keep those checks separate.
The application work that remains
An in-memory frozen object is not durable history. Storage needs its own transactions, edit policy and versioning. Authentication, tamper evidence, approved publication fields, payload limits, source trust and concurrency need separate design. A user can create a replacement object; freeze is not a signature or access-control mechanism.
The modest result is useful: a flat captured note does not keep the mutable input object, and unselected properties do not leak into the returned record. The surrounding application must decide what may be captured, stored and published.
Disclosure: this article and its code were generated with AI assistance for the owner of the linked projects. Eight bounded tests were executed locally. No private Pine source, personal measurements, website code changes or automated search-engine submissions are included. Educational software only; financial and health links supply reading context, not advice.
Top comments (0)