@goodandready/dsh-key-rotation 0.8.81 → 0.8.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.83 (2026-10-31)
4
+
5
+ ### TypeScript Migration & Type-Safe Core Architecture (Block 11 Release)
6
+ - **TypeScript Server-Side Architecture (Issue #524)**: Migrated server-side codebase to TypeScript with `src/` directory structure, strict `tsconfig.json`, and `tsdown` build pipeline generating `lib/index.d.ts`.
7
+ - **Domain Type Definitions (`src/types/`)**: Defined comprehensive TypeScript types for configuration schemas (`PoolConfig`, `ModelQuotaConfig`, `FailoverConfig`, `PluginConfig`), pool & key state machines (`KeyState`, `KeyStatus`, `PoolItem`, `ResolvedKey`, `RoutingDecision`, `QuotaWindow`), and Cordis service interaction contracts (`Context`, `LlmService`, `WebServerService`, `SettingsService`, `CredentialsService`).
8
+ - **Static Type Safety & Tooling**: Integrated `npm run typecheck` (`tsc --noEmit`) into `npm run check`, guaranteeing compile-time type safety across all configuration fields, service bindings, and event handlers with 0 errors.
9
+ - **Zero Runtime Dependencies**: All TypeScript build tools (`typescript`, `tsdown`, `@types/node`) maintained strictly in `devDependencies`, with `dependencies: {}` preserved.
10
+
11
+ ## 0.8.82 (2026-10-30)
12
+
13
+ ### Modernized UI Builder & JSX Pipeline (Block 10 Release)
14
+ - **Modern JSX/TSX Build Pipeline via esbuild (Issue #523)**: Replaced mechanical text concatenation with modern compiler-based build pipeline (`scripts/build-client.mjs`) using `esbuild`. Supports native JSX syntax with `React.createElement` transpilation.
15
+ - **Component JSX Modernization**: Migrated key client components (such as `Sparkline` and `KeyRotationHeaderChip`) to idiomatic JSX, improving clarity and maintainability while preserving strict modular scoping and zero runtime overhead.
16
+ - **Deterministic & Compliant Bundle Assembly**: Automated atomic output writing with size verification (< 256 KiB DSH Store limit, actual: ~207 KiB), UTF-8 charset preservation for multi-language localization (en, zh, ru), inline legal comments, and syntax verification (`node --check`).
17
+ - **CI/CD Drift Prevention**: Built-in `--check` mode in `build-client.mjs` ensuring `lib/client.js` is always strictly in sync with `lib/client-src/`.
18
+
3
19
  ## 0.8.81 (2026-10-30)
4
20
 
5
21
  ### Direct Extension Pipeline & Pure Key Resolution (Block 9 Release)
@@ -1,7 +1,9 @@
1
1
  function Sparkline({ points = [], width = 120, height = 22, stroke = 'var(--dsw-alias-brand-primary, currentColor)' }) {
2
2
  if (!Array.isArray(points) || points.length < 2) {
3
- return h('svg', { width, height, viewBox: `0 0 ${width} ${height}`, style: { opacity: 0.3 } },
4
- h('line', { x1: 0, y1: height / 2, x2: width, y2: height / 2, stroke, strokeWidth: 1, strokeDasharray: '3 3' })
3
+ return (
4
+ <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} style={{ opacity: 0.3 }}>
5
+ <line x1={0} y1={height / 2} x2={width} y2={height / 2} stroke={stroke} strokeWidth={1} strokeDasharray="3 3" />
6
+ </svg>
5
7
  );
6
8
  }
7
9
  const min = Math.min(...points);
@@ -14,19 +16,23 @@
14
16
  return `${x},${y}`;
15
17
  }).join(' ');
16
18
 
17
- return h('svg', {
18
- width,
19
- height,
20
- viewBox: `0 0 ${width} ${height}`,
21
- style: { display: 'inline-block', verticalAlign: 'middle', overflow: 'visible' },
22
- }, h('polyline', {
23
- points: coords,
24
- fill: 'none',
25
- stroke,
26
- strokeWidth: 2,
27
- strokeLinecap: 'round',
28
- strokeLinejoin: 'round',
29
- }));
19
+ return (
20
+ <svg
21
+ width={width}
22
+ height={height}
23
+ viewBox={`0 0 ${width} ${height}`}
24
+ style={{ display: 'inline-block', verticalAlign: 'middle', overflow: 'visible' }}
25
+ >
26
+ <polyline
27
+ points={coords}
28
+ fill="none"
29
+ stroke={stroke}
30
+ strokeWidth={2}
31
+ strokeLinecap="round"
32
+ strokeLinejoin="round"
33
+ />
34
+ </svg>
35
+ );
30
36
  }
