befly 3.18.2 → 3.18.4
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/apis/auth/login.js +1 -1
- package/checks/api.js +1 -1
- package/checks/hook.js +1 -1
- package/checks/menu.js +2 -2
- package/checks/plugin.js +1 -1
- package/hooks/auth.js +2 -2
- package/hooks/parser.js +1 -1
- package/hooks/permission.js +1 -1
- package/hooks/validator.js +1 -1
- package/index.js +64 -116
- package/package.json +2 -2
- package/plugins/cache.js +1 -1
- package/plugins/config.js +1 -1
- package/plugins/email.js +1 -1
- package/plugins/logger.js +1 -1
- package/plugins/mysql.js +1 -1
- package/plugins/redis.js +1 -1
- package/plugins/tool.js +1 -1
- package/sync/api.js +3 -2
- package/utils/scanFiles.js +2 -2
- package/utils/util.js +18 -0
- package/utils/sortModules.js +0 -75
- package/utils/toSessionTtlSeconds.js +0 -14
package/apis/auth/login.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UAParser } from "ua-parser-js";
|
|
2
2
|
|
|
3
3
|
import adminTable from "../../tables/admin.json";
|
|
4
|
-
import { toSessionTtlSeconds } from "../../utils/
|
|
4
|
+
import { toSessionTtlSeconds } from "../../utils/util.js";
|
|
5
5
|
|
|
6
6
|
export default {
|
|
7
7
|
name: "管理员登录",
|
package/checks/api.js
CHANGED
|
@@ -8,7 +8,7 @@ z.config(z.locales.zhCN());
|
|
|
8
8
|
|
|
9
9
|
const noTrimString = z.string().refine(isNoTrimStringAllowEmpty, "不允许首尾空格");
|
|
10
10
|
const lowerCamelRegex = /^_?[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
|
|
11
|
-
const apiPathRegex = /^\/api\/(core
|
|
11
|
+
const apiPathRegex = /^\/api\/(?:core\/[a-z][a-zA-Z0-9]*(?:\/[a-z][a-zA-Z0-9]*)*|(?!app(?:\/|$))[a-z][a-zA-Z0-9]*(?:\/[a-z][a-zA-Z0-9]*)*)$/;
|
|
12
12
|
|
|
13
13
|
const fieldSchema = z
|
|
14
14
|
.object({
|
package/checks/hook.js
CHANGED
package/checks/menu.js
CHANGED
|
@@ -5,7 +5,7 @@ import { formatZodIssues } from "../utils/formatZodIssues.js";
|
|
|
5
5
|
|
|
6
6
|
z.config(z.locales.zhCN());
|
|
7
7
|
|
|
8
|
-
const fullMenuPathRegex = /^\/(?:core(?:\/[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*)*|(?:
|
|
8
|
+
const fullMenuPathRegex = /^\/(?:core(?:\/[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*)*|(?:[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*(?:\/[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*)*)?)$/;
|
|
9
9
|
const childMenuPathRegex = /^\/[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
|
|
10
10
|
|
|
11
11
|
const childMenuSchema = z
|
|
@@ -36,7 +36,7 @@ const menuListSchema = z.array(menuSchema).superRefine((menuList, refineCtx) =>
|
|
|
36
36
|
if (!fullMenuPathRegex.test(childFullPath)) {
|
|
37
37
|
refineCtx.addIssue({
|
|
38
38
|
code: z.ZodIssueCode.custom,
|
|
39
|
-
message: "菜单完整路径必须是 /core/xxx
|
|
39
|
+
message: "菜单完整路径必须是 /core/xxx 或无前缀路径",
|
|
40
40
|
path: [menuIndex, "children", childIndex, "path"]
|
|
41
41
|
});
|
|
42
42
|
}
|
package/checks/plugin.js
CHANGED
package/hooks/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { toSessionTtlSeconds } from "../utils/
|
|
1
|
+
import { toSessionTtlSeconds } from "../utils/util.js";
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
|
-
|
|
4
|
+
order: 1,
|
|
5
5
|
handler: async (befly, ctx) => {
|
|
6
6
|
const authHeader = ctx.headers.get("authorization");
|
|
7
7
|
const ttlSeconds = toSessionTtlSeconds(befly.config?.session?.expireDays);
|
package/hooks/parser.js
CHANGED
|
@@ -13,7 +13,7 @@ const xmlParser = new XMLParser();
|
|
|
13
13
|
* - body: "raw" 时跳过解析,由 handler 自行处理原始请求
|
|
14
14
|
*/
|
|
15
15
|
export default {
|
|
16
|
-
|
|
16
|
+
order: 2,
|
|
17
17
|
handler: async (befly, ctx) => {
|
|
18
18
|
// apiBody=raw 模式:跳过解析,保留原始请求供 handler 自行处理
|
|
19
19
|
// 适用于:微信回调、支付回调、webhook 等需要手动解密/验签的场景
|
package/hooks/permission.js
CHANGED
package/hooks/validator.js
CHANGED
|
@@ -9,7 +9,7 @@ import { snakeCase } from "../utils/util.js";
|
|
|
9
9
|
* 根据 API 定义的 fields 和 required 验证请求参数
|
|
10
10
|
*/
|
|
11
11
|
export default {
|
|
12
|
-
|
|
12
|
+
order: 3,
|
|
13
13
|
handler: async (befly, ctx) => {
|
|
14
14
|
// 仅保留 fields 中声明的字段,并支持 snake_case 入参回退(例如 agent_id -> agentId)
|
|
15
15
|
const rawBody = isPlainObject(ctx.body) ? ctx.body : {};
|
package/index.js
CHANGED
|
@@ -32,7 +32,6 @@ import { calcPerfTime } from "./utils/calcPerfTime.js";
|
|
|
32
32
|
import { scanSources } from "./utils/scanSources.js";
|
|
33
33
|
import { isPrimaryProcess } from "./utils/is.js";
|
|
34
34
|
import { deepMerge } from "./utils/deepMerge.js";
|
|
35
|
-
import { sortModules } from "./utils/sortModules.js";
|
|
36
35
|
|
|
37
36
|
function prefixMenuPaths(menus, prefix) {
|
|
38
37
|
const output = [];
|
|
@@ -95,164 +94,113 @@ async function ensureSyncPrerequisites(ctx) {
|
|
|
95
94
|
}
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
*/
|
|
102
|
-
export class Befly {
|
|
103
|
-
/** 应用上下文 */
|
|
104
|
-
context = {
|
|
105
|
-
env: {},
|
|
106
|
-
config: {}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// 菜单配置
|
|
110
|
-
menus = [];
|
|
111
|
-
|
|
112
|
-
/** 插件列表 */
|
|
113
|
-
plugins = [];
|
|
114
|
-
|
|
115
|
-
/** 钩子列表 */
|
|
116
|
-
hooks = [];
|
|
117
|
-
|
|
118
|
-
/** API 路由映射表 */
|
|
119
|
-
apis = {};
|
|
97
|
+
export async function dbCheck(mysqlConfig) {
|
|
98
|
+
return syncDbCheck(mysqlConfig);
|
|
99
|
+
}
|
|
120
100
|
|
|
121
|
-
|
|
122
|
-
|
|
101
|
+
export async function dbApply(mysqlConfig) {
|
|
102
|
+
return syncDbApply(mysqlConfig);
|
|
103
|
+
}
|
|
123
104
|
|
|
124
|
-
|
|
125
|
-
|
|
105
|
+
export async function createBefly(env = {}, config = {}, menus = []) {
|
|
106
|
+
const mergedConfig = deepMerge(beflyConfig, config);
|
|
107
|
+
const mergedMenus = deepMerge(prefixMenuPaths(beflyMenus, "core"), menus);
|
|
126
108
|
|
|
127
|
-
|
|
128
|
-
|
|
109
|
+
const configHasError = await checkConfig(mergedConfig);
|
|
110
|
+
const { apis, tables, plugins, hooks } = await scanSources();
|
|
129
111
|
|
|
130
|
-
|
|
131
|
-
|
|
112
|
+
const apiHasError = await checkApi(apis);
|
|
113
|
+
const tableHasError = await checkTable(tables);
|
|
114
|
+
const pluginHasError = await checkPlugin(plugins);
|
|
115
|
+
const hookHasError = await checkHook(hooks);
|
|
116
|
+
const menuHasError = await checkMenu(mergedMenus);
|
|
132
117
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
instance.createdByFactory = true;
|
|
142
|
-
|
|
143
|
-
const configHasError = await checkConfig(instance.context.config);
|
|
144
|
-
const { apis, tables, plugins, hooks } = await scanSources();
|
|
145
|
-
|
|
146
|
-
const apiHasError = await checkApi(apis);
|
|
147
|
-
const tableHasError = await checkTable(tables);
|
|
148
|
-
const pluginHasError = await checkPlugin(plugins);
|
|
149
|
-
const hookHasError = await checkHook(hooks);
|
|
150
|
-
const menuHasError = await checkMenu(instance.menus);
|
|
151
|
-
|
|
152
|
-
if (configHasError || apiHasError || tableHasError || pluginHasError || hookHasError || menuHasError) {
|
|
153
|
-
throw new Error("检查失败:存在配置/结构问题", {
|
|
154
|
-
cause: null,
|
|
155
|
-
code: "policy",
|
|
156
|
-
subsystem: "checks",
|
|
157
|
-
operation: "checkAll"
|
|
158
|
-
});
|
|
159
|
-
}
|
|
118
|
+
if (configHasError || apiHasError || tableHasError || pluginHasError || hookHasError || menuHasError) {
|
|
119
|
+
throw new Error("检查失败:存在配置/结构问题", {
|
|
120
|
+
cause: null,
|
|
121
|
+
code: "policy",
|
|
122
|
+
subsystem: "checks",
|
|
123
|
+
operation: "checkAll"
|
|
124
|
+
});
|
|
125
|
+
}
|
|
160
126
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
127
|
+
return new Befly({
|
|
128
|
+
env: env,
|
|
129
|
+
config: mergedConfig,
|
|
130
|
+
menus: mergedMenus,
|
|
131
|
+
apis: apis,
|
|
132
|
+
hooks: hooks,
|
|
133
|
+
plugins: plugins,
|
|
134
|
+
createdByFactory: true
|
|
135
|
+
});
|
|
136
|
+
}
|
|
164
137
|
|
|
165
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Befly 框架核心类
|
|
140
|
+
* 职责:管理应用上下文和生命周期
|
|
141
|
+
*/
|
|
142
|
+
export class Befly {
|
|
143
|
+
constructor(init = {}) {
|
|
144
|
+
this.context = {
|
|
145
|
+
env: init.env || {},
|
|
146
|
+
config: init.config || {}
|
|
147
|
+
};
|
|
148
|
+
this.menus = Array.isArray(init.menus) ? init.menus : [];
|
|
149
|
+
this.hooks = Array.isArray(init.hooks) ? init.hooks : [];
|
|
150
|
+
this.apis = Array.isArray(init.apis) ? init.apis : [];
|
|
151
|
+
this.plugins = Array.isArray(init.plugins) ? init.plugins : [];
|
|
152
|
+
this.createdByFactory = init.createdByFactory === true;
|
|
166
153
|
}
|
|
167
154
|
|
|
168
|
-
ensureCreatedByFactory(
|
|
155
|
+
ensureCreatedByFactory() {
|
|
169
156
|
if (this.createdByFactory) {
|
|
170
157
|
return;
|
|
171
158
|
}
|
|
172
159
|
|
|
173
|
-
throw new Error(
|
|
160
|
+
throw new Error("请使用 createBefly(...) 创建实例后再调用 start", {
|
|
174
161
|
cause: null,
|
|
175
162
|
code: "policy",
|
|
176
|
-
subsystem:
|
|
163
|
+
subsystem: "start",
|
|
177
164
|
operation: "factoryGuard"
|
|
178
165
|
});
|
|
179
166
|
}
|
|
180
167
|
|
|
181
|
-
async dbCheck() {
|
|
182
|
-
this.ensureCreatedByFactory("dbCheck");
|
|
183
|
-
|
|
184
|
-
return syncDbCheck(this.context.config.mysql);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async dbApply() {
|
|
188
|
-
this.ensureCreatedByFactory("dbApply");
|
|
189
|
-
|
|
190
|
-
return syncDbApply(this.context.config.mysql);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
168
|
/**
|
|
194
169
|
* 启动完整的生命周期流程
|
|
195
170
|
* @returns HTTP 服务器实例
|
|
196
171
|
*/
|
|
197
172
|
async start() {
|
|
198
173
|
try {
|
|
199
|
-
this.ensureCreatedByFactory(
|
|
174
|
+
this.ensureCreatedByFactory();
|
|
200
175
|
|
|
201
176
|
const serverStartTime = Bun.nanoseconds();
|
|
202
|
-
if (!Array.isArray(this.scanApis) || !Array.isArray(this.scanPlugins) || !Array.isArray(this.scanHooks)) {
|
|
203
|
-
throw new Error("请使用 Befly.create(...) 完成预检后再调用 start", {
|
|
204
|
-
cause: null,
|
|
205
|
-
code: "policy",
|
|
206
|
-
subsystem: "start",
|
|
207
|
-
operation: "preflightGuard"
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
177
|
|
|
211
|
-
//
|
|
178
|
+
// 启动期建立基础连接(SQL + Redis)
|
|
212
179
|
// 说明:连接职责收敛到启动期单点;插件只消费已连接实例(Connect.getSql/getRedis)。
|
|
213
180
|
await Connect.connectRedis(this.context.config.redis);
|
|
214
181
|
await Connect.connectMysql(this.context.config.mysql);
|
|
215
182
|
|
|
216
|
-
//
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
for (const item of sortedPlugins) {
|
|
220
|
-
const pluginInstance = await item.handler(this.context);
|
|
221
|
-
this.context[item.fileName] = pluginInstance;
|
|
222
|
-
}
|
|
223
|
-
} else {
|
|
224
|
-
throw new Error("插件依赖关系错误", {
|
|
225
|
-
cause: null,
|
|
226
|
-
code: "policy"
|
|
227
|
-
});
|
|
183
|
+
// 加载插件
|
|
184
|
+
for (const item of this.plugins.sort((a, b) => a.order - b.order)) {
|
|
185
|
+
this.context[item.fileName] = await item.handler(this.context);
|
|
228
186
|
}
|
|
229
187
|
await ensureSyncPrerequisites(this.context);
|
|
230
188
|
|
|
231
|
-
//
|
|
189
|
+
// 自动同步(PM2 cluster:主进程执行,其它进程等待同步完成)
|
|
232
190
|
if (isPrimaryProcess(this.context.env)) {
|
|
233
|
-
await syncApi(this.context, this.
|
|
191
|
+
await syncApi(this.context, this.apis);
|
|
234
192
|
await syncMenu(this.context, this.menus);
|
|
235
193
|
await syncDev(this.context);
|
|
236
194
|
await syncCache(this.context);
|
|
237
195
|
}
|
|
238
196
|
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
if (sortedHooks) {
|
|
242
|
-
this.hooks = sortedHooks;
|
|
243
|
-
} else {
|
|
244
|
-
throw new Error("钩子依赖关系错误", {
|
|
245
|
-
cause: null,
|
|
246
|
-
code: "policy"
|
|
247
|
-
});
|
|
248
|
-
}
|
|
197
|
+
// 加载钩子
|
|
198
|
+
this.hooks = this.hooks.sort((a, b) => a.order - b.order);
|
|
249
199
|
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
this.apis[api.apiPath] = api;
|
|
253
|
-
}
|
|
200
|
+
// 加载所有 API
|
|
201
|
+
this.apis = Object.fromEntries(this.apis.map((api) => [api.apiPath, api]));
|
|
254
202
|
|
|
255
|
-
//
|
|
203
|
+
// 启动 HTTP服务器
|
|
256
204
|
const apiFetch = apiHandler(this.apis, this.hooks, this.context);
|
|
257
205
|
const staticFetch = staticHandler(this.context.config.cors);
|
|
258
206
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.18.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.18.4",
|
|
4
|
+
"gitHead": "403482610311798cc566fb6b550c069740fa413c",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|
package/plugins/cache.js
CHANGED
package/plugins/config.js
CHANGED
package/plugins/email.js
CHANGED
package/plugins/logger.js
CHANGED
package/plugins/mysql.js
CHANGED
package/plugins/redis.js
CHANGED
package/plugins/tool.js
CHANGED
package/sync/api.js
CHANGED
|
@@ -7,8 +7,9 @@ const getApiParentPath = (apiPath) => {
|
|
|
7
7
|
.filter((s) => s.length > 0);
|
|
8
8
|
|
|
9
9
|
// segments 示例:
|
|
10
|
-
// - /api/
|
|
11
|
-
|
|
10
|
+
// - /api/core/test/hi -> ["api","core","test","hi"]
|
|
11
|
+
// - /api/test/hi -> ["api","test","hi"]
|
|
12
|
+
const take = segments[1] === "core" ? 3 : 2;
|
|
12
13
|
const parentSegments = segments.slice(0, Math.min(take, segments.length));
|
|
13
14
|
if (parentSegments.length === 0) return "";
|
|
14
15
|
return `/${parentSegments.join("/")}`;
|
package/utils/scanFiles.js
CHANGED
|
@@ -13,7 +13,7 @@ import { isPlainObject } from "./is.js";
|
|
|
13
13
|
// fileName: "auth",
|
|
14
14
|
// apiPath: "/api/core/auth",
|
|
15
15
|
// name: "auth",
|
|
16
|
-
//
|
|
16
|
+
// order: 1,
|
|
17
17
|
// handler: [AsyncFunction: handler],
|
|
18
18
|
// }
|
|
19
19
|
// 🔥[ plugins ]-112 {
|
|
@@ -24,7 +24,7 @@ import { isPlainObject } from "./is.js";
|
|
|
24
24
|
// fileName: "cache",
|
|
25
25
|
// apiPath: "/api/core/cache",
|
|
26
26
|
// name: "cache",
|
|
27
|
-
//
|
|
27
|
+
// order: 1,
|
|
28
28
|
// handler: [AsyncFunction: handler],
|
|
29
29
|
// }
|
|
30
30
|
// 🔥[ tables ]-112 {
|
package/utils/util.js
CHANGED
|
@@ -357,6 +357,24 @@ export function keyBy(items, getKey) {
|
|
|
357
357
|
return out;
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
/**
|
|
361
|
+
* 将 session 有效天数转换为秒。
|
|
362
|
+
*/
|
|
363
|
+
export function toSessionTtlSeconds(ttlDays) {
|
|
364
|
+
if (typeof ttlDays === "number" && Number.isFinite(ttlDays) && ttlDays > 0) {
|
|
365
|
+
return Math.floor(ttlDays * 24 * 60 * 60);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (typeof ttlDays === "string") {
|
|
369
|
+
const parsed = Number(ttlDays.trim());
|
|
370
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
371
|
+
return Math.floor(parsed * 24 * 60 * 60);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return 7 * 24 * 60 * 60;
|
|
376
|
+
}
|
|
377
|
+
|
|
360
378
|
/**
|
|
361
379
|
* 生成短 ID
|
|
362
380
|
* 由时间戳(base36)+ 随机字符组成,约 13 位
|
package/utils/sortModules.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { Logger } from "../lib/logger.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 按 deps 拓扑排序 scanSources 扫描得到的插件/钩子。
|
|
5
|
-
*
|
|
6
|
-
* 说明:
|
|
7
|
-
* - 输入为 scanSources/scanFiles 的条目数组:每个条目包含 fileName 与 deps。
|
|
8
|
-
* - deps 里的字符串会与 fileName 匹配。
|
|
9
|
-
* - 若出现:重复 name、缺失依赖、循环依赖,则返回 false。
|
|
10
|
-
*/
|
|
11
|
-
export function sortModules(items) {
|
|
12
|
-
const result = [];
|
|
13
|
-
const visited = new Set();
|
|
14
|
-
const visiting = new Set();
|
|
15
|
-
|
|
16
|
-
const nameToItem = {};
|
|
17
|
-
let isPass = true;
|
|
18
|
-
|
|
19
|
-
// 1) 建表 + 重名检查
|
|
20
|
-
for (const item of items) {
|
|
21
|
-
if (nameToItem[item.fileName]) {
|
|
22
|
-
Logger.error("模块 名称重复,无法根据 deps 唯一定位", null, {
|
|
23
|
-
name: item.fileName,
|
|
24
|
-
first: nameToItem[item.fileName],
|
|
25
|
-
second: item
|
|
26
|
-
});
|
|
27
|
-
isPass = false;
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
nameToItem[item.fileName] = item;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (!isPass) return false;
|
|
35
|
-
|
|
36
|
-
// 2) 依赖存在性检查
|
|
37
|
-
for (const item of items) {
|
|
38
|
-
for (const dep of item.deps) {
|
|
39
|
-
if (!nameToItem[dep]) {
|
|
40
|
-
Logger.error("模块 依赖未找到", null, { module: item.fileName, dependency: dep });
|
|
41
|
-
isPass = false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!isPass) return false;
|
|
47
|
-
|
|
48
|
-
// 3) 拓扑排序(DFS)
|
|
49
|
-
const visit = (name) => {
|
|
50
|
-
if (visited.has(name)) return;
|
|
51
|
-
if (visiting.has(name)) {
|
|
52
|
-
Logger.error("模块 循环依赖", null, { module: name });
|
|
53
|
-
isPass = false;
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const item = nameToItem[name];
|
|
58
|
-
if (!item) return;
|
|
59
|
-
|
|
60
|
-
visiting.add(name);
|
|
61
|
-
for (const dep of item.deps) {
|
|
62
|
-
visit(dep);
|
|
63
|
-
}
|
|
64
|
-
visiting.delete(name);
|
|
65
|
-
|
|
66
|
-
visited.add(name);
|
|
67
|
-
result.push(item);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
for (const item of items) {
|
|
71
|
-
visit(item.fileName);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return isPass ? result : false;
|
|
75
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export function toSessionTtlSeconds(ttlDays) {
|
|
2
|
-
if (typeof ttlDays === "number" && Number.isFinite(ttlDays) && ttlDays > 0) {
|
|
3
|
-
return Math.floor(ttlDays * 24 * 60 * 60);
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
if (typeof ttlDays === "string") {
|
|
7
|
-
const parsed = Number(ttlDays.trim());
|
|
8
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
9
|
-
return Math.floor(parsed * 24 * 60 * 60);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return 7 * 24 * 60 * 60;
|
|
14
|
-
}
|