Initial commit: restructure to flat layout with ui/ and web/ at root
This commit is contained in:
61
web/server/lib/notifications/DOCUMENTATION.md
Normal file
61
web/server/lib/notifications/DOCUMENTATION.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Notifications Module Documentation
|
||||
|
||||
## Purpose
|
||||
This module provides notification message preparation utilities for the web server runtime, including text truncation and optional message summarization for system notifications.
|
||||
|
||||
## Entrypoints and structure
|
||||
- `packages/web/server/lib/notifications/index.js`: public entrypoint imported by `packages/web/server/index.js`.
|
||||
- `packages/web/server/lib/notifications/message.js`: helper implementation module.
|
||||
- `packages/web/server/lib/notifications/message.test.js`: unit tests for notification message helpers.
|
||||
|
||||
## Public exports
|
||||
|
||||
### Notifications API (re-exported from message.js)
|
||||
- `truncateNotificationText(text, maxLength)`: Truncates text to specified max length, appending `...` if truncated.
|
||||
- `prepareNotificationLastMessage({ message, settings, summarize })`: Prepares the last message for notification display, with optional summarization support.
|
||||
|
||||
## Constants
|
||||
|
||||
### Default values
|
||||
- `DEFAULT_NOTIFICATION_MESSAGE_MAX_LENGTH`: 250 (default max length for notification text).
|
||||
- `DEFAULT_NOTIFICATION_SUMMARY_THRESHOLD`: 200 (minimum message length to trigger summarization).
|
||||
- `DEFAULT_NOTIFICATION_SUMMARY_LENGTH`: 100 (target length for summarized messages).
|
||||
|
||||
## Settings object format
|
||||
|
||||
The `settings` parameter for `prepareNotificationLastMessage` supports:
|
||||
- `summarizeLastMessage` (boolean): Whether to enable summarization for long messages.
|
||||
- `summaryThreshold` (number): Minimum message length to trigger summarization (default: 200).
|
||||
- `summaryLength` (number): Target length for summarized messages (default: 100).
|
||||
- `maxLastMessageLength` (number): Maximum length for the final notification text (default: 250).
|
||||
|
||||
## Response contracts
|
||||
|
||||
### `truncateNotificationText`
|
||||
- Returns empty string for non-string input.
|
||||
- Returns original text if under max length.
|
||||
- Returns `${text.slice(0, maxLength)}...` for truncated text.
|
||||
|
||||
### `prepareNotificationLastMessage`
|
||||
- Returns empty string for empty/null message.
|
||||
- Returns truncated original message if summarization disabled, message under threshold, or summarization fails.
|
||||
- Returns truncated summary if summarization succeeds and returns non-empty string.
|
||||
- Always applies `maxLastMessageLength` truncation to final result.
|
||||
|
||||
## Notes for contributors
|
||||
|
||||
### Adding new notification helpers
|
||||
1. Add new helper functions to `packages/web/server/lib/notifications/message.js`.
|
||||
2. Export functions that are intended for public use.
|
||||
3. Follow existing patterns for input validation (e.g., type checking for strings).
|
||||
4. Use `resolvePositiveNumber` for numeric parameters with fallbacks to maintain safe defaults.
|
||||
5. Add corresponding unit tests in `packages/web/server/lib/notifications/message.test.js`.
|
||||
|
||||
### Error handling
|
||||
- `prepareNotificationLastMessage` catches summarization errors and falls back to original message.
|
||||
- Invalid numeric parameters default to safe fallback values.
|
||||
- Non-string inputs are handled gracefully (return empty string).
|
||||
|
||||
### Testing
|
||||
- Run `bun run type-check`, `bun run lint`, and `bun run build` before finalizing changes.
|
||||
- Unit tests should cover truncation behavior, summarization success/failure, and edge cases (empty strings, invalid inputs).
|
||||
1
web/server/lib/notifications/index.js
Normal file
1
web/server/lib/notifications/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { truncateNotificationText, prepareNotificationLastMessage } from './message.js';
|
||||
49
web/server/lib/notifications/message.js
Normal file
49
web/server/lib/notifications/message.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const DEFAULT_NOTIFICATION_MESSAGE_MAX_LENGTH = 250;
|
||||
const DEFAULT_NOTIFICATION_SUMMARY_THRESHOLD = 200;
|
||||
const DEFAULT_NOTIFICATION_SUMMARY_LENGTH = 100;
|
||||
|
||||
const resolvePositiveNumber = (value, fallback) => {
|
||||
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
||||
return fallback;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const truncateNotificationText = (text, maxLength = DEFAULT_NOTIFICATION_MESSAGE_MAX_LENGTH) => {
|
||||
if (typeof text !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const safeMaxLength = resolvePositiveNumber(maxLength, DEFAULT_NOTIFICATION_MESSAGE_MAX_LENGTH);
|
||||
if (text.length <= safeMaxLength) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return `${text.slice(0, safeMaxLength)}...`;
|
||||
};
|
||||
|
||||
export const prepareNotificationLastMessage = async ({ message, settings, summarize }) => {
|
||||
const originalMessage = typeof message === 'string' ? message : '';
|
||||
if (!originalMessage) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const shouldSummarize = settings?.summarizeLastMessage === true && typeof summarize === 'function';
|
||||
const summaryThreshold = resolvePositiveNumber(settings?.summaryThreshold, DEFAULT_NOTIFICATION_SUMMARY_THRESHOLD);
|
||||
const summaryLength = resolvePositiveNumber(settings?.summaryLength, DEFAULT_NOTIFICATION_SUMMARY_LENGTH);
|
||||
const maxLastMessageLength = resolvePositiveNumber(settings?.maxLastMessageLength, DEFAULT_NOTIFICATION_MESSAGE_MAX_LENGTH);
|
||||
|
||||
let messageForNotification = originalMessage;
|
||||
if (shouldSummarize && originalMessage.length > summaryThreshold) {
|
||||
try {
|
||||
const summary = await summarize(originalMessage, summaryLength);
|
||||
if (typeof summary === 'string' && summary.trim().length > 0) {
|
||||
messageForNotification = summary;
|
||||
}
|
||||
} catch {
|
||||
messageForNotification = originalMessage;
|
||||
}
|
||||
}
|
||||
|
||||
return truncateNotificationText(messageForNotification, maxLastMessageLength);
|
||||
};
|
||||
59
web/server/lib/notifications/message.test.js
Normal file
59
web/server/lib/notifications/message.test.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
|
||||
import { prepareNotificationLastMessage, truncateNotificationText } from './message.js';
|
||||
|
||||
describe('notification message helpers', () => {
|
||||
it('truncates oversized notification text', () => {
|
||||
expect(truncateNotificationText('abcdef', 3)).toBe('abc...');
|
||||
});
|
||||
|
||||
it('falls back to original message when summarization fails', async () => {
|
||||
const message = '0123456789';
|
||||
const summarize = async () => {
|
||||
throw new Error('summarization failed');
|
||||
};
|
||||
|
||||
const result = await prepareNotificationLastMessage({
|
||||
message,
|
||||
summarize,
|
||||
settings: {
|
||||
summarizeLastMessage: true,
|
||||
summaryThreshold: 5,
|
||||
summaryLength: 3,
|
||||
maxLastMessageLength: 4,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toBe('0123...');
|
||||
});
|
||||
|
||||
it('falls back to original message when summary is empty', async () => {
|
||||
const result = await prepareNotificationLastMessage({
|
||||
message: '0123456789',
|
||||
summarize: async () => ' ',
|
||||
settings: {
|
||||
summarizeLastMessage: true,
|
||||
summaryThreshold: 5,
|
||||
summaryLength: 3,
|
||||
maxLastMessageLength: 4,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toBe('0123...');
|
||||
});
|
||||
|
||||
it('uses summary when summarization succeeds', async () => {
|
||||
const result = await prepareNotificationLastMessage({
|
||||
message: '0123456789',
|
||||
summarize: async () => 'short summary',
|
||||
settings: {
|
||||
summarizeLastMessage: true,
|
||||
summaryThreshold: 5,
|
||||
summaryLength: 3,
|
||||
maxLastMessageLength: 100,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toBe('short summary');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user