31
37
 
32
38
  function parseBulkKeys(text) {
@@ -339,7 +339,7 @@
339
339
  );
340
340
  })();
341
341
 
342
- // ── per-model sub-pools: keys + token quotas, plus live usage meters ──
342
+ /*! ── per-model sub-pools: keys + token quotas, plus live usage meters ── */
343
343
  const modelPools = (() => {
344
344
  const models = modelEntriesOf(entry);
345
345
  const statusFor = (model) => status[entry.provider + '::' + model] ?? null;
@@ -80,58 +80,66 @@
80
80
  const color = !poolEntries.length ? 'var(--dsw-alias-label-tertiary)' : anyExhausted ? 'var(--dsw-alias-state-error-primary)' : healthy < total ? 'var(--dsw-alias-state-warn-primary)' : 'var(--dsw-alias-state-success-primary)';
81
81
  const label = poolEntries.length ? `${healthy}/${total} rot` : 'rot';
82
82
 
83
- return h('div', { ref, style: { position: 'relative', display: 'inline-flex' } },
84
- h('button', {
85
- type: 'button',
86
- className: 'krot-header-chip',
87
- title: t('title'),
88
- onClick: () => setOpen((v) => !v),
89
- },
90
- h('span', { style: { width: '8px', height: '8px', borderRadius: '50%', background: color, flex: 'none', boxShadow: `0 0 6px ${color}` } }),
91
- label,
92
- h('span', { style: { fontSize: '9px', opacity: 0.6 } }, open ? '▲' : '▼')
93
- ),
94
- open ? h('div', { className: 'krot-popover', style: { minWidth: '220px' } },
95
- h('div', { className: 'krot-pop-title' }, t('headerPoolsTitle')),
96
- !poolEntries.length ? h('div', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' } }, t('noActivePools')) : null,
97
- poolEntries.map(([name, p]) => {
98
- const c = p.exhausted ? 'var(--dsw-alias-state-error-primary)' : (p.healthy < p.total ? 'var(--dsw-alias-state-warn-primary)' : 'var(--dsw-alias-state-success-primary)');
99
- const provName = p.providerName || name;
100
- const tail = p.lastUsedTail;
101
- return h('div', { key: name, className: 'krot-pop-row', style: { display: 'flex', flexDirection: 'column', gap: '4px', padding: '6px 0', borderBottom: '1px solid var(--dsw-alias-border-subtle)' } },
102
- h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
103
- h('div', { className: 'krot-pop-name' },
104
- h('span', { style: { width: '7px', height: '7px', borderRadius: '50%', background: c, flex: 'none' } }),
105
- name,
106
- tail ? h('span', { style: { marginLeft: '6px', fontSize: '11px', color: 'var(--dsw-alias-label-tertiary)', fontFeatureSettings: '"tnum"', fontFamily: 'monospace' } }, `...${tail}`) : null
107
- ),
108
- h('div', { className: 'krot-pop-count', style: { color: c, fontWeight: 600 } }, `${p.healthy}/${p.total}`)
109
- ),
110
- h('div', { style: { display: 'flex', gap: '4px', marginTop: '2px' } },
111
- h('button', {
112
- type: 'button',
113
- disabled: busy,
114
- onClick: (e) => { e.stopPropagation(); runAction('skip', provName, p.lastUsed); },
115
- title: t('quickSkipTitle'),
116
- style: { padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }
117
- }, `⏭️ ${t('quickSkip')}`),
118
- h('button', {
119
- type: 'button',
120
- disabled: busy,
121
- onClick: (e) => { e.stopPropagation(); runAction('reset', provName, p.lastUsed); },
122
- title: t('quickResetTitle'),
123
- style: { padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }
124
- }, `🔄 ${t('quickReset')}`),
125
- h('button', {
126
- type: 'button',
127
- disabled: busy || !p.lastUsed,
128
- onClick: (e) => { e.stopPropagation(); runAction('pause', provName, p.lastUsed); },
129
- title: t('quickPauseTitle'),
130
- style: { padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }
131
- }, `⏸️ ${t('quickPause')}`)
132
- )
133
- );
134
- })
135
- ) : null
83
+ return (
84
+ <div ref={ref} style={{ position: 'relative', display: 'inline-flex' }}>
85
+ <button
86
+ type="button"
87
+ className="krot-header-chip"
88
+ title={t('title')}
89
+ onClick={() => setOpen((v) => !v)}
90
+ >
91
+ <span style={{ width: '8px', height: '8px', borderRadius: '50%', background: color, flex: 'none', boxShadow: `0 0 6px ${color}` }} />
92
+ {label}
93
+ <span style={{ fontSize: '9px', opacity: 0.6 }}>{open ? '▲' : '▼'}</span>
94
+ </button>
95
+ {open ? (
96
+ <div className="krot-popover" style={{ minWidth: '220px' }}>
97
+ <div className="krot-pop-title">{t('headerPoolsTitle')}</div>
98
+ {!poolEntries.length ? (
99
+ <div style={{ fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' }}>{t('noActivePools')}</div>
100
+ ) : null}
101
+ {poolEntries.map(([name, p]) => {
102
+ const c = p.exhausted ? 'var(--dsw-alias-state-error-primary)' : (p.healthy < p.total ? 'var(--dsw-alias-state-warn-primary)' : 'var(--dsw-alias-state-success-primary)');
103
+ const provName = p.providerName || name;
104
+ const tail = p.lastUsedTail;
105
+ return (
106
+ <div key={name} className="krot-pop-row" style={{ display: 'flex', flexDirection: 'column', gap: '4px', padding: '6px 0', borderBottom: '1px solid var(--dsw-alias-border-subtle)' }}>
107
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
108
+ <div className="krot-pop-name">
109
+ <span style={{ width: '7px', height: '7px', borderRadius: '50%', background: c, flex: 'none' }} />
110
+ {name}
111
+ {tail ? <span style={{ marginLeft: '6px', fontSize: '11px', color: 'var(--dsw-alias-label-tertiary)', fontFeatureSettings: '"tnum"', fontFamily: 'monospace' }}>{`...${tail}`}</span> : null}
112
+ </div>
113
+ <div className="krot-pop-count" style={{ color: c, fontWeight: 600 }}>{`${p.healthy}/${p.total}`}</div>
114
+ </div>
115
+ <div style={{ display: 'flex', gap: '4px', marginTop: '2px' }}>
116
+ <button
117
+ type="button"
118
+ disabled={busy}
119
+ onClick={(e) => { e.stopPropagation(); runAction('skip', provName, p.lastUsed); }}
120
+ title={t('quickSkipTitle')}
121
+ style={{ padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }}
122
+ >{`⏭️ ${t('quickSkip')}`}</button>
123
+ <button
124
+ type="button"
125
+ disabled={busy}
126
+ onClick={(e) => { e.stopPropagation(); runAction('reset', provName, p.lastUsed); }}
127
+ title={t('quickResetTitle')}
128
+ style={{ padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }}
129
+ >{`🔄 ${t('quickReset')}`}</button>
130
+ <button
131
+ type="button"
132
+ disabled={busy || !p.lastUsed}
133
+ onClick={(e) => { e.stopPropagation(); runAction('pause', provName, p.lastUsed); }}
134
+ title={t('quickPauseTitle')}
135
+ style={{ padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }}
136
+ >{`⏸️ ${t('quickPause')}`}</button>
137
+ </div>
138
+ </div>
139
+ );
140
+ })}
141
+ </div>
142
+ ) : null}
143
+ </div>
136
144
  );
137
145
  }