befly 3.9.37 → 3.9.39

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.
Files changed (155) hide show
  1. package/README.md +38 -39
  2. package/befly.config.ts +62 -40
  3. package/checks/checkApi.ts +16 -16
  4. package/checks/checkApp.ts +19 -25
  5. package/checks/checkTable.ts +42 -42
  6. package/docs/README.md +42 -35
  7. package/docs/{api.md → api/api.md} +225 -235
  8. package/docs/cipher.md +71 -69
  9. package/docs/database.md +155 -153
  10. package/docs/{examples.md → guide/examples.md} +181 -181
  11. package/docs/guide/quickstart.md +331 -0
  12. package/docs/hooks/auth.md +38 -0
  13. package/docs/hooks/cors.md +28 -0
  14. package/docs/{hook.md → hooks/hook.md} +140 -57
  15. package/docs/hooks/parser.md +19 -0
  16. package/docs/hooks/rateLimit.md +47 -0
  17. package/docs/{redis.md → infra/redis.md} +84 -93
  18. package/docs/plugins/cipher.md +61 -0
  19. package/docs/plugins/database.md +128 -0
  20. package/docs/{plugin.md → plugins/plugin.md} +83 -81
  21. package/docs/quickstart.md +26 -26
  22. package/docs/{addon.md → reference/addon.md} +46 -46
  23. package/docs/{config.md → reference/config.md} +32 -80
  24. package/docs/{logger.md → reference/logger.md} +52 -52
  25. package/docs/{sync.md → reference/sync.md} +32 -35
  26. package/docs/{table.md → reference/table.md} +7 -7
  27. package/docs/{validator.md → reference/validator.md} +57 -57
  28. package/hooks/auth.ts +8 -4
  29. package/hooks/cors.ts +13 -13
  30. package/hooks/parser.ts +37 -17
  31. package/hooks/permission.ts +26 -14
  32. package/hooks/rateLimit.ts +276 -0
  33. package/hooks/validator.ts +15 -7
  34. package/lib/asyncContext.ts +43 -0
  35. package/lib/cacheHelper.ts +212 -81
  36. package/lib/cacheKeys.ts +38 -0
  37. package/lib/cipher.ts +30 -30
  38. package/lib/connect.ts +28 -28
  39. package/lib/dbHelper.ts +211 -109
  40. package/lib/jwt.ts +16 -16
  41. package/lib/logger.ts +610 -19
  42. package/lib/redisHelper.ts +185 -44
  43. package/lib/sqlBuilder.ts +90 -91
  44. package/lib/validator.ts +59 -39
  45. package/loader/loadApis.ts +53 -47
  46. package/loader/loadHooks.ts +40 -14
  47. package/loader/loadPlugins.ts +16 -17
  48. package/main.ts +57 -47
  49. package/package.json +47 -45
  50. package/paths.ts +15 -14
  51. package/plugins/cache.ts +5 -4
  52. package/plugins/cipher.ts +3 -3
  53. package/plugins/config.ts +2 -2
  54. package/plugins/db.ts +9 -9
  55. package/plugins/jwt.ts +3 -3
  56. package/plugins/logger.ts +8 -12
  57. package/plugins/redis.ts +8 -8
  58. package/plugins/tool.ts +6 -6
  59. package/router/api.ts +85 -56
  60. package/router/static.ts +12 -12
  61. package/sync/syncAll.ts +12 -12
  62. package/sync/syncApi.ts +55 -54
  63. package/sync/syncDb/apply.ts +20 -19
  64. package/sync/syncDb/constants.ts +25 -23
  65. package/sync/syncDb/ddl.ts +35 -36
  66. package/sync/syncDb/helpers.ts +6 -9
  67. package/sync/syncDb/schema.ts +10 -9
  68. package/sync/syncDb/sqlite.ts +7 -8
  69. package/sync/syncDb/table.ts +37 -35
  70. package/sync/syncDb/tableCreate.ts +21 -20
  71. package/sync/syncDb/types.ts +23 -20
  72. package/sync/syncDb/version.ts +10 -10
  73. package/sync/syncDb.ts +43 -36
  74. package/sync/syncDev.ts +74 -66
  75. package/sync/syncMenu.ts +190 -57
  76. package/tests/api-integration-array-number.test.ts +282 -0
  77. package/tests/befly-config-env.test.ts +78 -0
  78. package/tests/cacheHelper.test.ts +135 -104
  79. package/tests/cacheKeys.test.ts +41 -0
  80. package/tests/cipher.test.ts +90 -89
  81. package/tests/dbHelper-advanced.test.ts +140 -134
  82. package/tests/dbHelper-all-array-types.test.ts +316 -0
  83. package/tests/dbHelper-array-serialization.test.ts +258 -0
  84. package/tests/dbHelper-columns.test.ts +56 -55
  85. package/tests/dbHelper-execute.test.ts +45 -44
  86. package/tests/dbHelper-joins.test.ts +124 -119
  87. package/tests/fields-redis-cache.test.ts +29 -27
  88. package/tests/fields-validate.test.ts +38 -38
  89. package/tests/getClientIp.test.ts +54 -0
  90. package/tests/integration.test.ts +69 -67
  91. package/tests/jwt.test.ts +27 -26
  92. package/tests/logger.test.ts +267 -34
  93. package/tests/rateLimit-hook.test.ts +477 -0
  94. package/tests/redisHelper.test.ts +187 -188
  95. package/tests/redisKeys.test.ts +6 -73
  96. package/tests/scanConfig.test.ts +144 -0
  97. package/tests/sqlBuilder-advanced.test.ts +217 -215
  98. package/tests/sqlBuilder.test.ts +92 -91
  99. package/tests/sync-connection.test.ts +29 -29
  100. package/tests/syncDb-apply.test.ts +97 -96
  101. package/tests/syncDb-array-number.test.ts +160 -0
  102. package/tests/syncDb-constants.test.ts +48 -47
  103. package/tests/syncDb-ddl.test.ts +99 -98
  104. package/tests/syncDb-helpers.test.ts +29 -28
  105. package/tests/syncDb-schema.test.ts +61 -60
  106. package/tests/syncDb-types.test.ts +60 -59
  107. package/tests/syncMenu-paths.test.ts +68 -0
  108. package/tests/util.test.ts +42 -41
  109. package/tests/validator-array-number.test.ts +310 -0
  110. package/tests/validator-default.test.ts +373 -0
  111. package/tests/validator.test.ts +271 -266
  112. package/tsconfig.json +4 -5
  113. package/types/api.d.ts +7 -12
  114. package/types/befly.d.ts +60 -13
  115. package/types/cache.d.ts +8 -4
  116. package/types/common.d.ts +17 -9
  117. package/types/context.d.ts +2 -2
  118. package/types/crypto.d.ts +23 -0
  119. package/types/database.d.ts +19 -19
  120. package/types/hook.d.ts +2 -2
  121. package/types/jwt.d.ts +118 -0
  122. package/types/logger.d.ts +30 -0
  123. package/types/plugin.d.ts +4 -4
  124. package/types/redis.d.ts +7 -3
  125. package/types/roleApisCache.ts +23 -0
  126. package/types/sync.d.ts +10 -10
  127. package/types/table.d.ts +50 -9
  128. package/types/validate.d.ts +69 -0
  129. package/utils/addonHelper.ts +90 -0
  130. package/utils/arrayKeysToCamel.ts +18 -0
  131. package/utils/calcPerfTime.ts +13 -0
  132. package/utils/configTypes.ts +3 -0
  133. package/utils/cors.ts +19 -0
  134. package/utils/fieldClear.ts +75 -0
  135. package/utils/genShortId.ts +12 -0
  136. package/utils/getClientIp.ts +45 -0
  137. package/utils/keysToCamel.ts +22 -0
  138. package/utils/keysToSnake.ts +22 -0
  139. package/utils/modules.ts +98 -0
  140. package/utils/pickFields.ts +19 -0
  141. package/utils/process.ts +56 -0
  142. package/utils/regex.ts +225 -0
  143. package/utils/response.ts +115 -0
  144. package/utils/route.ts +23 -0
  145. package/utils/scanConfig.ts +142 -0
  146. package/utils/scanFiles.ts +48 -0
  147. package/.prettierignore +0 -2
  148. package/.prettierrc +0 -12
  149. package/docs/1-/345/237/272/346/234/254/344/273/213/347/273/215.md +0 -35
  150. package/docs/2-/345/210/235/346/255/245/344/275/223/351/252/214.md +0 -64
  151. package/docs/3-/347/254/254/344/270/200/344/270/252/346/216/245/345/217/243.md +0 -46
  152. package/docs/4-/346/223/215/344/275/234/346/225/260/346/215/256/345/272/223.md +0 -172
  153. package/hooks/requestLogger.ts +0 -84
  154. package/types/index.ts +0 -24
  155. package/util.ts +0 -283
