@sfmc-bds/db-server 0.2.0-beta.5 → 0.2.0-beta.6
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/LICENSE +663 -0
- package/dist/domain/bridge.d.ts +57 -23
- package/dist/domain/bridge.d.ts.map +1 -1
- package/dist/domain/bridge.js +77 -6
- package/dist/domain/bridge.js.map +2 -2
- package/dist/domain/bridge.test.d.ts +2 -0
- package/dist/domain/bridge.test.d.ts.map +1 -0
- package/dist/domain/bridge.test.js +87 -0
- package/dist/domain/bridge.test.js.map +7 -0
- package/dist/domain/economy.d.ts +38 -7
- package/dist/domain/economy.d.ts.map +1 -1
- package/dist/domain/economy.js.map +2 -2
- package/dist/domain/messages-bridge-filter.test.d.ts +5 -0
- package/dist/domain/messages-bridge-filter.test.d.ts.map +1 -0
- package/dist/domain/messages-bridge-filter.test.js +20 -0
- package/dist/domain/messages-bridge-filter.test.js.map +7 -0
- package/dist/domain/qq-bind-status.test.d.ts +2 -0
- package/dist/domain/qq-bind-status.test.d.ts.map +1 -0
- package/dist/domain/qq-bind-status.test.js +211 -0
- package/dist/domain/qq-bind-status.test.js.map +7 -0
- package/dist/domain/qq-events.d.ts +84 -0
- package/dist/domain/qq-events.d.ts.map +1 -0
- package/dist/domain/qq-events.js +193 -0
- package/dist/domain/qq-events.js.map +7 -0
- package/dist/domain/qq-events.test.d.ts +5 -0
- package/dist/domain/qq-events.test.d.ts.map +1 -0
- package/dist/domain/qq-events.test.js +170 -0
- package/dist/domain/qq-events.test.js.map +7 -0
- package/dist/domain/qq-join.test.d.ts +2 -0
- package/dist/domain/qq-join.test.d.ts.map +1 -0
- package/dist/domain/qq-join.test.js +353 -0
- package/dist/domain/qq-join.test.js.map +7 -0
- package/dist/domain/schema.d.ts +17 -19
- package/dist/domain/schema.d.ts.map +1 -1
- package/dist/domain/schema.js +69 -0
- package/dist/domain/schema.js.map +2 -2
- package/dist/domain/system-status.d.ts +75 -0
- package/dist/domain/system-status.d.ts.map +1 -0
- package/dist/domain/system-status.js +181 -0
- package/dist/domain/system-status.js.map +7 -0
- package/dist/domain/system-status.test.d.ts +5 -0
- package/dist/domain/system-status.test.d.ts.map +1 -0
- package/dist/domain/system-status.test.js +19 -0
- package/dist/domain/system-status.test.js.map +7 -0
- package/dist/domain/transaction.d.ts +5 -4
- package/dist/domain/transaction.d.ts.map +1 -1
- package/dist/domain/transaction.js.map +2 -2
- package/dist/env.d.ts +7 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +55 -3
- package/dist/env.js.map +2 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +105 -19
- package/dist/index.js.map +2 -2
- package/dist/lib/idempotency-store.d.ts +17 -11
- package/dist/lib/idempotency-store.d.ts.map +1 -1
- package/dist/lib/idempotency-store.js.map +2 -2
- package/dist/lib/idempotency.d.ts +3 -4
- package/dist/lib/idempotency.d.ts.map +1 -1
- package/dist/lib/idempotency.js.map +2 -2
- package/dist/lib/module-state.d.ts +38 -8
- package/dist/lib/module-state.d.ts.map +1 -1
- package/dist/lib/module-state.js.map +2 -2
- package/dist/lib/order-by.d.ts +8 -6
- package/dist/lib/order-by.d.ts.map +1 -1
- package/dist/lib/order-by.js.map +2 -2
- package/dist/lib/sql-helpers.d.ts +25 -13
- package/dist/lib/sql-helpers.d.ts.map +1 -1
- package/dist/lib/sql-helpers.js.map +2 -2
- package/dist/lib/sqlite.d.ts +14 -5
- package/dist/lib/sqlite.d.ts.map +1 -1
- package/dist/lib/sqlite.js.map +2 -2
- package/dist/manifest-loader.d.ts +5 -2
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +16 -6
- package/dist/manifest-loader.js.map +2 -2
- package/dist/manifest.d.ts +1 -2
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +13 -8
- package/dist/manifest.js.map +2 -2
- package/dist/project-root.d.ts +1 -1
- package/dist/project-root.d.ts.map +1 -1
- package/dist/project-root.js +2 -2
- package/dist/project-root.js.map +2 -2
- package/dist/routes/_shared.d.ts +14 -5
- package/dist/routes/_shared.d.ts.map +1 -1
- package/dist/routes/_shared.js.map +2 -2
- package/dist/routes/config.d.ts +8 -11
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +17 -23
- package/dist/routes/config.js.map +2 -2
- package/dist/routes/db-routes.d.ts +6 -0
- package/dist/routes/db-routes.d.ts.map +1 -1
- package/dist/routes/db-routes.js.map +2 -2
- package/dist/routes/messages.d.ts +6 -2
- package/dist/routes/messages.d.ts.map +1 -1
- package/dist/routes/messages.js +17 -3
- package/dist/routes/messages.js.map +2 -2
- package/dist/routes/module-config-routes.d.ts +13 -9
- package/dist/routes/module-config-routes.d.ts.map +1 -1
- package/dist/routes/module-config-routes.js.map +2 -2
- package/dist/routes/modules.d.ts +6 -0
- package/dist/routes/modules.d.ts.map +1 -1
- package/dist/routes/modules.js.map +2 -2
- package/dist/routes/qq-bind.d.ts +24 -0
- package/dist/routes/qq-bind.d.ts.map +1 -0
- package/dist/routes/qq-bind.js +179 -0
- package/dist/routes/qq-bind.js.map +7 -0
- package/dist/routes/qq-events.d.ts +20 -0
- package/dist/routes/qq-events.d.ts.map +1 -0
- package/dist/routes/qq-events.js +39 -0
- package/dist/routes/qq-events.js.map +7 -0
- package/dist/routes/qq-join.d.ts +46 -0
- package/dist/routes/qq-join.d.ts.map +1 -0
- package/dist/routes/qq-join.js +277 -0
- package/dist/routes/qq-join.js.map +7 -0
- package/dist/routes/service-routes.d.ts +6 -0
- package/dist/routes/service-routes.d.ts.map +1 -1
- package/dist/routes/service-routes.js.map +2 -2
- package/dist/routes/status.d.ts +19 -0
- package/dist/routes/status.d.ts.map +1 -0
- package/dist/routes/status.js +74 -0
- package/dist/routes/status.js.map +7 -0
- package/dist/schema-registry.d.ts +16 -15
- package/dist/schema-registry.d.ts.map +1 -1
- package/dist/schema-registry.js +7 -1
- package/dist/schema-registry.js.map +2 -2
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js.map +2 -2
- package/dist/service-registry.d.ts +39 -9
- package/dist/service-registry.d.ts.map +1 -1
- package/dist/service-registry.js +30 -2
- package/dist/service-registry.js.map +2 -2
- package/dist/services/builtin-handlers.d.ts +27 -7
- package/dist/services/builtin-handlers.d.ts.map +1 -1
- package/dist/services/builtin-handlers.js.map +2 -2
- package/dist/tx-runner.d.ts +29 -3
- package/dist/tx-runner.d.ts.map +1 -1
- package/dist/tx-runner.js +29 -3
- package/dist/tx-runner.js.map +2 -2
- package/package.json +17 -15
|
@@ -1,15 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import type { ModuleLock, ModuleRuntimeState } from "@sfmc-bds/sdk/contracts";
|
|
2
|
+
type ModuleState = ModuleRuntimeState;
|
|
3
|
+
/**
|
|
4
|
+
* 读取 `module-lock.json` 模块启停锁文件;若文件不存在则初始化空骨架文件并自动落盘。
|
|
5
|
+
*
|
|
6
|
+
* @param filePath 锁文件绝对路径。
|
|
7
|
+
* @returns 模块锁数据结构。
|
|
8
|
+
*/
|
|
9
9
|
export declare function loadModuleLock(filePath: string): ModuleLock;
|
|
10
|
+
/**
|
|
11
|
+
* 持久化写入 `module-lock.json` 模块锁文件。
|
|
12
|
+
*
|
|
13
|
+
* @param filePath 目标文件绝对路径。
|
|
14
|
+
* @param lock 待持久化的模块锁数据。
|
|
15
|
+
*/
|
|
10
16
|
export declare function saveModuleLock(filePath: string, lock: ModuleLock): void;
|
|
17
|
+
/**
|
|
18
|
+
* 获取指定模块的启停状态信息。
|
|
19
|
+
*
|
|
20
|
+
* @param lock 模块锁数据。
|
|
21
|
+
* @param id 模块唯一标识符。
|
|
22
|
+
* @param defaults 回退默认状态。
|
|
23
|
+
* @returns 模块状态对象。
|
|
24
|
+
*/
|
|
11
25
|
export declare function getModuleState(lock: ModuleLock, id: string, defaults?: ModuleState): ModuleState;
|
|
26
|
+
/**
|
|
27
|
+
* 判断指定模块当前是否处于启用状态。
|
|
28
|
+
*
|
|
29
|
+
* @param lock 模块锁数据。
|
|
30
|
+
* @param id 模块唯一标识符。
|
|
31
|
+
* @param defaultValue 缺省默认启用值(默认为 `false`)。
|
|
32
|
+
* @returns 是否启用。
|
|
33
|
+
*/
|
|
12
34
|
export declare function isEnabled(lock: ModuleLock, id: string, defaultValue?: boolean): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* 更新指定模块的状态信息并刷新修改时间戳(`updatedAt`)。
|
|
37
|
+
*
|
|
38
|
+
* @param lock 模块锁数据对象。
|
|
39
|
+
* @param id 模块唯一标识符。
|
|
40
|
+
* @param patch 状态增量补丁。
|
|
41
|
+
* @returns 更新后的模块状态对象。
|
|
42
|
+
*/
|
|
13
43
|
export declare function updateModuleState(lock: ModuleLock, id: string, patch: Partial<Omit<ModuleState, "updatedAt">>): ModuleState;
|
|
14
44
|
export {};
|
|
15
45
|
//# sourceMappingURL=module-state.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-state.d.ts","sourceRoot":"","sources":["../../src/lib/module-state.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module-state.d.ts","sourceRoot":"","sources":["../../src/lib/module-state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE9E,KAAK,WAAW,GAAG,kBAAkB,CAAC;AAEtC;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAS3D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAEvE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,GAAE,WAAgB,GAAG,WAAW,CAGpG;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,GAAE,OAAe,GAAG,OAAO,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,GAC7C,WAAW,CASb"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/module-state.ts"],
|
|
4
|
-
"sourcesContent": ["import { readJson, writeJson } from \"@sfmc-bds/sdk/node/config\";\
|
|
5
|
-
"mappings": "AAAA,SAAS,UAAU,iBAAiB;
|
|
4
|
+
"sourcesContent": ["import { readJson, writeJson } from \"@sfmc-bds/sdk/node/config\";\nimport type { ModuleLock, ModuleRuntimeState } from \"@sfmc-bds/sdk/contracts\";\n\ntype ModuleState = ModuleRuntimeState;\n\n/**\n * \u8BFB\u53D6 `module-lock.json` \u6A21\u5757\u542F\u505C\u9501\u6587\u4EF6\uFF1B\u82E5\u6587\u4EF6\u4E0D\u5B58\u5728\u5219\u521D\u59CB\u5316\u7A7A\u9AA8\u67B6\u6587\u4EF6\u5E76\u81EA\u52A8\u843D\u76D8\u3002\n *\n * @param filePath \u9501\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84\u3002\n * @returns \u6A21\u5757\u9501\u6570\u636E\u7ED3\u6784\u3002\n */\nexport function loadModuleLock(filePath: string): ModuleLock {\n const data = readJson<ModuleLock>(filePath);\n const modules = data?.modules && typeof data.modules === \"object\" ? data.modules : {};\n const lock: ModuleLock = { version: 1, modules };\n // \u7F3A\u6587\u4EF6\u65F6\u843D\u76D8\u7A7A\u9AA8\u67B6\uFF0C\u4FBF\u4E8E\u540E\u7EED enable/disable\uFF08\u4E0E configs \u540C\u5C5E\u672C\u5730\u72B6\u6001\uFF09\n if (!data) {\n saveModuleLock(filePath, lock);\n }\n return lock;\n}\n\n/**\n * \u6301\u4E45\u5316\u5199\u5165 `module-lock.json` \u6A21\u5757\u9501\u6587\u4EF6\u3002\n *\n * @param filePath \u76EE\u6807\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84\u3002\n * @param lock \u5F85\u6301\u4E45\u5316\u7684\u6A21\u5757\u9501\u6570\u636E\u3002\n */\nexport function saveModuleLock(filePath: string, lock: ModuleLock): void {\n writeJson(filePath, lock);\n}\n\n/**\n * \u83B7\u53D6\u6307\u5B9A\u6A21\u5757\u7684\u542F\u505C\u72B6\u6001\u4FE1\u606F\u3002\n *\n * @param lock \u6A21\u5757\u9501\u6570\u636E\u3002\n * @param id \u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param defaults \u56DE\u9000\u9ED8\u8BA4\u72B6\u6001\u3002\n * @returns \u6A21\u5757\u72B6\u6001\u5BF9\u8C61\u3002\n */\nexport function getModuleState(lock: ModuleLock, id: string, defaults: ModuleState = {}): ModuleState {\n const state = lock.modules[id];\n return state && typeof state === \"object\" ? state : defaults;\n}\n\n/**\n * \u5224\u65AD\u6307\u5B9A\u6A21\u5757\u5F53\u524D\u662F\u5426\u5904\u4E8E\u542F\u7528\u72B6\u6001\u3002\n *\n * @param lock \u6A21\u5757\u9501\u6570\u636E\u3002\n * @param id \u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param defaultValue \u7F3A\u7701\u9ED8\u8BA4\u542F\u7528\u503C\uFF08\u9ED8\u8BA4\u4E3A `false`\uFF09\u3002\n * @returns \u662F\u5426\u542F\u7528\u3002\n */\nexport function isEnabled(lock: ModuleLock, id: string, defaultValue: boolean = false): boolean {\n return getModuleState(lock, id).enabled ?? defaultValue;\n}\n\n/**\n * \u66F4\u65B0\u6307\u5B9A\u6A21\u5757\u7684\u72B6\u6001\u4FE1\u606F\u5E76\u5237\u65B0\u4FEE\u6539\u65F6\u95F4\u6233\uFF08`updatedAt`\uFF09\u3002\n *\n * @param lock \u6A21\u5757\u9501\u6570\u636E\u5BF9\u8C61\u3002\n * @param id \u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param patch \u72B6\u6001\u589E\u91CF\u8865\u4E01\u3002\n * @returns \u66F4\u65B0\u540E\u7684\u6A21\u5757\u72B6\u6001\u5BF9\u8C61\u3002\n */\nexport function updateModuleState(\n lock: ModuleLock,\n id: string,\n patch: Partial<Omit<ModuleState, \"updatedAt\">>\n): ModuleState {\n const now = Date.now();\n const previous = getModuleState(lock, id);\n lock.modules[id] = {\n ...previous,\n ...patch,\n updatedAt: now,\n };\n return lock.modules[id];\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,UAAU,iBAAiB;AAW7B,SAAS,eAAe,UAA8B;AAC3D,QAAM,OAAO,SAAqB,QAAQ;AAC1C,QAAM,UAAU,MAAM,WAAW,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,CAAC;AACpF,QAAM,OAAmB,EAAE,SAAS,GAAG,QAAQ;AAE/C,MAAI,CAAC,MAAM;AACT,mBAAe,UAAU,IAAI;AAAA,EAC/B;AACA,SAAO;AACT;AAQO,SAAS,eAAe,UAAkB,MAAwB;AACvE,YAAU,UAAU,IAAI;AAC1B;AAUO,SAAS,eAAe,MAAkB,IAAY,WAAwB,CAAC,GAAgB;AACpG,QAAM,QAAQ,KAAK,QAAQ,EAAE;AAC7B,SAAO,SAAS,OAAO,UAAU,WAAW,QAAQ;AACtD;AAUO,SAAS,UAAU,MAAkB,IAAY,eAAwB,OAAgB;AAC9F,SAAO,eAAe,MAAM,EAAE,EAAE,WAAW;AAC7C;AAUO,SAAS,kBACd,MACA,IACA,OACa;AACb,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,WAAW,eAAe,MAAM,EAAE;AACxC,OAAK,QAAQ,EAAE,IAAI;AAAA,IACjB,GAAG;AAAA,IACH,GAAG;AAAA,IACH,WAAW;AAAA,EACb;AACA,SAAO,KAAK,QAAQ,EAAE;AACxB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/order-by.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* order-by.ts —
|
|
2
|
+
* order-by.ts — 数据库查询排序项(QueryOptions.orderBy)规范化工具
|
|
3
3
|
*
|
|
4
|
-
* SDK
|
|
5
|
-
*
|
|
6
|
-
* `ORDER BY "undefined"` / `orderBy bad column` 静默炸。
|
|
4
|
+
* 兼容处理 SDK 的标准形态 `{ field, dir? }` 与服务端的 `{ col, dir? }` 格式,
|
|
5
|
+
* 统一转化为标准化的 `{ col, dir }[]` 结构,防止由于字段名不匹配导致 SQL 拼出 `ORDER BY "undefined"` 错误。
|
|
7
6
|
*/
|
|
8
7
|
export type NormalizedOrder = {
|
|
9
8
|
col: string;
|
|
10
9
|
dir: "asc" | "desc";
|
|
11
10
|
};
|
|
12
11
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* 将多形态的 orderBy 配置(单项或数组、`field` 或 `col`)统一规范化为 `{ col, dir }[]`。
|
|
13
|
+
*
|
|
14
|
+
* @param orderBy 原始排序配置项。
|
|
15
|
+
* @returns 规范化的排序描述数组。
|
|
16
|
+
* @throws 当排序项类型非法或缺少列名时抛出异常。
|
|
15
17
|
*/
|
|
16
18
|
export declare function normalizeOrderBy(orderBy: unknown): NormalizedOrder[];
|
|
17
19
|
//# sourceMappingURL=order-by.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"order-by.d.ts","sourceRoot":"","sources":["../../src/lib/order-by.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"order-by.d.ts","sourceRoot":"","sources":["../../src/lib/order-by.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,eAAe,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;CAAE,CAAC;AAEnE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CAoBpE"}
|
package/dist/lib/order-by.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/order-by.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * order-by.ts \u2014 \u89C4\u8303\u5316
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * order-by.ts \u2014 \u6570\u636E\u5E93\u67E5\u8BE2\u6392\u5E8F\u9879\uFF08QueryOptions.orderBy\uFF09\u89C4\u8303\u5316\u5DE5\u5177\n *\n * \u517C\u5BB9\u5904\u7406 SDK \u7684\u6807\u51C6\u5F62\u6001 `{ field, dir? }` \u4E0E\u670D\u52A1\u7AEF\u7684 `{ col, dir? }` \u683C\u5F0F\uFF0C\n * \u7EDF\u4E00\u8F6C\u5316\u4E3A\u6807\u51C6\u5316\u7684 `{ col, dir }[]` \u7ED3\u6784\uFF0C\u9632\u6B62\u7531\u4E8E\u5B57\u6BB5\u540D\u4E0D\u5339\u914D\u5BFC\u81F4 SQL \u62FC\u51FA `ORDER BY \"undefined\"` \u9519\u8BEF\u3002\n */\n\nexport type NormalizedOrder = { col: string; dir: \"asc\" | \"desc\" };\n\n/**\n * \u5C06\u591A\u5F62\u6001\u7684 orderBy \u914D\u7F6E\uFF08\u5355\u9879\u6216\u6570\u7EC4\u3001`field` \u6216 `col`\uFF09\u7EDF\u4E00\u89C4\u8303\u5316\u4E3A `{ col, dir }[]`\u3002\n *\n * @param orderBy \u539F\u59CB\u6392\u5E8F\u914D\u7F6E\u9879\u3002\n * @returns \u89C4\u8303\u5316\u7684\u6392\u5E8F\u63CF\u8FF0\u6570\u7EC4\u3002\n * @throws \u5F53\u6392\u5E8F\u9879\u7C7B\u578B\u975E\u6CD5\u6216\u7F3A\u5C11\u5217\u540D\u65F6\u629B\u51FA\u5F02\u5E38\u3002\n */\nexport function normalizeOrderBy(orderBy: unknown): NormalizedOrder[] {\n\n if (orderBy == null) return [];\n const list = Array.isArray(orderBy) ? orderBy : [orderBy];\n const out: NormalizedOrder[] = [];\n for (const item of list) {\n if (item == null || typeof item !== \"object\") {\n throw new Error(\"[tx] orderBy \u9879\u5FC5\u987B\u662F\u5BF9\u8C61\");\n }\n const rec = item as Record<string, unknown>;\n const col = rec.col ?? rec.field;\n if (typeof col !== \"string\" || !col) {\n throw new Error(\"[tx] orderBy \u7F3A\u5C11 field/col\");\n }\n out.push({\n col,\n dir: rec.dir === \"desc\" ? \"desc\" : \"asc\",\n });\n }\n return out;\n}\n"],
|
|
5
|
+
"mappings": "AAgBO,SAAS,iBAAiB,SAAqC;AAEpE,MAAI,WAAW,KAAM,QAAO,CAAC;AAC7B,QAAM,OAAO,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO;AACxD,QAAM,MAAyB,CAAC;AAChC,aAAW,QAAQ,MAAM;AACvB,QAAI,QAAQ,QAAQ,OAAO,SAAS,UAAU;AAC5C,YAAM,IAAI,MAAM,mDAAqB;AAAA,IACvC;AACA,UAAM,MAAM;AACZ,UAAM,MAAM,IAAI,OAAO,IAAI;AAC3B,QAAI,OAAO,QAAQ,YAAY,CAAC,KAAK;AACnC,YAAM,IAAI,MAAM,qCAA2B;AAAA,IAC7C;AACA,QAAI,KAAK;AAAA,MACP;AAAA,MACA,KAAK,IAAI,QAAQ,SAAS,SAAS;AAAA,IACrC,CAAC;AAAA,EACH;AACA,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,31 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/sql-helpers.ts — SQL
|
|
2
|
+
* lib/sql-helpers.ts — SQL 语句构造辅助工具
|
|
3
3
|
*
|
|
4
|
-
* sql-template-strings
|
|
5
|
-
*
|
|
6
|
-
* `FROM ?`
|
|
4
|
+
* `sql-template-strings` 会将所有 `${}` 模板插值自动转为 `?` 参数化绑定值。
|
|
5
|
+
* 数据表名、列名等受信任的标识符不能作为参数绑定,必须直接嵌入 SQL 文本中,
|
|
6
|
+
* 否则会导致 `FROM ?` 语法解析错误(`near "?": syntax error`)。
|
|
7
7
|
*/
|
|
8
8
|
import { type SQLStatement } from "sql-template-strings";
|
|
9
|
-
/**
|
|
9
|
+
/** 参数化查询对象,与 `createQuery` / `AnyQuery` 的查询输入格式完全对齐。 */
|
|
10
10
|
export type BoundSql = {
|
|
11
11
|
sql: string;
|
|
12
12
|
values: unknown[];
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* 构造安全拼接后的参数化查询结构(标识符安全嵌入文本,动态变量作为参数绑定)。
|
|
16
|
+
* 表名等受信任的系统常量可直接通过模板字符串拼接,用户输入必须存入 `values` 数组中。
|
|
17
17
|
*
|
|
18
18
|
* @example
|
|
19
|
-
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* sql(`SELECT * FROM ${TABLE} WHERE id = ?`, [id])
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @param text 包含 `?` 占位符的 SQL 文本。
|
|
24
|
+
* @param values 对应的参数绑定值数组。
|
|
25
|
+
* @returns 构造的参数化查询结构。
|
|
20
26
|
*/
|
|
21
27
|
export declare function sql(text: string, values?: unknown[]): BoundSql;
|
|
22
28
|
/**
|
|
23
|
-
*
|
|
29
|
+
* 将一段静态 SQL 片段包装为 `SQLStatement` 对象,专用于配合 `.append(...)` 链式拼接。
|
|
30
|
+
*
|
|
31
|
+
* 注意事项:
|
|
32
|
+
* 切勿写成 `SQL`... FROM ${raw(table)} ...`` —— 模板插值仍会被转为 `?` 占位符。
|
|
33
|
+
* 正确用法:
|
|
34
|
+
* ```ts
|
|
35
|
+
* SQL`SELECT * FROM `.append(raw(TABLE)).append(SQL` WHERE id = ${id}`)
|
|
36
|
+
* ```
|
|
37
|
+
* 推荐在日常业务中优先使用 {@link sql} 函数。
|
|
24
38
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* SQL`SELECT * FROM `.append(raw(TABLE)).append(SQL` WHERE id = ${id}`)
|
|
28
|
-
* 纯字符串 `.append(TABLE)` 效果相同;优先用 {@link sql}。
|
|
39
|
+
* @param text 静态 SQL 文本片段。
|
|
40
|
+
* @returns 包装后的 SQLStatement 实例。
|
|
29
41
|
*/
|
|
30
42
|
export declare function raw(text: string): SQLStatement;
|
|
31
43
|
//# sourceMappingURL=sql-helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql-helpers.d.ts","sourceRoot":"","sources":["../../src/lib/sql-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAE9D,
|
|
1
|
+
{"version":3,"file":"sql-helpers.d.ts","sourceRoot":"","sources":["../../src/lib/sql-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAE9D,wDAAwD;AACxD,MAAM,MAAM,QAAQ,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC;AAE1D;;;;;;;;;;;;GAYG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,QAAQ,CAElE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAE9C"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/sql-helpers.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * lib/sql-helpers.ts \u2014 SQL \u6784\u9020\u8F85\u52A9\n *\n * sql-template-strings \u4F1A\
|
|
5
|
-
"mappings": "AAQA,SAAS,WAA8B;
|
|
4
|
+
"sourcesContent": ["/**\n * lib/sql-helpers.ts \u2014 SQL \u8BED\u53E5\u6784\u9020\u8F85\u52A9\u5DE5\u5177\n *\n * `sql-template-strings` \u4F1A\u5C06\u6240\u6709 `${}` \u6A21\u677F\u63D2\u503C\u81EA\u52A8\u8F6C\u4E3A `?` \u53C2\u6570\u5316\u7ED1\u5B9A\u503C\u3002\n * \u6570\u636E\u8868\u540D\u3001\u5217\u540D\u7B49\u53D7\u4FE1\u4EFB\u7684\u6807\u8BC6\u7B26\u4E0D\u80FD\u4F5C\u4E3A\u53C2\u6570\u7ED1\u5B9A\uFF0C\u5FC5\u987B\u76F4\u63A5\u5D4C\u5165 SQL \u6587\u672C\u4E2D\uFF0C\n * \u5426\u5219\u4F1A\u5BFC\u81F4 `FROM ?` \u8BED\u6CD5\u89E3\u6790\u9519\u8BEF\uFF08`near \"?\": syntax error`\uFF09\u3002\n */\n\nimport { SQL, type SQLStatement } from \"sql-template-strings\";\n\n/** \u53C2\u6570\u5316\u67E5\u8BE2\u5BF9\u8C61\uFF0C\u4E0E `createQuery` / `AnyQuery` \u7684\u67E5\u8BE2\u8F93\u5165\u683C\u5F0F\u5B8C\u5168\u5BF9\u9F50\u3002 */\nexport type BoundSql = { sql: string; values: unknown[] };\n\n/**\n * \u6784\u9020\u5B89\u5168\u62FC\u63A5\u540E\u7684\u53C2\u6570\u5316\u67E5\u8BE2\u7ED3\u6784\uFF08\u6807\u8BC6\u7B26\u5B89\u5168\u5D4C\u5165\u6587\u672C\uFF0C\u52A8\u6001\u53D8\u91CF\u4F5C\u4E3A\u53C2\u6570\u7ED1\u5B9A\uFF09\u3002\n * \u8868\u540D\u7B49\u53D7\u4FE1\u4EFB\u7684\u7CFB\u7EDF\u5E38\u91CF\u53EF\u76F4\u63A5\u901A\u8FC7\u6A21\u677F\u5B57\u7B26\u4E32\u62FC\u63A5\uFF0C\u7528\u6237\u8F93\u5165\u5FC5\u987B\u5B58\u5165 `values` \u6570\u7EC4\u4E2D\u3002\n *\n * @example\n * ```ts\n * sql(`SELECT * FROM ${TABLE} WHERE id = ?`, [id])\n * ```\n *\n * @param text \u5305\u542B `?` \u5360\u4F4D\u7B26\u7684 SQL \u6587\u672C\u3002\n * @param values \u5BF9\u5E94\u7684\u53C2\u6570\u7ED1\u5B9A\u503C\u6570\u7EC4\u3002\n * @returns \u6784\u9020\u7684\u53C2\u6570\u5316\u67E5\u8BE2\u7ED3\u6784\u3002\n */\nexport function sql(text: string, values: unknown[] = []): BoundSql {\n return { sql: text, values };\n}\n\n/**\n * \u5C06\u4E00\u6BB5\u9759\u6001 SQL \u7247\u6BB5\u5305\u88C5\u4E3A `SQLStatement` \u5BF9\u8C61\uFF0C\u4E13\u7528\u4E8E\u914D\u5408 `.append(...)` \u94FE\u5F0F\u62FC\u63A5\u3002\n *\n * \u6CE8\u610F\u4E8B\u9879\uFF1A\n * \u5207\u52FF\u5199\u6210 `SQL`... FROM ${raw(table)} ...`` \u2014\u2014 \u6A21\u677F\u63D2\u503C\u4ECD\u4F1A\u88AB\u8F6C\u4E3A `?` \u5360\u4F4D\u7B26\u3002\n * \u6B63\u786E\u7528\u6CD5\uFF1A\n * ```ts\n * SQL`SELECT * FROM `.append(raw(TABLE)).append(SQL` WHERE id = ${id}`)\n * ```\n * \u63A8\u8350\u5728\u65E5\u5E38\u4E1A\u52A1\u4E2D\u4F18\u5148\u4F7F\u7528 {@link sql} \u51FD\u6570\u3002\n *\n * @param text \u9759\u6001 SQL \u6587\u672C\u7247\u6BB5\u3002\n * @returns \u5305\u88C5\u540E\u7684 SQLStatement \u5B9E\u4F8B\u3002\n */\nexport function raw(text: string): SQLStatement {\n return SQL([text]) as SQLStatement;\n}\n\n"],
|
|
5
|
+
"mappings": "AAQA,SAAS,WAA8B;AAkBhC,SAAS,IAAI,MAAc,SAAoB,CAAC,GAAa;AAClE,SAAO,EAAE,KAAK,MAAM,OAAO;AAC7B;AAgBO,SAAS,IAAI,MAA4B;AAC9C,SAAO,IAAI,CAAC,IAAI,CAAC;AACnB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/sqlite.d.ts
CHANGED
|
@@ -7,14 +7,23 @@ export type QueryFn = (sql: string | SQLStatement | {
|
|
|
7
7
|
changes: number | bigint;
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* 创建统一的 SQL 查询接口(内置 LRU 预编译语句缓存机制)。
|
|
11
|
+
* 支持三种调用形态:
|
|
12
|
+
* 1. `query("SELECT * FROM t WHERE id = ?", [1])`
|
|
13
|
+
* 2. `query({ sql: "SELECT * FROM t WHERE id = ?", values: [1] })`
|
|
14
|
+
* 3. `query(SQL`SELECT * FROM t WHERE id = ${1}`)`(sql-template-strings)
|
|
15
|
+
*
|
|
16
|
+
* @param db SQLite 数据库连接实例。
|
|
17
|
+
* @param maxStatements 预编译语句缓存上限(默认为 200)。
|
|
18
|
+
* @returns 统一查询执行函数。
|
|
14
19
|
*/
|
|
15
20
|
export declare function createQuery(db: DatabaseSync, maxStatements?: number): QueryFn;
|
|
16
21
|
/**
|
|
17
|
-
* 打开 SQLite
|
|
22
|
+
* 打开 SQLite 数据库文件(基于 Node.js 原生 DatabaseSync 同步模块)。
|
|
23
|
+
* 自动递归创建所在父级目录,并初始化配置外键约束(`foreign_keys = ON`)、预写日志(`journal_mode = WAL`)与忙等待超时(`busy_timeout = 5000`)。
|
|
24
|
+
*
|
|
25
|
+
* @param filePath 数据库文件绝对路径。
|
|
26
|
+
* @returns SQLite DatabaseSync 连接实例。
|
|
18
27
|
*/
|
|
19
28
|
export declare function openDatabase(filePath: string): DatabaseSync;
|
|
20
29
|
//# sourceMappingURL=sqlite.d.ts.map
|
package/dist/lib/sqlite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/lib/sqlite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAiB,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,EAChE,MAAM,CAAC,EAAE,OAAO,EAAE,KACf,OAAO,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAE9C
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/lib/sqlite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAiB,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,EAChE,MAAM,CAAC,EAAE,OAAO,EAAE,KACf,OAAO,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAE9C;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,EAAE,aAAa,GAAE,MAAY,GAAG,OAAO,CAwClF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAO3D"}
|
package/dist/lib/sqlite.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/sqlite.ts"],
|
|
4
|
-
"sourcesContent": ["import { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { DatabaseSync, StatementSync } from \"node:sqlite\";\nimport type { SQLStatement } from \"sql-template-strings\";\n\nexport type QueryFn = (\n sql: string | SQLStatement | { sql: string; values?: unknown[] },\n values?: unknown[]\n) => unknown[] | { changes: number | bigint };\n\n/**\n * \u7EDF\u4E00 SQL \u67E5\u8BE2\u63A5\u53E3 \
|
|
5
|
-
"mappings": "AAAA,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AACxB,SAAS,cAAc,qBAAqB;
|
|
4
|
+
"sourcesContent": ["import { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { DatabaseSync, StatementSync } from \"node:sqlite\";\nimport type { SQLStatement } from \"sql-template-strings\";\n\nexport type QueryFn = (\n sql: string | SQLStatement | { sql: string; values?: unknown[] },\n values?: unknown[]\n) => unknown[] | { changes: number | bigint };\n\n/**\n * \u521B\u5EFA\u7EDF\u4E00\u7684 SQL \u67E5\u8BE2\u63A5\u53E3\uFF08\u5185\u7F6E LRU \u9884\u7F16\u8BD1\u8BED\u53E5\u7F13\u5B58\u673A\u5236\uFF09\u3002\n * \u652F\u6301\u4E09\u79CD\u8C03\u7528\u5F62\u6001\uFF1A\n * 1. `query(\"SELECT * FROM t WHERE id = ?\", [1])`\n * 2. `query({ sql: \"SELECT * FROM t WHERE id = ?\", values: [1] })`\n * 3. `query(SQL`SELECT * FROM t WHERE id = ${1}`)`\uFF08sql-template-strings\uFF09\n *\n * @param db SQLite \u6570\u636E\u5E93\u8FDE\u63A5\u5B9E\u4F8B\u3002\n * @param maxStatements \u9884\u7F16\u8BD1\u8BED\u53E5\u7F13\u5B58\u4E0A\u9650\uFF08\u9ED8\u8BA4\u4E3A 200\uFF09\u3002\n * @returns \u7EDF\u4E00\u67E5\u8BE2\u6267\u884C\u51FD\u6570\u3002\n */\nexport function createQuery(db: DatabaseSync, maxStatements: number = 200): QueryFn {\n\n const stmts = new Map<string, StatementSync>();\n\n function getStmt(sql: string): StatementSync {\n let s = stmts.get(sql);\n if (!s) {\n s = db.prepare(sql);\n if (stmts.size >= maxStatements) {\n const first = stmts.keys().next().value;\n if (first !== undefined) stmts.delete(first);\n }\n stmts.set(sql, s);\n }\n return s;\n }\n\n function runQuery(sql: string, values: unknown[]): any[] | { changes: number | bigint } {\n const s = getStmt(sql);\n const upper = sql.trim().toUpperCase();\n const needsRows =\n upper.startsWith(\"SELECT\") ||\n upper.startsWith(\"WITH\") ||\n upper.startsWith(\"PRAGMA\") ||\n /\\bRETURNING\\b/.test(upper);\n const bind = values as any[];\n if (needsRows) return s.all(...bind);\n const r = s.run(...bind);\n return { changes: r.changes };\n }\n\n return function query(raw: string | { sql: string; values?: unknown[] }, maybeValues?: unknown[]): any[] | { changes: number | bigint } {\n // \u683C\u5F0F 1\uFF1Astring + values\n if (typeof raw === \"string\") {\n return runQuery(raw, maybeValues ?? []);\n }\n // \u683C\u5F0F 2 / 3\uFF1A{ sql, values } \u6216 sql-template-strings \u5BF9\u8C61\n const o = raw as { sql: string; values?: unknown[] };\n return runQuery(o.sql, o.values ?? []);\n };\n}\n\n/**\n * \u6253\u5F00 SQLite \u6570\u636E\u5E93\u6587\u4EF6\uFF08\u57FA\u4E8E Node.js \u539F\u751F DatabaseSync \u540C\u6B65\u6A21\u5757\uFF09\u3002\n * \u81EA\u52A8\u9012\u5F52\u521B\u5EFA\u6240\u5728\u7236\u7EA7\u76EE\u5F55\uFF0C\u5E76\u521D\u59CB\u5316\u914D\u7F6E\u5916\u952E\u7EA6\u675F\uFF08`foreign_keys = ON`\uFF09\u3001\u9884\u5199\u65E5\u5FD7\uFF08`journal_mode = WAL`\uFF09\u4E0E\u5FD9\u7B49\u5F85\u8D85\u65F6\uFF08`busy_timeout = 5000`\uFF09\u3002\n *\n * @param filePath \u6570\u636E\u5E93\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84\u3002\n * @returns SQLite DatabaseSync \u8FDE\u63A5\u5B9E\u4F8B\u3002\n */\nexport function openDatabase(filePath: string): DatabaseSync {\n mkdirSync(dirname(filePath), { recursive: true });\n const db = new DatabaseSync(filePath);\n db.exec(\"PRAGMA foreign_keys = ON\");\n db.exec(\"PRAGMA journal_mode = WAL\");\n db.exec(\"PRAGMA busy_timeout = 5000\");\n return db;\n}\n\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AACxB,SAAS,cAAc,qBAAqB;AAmBrC,SAAS,YAAY,IAAkB,gBAAwB,KAAc;AAElF,QAAM,QAAQ,oBAAI,IAA2B;AAE7C,WAAS,QAAQ,KAA4B;AAC3C,QAAI,IAAI,MAAM,IAAI,GAAG;AACrB,QAAI,CAAC,GAAG;AACN,UAAI,GAAG,QAAQ,GAAG;AAClB,UAAI,MAAM,QAAQ,eAAe;AAC/B,cAAM,QAAQ,MAAM,KAAK,EAAE,KAAK,EAAE;AAClC,YAAI,UAAU,OAAW,OAAM,OAAO,KAAK;AAAA,MAC7C;AACA,YAAM,IAAI,KAAK,CAAC;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,KAAa,QAAyD;AACtF,UAAM,IAAI,QAAQ,GAAG;AACrB,UAAM,QAAQ,IAAI,KAAK,EAAE,YAAY;AACrC,UAAM,YACJ,MAAM,WAAW,QAAQ,KACzB,MAAM,WAAW,MAAM,KACvB,MAAM,WAAW,QAAQ,KACzB,gBAAgB,KAAK,KAAK;AAC5B,UAAM,OAAO;AACb,QAAI,UAAW,QAAO,EAAE,IAAI,GAAG,IAAI;AACnC,UAAM,IAAI,EAAE,IAAI,GAAG,IAAI;AACvB,WAAO,EAAE,SAAS,EAAE,QAAQ;AAAA,EAC9B;AAEA,SAAO,SAAS,MAAM,KAAmD,aAA+D;AAEtI,QAAI,OAAO,QAAQ,UAAU;AAC3B,aAAO,SAAS,KAAK,eAAe,CAAC,CAAC;AAAA,IACxC;AAEA,UAAM,IAAI;AACV,WAAO,SAAS,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;AAAA,EACvC;AACF;AASO,SAAS,aAAa,UAAgC;AAC3D,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,QAAM,KAAK,IAAI,aAAa,QAAQ;AACpC,KAAG,KAAK,0BAA0B;AAClC,KAAG,KAAK,2BAA2B;AACnC,KAAG,KAAK,4BAA4B;AACpC,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* "schemaVersion": 2,
|
|
7
7
|
* "id": "feature-land",
|
|
8
8
|
* "name": "领地",
|
|
9
|
-
* "type": "feature",
|
|
10
9
|
* "configKey": "land",
|
|
11
10
|
* "requires": ["feature-economy"],
|
|
12
11
|
* "permissions": ["db:read:lands", "service:economy.account"],
|
|
@@ -36,7 +35,6 @@ export interface ModuleManifestV2 {
|
|
|
36
35
|
schemaVersion: 2;
|
|
37
36
|
id: string;
|
|
38
37
|
name: string;
|
|
39
|
-
type: "core" | "feature";
|
|
40
38
|
configKey: string;
|
|
41
39
|
requires: string[];
|
|
42
40
|
permissions: string[];
|
|
@@ -44,6 +42,8 @@ export interface ModuleManifestV2 {
|
|
|
44
42
|
provides: ServiceEntry[];
|
|
45
43
|
requires: ServiceEntry[];
|
|
46
44
|
};
|
|
45
|
+
enabledByDefault?: boolean | undefined;
|
|
46
|
+
canDisable?: boolean | undefined;
|
|
47
47
|
notes?: string | undefined;
|
|
48
48
|
}
|
|
49
49
|
/** v1 / 缺 schemaVersion:跳过(直到所有模块迁 v2) */
|
|
@@ -59,6 +59,9 @@ export interface LoadedManifests {
|
|
|
59
59
|
/**
|
|
60
60
|
* 启动期调。throw = 启动失败。
|
|
61
61
|
* 注:不读 v1 manifest。只有 schemaVersion=2 才走。
|
|
62
|
+
*
|
|
63
|
+
* 形状校验:核心字段句式可与 `@sfmc-bds/sdk/validation` 对齐;本加载器另有
|
|
64
|
+
* 弃用字段拒绝、service 冲突、权限门等域规则,故暂未整段委托 validateManifest。
|
|
62
65
|
*/
|
|
63
66
|
export declare function loadManifestV2(packagesDir?: string): LoadedManifests;
|
|
64
67
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../src/manifest-loader.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../src/manifest-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACtC,MAAM,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,CAAC,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE;QAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;QAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;IACjE,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAID,0CAA0C;AAC1C,qBAAa,oBAAqB,SAAQ,KAAK;IAC1B,QAAQ,EAAE,MAAM;IAAS,eAAe,EAAE,OAAO;IAApE,YAAmB,QAAQ,EAAE,MAAM,EAAS,eAAe,EAAE,OAAO,EAEnE;CACF;AAkGD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC1C,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,WAAW,GAAE,MAA6B,GAAG,eAAe,CAqE1F;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAMlC"}
|
package/dist/manifest-loader.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { defaultPackagesDir } from "./manifest.js";
|
|
4
4
|
import { validateManifestPermissions } from "./permission-gate.js";
|
|
5
|
-
const VALID_TYPES = /* @__PURE__ */ new Set(["core", "feature"]);
|
|
6
5
|
const IDENT = /^[A-Za-z0-9_-]+$/;
|
|
7
6
|
class ModuleV1SkippedError extends Error {
|
|
8
7
|
constructor(moduleId, declaredVersion) {
|
|
@@ -26,8 +25,11 @@ function assertModuleObject(modId, obj) {
|
|
|
26
25
|
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
27
26
|
throw new Error(`[manifest] ${modId}: name \u7F3A\u5931`);
|
|
28
27
|
}
|
|
29
|
-
if (typeof obj.
|
|
30
|
-
throw new Error(`[manifest] ${modId}:
|
|
28
|
+
if (obj.enabledByDefault !== void 0 && typeof obj.enabledByDefault !== "boolean") {
|
|
29
|
+
throw new Error(`[manifest] ${modId}: enabledByDefault \u5FC5\u987B\u662F boolean`);
|
|
30
|
+
}
|
|
31
|
+
if (obj.canDisable !== void 0 && typeof obj.canDisable !== "boolean") {
|
|
32
|
+
throw new Error(`[manifest] ${modId}: canDisable \u5FC5\u987B\u662F boolean`);
|
|
31
33
|
}
|
|
32
34
|
if (typeof obj.configKey !== "string" || obj.configKey.length === 0) {
|
|
33
35
|
throw new Error(`[manifest] ${modId}: configKey \u7F3A\u5931`);
|
|
@@ -93,11 +95,12 @@ function parseMod(modId, raw) {
|
|
|
93
95
|
schemaVersion: 2,
|
|
94
96
|
id: raw.id,
|
|
95
97
|
name: raw.name,
|
|
96
|
-
type: raw.type,
|
|
97
98
|
configKey: raw.configKey,
|
|
98
99
|
requires,
|
|
99
100
|
permissions,
|
|
100
101
|
services: parseServices(modId, raw.services),
|
|
102
|
+
...typeof raw.enabledByDefault === "boolean" ? { enabledByDefault: raw.enabledByDefault } : {},
|
|
103
|
+
...typeof raw.canDisable === "boolean" ? { canDisable: raw.canDisable } : {},
|
|
101
104
|
notes: typeof raw.notes === "string" ? raw.notes : void 0
|
|
102
105
|
};
|
|
103
106
|
}
|
|
@@ -105,7 +108,14 @@ function loadManifestV2(packagesDir = defaultPackagesDir()) {
|
|
|
105
108
|
if (!existsSync(packagesDir)) {
|
|
106
109
|
throw new Error(`[manifest] ${packagesDir} \u4E0D\u5B58\u5728;\u65E0\u6CD5\u542F\u52A8 v2 \u534F\u8BAE`);
|
|
107
110
|
}
|
|
108
|
-
const ids = readdirSync(packagesDir, { withFileTypes: true }).filter((e) =>
|
|
111
|
+
const ids = readdirSync(packagesDir, { withFileTypes: true }).filter((e) => {
|
|
112
|
+
if (e.name.startsWith(".")) return false;
|
|
113
|
+
try {
|
|
114
|
+
return statSync(resolve(packagesDir, e.name)).isDirectory();
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}).map((e) => e.name);
|
|
109
119
|
const modules = {};
|
|
110
120
|
const providesMap = /* @__PURE__ */ new Map();
|
|
111
121
|
for (const id of ids) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/manifest-loader.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * manifest-loader.ts \u2014 \u8BFB v2 manifest,\u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n *\n * v2 schema:\n * {\n * \"schemaVersion\": 2,\n * \"id\": \"feature-land\",\n * \"name\": \"\u9886\u5730\",\n * \"type\": \"feature\",\n * \"configKey\": \"land\",\n * \"requires\": [\"feature-economy\"],\n * \"permissions\": [\"db:read:lands\", \"service:economy.account\"],\n * \"services\": {\n * \"provides\": [{ \"name\": \"lands.byOwner\", \"input\": {...}, \"output\": {...} }],\n * \"requires\": [{ \"name\": \"economy.account\" }]\n * },\n * \"notes\": \"...\"\n * }\n *\n * \u4E0D\u8981 routes / tables / migrations / seeds / handlers / events\u3002\n *\n * \u8C03\u7528\u65B9:db-server/src/index.ts \u542F\u52A8\u65F6\u4E00\u6B21 loadManifestV2,\n * throw \u5373 crash\u3002\n */\n\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { defaultPackagesDir } from \"./manifest.js\";\nimport { validateManifestPermissions } from \"./permission-gate.js\";\n\nexport interface ManifestServiceIO {\n type: \"object\" | \"array\" | \"string\" | \"number\" | \"boolean\" | \"null\";\n properties?: Record<string, unknown>;\n required?: string[];\n}\n\nexport interface ServiceEntry {\n name: string;\n input?: ManifestServiceIO | undefined;\n output?: ManifestServiceIO | undefined;\n}\n\nexport interface ModuleManifestV2 {\n schemaVersion: 2;\n id: string;\n name: string;\n type: \"core\" | \"feature\";\n configKey: string;\n requires: string[];\n permissions: string[];\n services: { provides: ServiceEntry[]; requires: ServiceEntry[] };\n notes?: string | undefined;\n}\n\nconst VALID_TYPES = new Set([\"core\", \"feature\"]);\nconst IDENT = /^[A-Za-z0-9_-]+$/;\n\n/** v1 / \u7F3A schemaVersion:\u8DF3\u8FC7(\u76F4\u5230\u6240\u6709\u6A21\u5757\u8FC1 v2) */\nexport class ModuleV1SkippedError extends Error {\n constructor(public moduleId: string, public declaredVersion: unknown) {\n super(`moduleId=${moduleId} schemaVersion=${String(declaredVersion)} (\u9700\u8981 2),\u8DF3\u8FC7`);\n }\n}\n\nfunction assertModuleObject(modId: string, obj: Record<string, unknown>): void {\n if (typeof obj.schemaVersion !== \"number\") {\n throw new Error(`[manifest] ${modId}: schemaVersion \u7F3A\u5931`);\n }\n if (obj.schemaVersion !== 2) {\n throw new Error(`[manifest] ${modId}: schemaVersion=${obj.schemaVersion} (\u9700\u8981 2)`);\n }\n if (typeof obj.id !== \"string\" || !IDENT.test(obj.id)) {\n throw new Error(`[manifest] ${modId}: id \u7F3A\u5931\u6216\u975E\u6CD5 \"${String(obj.id)}\"`);\n }\n if (typeof obj.name !== \"string\" || obj.name.length === 0) {\n throw new Error(`[manifest] ${modId}: name \u7F3A\u5931`);\n }\n if (typeof obj.type !== \"string\" || !VALID_TYPES.has(obj.type)) {\n throw new Error(`[manifest] ${modId}: type \u5FC5\u987B\u662F core|feature (\"${String(obj.type)}\")`);\n }\n if (typeof obj.configKey !== \"string\" || obj.configKey.length === 0) {\n throw new Error(`[manifest] ${modId}: configKey \u7F3A\u5931`);\n }\n if (Array.isArray(obj.routes)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 routes \u5B57\u6BB5,\u6539\u7528 db.defineTable + platform routes`);\n }\n if (Array.isArray(obj.migrations)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 migrations \u5B57\u6BB5,\u6539\u7528 db.defineTable \u81EA\u52A8\u5EFA\u8868`);\n }\n if (Array.isArray(obj.tables)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 tables \u5B57\u6BB5,\u6539\u7528 db.defineTable`);\n }\n if (Array.isArray(obj.events)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 events \u5B57\u6BB5,\u6539\u7528 platform event-bus`);\n }\n}\n\nfunction parseServices(modId: string, raw: unknown): { provides: ServiceEntry[]; requires: ServiceEntry[] } {\n if (raw === undefined) return { provides: [], requires: [] };\n if (typeof raw !== \"object\" || raw === null) {\n throw new Error(`[manifest] ${modId}: services \u5FC5\u987B\u662F\u5BF9\u8C61`);\n }\n const obj = raw as Record<string, unknown>;\n const provides = parseServiceList(modId, \"provides\", obj.provides);\n const requires = parseServiceList(modId, \"requires\", obj.requires);\n for (const r of requires) {\n if (provides.find((p) => p.name === r.name)) {\n throw new Error(`[manifest] ${modId}: service \"${r.name}\" \u65E2 provides \u53C8 requires`);\n }\n }\n return { provides, requires };\n}\n\nfunction parseServiceList(modId: string, dir: string, raw: unknown): ServiceEntry[] {\n if (raw === undefined) return [];\n if (!Array.isArray(raw)) throw new Error(`[manifest] ${modId}: services.${dir} \u5FC5\u987B\u662F\u6570\u7EC4`);\n const out: ServiceEntry[] = [];\n for (const item of raw) {\n if (typeof item !== \"object\" || item === null) {\n throw new Error(`[manifest] ${modId}: services.${dir} \u9879\u5FC5\u987B\u662F\u5BF9\u8C61`);\n }\n const e = item as Record<string, unknown>;\n if (typeof e.name !== \"string\" || e.name.length === 0) {\n throw new Error(`[manifest] ${modId}: services.${dir} \u9879\u7F3A\u5C11 name`);\n }\n const seen = new Set<string>();\n if (seen.has(e.name)) {\n throw new Error(`[manifest] ${modId}: services.${dir} name \u91CD\u590D \"${e.name}\"`);\n }\n seen.add(e.name);\n out.push({ name: e.name, input: e.input as ManifestServiceIO | undefined, output: e.output as ManifestServiceIO | undefined });\n }\n return out;\n}\n\nfunction parseMod(modId: string, raw: Record<string, unknown>): ModuleManifestV2 {\n if (raw.schemaVersion !== 2) {\n throw new ModuleV1SkippedError(modId, raw.schemaVersion);\n }\n assertModuleObject(modId, raw);\n const requires = Array.isArray(raw.requires) ? (raw.requires as unknown[]).filter((x) => typeof x === \"string\") as string[] : [];\n const permissions = Array.isArray(raw.permissions) ? (raw.permissions as unknown[]).filter((x) => typeof x === \"string\") as string[] : [];\n validateManifestPermissions(modId, permissions);\n return {\n schemaVersion: 2,\n id: raw.id as string,\n name: raw.name as string,\n type: raw.type as \"core\" | \"feature\",\n configKey: raw.configKey as string,\n requires,\n permissions,\n services: parseServices(modId, raw.services),\n notes: typeof raw.notes === \"string\" ? raw.notes : undefined,\n };\n}\n\nexport interface LoadedManifests {\n modules: Record<string, ModuleManifestV2>;\n providesMap: Map<string, string>; // service name \u2192 moduleId\n}\n\n/**\n * \u542F\u52A8\u671F\u8C03\u3002throw = \u542F\u52A8\u5931\u8D25\u3002\n * \u6CE8:\u4E0D\u8BFB v1 manifest\u3002\u53EA\u6709 schemaVersion=2 \u624D\u8D70\u3002\n */\nexport function loadManifestV2(packagesDir: string = defaultPackagesDir()): LoadedManifests {\n if (!existsSync(packagesDir)) {\n throw new Error(`[manifest] ${packagesDir} \u4E0D\u5B58\u5728;\u65E0\u6CD5\u542F\u52A8 v2 \u534F\u8BAE`);\n }\n const ids = readdirSync(packagesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n const modules: Record<string, ModuleManifestV2> = {};\n const providesMap = new Map<string, string>();\n\n for (const id of ids) {\n const p = resolve(packagesDir, id, \"sapi\", \"manifest.json\");\n if (!existsSync(p)) continue;\n let raw: string;\n try {\n raw = readFileSync(p, \"utf8\");\n } catch (e) {\n throw new Error(`[manifest] ${id}: \u8BFB ${p} \u5931\u8D25: ${(e as Error).message}`);\n }\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (e) {\n throw new Error(`[manifest] ${id}: ${p} \u975E\u5408\u6CD5 JSON: ${(e as Error).message}`);\n }\n try {\n const m = parseMod(id, parsed);\n modules[m.id] = m;\n for (const s of m.services.provides) {\n const prev = providesMap.get(s.name);\n if (prev) {\n throw new Error(`[manifest] service \"${s.name}\" \u5DF2\u88AB ${prev} provides,${m.id} \u62A2\u6CE8`);\n }\n providesMap.set(s.name, m.id);\n }\n } catch (e) {\n if (e instanceof ModuleV1SkippedError) {\n // PoC \u517C\u5BB9:\u4E0D\u662F v2 \u7684\u6A21\u5757\u8DF3\u8FC7,\u7B49\u5230\u6240\u6709\u6A21\u5757\u8FC1 v2 \u540E\u6539\u4E3A throw\n console.warn(`[manifest] ${id}: ${e.message}; v1 \u8DF3\u8FC7`);\n continue;\n }\n throw e;\n }\n }\n\n // requires:\u6821\u9A8C\u76EE\u6807 service \u5728\u67D0\u4E2A provides \u91CC\n for (const m of Object.values(modules)) {\n for (const r of m.services.requires) {\n const owner = providesMap.get(r.name);\n if (!owner) {\n throw new Error(`[manifest] ${m.id}.services.requires.${r.name} \u6CA1\u6709\u6A21\u5757 provides`);\n }\n }\n // requires \u6A21\u5757\u4F9D\u8D56\u4E5F\u8981\u4E48 enabled \u8981\u4E48\u672C\u8EAB installed (\u542F\u52A8\u671F\u4E0D\u5F3A\u6C42 enabled;\u8FD0\u884C\u65F6\u518D\u67E5)\n for (const dep of m.requires) {\n if (!modules[dep]) {\n throw new Error(`[manifest] ${m.id}.requires.${dep} \u6CA1\u627E\u5230\u5BF9\u5E94\u6A21\u5757`);\n }\n }\n }\n\n return { modules, providesMap };\n}\n\n/**\n * \u7ED9\u5B9A moduleId \u96C6\u5408,\u8FC7\u6EE4\u51FA enabled (= lock \u6587\u4EF6 enabled \u7684) \u5B50\u96C6\u3002\n * map(moduleId \u2192 manifest) \u2014 \u6CA1\u88C5\u5219\u8DF3\u8FC7\u3002\n */\nexport function filterEnabled(\n loaded: LoadedManifests,\n enabledIds: ReadonlySet<string>\n): Record<string, ModuleManifestV2> {\n const out: Record<string, ModuleManifestV2> = {};\n for (const [id, m] of Object.entries(loaded.modules)) {\n if (enabledIds.has(id)) out[id] = m;\n }\n return out;\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * manifest-loader.ts \u2014 \u8BFB v2 manifest,\u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n *\n * v2 schema:\n * {\n * \"schemaVersion\": 2,\n * \"id\": \"feature-land\",\n * \"name\": \"\u9886\u5730\",\n * \"configKey\": \"land\",\n * \"requires\": [\"feature-economy\"],\n * \"permissions\": [\"db:read:lands\", \"service:economy.account\"],\n * \"services\": {\n * \"provides\": [{ \"name\": \"lands.byOwner\", \"input\": {...}, \"output\": {...} }],\n * \"requires\": [{ \"name\": \"economy.account\" }]\n * },\n * \"notes\": \"...\"\n * }\n *\n * \u4E0D\u8981 routes / tables / migrations / seeds / handlers / events\u3002\n *\n * \u8C03\u7528\u65B9:db-server/src/index.ts \u542F\u52A8\u65F6\u4E00\u6B21 loadManifestV2,\n * throw \u5373 crash\u3002\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { defaultPackagesDir } from \"./manifest.js\";\nimport { validateManifestPermissions } from \"./permission-gate.js\";\n\nexport interface ManifestServiceIO {\n type: \"object\" | \"array\" | \"string\" | \"number\" | \"boolean\" | \"null\";\n properties?: Record<string, unknown>;\n required?: string[];\n}\n\nexport interface ServiceEntry {\n name: string;\n input?: ManifestServiceIO | undefined;\n output?: ManifestServiceIO | undefined;\n}\n\nexport interface ModuleManifestV2 {\n schemaVersion: 2;\n id: string;\n name: string;\n configKey: string;\n requires: string[];\n permissions: string[];\n services: { provides: ServiceEntry[]; requires: ServiceEntry[] };\n enabledByDefault?: boolean | undefined;\n canDisable?: boolean | undefined;\n notes?: string | undefined;\n}\n\nconst IDENT = /^[A-Za-z0-9_-]+$/;\n\n/** v1 / \u7F3A schemaVersion:\u8DF3\u8FC7(\u76F4\u5230\u6240\u6709\u6A21\u5757\u8FC1 v2) */\nexport class ModuleV1SkippedError extends Error {\n constructor(public moduleId: string, public declaredVersion: unknown) {\n super(`moduleId=${moduleId} schemaVersion=${String(declaredVersion)} (\u9700\u8981 2),\u8DF3\u8FC7`);\n }\n}\n\nfunction assertModuleObject(modId: string, obj: Record<string, unknown>): void {\n if (typeof obj.schemaVersion !== \"number\") {\n throw new Error(`[manifest] ${modId}: schemaVersion \u7F3A\u5931`);\n }\n if (obj.schemaVersion !== 2) {\n throw new Error(`[manifest] ${modId}: schemaVersion=${obj.schemaVersion} (\u9700\u8981 2)`);\n }\n if (typeof obj.id !== \"string\" || !IDENT.test(obj.id)) {\n throw new Error(`[manifest] ${modId}: id \u7F3A\u5931\u6216\u975E\u6CD5 \"${String(obj.id)}\"`);\n }\n if (typeof obj.name !== \"string\" || obj.name.length === 0) {\n throw new Error(`[manifest] ${modId}: name \u7F3A\u5931`);\n }\n if (obj.enabledByDefault !== undefined && typeof obj.enabledByDefault !== \"boolean\") {\n throw new Error(`[manifest] ${modId}: enabledByDefault \u5FC5\u987B\u662F boolean`);\n }\n if (obj.canDisable !== undefined && typeof obj.canDisable !== \"boolean\") {\n throw new Error(`[manifest] ${modId}: canDisable \u5FC5\u987B\u662F boolean`);\n }\n if (typeof obj.configKey !== \"string\" || obj.configKey.length === 0) {\n throw new Error(`[manifest] ${modId}: configKey \u7F3A\u5931`);\n }\n if (Array.isArray(obj.routes)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 routes \u5B57\u6BB5,\u6539\u7528 db.defineTable + platform routes`);\n }\n if (Array.isArray(obj.migrations)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 migrations \u5B57\u6BB5,\u6539\u7528 db.defineTable \u81EA\u52A8\u5EFA\u8868`);\n }\n if (Array.isArray(obj.tables)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 tables \u5B57\u6BB5,\u6539\u7528 db.defineTable`);\n }\n if (Array.isArray(obj.events)) {\n throw new Error(`[manifest] ${modId}: \u4E0D\u518D\u652F\u6301 events \u5B57\u6BB5,\u6539\u7528 platform event-bus`);\n }\n}\n\nfunction parseServices(modId: string, raw: unknown): { provides: ServiceEntry[]; requires: ServiceEntry[] } {\n if (raw === undefined) return { provides: [], requires: [] };\n if (typeof raw !== \"object\" || raw === null) {\n throw new Error(`[manifest] ${modId}: services \u5FC5\u987B\u662F\u5BF9\u8C61`);\n }\n const obj = raw as Record<string, unknown>;\n const provides = parseServiceList(modId, \"provides\", obj.provides);\n const requires = parseServiceList(modId, \"requires\", obj.requires);\n for (const r of requires) {\n if (provides.find((p) => p.name === r.name)) {\n throw new Error(`[manifest] ${modId}: service \"${r.name}\" \u65E2 provides \u53C8 requires`);\n }\n }\n return { provides, requires };\n}\n\nfunction parseServiceList(modId: string, dir: string, raw: unknown): ServiceEntry[] {\n if (raw === undefined) return [];\n if (!Array.isArray(raw)) throw new Error(`[manifest] ${modId}: services.${dir} \u5FC5\u987B\u662F\u6570\u7EC4`);\n const out: ServiceEntry[] = [];\n for (const item of raw) {\n if (typeof item !== \"object\" || item === null) {\n throw new Error(`[manifest] ${modId}: services.${dir} \u9879\u5FC5\u987B\u662F\u5BF9\u8C61`);\n }\n const e = item as Record<string, unknown>;\n if (typeof e.name !== \"string\" || e.name.length === 0) {\n throw new Error(`[manifest] ${modId}: services.${dir} \u9879\u7F3A\u5C11 name`);\n }\n const seen = new Set<string>();\n if (seen.has(e.name)) {\n throw new Error(`[manifest] ${modId}: services.${dir} name \u91CD\u590D \"${e.name}\"`);\n }\n seen.add(e.name);\n out.push({ name: e.name, input: e.input as ManifestServiceIO | undefined, output: e.output as ManifestServiceIO | undefined });\n }\n return out;\n}\n\nfunction parseMod(modId: string, raw: Record<string, unknown>): ModuleManifestV2 {\n if (raw.schemaVersion !== 2) {\n throw new ModuleV1SkippedError(modId, raw.schemaVersion);\n }\n assertModuleObject(modId, raw);\n const requires = Array.isArray(raw.requires) ? (raw.requires as unknown[]).filter((x) => typeof x === \"string\") as string[] : [];\n const permissions = Array.isArray(raw.permissions) ? (raw.permissions as unknown[]).filter((x) => typeof x === \"string\") as string[] : [];\n validateManifestPermissions(modId, permissions);\n return {\n schemaVersion: 2,\n id: raw.id as string,\n name: raw.name as string,\n configKey: raw.configKey as string,\n requires,\n permissions,\n services: parseServices(modId, raw.services),\n ...(typeof raw.enabledByDefault === \"boolean\" ? { enabledByDefault: raw.enabledByDefault } : {}),\n ...(typeof raw.canDisable === \"boolean\" ? { canDisable: raw.canDisable } : {}),\n notes: typeof raw.notes === \"string\" ? raw.notes : undefined,\n };\n}\n\nexport interface LoadedManifests {\n modules: Record<string, ModuleManifestV2>;\n providesMap: Map<string, string>; // service name \u2192 moduleId\n}\n\n/**\n * \u542F\u52A8\u671F\u8C03\u3002throw = \u542F\u52A8\u5931\u8D25\u3002\n * \u6CE8:\u4E0D\u8BFB v1 manifest\u3002\u53EA\u6709 schemaVersion=2 \u624D\u8D70\u3002\n *\n * \u5F62\u72B6\u6821\u9A8C\uFF1A\u6838\u5FC3\u5B57\u6BB5\u53E5\u5F0F\u53EF\u4E0E `@sfmc-bds/sdk/validation` \u5BF9\u9F50\uFF1B\u672C\u52A0\u8F7D\u5668\u53E6\u6709\n * \u5F03\u7528\u5B57\u6BB5\u62D2\u7EDD\u3001service \u51B2\u7A81\u3001\u6743\u9650\u95E8\u7B49\u57DF\u89C4\u5219\uFF0C\u6545\u6682\u672A\u6574\u6BB5\u59D4\u6258 validateManifest\u3002\n */\nexport function loadManifestV2(packagesDir: string = defaultPackagesDir()): LoadedManifests {\n if (!existsSync(packagesDir)) {\n throw new Error(`[manifest] ${packagesDir} \u4E0D\u5B58\u5728;\u65E0\u6CD5\u542F\u52A8 v2 \u534F\u8BAE`);\n }\n const ids = readdirSync(packagesDir, { withFileTypes: true })\n .filter((e) => {\n if (e.name.startsWith(\".\")) return false;\n try {\n return statSync(resolve(packagesDir, e.name)).isDirectory();\n } catch {\n return false;\n }\n })\n .map((e) => e.name);\n const modules: Record<string, ModuleManifestV2> = {};\n const providesMap = new Map<string, string>();\n\n for (const id of ids) {\n const p = resolve(packagesDir, id, \"sapi\", \"manifest.json\");\n if (!existsSync(p)) continue;\n let raw: string;\n try {\n raw = readFileSync(p, \"utf8\");\n } catch (e) {\n throw new Error(`[manifest] ${id}: \u8BFB ${p} \u5931\u8D25: ${(e as Error).message}`);\n }\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (e) {\n throw new Error(`[manifest] ${id}: ${p} \u975E\u5408\u6CD5 JSON: ${(e as Error).message}`);\n }\n try {\n const m = parseMod(id, parsed);\n modules[m.id] = m;\n for (const s of m.services.provides) {\n const prev = providesMap.get(s.name);\n if (prev) {\n throw new Error(`[manifest] service \"${s.name}\" \u5DF2\u88AB ${prev} provides,${m.id} \u62A2\u6CE8`);\n }\n providesMap.set(s.name, m.id);\n }\n } catch (e) {\n if (e instanceof ModuleV1SkippedError) {\n // PoC \u517C\u5BB9:\u4E0D\u662F v2 \u7684\u6A21\u5757\u8DF3\u8FC7,\u7B49\u5230\u6240\u6709\u6A21\u5757\u8FC1 v2 \u540E\u6539\u4E3A throw\n console.warn(`[manifest] ${id}: ${e.message}; v1 \u8DF3\u8FC7`);\n continue;\n }\n throw e;\n }\n }\n\n // requires:\u6821\u9A8C\u76EE\u6807 service \u5728\u67D0\u4E2A provides \u91CC\n for (const m of Object.values(modules)) {\n for (const r of m.services.requires) {\n const owner = providesMap.get(r.name);\n if (!owner) {\n throw new Error(`[manifest] ${m.id}.services.requires.${r.name} \u6CA1\u6709\u6A21\u5757 provides`);\n }\n }\n // requires \u6A21\u5757\u4F9D\u8D56\u4E5F\u8981\u4E48 enabled \u8981\u4E48\u672C\u8EAB installed (\u542F\u52A8\u671F\u4E0D\u5F3A\u6C42 enabled;\u8FD0\u884C\u65F6\u518D\u67E5)\n for (const dep of m.requires) {\n if (!modules[dep]) {\n throw new Error(`[manifest] ${m.id}.requires.${dep} \u6CA1\u627E\u5230\u5BF9\u5E94\u6A21\u5757`);\n }\n }\n }\n\n return { modules, providesMap };\n}\n\n/**\n * \u7ED9\u5B9A moduleId \u96C6\u5408,\u8FC7\u6EE4\u51FA enabled (= lock \u6587\u4EF6 enabled \u7684) \u5B50\u96C6\u3002\n * map(moduleId \u2192 manifest) \u2014 \u6CA1\u88C5\u5219\u8DF3\u8FC7\u3002\n */\nexport function filterEnabled(\n loaded: LoadedManifests,\n enabledIds: ReadonlySet<string>\n): Record<string, ModuleManifestV2> {\n const out: Record<string, ModuleManifestV2> = {};\n for (const [id, m] of Object.entries(loaded.modules)) {\n if (enabledIds.has(id)) out[id] = m;\n }\n return out;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/manifest.d.ts
CHANGED
|
@@ -26,7 +26,6 @@ export interface ModuleManifestMigration {
|
|
|
26
26
|
}
|
|
27
27
|
export interface ModuleManifestEntry {
|
|
28
28
|
name?: string | undefined;
|
|
29
|
-
type?: string | undefined;
|
|
30
29
|
configKey?: string | undefined;
|
|
31
30
|
requires?: string[] | undefined;
|
|
32
31
|
handlers?: string[] | undefined;
|
|
@@ -45,7 +44,7 @@ export type PackagesDirResolver = () => string;
|
|
|
45
44
|
* 优先级:
|
|
46
45
|
* 1. `process.env.SFMC_PACKAGES_DIR` —— sfmc spawnService 启动 db 时注入
|
|
47
46
|
* (通常与 SFMC_ROOT 对齐)
|
|
48
|
-
* 2.
|
|
47
|
+
* 2. PROJECT_ROOT/modules/packages —— 独立启动时兜底
|
|
49
48
|
*/
|
|
50
49
|
export declare function defaultPackagesDir(): string;
|
|
51
50
|
/**
|
package/dist/manifest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,UAAU,EAAE,uBAAuB,EAAE,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC9C;AAID,MAAM,MAAM,mBAAmB,GAAG,MAAM,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,WAAW,GAAE,MAA6B,GAAG,cAAc,CAoDvF;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,cAAc,GAAG,eAAe,CAgB5D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAYnF"}
|
package/dist/manifest.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { PROJECT_ROOT } from "./project-root.js";
|
|
4
4
|
const SUPPORTED_SCHEMA = 1;
|
|
5
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
5
|
function defaultPackagesDir() {
|
|
7
|
-
return process.env.SFMC_PACKAGES_DIR ||
|
|
6
|
+
return process.env.SFMC_PACKAGES_DIR || join(PROJECT_ROOT, "modules", "packages");
|
|
8
7
|
}
|
|
9
8
|
function loadManifest(packagesDir = defaultPackagesDir()) {
|
|
10
9
|
const modules = {};
|
|
@@ -12,9 +11,16 @@ function loadManifest(packagesDir = defaultPackagesDir()) {
|
|
|
12
11
|
console.warn(`[manifest] ${packagesDir} does not exist; db-server will run with zero modules`);
|
|
13
12
|
return { schemaVersion: SUPPORTED_SCHEMA, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), modules };
|
|
14
13
|
}
|
|
15
|
-
const ids = readdirSync(packagesDir, { withFileTypes: true }).filter((e) =>
|
|
14
|
+
const ids = readdirSync(packagesDir, { withFileTypes: true }).filter((e) => {
|
|
15
|
+
if (e.name.startsWith(".")) return false;
|
|
16
|
+
try {
|
|
17
|
+
return statSync(join(packagesDir, e.name)).isDirectory();
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}).map((e) => e.name).sort();
|
|
16
22
|
for (const id of ids) {
|
|
17
|
-
const manifestPath =
|
|
23
|
+
const manifestPath = join(packagesDir, id, "sapi", "manifest.json");
|
|
18
24
|
if (!existsSync(manifestPath)) continue;
|
|
19
25
|
let raw;
|
|
20
26
|
try {
|
|
@@ -35,7 +41,6 @@ function loadManifest(packagesDir = defaultPackagesDir()) {
|
|
|
35
41
|
}
|
|
36
42
|
modules[id] = {
|
|
37
43
|
name: parsed.name,
|
|
38
|
-
type: parsed.type,
|
|
39
44
|
configKey: parsed.configKey,
|
|
40
45
|
requires: Array.isArray(parsed.requires) ? parsed.requires : [],
|
|
41
46
|
handlers: Array.isArray(parsed.handlers) ? parsed.handlers : [],
|
package/dist/manifest.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/manifest.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * manifest.ts \u2014 runtime SAPI module manifest reader.\n *\n * At db-server startup, scan `modules/packages/<id>/sapi/manifest.json` for\n * every installed module and aggregate them into a single in-memory manifest.\n * No pre-built `modules/_manifests/module-manifests.json` is required; the\n * per-module files are the single source of truth.\n *\n * Behavior:\n * - Missing file: skip that module (WARN).\n * - Corrupt JSON: skip + WARN.\n * - schemaVersion > supported: WARN, continue best-effort.\n * - schemaVersion < supported: refuse to load that module.\n *\n * Reconcile: every route declared in a module's manifest is checked against\n * the prefix table; mismatches WARN but do not block startup.\n */\n\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport {
|
|
5
|
-
"mappings": "AAkBA,SAAS,YAAY,aAAa,
|
|
4
|
+
"sourcesContent": ["/**\n * manifest.ts \u2014 runtime SAPI module manifest reader.\n *\n * At db-server startup, scan `modules/packages/<id>/sapi/manifest.json` for\n * every installed module and aggregate them into a single in-memory manifest.\n * No pre-built `modules/_manifests/module-manifests.json` is required; the\n * per-module files are the single source of truth.\n *\n * Behavior:\n * - Missing file: skip that module (WARN).\n * - Corrupt JSON: skip + WARN.\n * - schemaVersion > supported: WARN, continue best-effort.\n * - schemaVersion < supported: refuse to load that module.\n *\n * Reconcile: every route declared in a module's manifest is checked against\n * the prefix table; mismatches WARN but do not block startup.\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { PROJECT_ROOT } from \"./project-root.js\";\n\nexport interface ModuleManifestRoute {\n method: string;\n path: string;\n handler: string;\n}\n\nexport interface ModuleManifestMigration {\n name: string;\n version: number;\n}\n\nexport interface ModuleManifestEntry {\n name?: string | undefined;\n configKey?: string | undefined;\n requires?: string[] | undefined;\n handlers?: string[] | undefined;\n routes: ModuleManifestRoute[];\n migrations: ModuleManifestMigration[];\n notes?: string | undefined;\n}\n\nexport interface ModuleManifest {\n schemaVersion: number;\n generatedAt: string;\n modules: Record<string, ModuleManifestEntry>;\n}\n\nconst SUPPORTED_SCHEMA = 1;\n\nexport type PackagesDirResolver = () => string;\n\n/** Resolve where db-server should look for `modules/packages/`.\n *\n * \u4F18\u5148\u7EA7:\n * 1. `process.env.SFMC_PACKAGES_DIR` \u2014\u2014 sfmc spawnService \u542F\u52A8 db \u65F6\u6CE8\u5165\n * (\u901A\u5E38\u4E0E SFMC_ROOT \u5BF9\u9F50)\n * 2. PROJECT_ROOT/modules/packages \u2014\u2014 \u72EC\u7ACB\u542F\u52A8\u65F6\u515C\u5E95\n */\nexport function defaultPackagesDir(): string {\n return process.env.SFMC_PACKAGES_DIR || join(PROJECT_ROOT, \"modules\", \"packages\");\n}\n\n/**\n * Read every `modules/packages/<id>/sapi/manifest.json` and aggregate.\n * Modules without a manifest are silently skipped (the SAPI side will fall\n * back to a default-empty surface); modules with a corrupt or too-new\n * manifest produce warnings.\n */\nexport function loadManifest(packagesDir: string = defaultPackagesDir()): ModuleManifest {\n const modules: Record<string, ModuleManifestEntry> = {};\n if (!existsSync(packagesDir)) {\n console.warn(`[manifest] ${packagesDir} does not exist; db-server will run with zero modules`);\n return { schemaVersion: SUPPORTED_SCHEMA, generatedAt: new Date().toISOString(), modules };\n }\n const ids = readdirSync(packagesDir, { withFileTypes: true })\n .filter((e) => {\n if (e.name.startsWith(\".\")) return false;\n try {\n return statSync(join(packagesDir, e.name)).isDirectory();\n } catch {\n return false;\n }\n })\n .map((e) => e.name)\n .sort();\n for (const id of ids) {\n const manifestPath = join(packagesDir, id, \"sapi\", \"manifest.json\");\n if (!existsSync(manifestPath)) continue;\n let raw: string;\n try {\n raw = readFileSync(manifestPath, \"utf8\");\n } catch (err) {\n console.warn(`[manifest] ${id}: cannot read ${manifestPath}: ${(err as Error).message}`);\n continue;\n }\n let parsed: Partial<ModuleManifestEntry> & { schemaVersion?: number };\n try {\n parsed = JSON.parse(raw) as typeof parsed;\n } catch (err) {\n console.warn(`[manifest] ${id}: ${manifestPath} is not valid JSON: ${(err as Error).message}`);\n continue;\n }\n if (typeof parsed.schemaVersion === \"number\" && parsed.schemaVersion > SUPPORTED_SCHEMA) {\n console.warn(`[manifest] ${id}: schemaVersion=${parsed.schemaVersion} > supported ${SUPPORTED_SCHEMA}; parsing best-effort`);\n }\n modules[id] = {\n name: parsed.name,\n configKey: parsed.configKey,\n requires: Array.isArray(parsed.requires) ? parsed.requires : [],\n handlers: Array.isArray(parsed.handlers) ? parsed.handlers : [],\n routes: Array.isArray(parsed.routes) ? parsed.routes : [],\n migrations: Array.isArray(parsed.migrations) ? parsed.migrations : [],\n notes: parsed.notes,\n };\n }\n return {\n schemaVersion: SUPPORTED_SCHEMA,\n generatedAt: new Date().toISOString(),\n modules,\n };\n}\n\nexport interface ManifestSummary {\n moduleCount: number;\n routeCount: number;\n handlerCount: number;\n migrationCount: number;\n moduleIds: string[];\n}\n\nexport function summarize(m: ModuleManifest): ManifestSummary {\n let routeCount = 0;\n let handlerCount = 0;\n let migrationCount = 0;\n for (const entry of Object.values(m.modules)) {\n routeCount += entry.routes.length;\n handlerCount += entry.handlers?.length ?? 0;\n migrationCount += entry.migrations.length;\n }\n return {\n moduleCount: Object.keys(m.modules).length,\n routeCount,\n handlerCount,\n migrationCount,\n moduleIds: Object.keys(m.modules).sort(),\n };\n}\n\n/**\n * Reconcile manifest routes against the routes the db-server currently serves.\n * Returns a list of warnings for routes declared in the manifest that no route\n * file covers. Routes already covered by `routes/*.ts` files are not flagged.\n */\nexport function reconcile(m: ModuleManifest, knownRoutePrefixes: string[]): string[] {\n const warnings: string[] = [];\n for (const [moduleId, entry] of Object.entries(m.modules)) {\n for (const route of entry.routes) {\n const prefix = route.path.split(\"/\").slice(0, 4).join(\"/\");\n const matched = knownRoutePrefixes.some((p) => prefix.startsWith(p.split(\"/\").slice(0, 4).join(\"/\")));\n if (!matched) {\n warnings.push(`${moduleId}: route ${route.method} ${route.path} is not covered by db-server`);\n }\n }\n }\n return warnings;\n}"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/project-root.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-root.d.ts","sourceRoot":"","sources":["../src/project-root.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,wCAAwC;AACxC,eAAO,MAAM,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"project-root.d.ts","sourceRoot":"","sources":["../src/project-root.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,wCAAwC;AACxC,eAAO,MAAM,YAAY,EAAE,MAAiC,CAAC"}
|