← All @molecule/* packages · App templates
@molecule/api-resource-journal-entryAPI resource · resource-journal-entry · API (Node) · v1.0.1 · Apache-2.0
Encrypted journal + mood linkage + streaks + export
npm install @molecule/api-resource-journal-entry@molecule/api-resource-journal-entry is an API resource: the routes, validation and storage for resource-journal-entry, built on the database and auth cores so it runs on whichever providers your app has bonded.
import { createJournalEntryRouter } from '@molecule/api-resource-journal-entry'
import express from 'express'
const app = express()
app.use('/api/journal', createJournalEntryRouter())Works with: @molecule/api-bonds-default-express, @molecule/api-database, @molecule/api-encryption, @molecule/api-i18n, @molecule/api-logger, @molecule/api-middleware-validation
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
@molecule/api-resource-journal-entry — owner-scoped diary entries
with optional at-rest encryption, mood-score linkage, daily-write
streaks, and JSON/CSV/TXT export.
Extracted from the mental-health-journal flagship. When
@molecule/api-encryption is bonded, entry bodies are stored
encrypted-at-rest; otherwise the plaintext column is used (the read
path falls back automatically so key rotation never bricks reads).
import { createJournalEntryRouter } from '@molecule/api-resource-journal-entry'
import express from 'express'
const app = express()
app.use('/api/journal', createJournalEntryRouter())
resource
npm install @molecule/api-resource-journal-entry @molecule/api-bonds-default-express @molecule/api-database @molecule/api-encryption @molecule/api-i18n @molecule/api-logger @molecule/api-middleware-validation express zod
npm install -D @types/express
CreateEntryInputInput fields accepted when creating a new journal entry.
interface CreateEntryInput {
mood?: MoodLevel
title?: string
body: string
tags?: string[]
prompt_id?: string | null
written_at?: string | Date
}
ExportRecordFlattened, export-friendly representation of a single journal entry.
interface ExportRecord {
id: string
date: string
title: string
mood: MoodLevel
tags: string[]
word_count: number
body: string
}
JournalEntryRowRow shape persisted in journal_entries.
interface JournalEntryRow {
id: string
user_id: string
written_at: string | Date
title: string | null
body: string | null
body_encrypted: string | null
body_iv: string | null
mood_id: string | null
prompt_id: string | null
activity_ids: unknown
tags: unknown
word_count: number
ai_summary: string | null
ai_themes: unknown
is_private: boolean
created_at: string | Date
}
MoodEntryRowRow shape persisted in mood_entries.
interface MoodEntryRow {
id: string
user_id: string
recorded_at: string | Date
score: number
energy: number | null
anxiety: number | null
label: string | null
activities: unknown
notes: string | null
journal_entry_id: string | null
created_at: string | Date
}
PublicJournalEntryPublic-facing decrypted entry (returned by the service / routes).
interface PublicJournalEntry {
id: string
date: string
title: string
preview: string
body: string
mood: MoodLevel
tags: string[]
word_count: number
ai_summary: string | null
ai_themes: string[]
prompt: string | null
}
UpdateEntryInputFields that can be patched on an existing journal entry.
interface UpdateEntryInput {
mood?: MoodLevel
title?: string
body?: string
tags?: string[]
}
MoodLevelDiscrete mood-level labels mapped to a 1..5 score.
type MoodLevel = 'radiant' | 'good' | 'neutral' | 'low' | 'struggling'
computeStreak(userId)Daily-write streak: consecutive distinct UTC days ending today/yesterday.
function computeStreak(userId: string): Promise<number>
createEntryForOwner(userId, input)Create a journal entry and, if mood is provided, upsert today's
mood_entries row + link it to the journal entry.
function createEntryForOwner(
userId: string,
input: CreateEntryInput,
): Promise<PublicJournalEntry | null>
createJournalEntryRouter()Build the journal-entry router.
function createJournalEntryRouter(): Router
decryptIfNeeded(row)Decrypt the entry body if encryption is bonded; otherwise return plaintext.
function decryptIfNeeded(row: JournalEntryRow): Promise<string>
deleteEntryForOwner(userId, id)Owner-scoped delete — true if it deleted, false if not owned / missing.
function deleteEntryForOwner(userId: string, id: string): Promise<boolean>
exportEntries(userId, max?)Serialize all entries for an owner into an export-friendly array.
function exportEntries(userId: string, max?: number): Promise<ExportRecord[]>
formatExport(records, format)Format export records as JSON / CSV / TXT.
function formatExport(
records: ExportRecord[],
format: 'json' | 'csv' | 'txt',
): { contentType: string; body: string }
getEntryForOwner(userId, id)Owner-scoped read — returns null when missing or not owned.
function getEntryForOwner(userId: string, id: string): Promise<PublicJournalEntry | null>
levelByScore(score)Round + clamp a raw score and return its discrete label.
function levelByScore(score: number | null | undefined): MoodLevel
listEntriesForOwner(userId, limit?)List the most-recent entries for an owner.
function listEntriesForOwner(userId: string, limit?: number): Promise<PublicJournalEntry[]>
normalizeScore(score)Normalize a 1..5 score to 0..1 (useful for sparkline charts).
function normalizeScore(score: number | null | undefined): number
shapeEntry(entry, body)Shape a row into the public-facing representation.
function shapeEntry(entry: JournalEntryRow, body: string): Promise<PublicJournalEntry>
updateEntryForOwner(userId, id, input)Owner-scoped patch update — returns null when missing or not owned.
function updateEntryForOwner(
userId: string,
id: string,
input: UpdateEntryInput,
): Promise<PublicJournalEntry | null>
createEntrySchemaValidator for creating a new journal entry.
const createEntrySchema: z.ZodObject<
{
mood: z.ZodOptional<
z.ZodEnum<{
radiant: 'radiant'
good: 'good'
neutral: 'neutral'
low: 'low'
struggling: 'struggling'
}>
>
title: z.ZodOptional<z.ZodString>
body: z.ZodString
tags: z.ZodOptional<z.ZodArray<z.ZodString>>
prompt_id: z.ZodOptional<z.ZodString>
written_at: z.ZodOptional<z.ZodString>
},
z.core.$strip
>
moodLevelSchemaMood level enum used by journal-entry payloads.
const moodLevelSchema: z.ZodEnum<{
radiant: 'radiant'
good: 'good'
neutral: 'neutral'
low: 'low'
struggling: 'struggling'
}>
SCORE_BY_LEVELMaps each MoodLevel label to its corresponding numeric score (1..5).
const SCORE_BY_LEVEL: Record<MoodLevel, number>
updateEntrySchemaValidator for updating an existing journal entry.
const updateEntrySchema: z.ZodObject<
{
mood: z.ZodOptional<
z.ZodOptional<
z.ZodEnum<{
radiant: 'radiant'
good: 'good'
neutral: 'neutral'
low: 'low'
struggling: 'struggling'
}>
>
>
title: z.ZodOptional<z.ZodOptional<z.ZodString>>
tags: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>
prompt_id: z.ZodOptional<z.ZodOptional<z.ZodString>>
written_at: z.ZodOptional<z.ZodOptional<z.ZodString>>
body: z.ZodOptional<z.ZodString>
},
z.core.$strip
>
Peer dependencies:
@molecule/api-bonds-default-express ^1.0.1@molecule/api-logger ^1.0.1@molecule/api-database ^1.0.1@molecule/api-encryption ^1.0.1@molecule/api-i18n ^1.0.1@molecule/api-middleware-validation ^1.0.1express ^5.0.0zod ^4.0.0@molecule/api-bonds-default-express@molecule/api-database@molecule/api-encryption@molecule/api-i18n@molecule/api-logger@molecule/api-middleware-validationexpresszodSchema lives in __setup__/journal_entries.sql — two tables:
journal_entries + mood_entries. Mood rows are upserted per
(user, day) so multiple entries in a day share one mood row.
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual journal screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip. This is a PRIVATE journal, so privacy is the defining requirement: