Skip to content

Commit 25efe8e

Browse files
feat(limits): add default resource limits to prevent unbounded queries
- Add DEFAULT_FRAME_LIMIT (1000), DEFAULT_EVENT_LIMIT (500), DEFAULT_ANCHOR_LIMIT (200) - Update getFramesByProject(), getFrameEvents(), getFrameAnchors() with default limits - Add countFrames() method for efficient counting without loading data - Export limit constants for external use Gap #4 (Resource limits) addressed for 1.0.0 readiness. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 98a42f1 commit 25efe8e

3 files changed

Lines changed: 57 additions & 16 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stackmemoryai/stackmemory",
3-
"version": "0.5.37",
3+
"version": "0.5.38",
44
"description": "Lossless memory runtime for AI coding tools - organizes context as a call stack instead of linear chat logs, with team collaboration and infinite retention",
55
"engines": {
66
"node": ">=20.0.0",

src/core/context/frame-database.ts

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ interface MaxSeqRow {
5454
max_seq: number | null;
5555
}
5656

57+
// Default limits to prevent unbounded queries
58+
export const DEFAULT_FRAME_LIMIT = 1000;
59+
export const DEFAULT_EVENT_LIMIT = 500;
60+
export const DEFAULT_ANCHOR_LIMIT = 200;
61+
5762
// Safe JSON parse with fallback
5863
function safeJsonParse<T>(json: string | null | undefined, fallback: T): T {
5964
if (!json) return fallback;
@@ -308,13 +313,17 @@ export class FrameDatabase {
308313
/**
309314
* Get frames by project and state
310315
*/
311-
getFramesByProject(projectId: string, state?: 'active' | 'closed'): Frame[] {
316+
getFramesByProject(
317+
projectId: string,
318+
state?: 'active' | 'closed',
319+
limit: number = DEFAULT_FRAME_LIMIT
320+
): Frame[] {
312321
try {
313322
const query = state
314-
? 'SELECT * FROM frames WHERE project_id = ? AND state = ? ORDER BY created_at'
315-
: 'SELECT * FROM frames WHERE project_id = ? ORDER BY created_at';
323+
? 'SELECT * FROM frames WHERE project_id = ? AND state = ? ORDER BY created_at LIMIT ?'
324+
: 'SELECT * FROM frames WHERE project_id = ? ORDER BY created_at LIMIT ?';
316325

317-
const params = state ? [projectId, state] : [projectId];
326+
const params = state ? [projectId, state, limit] : [projectId, limit];
318327
const rows = this.db.prepare(query).all(...params) as FrameRow[];
319328

320329
return rows.map((row) => ({
@@ -397,13 +406,14 @@ export class FrameDatabase {
397406
/**
398407
* Get events for a frame
399408
*/
400-
getFrameEvents(frameId: string, limit?: number): Event[] {
409+
getFrameEvents(
410+
frameId: string,
411+
limit: number = DEFAULT_EVENT_LIMIT
412+
): Event[] {
401413
try {
402-
const query = limit
403-
? 'SELECT * FROM events WHERE frame_id = ? ORDER BY seq DESC LIMIT ?'
404-
: 'SELECT * FROM events WHERE frame_id = ? ORDER BY seq ASC';
405-
406-
const params = limit ? [frameId, limit] : [frameId];
414+
const query =
415+
'SELECT * FROM events WHERE frame_id = ? ORDER BY seq DESC LIMIT ?';
416+
const params = [frameId, limit];
407417
const rows = this.db.prepare(query).all(...params) as EventRow[];
408418

409419
return rows.map((row) => ({
@@ -500,13 +510,16 @@ export class FrameDatabase {
500510
/**
501511
* Get anchors for a frame
502512
*/
503-
getFrameAnchors(frameId: string): Anchor[] {
513+
getFrameAnchors(
514+
frameId: string,
515+
limit: number = DEFAULT_ANCHOR_LIMIT
516+
): Anchor[] {
504517
try {
505518
const rows = this.db
506519
.prepare(
507-
'SELECT * FROM anchors WHERE frame_id = ? ORDER BY priority DESC, created_at ASC'
520+
'SELECT * FROM anchors WHERE frame_id = ? ORDER BY priority DESC, created_at ASC LIMIT ?'
508521
)
509-
.all(frameId) as AnchorRow[];
522+
.all(frameId, limit) as AnchorRow[];
510523

511524
return rows.map((row) => ({
512525
...row,
@@ -543,6 +556,34 @@ export class FrameDatabase {
543556
}
544557
}
545558

559+
/**
560+
* Count frames by project and state (without loading all data)
561+
*/
562+
countFrames(projectId?: string, state?: 'active' | 'closed'): number {
563+
try {
564+
let query = 'SELECT COUNT(*) as count FROM frames';
565+
const params: (string | undefined)[] = [];
566+
567+
if (projectId) {
568+
query += ' WHERE project_id = ?';
569+
params.push(projectId);
570+
if (state) {
571+
query += ' AND state = ?';
572+
params.push(state);
573+
}
574+
} else if (state) {
575+
query += ' WHERE state = ?';
576+
params.push(state);
577+
}
578+
579+
const result = this.db.prepare(query).get(...params) as CountRow;
580+
return result.count;
581+
} catch (error: unknown) {
582+
logger.warn('Failed to count frames', { error, projectId, state });
583+
return 0;
584+
}
585+
}
586+
546587
/**
547588
* Get database statistics
548589
*/

0 commit comments

Comments
 (0)