AI-Repo-Commander/Docs/diagrams/file_diagrams/queue.puml

281 lines
7.6 KiB
Plaintext

' ===================================================================
' File: queue.puml
' Purpose: Single source of truth for module-level activity + per-method sequences.
' Module: queue.js — Rate-limited FIFO queue (min delay, max per minute), async drain.
' Edit rules: Follow the legend at bottom; preserve VIEW/METHOD anchors for automation.
' ===================================================================
' Neutral defaults — typography/layout only (keeps partition colors intact)
skinparam Shadowing false
skinparam SequenceMessageAlign center
skinparam SequenceLifeLineBorderColor #666666
skinparam SequenceLifeLineBorderThickness 1
' ==== VIEW: Branch Flow (queue.js) =========================================
@startuml
title queue.js — Branch Flow (full module)
start
:queue;
fork
' -------- constructor(opts) --------
partition "constructor(opts)" #E7FAE3 {
:constructor;
:minDelayMs = opts.minDelayMs ?? cfg.queue.minDelayMs ?? 1500;
:maxPerMinute = opts.maxPerMinute ?? cfg.queue.maxPerMinute ?? 15;
:q=[]; running=false; timestamps=[]; onSizeChange=null;
kill
}
fork again
' -------- push(task) --------
partition "push(task)" #FFF6D1 {
:push;
:q.push(task); onSizeChange?.(q.length);
:if !running -> _drain();
kill
}
fork again
' -------- clear() --------
partition "clear()" #FFE1DB {
:clear;
:q.length = 0; onSizeChange?.(0);
kill
}
fork again
' -------- size() --------
partition "size()" #DCF9EE {
:size;
:return q.length;
kill
}
fork again
' -------- _withinBudget() --------
partition "_withinBudget()" #FFE6F0 {
:_withinBudget;
:timestamps = timestamps.filter(now - t < 60000);
:return timestamps.length < maxPerMinute;
kill
}
fork again
' -------- _drain() --------
partition "_drain()" #E6F3FF {
:_drain;
:guard running; set running=true;
:while q.length > 0;
:while !_withinBudget -> _delay(400);
:fn = q.shift(); onSizeChange?.(q.length);
:try await fn(); catch -> log.warn("Queue task error");
:timestamps.push(Date.now());
:await _delay(minDelayMs);
:end while; running=false;
kill
}
fork again
' -------- _delay(ms) --------
partition "_delay(ms)" #F0E6FA {
:_delay;
:return new Promise(resolve after setTimeout(ms));
kill
}
end fork
@enduml
' ==== METHOD: constructor(opts) ============================================
@startuml
title queue:constructor(opts): \n Initialize rate limits, internal state, and callbacks
participant "Caller" as CL
participant "constructor(opts)" as CTOR
participant "Config" as CFG
activate CL
CL -> CTOR : new ExecutionQueue(opts)
activate CTOR
CTOR -> CFG : get('queue.minDelayMs') / get('queue.maxPerMinute')
CFG --> CTOR : minCfg / maxCfg
CTOR -> CTOR : minDelayMs = opts.minDelayMs ?? minCfg ?? 1500
CTOR -> CTOR : maxPerMinute = opts.maxPerMinute ?? maxCfg ?? 15
CTOR -> CTOR : q=[]; running=false; timestamps=[]; onSizeChange=null
CTOR --> CL : instance
deactivate CTOR
deactivate CL
@enduml
' ==== METHOD: push(task) ====================================================
@startuml
title queue:push(task): \n Enqueue a task and start the drain loop if idle
participant "Caller" as CL
participant "push(task)" as PUSH
participant "_drain()" as DRN
activate CL
CL -> PUSH : task (async function)
activate PUSH
PUSH -> PUSH : q.push(task)
PUSH -> PUSH : onSizeChange?.(q.length)
PUSH -> PUSH : if (!running) -> start drain
PUSH -> DRN : _drain()
DRN --> PUSH : (started | already running)
PUSH --> CL : (void)
deactivate PUSH
deactivate CL
@enduml
' ==== METHOD: clear() =======================================================
@startuml
title queue:clear(): \n Drop all pending tasks and notify size change
participant "Caller" as CL
participant "clear()" as CLR
activate CL
CL -> CLR : initial request
activate CLR
CLR -> CLR : q.length = 0
CLR -> CLR : onSizeChange?.(0)
CLR --> CL : (void)
deactivate CLR
deactivate CL
@enduml
' ==== METHOD: size() ========================================================
@startuml
title queue:size(): \n Return current queue length
participant "Caller" as CL
participant "size()" as SIZE
activate CL
CL -> SIZE : initial request
activate SIZE
SIZE --> CL : q.length
deactivate SIZE
deactivate CL
@enduml
' ==== METHOD: _withinBudget() ==============================================
@startuml
title queue:_withinBudget(): \n Enforce rolling 60s window and max tasks per minute
participant "Caller" as CL
participant "_withinBudget()" as WBG
activate CL
CL -> WBG : initial request
activate WBG
WBG -> WBG : now = Date.now()
WBG -> WBG : timestamps = timestamps.filter(now - t < 60000)
WBG --> CL : (timestamps.length < maxPerMinute)
deactivate WBG
deactivate CL
@enduml
' ==== METHOD: _drain() ======================================================
@startuml
title queue:_drain(): \n Process tasks while respecting rate limits and min spacing
participant "Caller" as CL
participant "_drain()" as DRN
participant "_withinBudget()" as WBG
participant "_delay(ms)" as DLY
participant "Logger" as LOG
activate CL
CL -> DRN : initial request
activate DRN
' guard
DRN -> DRN : if (running) return
DRN -> DRN : running = true
loop while q.length > 0
' wait for budget
DRN -> WBG : _withinBudget()
WBG --> DRN : ok (bool)
alt !ok
DRN -> DLY : _delay(400)
DLY --> DRN : wake
DRN -> WBG : _withinBudget()
WBG --> DRN : ok (bool)
end
' get next task
DRN -> DRN : fn = q.shift()
DRN -> DRN : onSizeChange?.(q.length)
' execute task safely
alt try/await fn()
DRN -> DRN : await fn()
else error
DRN -> LOG : warn("Queue task error", {error})
end
' record + spacing
DRN -> DRN : timestamps.push(Date.now())
DRN -> DLY : _delay(minDelayMs)
DLY --> DRN : wake
end
DRN -> DRN : running = false
DRN --> CL : (void)
deactivate DRN
deactivate CL
@enduml
' ==== METHOD: _delay(ms) ====================================================
@startuml
title queue:_delay(ms): \n Resolve after a timeout
participant "Caller" as CL
participant "_delay(ms)" as DLY
participant "Timer" as TMR
activate CL
CL -> DLY : ms
activate DLY
DLY -> TMR : setTimeout(ms)
TMR --> DLY : wake
DLY --> CL : (void)
deactivate DLY
deactivate CL
@enduml
' ==== LEGEND ===============================================================
@startuml
legend bottom
== queue UML Style Guide (for future edits) ==
• Scope: One .puml per module. Keep two views:
(1) Activity "Branch Flow" for the whole module (partitions + soft colors),
(2) Per-function Sequence diagrams for each exported or significant internal method.
• Sequence conventions:
1) First participant is the external caller (use "Caller" or a concrete origin).
2) Do NOT add a module lifeline; the module name appears in the title only.
3) Include every directly-called method or subsystem as a participant
(e.g., "push()", "clear()", "size()", "_withinBudget()", "_drain()", "_delay()", "Logger", "Config", "Timer").
4) Prefer simple messages; Use --> for returns; -> for calls.
5) Use activate/deactivate as you see fit for clarity (no strict rule).
6) Use alt blocks only when branches meaningfully change the message flow.
• Activity view conventions:
A) Start with module node then fork partitions for each function/method.
B) One partition per function; soft background color; terminate branches with 'kill'.
C) Keep wording aligned with code (e.g., 60_000s window, minDelayMs, maxPerMinute).
• Color palette (soft pastels)
• Use --> for returns; -> for calls.
• Participants use quoted method names for internals (e.g., "_drain()"), and plain nouns for systems ("Logger", "Timer").
• Keep this legend at the end of the file to standardize edits.
endlegend
@enduml