@sonicjs-cms/core 2.0.0 → 2.0.2
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/dist/chunk-3NVJ6W27.cjs +1076 -0
- package/dist/chunk-3NVJ6W27.cjs.map +1 -0
- package/dist/chunk-4BJGEGX5.cjs +236 -0
- package/dist/chunk-4BJGEGX5.cjs.map +1 -0
- package/dist/{chunk-QUMBDPNJ.cjs → chunk-7G6XT62S.cjs} +4583 -540
- package/dist/chunk-7G6XT62S.cjs.map +1 -0
- package/dist/{chunk-ET5I4GBD.cjs → chunk-ALOS2CBJ.cjs} +194 -4
- package/dist/chunk-ALOS2CBJ.cjs.map +1 -0
- package/dist/{chunk-7N3HK7ZK.js → chunk-CDBVZEWR.js} +7 -904
- package/dist/chunk-CDBVZEWR.js.map +1 -0
- package/dist/{chunk-BITQ4MFX.js → chunk-EAELJXRV.js} +93 -115
- package/dist/chunk-EAELJXRV.js.map +1 -0
- package/dist/chunk-EGFHFM4N.cjs +76 -0
- package/dist/chunk-EGFHFM4N.cjs.map +1 -0
- package/dist/chunk-FICTAGD4.js +59 -0
- package/dist/chunk-FICTAGD4.js.map +1 -0
- package/dist/{chunk-FVMV5DKA.cjs → chunk-HJZOA2O5.cjs} +93 -115
- package/dist/chunk-HJZOA2O5.cjs.map +1 -0
- package/dist/{chunk-RNR4HA23.cjs → chunk-LEG4KNFP.cjs} +6 -945
- package/dist/chunk-LEG4KNFP.cjs.map +1 -0
- package/dist/chunk-LH4Z7QID.js +1030 -0
- package/dist/chunk-LH4Z7QID.js.map +1 -0
- package/dist/chunk-M6FPVS7E.js +214 -0
- package/dist/chunk-M6FPVS7E.js.map +1 -0
- package/dist/{chunk-P3VS4DV3.js → chunk-O46XKBFM.js} +193 -5
- package/dist/chunk-O46XKBFM.js.map +1 -0
- package/dist/chunk-P2PTTBO5.js +74 -0
- package/dist/chunk-P2PTTBO5.js.map +1 -0
- package/dist/chunk-RCQ2HIQD.cjs +61 -0
- package/dist/chunk-RCQ2HIQD.cjs.map +1 -0
- package/dist/{chunk-JETM2U2D.js → chunk-SGGHTIWV.js} +4414 -374
- package/dist/chunk-SGGHTIWV.js.map +1 -0
- package/dist/{chunk-RGCQSFKC.cjs → chunk-UL32L2KV.cjs} +9 -58
- package/dist/chunk-UL32L2KV.cjs.map +1 -0
- package/dist/{chunk-JIINOD2W.js → chunk-XJETEIRU.js} +8 -58
- package/dist/chunk-XJETEIRU.js.map +1 -0
- package/dist/index.cjs +241 -218
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +20 -15
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +27 -22
- package/dist/middleware.js +3 -2
- package/dist/plugins.cjs +7 -7
- package/dist/plugins.js +1 -1
- package/dist/routes.cjs +36 -23
- package/dist/routes.js +7 -6
- package/dist/services.cjs +28 -28
- package/dist/services.js +2 -2
- package/dist/templates.cjs +24 -24
- package/dist/templates.js +2 -2
- package/dist/utils.cjs +20 -11
- package/dist/utils.js +2 -1
- package/package.json +2 -16
- package/dist/chunk-3MNMOLSA.js +0 -133
- package/dist/chunk-3MNMOLSA.js.map +0 -1
- package/dist/chunk-4XI3YBKU.cjs +0 -266
- package/dist/chunk-4XI3YBKU.cjs.map +0 -1
- package/dist/chunk-7N3HK7ZK.js.map +0 -1
- package/dist/chunk-AGOE25LF.cjs +0 -137
- package/dist/chunk-AGOE25LF.cjs.map +0 -1
- package/dist/chunk-BITQ4MFX.js.map +0 -1
- package/dist/chunk-BUKT6HP5.cjs +0 -776
- package/dist/chunk-BUKT6HP5.cjs.map +0 -1
- package/dist/chunk-ET5I4GBD.cjs.map +0 -1
- package/dist/chunk-FVMV5DKA.cjs.map +0 -1
- package/dist/chunk-JETM2U2D.js.map +0 -1
- package/dist/chunk-JIINOD2W.js.map +0 -1
- package/dist/chunk-LU6J53IX.js +0 -262
- package/dist/chunk-LU6J53IX.js.map +0 -1
- package/dist/chunk-P3VS4DV3.js.map +0 -1
- package/dist/chunk-QUMBDPNJ.cjs.map +0 -1
- package/dist/chunk-RGCQSFKC.cjs.map +0 -1
- package/dist/chunk-RNR4HA23.cjs.map +0 -1
- package/dist/chunk-WESS2U3K.js +0 -755
- package/dist/chunk-WESS2U3K.js.map +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { init_admin_layout_catalyst_template, init_logo_template } from './chunk-O46XKBFM.js';
|
|
2
|
+
|
|
3
|
+
// src/templates/filter-bar.template.ts
|
|
4
|
+
function renderFilterBar(data) {
|
|
5
|
+
return `
|
|
6
|
+
<div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6 mb-6">
|
|
7
|
+
<form id="filter-form" class="flex flex-wrap gap-4 items-center">
|
|
8
|
+
${data.filters.map((filter) => `
|
|
9
|
+
<div class="flex items-center space-x-2">
|
|
10
|
+
<label class="text-sm font-medium text-zinc-500 dark:text-zinc-400">${filter.label}:</label>
|
|
11
|
+
<select
|
|
12
|
+
name="${filter.name}"
|
|
13
|
+
class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 focus:ring-2 focus:ring-blue-600 dark:focus:ring-blue-500 focus:outline-none transition-colors"
|
|
14
|
+
onchange="updateFilters()"
|
|
15
|
+
>
|
|
16
|
+
${filter.options.map((option) => `
|
|
17
|
+
<option value="${option.value}" ${option.selected ? "selected" : ""}>
|
|
18
|
+
${option.label}
|
|
19
|
+
</option>
|
|
20
|
+
`).join("")}
|
|
21
|
+
</select>
|
|
22
|
+
</div>
|
|
23
|
+
`).join("")}
|
|
24
|
+
|
|
25
|
+
${data.actions && data.actions.length > 0 ? `
|
|
26
|
+
<div class="flex items-center space-x-2 ml-auto">
|
|
27
|
+
${data.actions.map((action) => `
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
class="inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors"
|
|
31
|
+
${action.onclick ? `onclick="${action.onclick}"` : ""}
|
|
32
|
+
${action.hxGet ? `hx-get="${action.hxGet}"` : ""}
|
|
33
|
+
${action.hxTarget ? `hx-target="${action.hxTarget}"` : ""}
|
|
34
|
+
>
|
|
35
|
+
${action.label}
|
|
36
|
+
</button>
|
|
37
|
+
`).join("")}
|
|
38
|
+
</div>
|
|
39
|
+
` : ""}
|
|
40
|
+
</form>
|
|
41
|
+
|
|
42
|
+
<script>
|
|
43
|
+
function updateFilters() {
|
|
44
|
+
const form = document.getElementById('filter-form');
|
|
45
|
+
const formData = new FormData(form);
|
|
46
|
+
const params = new URLSearchParams(window.location.search);
|
|
47
|
+
|
|
48
|
+
// Update params with form values
|
|
49
|
+
for (const [key, value] of formData.entries()) {
|
|
50
|
+
if (value) {
|
|
51
|
+
params.set(key, value);
|
|
52
|
+
} else {
|
|
53
|
+
params.delete(key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Reset to page 1 when filters change
|
|
58
|
+
params.set('page', '1');
|
|
59
|
+
|
|
60
|
+
// Update URL and reload
|
|
61
|
+
window.location.href = window.location.pathname + '?' + params.toString();
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
</div>
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/templates/index.ts
|
|
69
|
+
init_admin_layout_catalyst_template();
|
|
70
|
+
init_logo_template();
|
|
71
|
+
|
|
72
|
+
export { renderFilterBar };
|
|
73
|
+
//# sourceMappingURL=chunk-P2PTTBO5.js.map
|
|
74
|
+
//# sourceMappingURL=chunk-P2PTTBO5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/templates/filter-bar.template.ts","../src/templates/index.ts"],"names":[],"mappings":";;;AA8BO,SAAS,gBAAgB,IAAA,EAA6B;AAC3D,EAAA,OAAO;AAAA;AAAA;AAAA,QAAA,EAGC,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,CAAA,MAAA,KAAU;AAAA;AAAA,gFAAA,EAE6C,OAAO,KAAK,CAAA;AAAA;AAAA,oBAAA,EAExE,OAAO,IAAI,CAAA;AAAA;AAAA;AAAA;AAAA,cAAA,EAIjB,MAAA,CAAO,OAAA,CAAQ,GAAA,CAAI,CAAA,MAAA,KAAU;AAAA,+BAAA,EACZ,OAAO,KAAK,CAAA,EAAA,EAAK,MAAA,CAAO,QAAA,GAAW,aAAa,EAAE,CAAA;AAAA,kBAAA,EAC/D,OAAO,KAAK;AAAA;AAAA,cAAA,CAEjB,CAAA,CAAE,IAAA,CAAK,EAAE,CAAC;AAAA;AAAA;AAAA,QAAA,CAGhB,CAAA,CAAE,IAAA,CAAK,EAAE,CAAC;;AAAA,QAAA,EAET,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA,GAAI;AAAA;AAAA,YAAA,EAEtC,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,CAAA,MAAA,KAAU;AAAA;AAAA;AAAA;AAAA,gBAAA,EAIvB,OAAO,OAAA,GAAU,CAAA,SAAA,EAAY,MAAA,CAAO,OAAO,MAAM,EAAE;AAAA,gBAAA,EACnD,OAAO,KAAA,GAAQ,CAAA,QAAA,EAAW,MAAA,CAAO,KAAK,MAAM,EAAE;AAAA,gBAAA,EAC9C,OAAO,QAAA,GAAW,CAAA,WAAA,EAAc,MAAA,CAAO,QAAQ,MAAM,EAAE;AAAA;AAAA,gBAAA,EAEvD,OAAO,KAAK;AAAA;AAAA,YAAA,CAEjB,CAAA,CAAE,IAAA,CAAK,EAAE,CAAC;AAAA;AAAA,QAAA,CAAA,GAEX,EAAE;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA,CAAA;AA2Bd;;;AC5DA,mCAAA,EAAA;AAKA,kBAAA,EAAA","file":"chunk-P2PTTBO5.js","sourcesContent":["export interface FilterOption {\n value: string\n label: string\n selected?: boolean\n color?: string\n}\n\nexport interface Filter {\n name: string\n label: string\n options: FilterOption[]\n}\n\nexport interface FilterBarData {\n filters: Filter[]\n actions?: Array<{\n label: string\n className?: string\n onclick?: string\n hxGet?: string\n hxTarget?: string\n }>\n bulkActions?: Array<{\n label: string\n value: string\n icon?: string\n className?: string\n }>\n}\n\nexport function renderFilterBar(data: FilterBarData): string {\n return `\n <div class=\"rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6 mb-6\">\n <form id=\"filter-form\" class=\"flex flex-wrap gap-4 items-center\">\n ${data.filters.map(filter => `\n <div class=\"flex items-center space-x-2\">\n <label class=\"text-sm font-medium text-zinc-500 dark:text-zinc-400\">${filter.label}:</label>\n <select\n name=\"${filter.name}\"\n class=\"rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 focus:ring-2 focus:ring-blue-600 dark:focus:ring-blue-500 focus:outline-none transition-colors\"\n onchange=\"updateFilters()\"\n >\n ${filter.options.map(option => `\n <option value=\"${option.value}\" ${option.selected ? 'selected' : ''}>\n ${option.label}\n </option>\n `).join('')}\n </select>\n </div>\n `).join('')}\n\n ${data.actions && data.actions.length > 0 ? `\n <div class=\"flex items-center space-x-2 ml-auto\">\n ${data.actions.map(action => `\n <button\n type=\"button\"\n class=\"inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors\"\n ${action.onclick ? `onclick=\"${action.onclick}\"` : ''}\n ${action.hxGet ? `hx-get=\"${action.hxGet}\"` : ''}\n ${action.hxTarget ? `hx-target=\"${action.hxTarget}\"` : ''}\n >\n ${action.label}\n </button>\n `).join('')}\n </div>\n ` : ''}\n </form>\n\n <script>\n function updateFilters() {\n const form = document.getElementById('filter-form');\n const formData = new FormData(form);\n const params = new URLSearchParams(window.location.search);\n\n // Update params with form values\n for (const [key, value] of formData.entries()) {\n if (value) {\n params.set(key, value);\n } else {\n params.delete(key);\n }\n }\n\n // Reset to page 1 when filters change\n params.set('page', '1');\n\n // Update URL and reload\n window.location.href = window.location.pathname + '?' + params.toString();\n }\n </script>\n </div>\n `\n}","/**\n * Templates Module Exports\n *\n * Reusable HTML template components for SonicJS\n */\n\n// Form templates\nexport { renderForm, renderFormField } from './form.template'\nexport type { FormField, FormData } from './form.template'\n\n// Table templates\nexport { renderTable } from './table.template'\nexport type { TableColumn, TableData } from './table.template'\n\n// Pagination templates\nexport { renderPagination } from './pagination.template'\nexport type { PaginationData } from './pagination.template'\n\n// Alert templates\nexport { renderAlert } from './alert.template'\nexport type { AlertData } from './alert.template'\n\n// Confirmation dialog templates\nexport { renderConfirmationDialog, getConfirmationDialogScript } from './confirmation-dialog.template'\nexport type { ConfirmationDialogOptions } from './confirmation-dialog.template'\n\n// Filter bar templates\nexport { renderFilterBar } from './filter-bar.template'\nexport type { FilterBarData, Filter, FilterOption } from './filter-bar.template'\n\n// Layout templates\nexport { renderAdminLayout } from './layouts/admin-layout-v2.template'\nexport { renderAdminLayoutCatalyst } from './layouts/admin-layout-catalyst.template'\nexport type { AdminLayoutData } from './layouts/admin-layout-v2.template'\nexport type { AdminLayoutCatalystData } from './layouts/admin-layout-catalyst.template'\n\n// Component templates\nexport { renderLogo } from './components/logo.template'\n\n// Page templates - Admin\nexport { renderDesignPage } from './pages/admin-design.template'\nexport type { DesignPageData } from './pages/admin-design.template'\nexport { renderCheckboxPage } from './pages/admin-checkboxes.template'\nexport type { CheckboxPageData } from './pages/admin-checkboxes.template'\nexport { renderFAQList } from './pages/admin-faq-list.template'\nexport { renderTestimonialsList } from './pages/admin-testimonials-list.template'\nexport { renderCodeExamplesList } from './pages/admin-code-examples-list.template'\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/utils/metrics.ts
|
|
4
|
+
var MetricsTracker = class {
|
|
5
|
+
requests = [];
|
|
6
|
+
windowSize = 1e4;
|
|
7
|
+
// 10 seconds window
|
|
8
|
+
/**
|
|
9
|
+
* Record a new request
|
|
10
|
+
*/
|
|
11
|
+
recordRequest() {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
this.requests.push({ timestamp: now });
|
|
14
|
+
this.cleanup(now);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Clean up old requests outside the window
|
|
18
|
+
*/
|
|
19
|
+
cleanup(now) {
|
|
20
|
+
const cutoff = now - this.windowSize;
|
|
21
|
+
this.requests = this.requests.filter((req) => req.timestamp > cutoff);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get current requests per second
|
|
25
|
+
*/
|
|
26
|
+
getRequestsPerSecond() {
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
this.cleanup(now);
|
|
29
|
+
if (this.requests.length === 0) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
const oneSecondAgo = now - 1e3;
|
|
33
|
+
const recentRequests = this.requests.filter((req) => req.timestamp > oneSecondAgo);
|
|
34
|
+
return recentRequests.length;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get total requests in the current window
|
|
38
|
+
*/
|
|
39
|
+
getTotalRequests() {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
this.cleanup(now);
|
|
42
|
+
return this.requests.length;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get average requests per second over the window
|
|
46
|
+
*/
|
|
47
|
+
getAverageRPS() {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
this.cleanup(now);
|
|
50
|
+
if (this.requests.length === 0) {
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
const windowSeconds = this.windowSize / 1e3;
|
|
54
|
+
return this.requests.length / windowSeconds;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var metricsTracker = new MetricsTracker();
|
|
58
|
+
|
|
59
|
+
exports.metricsTracker = metricsTracker;
|
|
60
|
+
//# sourceMappingURL=chunk-RCQ2HIQD.cjs.map
|
|
61
|
+
//# sourceMappingURL=chunk-RCQ2HIQD.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/metrics.ts"],"names":[],"mappings":";;;AASA,IAAM,iBAAN,MAAqB;AAAA,EACX,WAA6B,EAAC;AAAA,EACrB,UAAA,GAAa,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAK9B,aAAA,GAAsB;AACpB,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,EAAE,SAAA,EAAW,KAAK,CAAA;AACrC,IAAA,IAAA,CAAK,QAAQ,GAAG,CAAA;AAAA;AAClB;AAAA;AAAA;AAAA,EAKQ,QAAQ,GAAA,EAAmB;AACjC,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,UAAA;AAC1B,IAAA,IAAA,CAAK,WAAW,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA,GAAA,KAAO,GAAA,CAAI,YAAY,MAAM,CAAA;AAAA;AACpE;AAAA;AAAA;AAAA,EAKA,oBAAA,GAA+B;AAC7B,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAA,CAAK,QAAQ,GAAG,CAAA;AAEhB,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG;AAC9B,MAAA,OAAO,CAAA;AAAA;AAIT,IAAA,MAAM,eAAe,GAAA,GAAM,GAAA;AAC3B,IAAA,MAAM,iBAAiB,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA,GAAA,KAAO,GAAA,CAAI,YAAY,YAAY,CAAA;AAE/E,IAAA,OAAO,cAAA,CAAe,MAAA;AAAA;AACxB;AAAA;AAAA;AAAA,EAKA,gBAAA,GAA2B;AACzB,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAA,CAAK,QAAQ,GAAG,CAAA;AAChB,IAAA,OAAO,KAAK,QAAA,CAAS,MAAA;AAAA;AACvB;AAAA;AAAA;AAAA,EAKA,aAAA,GAAwB;AACtB,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAA,CAAK,QAAQ,GAAG,CAAA;AAEhB,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG;AAC9B,MAAA,OAAO,CAAA;AAAA;AAGT,IAAA,MAAM,aAAA,GAAgB,KAAK,UAAA,GAAa,GAAA;AACxC,IAAA,OAAO,IAAA,CAAK,SAAS,MAAA,GAAS,aAAA;AAAA;AAElC,CAAA;AAGO,IAAM,cAAA,GAAiB,IAAI,cAAA","file":"chunk-RCQ2HIQD.cjs","sourcesContent":["/**\n * Simple in-memory metrics tracker for real-time analytics\n * Tracks requests per second using a sliding window\n */\n\ninterface RequestMetrics {\n timestamp: number\n}\n\nclass MetricsTracker {\n private requests: RequestMetrics[] = []\n private readonly windowSize = 10000 // 10 seconds window\n\n /**\n * Record a new request\n */\n recordRequest(): void {\n const now = Date.now()\n this.requests.push({ timestamp: now })\n this.cleanup(now)\n }\n\n /**\n * Clean up old requests outside the window\n */\n private cleanup(now: number): void {\n const cutoff = now - this.windowSize\n this.requests = this.requests.filter(req => req.timestamp > cutoff)\n }\n\n /**\n * Get current requests per second\n */\n getRequestsPerSecond(): number {\n const now = Date.now()\n this.cleanup(now)\n\n if (this.requests.length === 0) {\n return 0\n }\n\n // Calculate RPS over the last second\n const oneSecondAgo = now - 1000\n const recentRequests = this.requests.filter(req => req.timestamp > oneSecondAgo)\n\n return recentRequests.length\n }\n\n /**\n * Get total requests in the current window\n */\n getTotalRequests(): number {\n const now = Date.now()\n this.cleanup(now)\n return this.requests.length\n }\n\n /**\n * Get average requests per second over the window\n */\n getAverageRPS(): number {\n const now = Date.now()\n this.cleanup(now)\n\n if (this.requests.length === 0) {\n return 0\n }\n\n const windowSeconds = this.windowSize / 1000\n return this.requests.length / windowSeconds\n }\n}\n\n// Global singleton instance\nexport const metricsTracker = new MetricsTracker()\n"]}
|