@maiyunnet/kebab 8.6.5 → 9.0.0

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.
@@ -57,6 +57,10 @@ export default class extends sCtr.Ctr {
57
57
  coreGlobal(): Promise<string>;
58
58
  crypto(): Promise<string>;
59
59
  db(): Promise<kebab.Json>;
60
+ /**
61
+ * --- 数据库读写分离测试 ---
62
+ */
63
+ dbRead(): string;
60
64
  private _dbTable;
61
65
  vector(): Promise<any>;
62
66
  kv(): Promise<kebab.Json>;
@@ -119,6 +123,36 @@ export default class extends sCtr.Ctr {
119
123
  * --- ?mode=block 模拟死循环阻塞,否则模拟非阻塞 CPU 骤升 ---
120
124
  */
121
125
  monitorSpike(): Promise<string>;
126
+ /**
127
+ * --- Rate Limit 限流测试 ---
128
+ */
129
+ ratelimit(): Promise<string>;
130
+ /**
131
+ * --- React 全页模式测试:组件自主渲染完整 HTML 文档 + 客户端水合,无需 EJS ---
132
+ */
133
+ reactPage(): Promise<string>;
134
+ /**
135
+ * --- 根据前端路由路径获取对应页面数据,SSR 和 API 共用此方法 ---
136
+ * @param routePath 前端路由路径,如 /user、/user/42
137
+ */
138
+ private _getRouteData;
139
+ /**
140
+ * --- BrowserRouter 全页面演示:地址栏联动 + 嵌套路由 + 深链接直接访问 ---
141
+ * --- SSR 时通过 _getRouteData 获取匹配路由的数据注入 props ---
142
+ */
143
+ reactRouterPage(): Promise<string>;
144
+ /**
145
+ * --- BrowserRouter SPA 导航时的数据 API,与 SSR 共用 _getRouteData ---
146
+ */
147
+ reactRouterPageData(): kebab.Json[];
148
+ /**
149
+ * --- .env 环境变量测试 ---
150
+ */
151
+ coreEnv(): string;
152
+ /**
153
+ * --- CORS 精细化配置测试 ---
154
+ */
155
+ ctrCrossFine(): string;
122
156
  /**
123
157
  * --- END ---
124
158
  */
@@ -20,6 +20,7 @@ import * as lZip from '#kebab/lib/zip.js';
20
20
  import * as lBuffer from '#kebab/lib/buffer.js';
21
21
  import * as lLan from '#kebab/lib/lan.js';
22
22
  import * as lCron from '#kebab/lib/cron.js';
23
+ import * as lRateLimit from '#kebab/lib/ratelimit.js';
23
24
  import * as lAi from '#kebab/lib/ai.js';
24
25
  import * as sMonitor from '#kebab/sys/monitor.js';
25
26
  import * as sCtr from '#kebab/sys/ctr.js';
@@ -94,6 +95,9 @@ export default class extends sCtr.Ctr {
94
95
  `<br><br><a href="${this._config.const.urlBase}test/qs?a=1&b=2">View "test/qs?a=1&b=2"</a>`,
95
96
  '<br><br><b>View:</b>',
96
97
  `<br><br><a href="${this._config.const.urlBase}test/view">View "test/view"</a>`,
98
+ `<br><a href="${this._config.const.urlBase}test/react-page">View "test/react-page" (SSR)</a>`,
99
+ `<br><a href="${this._config.const.urlBase}test/react-router-page">View "test/react-router-page" (SSR + BrowserRouter)</a>`,
100
+ `<br><a href="${this._config.const.urlBase}test/react-router-page-data?path=/user">View "test/react-router-page-data?path=/user" (Data API)</a>`,
97
101
  '<br><br><b>Return json:</b>',
98
102
  `<br><br><a href="${this._config.const.urlBase}test/json?type=1">View "test/json?type=1"</a>`,
99
103
  `<br><a href="${this._config.const.urlBase}test/json?type=2">View "test/json?type=2"</a>`,
@@ -111,6 +115,7 @@ export default class extends sCtr.Ctr {
111
115
  `<br><a href="${this._config.const.urlBase}test/ctr-cachettl">View "test/ctr-cachettl"</a>`,
112
116
  `<br><a href="${this._config.const.urlBase}test/ctr-httpcode">View "test/ctr-httpcode"</a>`,
113
117
  `<br><a href="${this._config.const.urlBase}test/ctr-cross">View "test/ctr-cross"</a>`,
118
+ `<br><a href="${this._config.const.urlBase}test/ctr-cross-fine">View "test/ctr-cross-fine"</a>`,
114
119
  `<br><a href="${this._config.const.urlBase}test/ctr-readable">View "test/ctr-readable"</a>`,
115
120
  `<br><a href="${this._config.const.urlBase}test/ctr-asynctask">View "test/ctr-asynctask"</a>`,
116
121
  `<br><a href="${this._config.const.urlBase}test/ctr-timeout-long">View "test/ctr-timeout-long"</a>`,
@@ -151,10 +156,12 @@ export default class extends sCtr.Ctr {
151
156
  `<br><a href="${this._config.const.urlBase}test/core-npm">View "test/core-npm"</a>`,
152
157
  `<br><a href="${this._config.const.urlBase}test/core-global">View "test/core-global"</a>`,
153
158
  `<br><a href="${this._config.const.urlBase}test/core-updatecode">View "test/core-updatecode"</a>`,
159
+ `<br><a href="${this._config.const.urlBase}test/core-env">View "test/core-env"</a>`,
154
160
  '<br><br><b>Crypto:</b>',
155
161
  `<br><br><a href="${this._config.const.urlBase}test/crypto">View "test/crypto"</a>`,
156
162
  '<br><br><b>Db:</b>',
157
163
  `<br><br><a href="${this._config.const.urlBase}test/db">View "test/db"</a> <a href="${this._config.const.urlBase}test/db?s=pgsql">pgsql</a>`,
164
+ `<br><br><a href="${this._config.const.urlBase}test/db-read">View "test/db-read"</a>`,
158
165
  `<br><br><b>Vector:</b>`,
159
166
  `<br><br><a href="${this._config.const.urlBase}test/vector">View "test/vector"</a>`,
160
167
  '<br><br><b>Kv:</b>',
@@ -232,6 +239,8 @@ export default class extends sCtr.Ctr {
232
239
  `<br><br><a href="${this._config.const.urlBase}test/monitor-snapshot">View "test/monitor-snapshot"</a>`,
233
240
  `<br><a href="${this._config.const.urlBase}test/monitor-spike">View "test/monitor-spike" (non-blocking CPU spike)</a>`,
234
241
  `<br><a href="${this._config.const.urlBase}test/monitor-spike?mode=block">View "test/monitor-spike?mode=block" (event loop block)</a>`,
242
+ '<br><br><b>Rate Limit:</b>',
243
+ `<br><br><a href="${this._config.const.urlBase}test/ratelimit">View "test/ratelimit"</a>`,
235
244
  ];
236
245
  echo.push('<br><br>' + this._getEnd());
237
246
  return echo.join('');
@@ -1644,6 +1653,20 @@ exec: ${JSON.stringify(exec)}<br><br>`);
1644
1653
  this._dbTable(stmt, echo);
1645
1654
  return echo.join('') + '<br>queries: ' + db.getQueries().toString() + '<br>' + this._getEnd();
1646
1655
  }
1656
+ /**
1657
+ * --- 数据库读写分离测试 ---
1658
+ */
1659
+ dbRead() {
1660
+ const echo = [];
1661
+ echo.push('<b>DB Read Separation Test</b><hr>');
1662
+ echo.push('<pre>const db = lDb.get(this);\nconst dbRead = lDb.get(this, { \'read\': true });</pre>');
1663
+ const config = this._config;
1664
+ const service = config.db.default;
1665
+ echo.push(`Service: ${service}`);
1666
+ echo.push(`<br>Default host: ${config.db[service]?.default?.host ?? '(not configured)'}`);
1667
+ echo.push(`<br>Read host: ${config.db[service]?.read?.host ?? '(not configured, will fallback to default)'}`);
1668
+ return echo.join('') + '<br><br>' + this._getEnd();
1669
+ }
1647
1670
  _dbTable(stmt, echo) {
1648
1671
  echo.push('<table style="width: 100%;"><tr>');
1649
1672
  if (stmt.rows) {
@@ -3863,6 +3886,123 @@ send.addEventListener('click', async () => {
3863
3886
  echo.push('<br><br>' + this._getEnd());
3864
3887
  return echo.join('');
3865
3888
  }
3889
+ /**
3890
+ * --- Rate Limit 限流测试 ---
3891
+ */
3892
+ async ratelimit() {
3893
+ const echo = [];
3894
+ echo.push('<b>Rate Limit Test</b><hr>');
3895
+ const kv = lKv.get(this);
3896
+ // --- 滑动窗口限流 ---
3897
+ echo.push('<pre>const kv = lKv.get(this);\nconst rtn = await lRateLimit.check(kv, \'test-api\', { \'max\': 5, \'window\': 60 });</pre>');
3898
+ const rtn = await lRateLimit.check(kv, 'test-api', { 'max': 5, 'window': 60 });
3899
+ echo.push(`Result: ${JSON.stringify(rtn)}`);
3900
+ // --- 固定窗口限流 ---
3901
+ echo.push('<br><br><pre>const rtn2 = await lRateLimit.checkFixed(kv, \'test-fixed\', { \'max\': 10, \'window\': 60 });</pre>');
3902
+ const rtn2 = await lRateLimit.checkFixed(kv, 'test-fixed', { 'max': 10, 'window': 60 });
3903
+ echo.push(`Result: ${JSON.stringify(rtn2)}`);
3904
+ return echo.join('') + '<br><br>' + this._getEnd();
3905
+ }
3906
+ /**
3907
+ * --- React 全页模式测试:组件自主渲染完整 HTML 文档 + 客户端水合,无需 EJS ---
3908
+ */
3909
+ async reactPage() {
3910
+ // --- Ctr 方法负责数据准备(可以查数据库、调接口等),组件不包含任何服务端专属代码 ---
3911
+ return this._loadReactPage('view/react-page', {
3912
+ 'title': 'Kebab React Full Page',
3913
+ 'serverTime': lTime.format(this, 'Y-m-d H:i:s'),
3914
+ 'node': process.version,
3915
+ });
3916
+ }
3917
+ /**
3918
+ * --- 根据前端路由路径获取对应页面数据,SSR 和 API 共用此方法 ---
3919
+ * @param routePath 前端路由路径,如 /user、/user/42
3920
+ */
3921
+ _getRouteData(routePath) {
3922
+ const data = {};
3923
+ // --- 演示用户数据(实际项目中可查数据库) ---
3924
+ const users = [
3925
+ { 'id': '1', 'name': 'Alice', 'email': 'alice@example.com' },
3926
+ { 'id': '2', 'name': 'Bob', 'email': 'bob@example.com' },
3927
+ { 'id': '42', 'name': 'Eve', 'email': 'eve@example.com' },
3928
+ ];
3929
+ if (routePath === '/user' || routePath === '/user/') {
3930
+ data['users'] = users;
3931
+ }
3932
+ else {
3933
+ const match = /^\/user\/(\d+)/.exec(routePath);
3934
+ if (match) {
3935
+ const user = users.find(u => u.id === match[1]);
3936
+ if (user) {
3937
+ data['user'] = user;
3938
+ }
3939
+ }
3940
+ }
3941
+ return data;
3942
+ }
3943
+ /**
3944
+ * --- BrowserRouter 全页面演示:地址栏联动 + 嵌套路由 + 深链接直接访问 ---
3945
+ * --- SSR 时通过 _getRouteData 获取匹配路由的数据注入 props ---
3946
+ */
3947
+ async reactRouterPage() {
3948
+ // --- 从请求 URL 中解析前端路由路径 ---
3949
+ const reqUrl = (this._req.url ?? '/').split('?')[0];
3950
+ const basePrefix = this._config.const.urlBase + 'test/react-router-page';
3951
+ const routePath = reqUrl.startsWith(basePrefix)
3952
+ ? (reqUrl.substring(basePrefix.length) || '/')
3953
+ : '/';
3954
+ const routeData = this._getRouteData(routePath);
3955
+ return this._loadReactPage('view/react-router-page', {
3956
+ 'title': 'Kebab React Router',
3957
+ 'serverTime': lTime.format(this, 'Y-m-d H:i:s'),
3958
+ 'node': process.version,
3959
+ ...routeData,
3960
+ }, {
3961
+ 'router': 'browser',
3962
+ 'routerBase': 'test/react-router-page',
3963
+ });
3964
+ }
3965
+ /**
3966
+ * --- BrowserRouter SPA 导航时的数据 API,与 SSR 共用 _getRouteData ---
3967
+ */
3968
+ reactRouterPageData() {
3969
+ const routePath = this._get['path'] ?? '/';
3970
+ return [1, this._getRouteData(routePath)];
3971
+ }
3972
+ /**
3973
+ * --- .env 环境变量测试 ---
3974
+ */
3975
+ coreEnv() {
3976
+ const echo = [];
3977
+ echo.push('<b>Env Test</b><hr>');
3978
+ echo.push('<pre>process.env.NODE_ENV</pre>');
3979
+ echo.push(`Result: ${lText.htmlescape(process.env.NODE_ENV ?? '(not set)')}`);
3980
+ echo.push('<br><br><pre>lCore.globalConfig.logFormat</pre>');
3981
+ echo.push(`Result: ${lText.htmlescape(lCore.globalConfig.logFormat ?? 'jsonl')}`);
3982
+ echo.push('<br><br><pre>lCore.globalConfig.debug</pre>');
3983
+ echo.push(`Result: ${lCore.globalConfig.debug}`);
3984
+ return echo.join('') + '<br><br>' + this._getEnd();
3985
+ }
3986
+ /**
3987
+ * --- CORS 精细化配置测试 ---
3988
+ */
3989
+ ctrCrossFine() {
3990
+ const echo = [];
3991
+ echo.push('<b>CORS Fine-grained Test</b><hr>');
3992
+ echo.push('<pre>this._cross({ \'origins\': [\'http://localhost:3000\'], \'credentials\': true });</pre>');
3993
+ this._cross({
3994
+ 'origins': ['http://localhost:3000', 'http://127.0.0.1:8081'],
3995
+ 'credentials': true,
3996
+ });
3997
+ const headers = {};
3998
+ for (const [key, val] of Object.entries(this._res.getHeaders())) {
3999
+ if (key.startsWith('access-control-')) {
4000
+ headers[key] = String(val);
4001
+ }
4002
+ }
4003
+ echo.push(`Response CORS Headers: <pre>${JSON.stringify(headers, null, 4)}</pre>`);
4004
+ return echo.join('') + '<br><br>' + this._getEnd();
4005
+ }
3866
4006
  /**
3867
4007
  * --- END ---
3868
4008
  */
@@ -2,5 +2,6 @@
2
2
  "#404": "test/notfound",
3
3
  "@": "main",
4
4
  "article\\/([0-9]+?)": "test/article",
5
- "test\\/net-rproxy\\/.+?": "test/netRproxy"
5
+ "test\\/net-rproxy\\/.+?": "test/netRproxy",
6
+ "test\\/react-router-page(\\/.+)?": "test/reactRouterPage"
6
7
  }