@@ -1,9 +1,11 @@
1
- import { describe, test, expect, beforeAll, afterAll, afterEach } from 'bun:test';
2
- import { Logger } from '../lib/logger';
3
- import { existsSync, mkdirSync, rmSync } from 'node:fs';
4
- import { join } from 'node:path';
1
+ import { describe, test, expect, beforeAll, afterAll } from "bun:test";
2
+ import { existsSync, mkdirSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
5
4
 
6
- const testLogDir = join(process.cwd(), 'temp', 'test-logs');
5
+ import { withCtx } from "../lib/asyncContext";
6
+ import { Logger } from "../lib/logger";
7
+
8
+ const testLogDir = join(process.cwd(), "temp", "test-logs");
7
9
 
8
10
  beforeAll(() => {
9
11
  if (!existsSync(testLogDir)) {
@@ -12,7 +14,8 @@ beforeAll(() => {
12
14
  Logger.configure({
13
15
  dir: testLogDir,
14
16
  console: 0,
15
- debug: 1
17
+ debug: 1,
18
+ excludeFields: ["*Secret", "*nick*"]
16
19
  });
17
20
  });
18
21
 
@@ -24,69 +27,299 @@ afterAll(async () => {
24
27
  }
25
28
  });
26
29
 
27
- describe('Logger - 纯字符串消息', () => {
28
- test('info(msg)', () => {
29
- Logger.info('Test info message');
30
+ describe("Logger - 纯字符串消息", () => {
31
+ test("info(msg)", () => {
32
+ Logger.info("Test info message");
30
33
  expect(true).toBe(true);
31
34
  });
32
35
 
33
- test('warn(msg)', () => {
34
- Logger.warn('Test warning');
36
+ test("warn(msg)", () => {
37
+ Logger.warn("Test warning");
35
38
  expect(true).toBe(true);
36
39
  });
37
40
 
38
- test('error(msg)', () => {
39
- Logger.error('Test error');
41
+ test("error(msg)", () => {
42
+ Logger.error("Test error");
40
43
  expect(true).toBe(true);
41
44
  });
42
45
 
43
- test('debug(msg)', () => {
44
- Logger.debug('Test debug');
46
+ test("debug(msg)", () => {
47
+ Logger.debug("Test debug");
45
48
  expect(true).toBe(true);
46
49
  });
47
50
  });
48
51
 
49
- describe('Logger - 对象 + 消息 (pino 原生格式)', () => {
50
- test('info(obj, msg)', () => {
51
- Logger.info({ userId: 1, action: 'login' }, 'User action');
52
+ describe("Logger - 对象 + 消息 (pino 原生格式)", () => {
53
+ test("info(obj, msg)", () => {
54
+ Logger.info({ userId: 1, action: "login" }, "User action");
52
55
  expect(true).toBe(true);
53
56
  });
54
57
 
55
- test('warn(obj, msg)', () => {
56
- Logger.warn({ ip: '127.0.0.1', count: 100 }, 'Rate limit warning');
58
+ test("warn(obj, msg)", () => {
59
+ Logger.warn({ ip: "127.0.0.1", count: 100 }, "Rate limit warning");
57
60
  expect(true).toBe(true);
58
61
  });
59
62
 
60
- test('error(obj, msg)', () => {
61
- const err = new Error('Something went wrong');
62
- Logger.error({ err: err }, 'Request failed');
63
+ test("error(obj, msg)", () => {
64
+ const err = new Error("Something went wrong");
65
+ Logger.error({ err: err }, "Request failed");
63
66
  expect(true).toBe(true);
64
67
  });
65
68
 
66
- test('debug(obj, msg)', () => {
67
- Logger.debug({ key: 'value', nested: { a: 1 } }, 'Debug data');
69
+ test("debug(obj, msg)", () => {
70
+ Logger.debug({ key: "value", nested: { a: 1 } }, "Debug data");
68
71
  expect(true).toBe(true);
69
72
  });
70
73
  });
71
74
 
72
- describe('Logger - 仅对象', () => {
73
- test('info(obj)', () => {
74
- Logger.info({ event: 'startup', port: 3000 });
75
+ describe("Logger - 仅对象", () => {
76
+ test("info(obj)", () => {
77
+ Logger.info({ event: "startup", port: 3000 });
75
78
  expect(true).toBe(true);
76
79
  });
77
80
 
78
- test('warn(obj)', () => {
79
- Logger.warn({ type: 'deprecation', feature: 'oldApi' });
81
+ test("warn(obj)", () => {
82
+ Logger.warn({ type: "deprecation", feature: "oldApi" });
80
83
  expect(true).toBe(true);
81
84
  });
82
85
 
83
- test('error(obj)', () => {
84
- Logger.error({ code: 500, message: 'Internal error' });
86
+ test("error(obj)", () => {
87
+ Logger.error({ code: 500, message: "Internal error" });
85
88
  expect(true).toBe(true);
86
89
  });
87
90
 
88
- test('debug(obj)', () => {
89
- Logger.debug({ query: 'SELECT * FROM users', duration: 15 });
91
+ test("debug(obj)", () => {
92
+ Logger.debug({ query: "SELECT * FROM users", duration: 15 });
90
93
  expect(true).toBe(true);
91
94
  });
92
95
  });
96
+
97
+ describe("Logger - AsyncLocalStorage 注入", () => {
98
+ test("无 store 时不注入", () => {
99
+ const calls: any[] = [];
100
+
101
+ const mock: any = {
102
+ info(...args: any[]) {
103
+ calls.push({ level: "info", args: args });
104
+ },
105
+ warn(...args: any[]) {
106
+ calls.push({ level: "warn", args: args });
107
+ },
108
+ error(...args: any[]) {
109
+ calls.push({ level: "error", args: args });
110
+ },
111
+ debug(...args: any[]) {
112
+ calls.push({ level: "debug", args: args });
113
+ }
114
+ };
115
+
116
+ Logger.setMock(mock);
117
+ Logger.info({ foo: 1 }, "hello");
118
+ Logger.setMock(null);
119
+
120
+ expect(calls.length).toBe(1);
121
+ expect(calls[0].level).toBe("info");
122
+ expect(calls[0].args[0]).toEqual({ foo: 1 });
123
+ });
124
+
125
+ test("纯字符串消息会注入 meta", () => {
126
+ const calls: any[] = [];
127
+
128
+ const mock: any = {
129
+ info(...args: any[]) {
130
+ calls.push({ level: "info", args: args });
131
+ },
132
+ warn(...args: any[]) {
133
+ calls.push({ level: "warn", args: args });
134
+ },
135
+ error(...args: any[]) {
136
+ calls.push({ level: "error", args: args });
137
+ },
138
+ debug(...args: any[]) {
139
+ calls.push({ level: "debug", args: args });
140
+ }
141
+ };
142
+
143
+ Logger.setMock(mock);
144
+ withCtx(
145
+ {
146
+ requestId: "rid_1",
147
+ method: "POST",
148
+ route: "POST/api/test",
149
+ ip: "127.0.0.1",
150
+ now: 123,
151
+ userId: 9,
152
+ roleCode: "admin"
153
+ },
154
+ () => {
155
+ Logger.info("hello");
156
+ }
157
+ );
158
+ Logger.setMock(null);
159
+
160
+ expect(calls.length).toBe(1);
161
+ expect(calls[0].args[0].requestId).toBe("rid_1");
162
+ expect(calls[0].args[0].method).toBe("POST");
163
+ expect(calls[0].args[0].route).toBe("POST/api/test");
164
+ expect(calls[0].args[0].userId).toBe(9);
165
+ expect(typeof calls[0].args[0].durationSinceNowMs).toBe("number");
166
+ expect(calls[0].args[0].durationSinceNowMs).toBeGreaterThanOrEqual(0);
167
+ expect(calls[0].args[1]).toBe("hello");
168
+ });
169
+
170
+ test("对象 + msg:meta 只补齐不覆盖", () => {
171
+ const calls: any[] = [];
172
+
173
+ const mock: any = {
174
+ info(...args: any[]) {
175
+ calls.push({ level: "info", args: args });
176
+ },
177
+ warn(...args: any[]) {
178
+ calls.push({ level: "warn", args: args });
179
+ },
180
+ error(...args: any[]) {
181
+ calls.push({ level: "error", args: args });
182
+ },
183
+ debug(...args: any[]) {
184
+ calls.push({ level: "debug", args: args });
185
+ }
186
+ };
187
+
188
+ Logger.setMock(mock);
189
+ withCtx(
190
+ {
191
+ requestId: "rid_2",
192
+ method: "POST",
193
+ route: "POST/api/test",
194
+ ip: "127.0.0.1",
195
+ now: 456
196
+ },
197
+ () => {
198
+ Logger.info({ requestId: "explicit", foo: 1 }, "m");
199
+ }
200
+ );
201
+ Logger.setMock(null);
202
+
203
+ expect(calls.length).toBe(1);
204
+ expect(calls[0].args[0].requestId).toBe("explicit");
205
+ expect(calls[0].args[0].route).toBe("POST/api/test");
206
+ expect(calls[0].args[0].foo).toBe(1);
207
+ expect(calls[0].args[1]).toBe("m");
208
+ });
209
+
210
+ test("兼容 Logger.error(msg, err)", () => {
211
+ const calls: any[] = [];
212
+
213
+ const mock: any = {
214
+ info(...args: any[]) {
215
+ calls.push({ level: "info", args: args });
216
+ },
217
+ warn(...args: any[]) {
218
+ calls.push({ level: "warn", args: args });
219
+ },
220
+ error(...args: any[]) {
221
+ calls.push({ level: "error", args: args });
222
+ },
223
+ debug(...args: any[]) {
224
+ calls.push({ level: "debug", args: args });
225
+ }
226
+ };
227
+
228
+ Logger.setMock(mock);
229
+ withCtx(
230
+ {
231
+ requestId: "rid_3",
232
+ method: "POST",
233
+ route: "POST/api/test",
234
+ ip: "127.0.0.1",
235
+ now: 789
236
+ },
237
+ () => {
238
+ const err = new Error("boom");
239
+ Logger.error("Redis getObject 错误", err);
240
+ }
241
+ );
242
+ Logger.setMock(null);
243
+
244
+ expect(calls.length).toBe(1);
245
+ expect(calls[0].args[0].requestId).toBe("rid_3");
246
+ expect(calls[0].args[0].err.message).toBe("boom");
247
+ expect(calls[0].args[1]).toBe("Redis getObject 错误");
248
+ });
249
+
250
+ test("对象裁剪:敏感 key 掩码 + 字符串截断 + 数组截断 + 统计字段", () => {
251
+ const calls: any[] = [];
252
+
253
+ const mock: any = {
254
+ info(...args: any[]) {
255
+ calls.push({ level: "info", args: args });
256
+ },
257
+ warn(...args: any[]) {
258
+ calls.push({ level: "warn", args: args });
259
+ },
260
+ error(...args: any[]) {
261
+ calls.push({ level: "error", args: args });
262
+ },
263
+ debug(...args: any[]) {
264
+ calls.push({ level: "debug", args: args });
265
+ }
266
+ };
267
+
268
+ const longStr = "x".repeat(150);
269
+ const longArr: any[] = [];
270
+ for (let i = 0; i < 130; i++) {
271
+ longArr.push({ val: longStr, mySecret: "shouldMask" });
272
+ }
273
+
274
+ Logger.setMock(mock);
275
+ withCtx(
276
+ {
277
+ requestId: "rid_trim",
278
+ method: "POST",
279
+ route: "POST/api/test",
280
+ ip: "127.0.0.1",
281
+ now: 1
282
+ },
283
+ () => {
284
+ Logger.info(
285
+ {
286
+ password: "p".repeat(200),
287
+ mySecret: "s".repeat(200),
288
+ nickname: "n".repeat(200),
289
+ normal: longStr,
290
+ nested: {
291
+ token: "t".repeat(200),
292
+ a: longStr,
293
+ deep: { b: longStr }
294
+ },
295
+ items: longArr,
296
+ okNumber: 123,
297
+ okBool: true
298
+ },
299
+ "trim"
300
+ );
301
+ }
302
+ );
303
+ Logger.setMock(null);
304
+
305
+ expect(calls.length).toBe(1);
306
+ const obj = calls[0].args[0];
307
+ expect(obj.password).toBe("[MASKED]");
308
+ expect(obj.mySecret).toBe("[MASKED]");
309
+ expect(obj.nickname).toBe("[MASKED]");
310
+ expect(obj.normal.length).toBe(100);
311
+ expect(obj.nested.token).toBe("[MASKED]");
312
+ expect(obj.nested.a.length).toBe(100);
313
+ expect(Array.isArray(obj.items)).toBe(true);
314
+ expect(obj.items.length).toBe(100);
315
+ expect(obj.items[0].val.length).toBe(100);
316
+ expect(obj.items[0].mySecret).toBe("[MASKED]");
317
+ expect(obj.okNumber).toBe(123);
318
+ expect(obj.okBool).toBe(true);
319
+ expect(obj.logTrimStats).toBeTruthy();
320
+ expect(obj.logTrimStats.maskedKeys).toBeGreaterThan(0);
321
+ expect(obj.logTrimStats.truncatedStrings).toBeGreaterThan(0);
322
+ expect(obj.logTrimStats.arraysTruncated).toBeGreaterThanOrEqual(1);
323
+ expect(obj.logTrimStats.arrayItemsOmitted).toBeGreaterThanOrEqual(30);
324
+ });
325
+ });