create-windy 0.2.18 → 0.2.20

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 (84) hide show
  1. package/README.md +4 -0
  2. package/dist/cli.js +752 -186
  3. package/package.json +1 -1
  4. package/template/.windy-template.json +2 -2
  5. package/template/apps/server/src/application-services.ts +2 -2
  6. package/template/apps/server/src/audit/search-summary.ts +15 -8
  7. package/template/apps/server/src/example-modules.ts +58 -70
  8. package/template/apps/server/src/foundation.ts +8 -4
  9. package/template/apps/server/src/guards.test.ts +13 -0
  10. package/template/apps/server/src/guards.ts +12 -2
  11. package/template/apps/server/src/index.ts +48 -91
  12. package/template/apps/server/src/installed-business-modules.ts +23 -4
  13. package/template/apps/server/src/module-bootstrap.ts +83 -0
  14. package/template/apps/server/src/module-composition.test.ts +45 -1
  15. package/template/apps/server/src/module-composition.ts +45 -0
  16. package/template/apps/server/src/module-host/initialize.test.ts +123 -0
  17. package/template/apps/server/src/module-host/initialize.ts +331 -0
  18. package/template/apps/server/src/module-host/request-context.test.ts +66 -0
  19. package/template/apps/server/src/module-host/request-context.ts +156 -0
  20. package/template/apps/server/src/module-host/role-presets.test.ts +94 -0
  21. package/template/apps/server/src/module-host/role-presets.ts +76 -0
  22. package/template/apps/server/src/module-host/route-installer.test.ts +317 -0
  23. package/template/apps/server/src/module-host/route-installer.ts +97 -0
  24. package/template/apps/server/src/module-host/route-registration.test.ts +160 -0
  25. package/template/apps/server/src/module-host/search-providers.ts +31 -0
  26. package/template/apps/server/src/module-host/storage-port.test.ts +165 -0
  27. package/template/apps/server/src/module-host/storage-port.ts +59 -0
  28. package/template/apps/server/src/module-host/task-definitions.ts +41 -0
  29. package/template/apps/server/src/module-host/test-fixtures.ts +26 -0
  30. package/template/apps/server/src/module-host/tool-handlers.ts +25 -0
  31. package/template/apps/server/src/module-host.test.ts +202 -58
  32. package/template/apps/server/src/module-host.ts +34 -113
  33. package/template/apps/server/src/route-guards.test.ts +112 -1
  34. package/template/apps/server/src/runtime-feature.ts +66 -31
  35. package/template/apps/server/src/runtime.test.ts +1 -0
  36. package/template/apps/server/src/runtime.ts +3 -1
  37. package/template/apps/server/src/system/built-in-roles.ts +1 -1
  38. package/template/apps/server/src/work-order/foundation-modules.test.ts +30 -10
  39. package/template/apps/server/src/work-order/routes.test.ts +30 -18
  40. package/template/apps/server/src/work-order/routes.ts +104 -135
  41. package/template/apps/server/src/work-order/search-api.test.ts +14 -3
  42. package/template/apps/server/src/work-order/search-provider.test.ts +23 -15
  43. package/template/apps/server/src/work-order/search-provider.ts +17 -12
  44. package/template/apps/server/src/work-order/task.test.ts +16 -11
  45. package/template/apps/web/Dockerfile +4 -1
  46. package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +4 -1
  47. package/template/apps/web/src/layout/GlobalWatermark.vue +2 -0
  48. package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +4 -1
  49. package/template/docs/architecture/object-storage.md +12 -0
  50. package/template/packages/config/index.test.ts +57 -0
  51. package/template/packages/config/package.json +1 -1
  52. package/template/packages/config/src/platform.ts +28 -1
  53. package/template/packages/database/package.json +1 -1
  54. package/template/packages/database/src/runner.ts +7 -9
  55. package/template/packages/example-work-order/index.ts +1 -0
  56. package/template/packages/example-work-order/package.json +2 -0
  57. package/template/packages/example-work-order/src/server-module.ts +61 -0
  58. package/template/packages/jobs/package.json +1 -1
  59. package/template/packages/jobs/src/types.ts +7 -1
  60. package/template/packages/modules/package.json +1 -1
  61. package/template/packages/modules/src/catalog-validation.test.ts +212 -0
  62. package/template/packages/modules/src/catalog-validation.ts +19 -2
  63. package/template/packages/modules/src/migration-bundle-validation.test.ts +191 -0
  64. package/template/packages/server-sdk/index.ts +42 -0
  65. package/template/packages/server-sdk/package.json +16 -3
  66. package/template/packages/server-sdk/src/actor.ts +15 -0
  67. package/template/packages/server-sdk/src/audit-port.ts +22 -0
  68. package/template/packages/server-sdk/src/jobs-port.ts +24 -0
  69. package/template/packages/server-sdk/src/module-host.ts +36 -1
  70. package/template/packages/server-sdk/src/namespace.test.ts +26 -0
  71. package/template/packages/server-sdk/src/namespace.ts +13 -0
  72. package/template/packages/server-sdk/src/request-context.ts +21 -0
  73. package/template/packages/server-sdk/src/role-preset.ts +18 -0
  74. package/template/packages/server-sdk/src/route-definition.ts +26 -0
  75. package/template/packages/server-sdk/src/search-provider-port.ts +48 -0
  76. package/template/packages/server-sdk/src/storage-port.ts +25 -0
  77. package/template/packages/server-sdk/src/task-registration.ts +18 -0
  78. package/template/packages/server-sdk/src/tool-registration.ts +29 -0
  79. package/template/packages/shared/index.ts +1 -0
  80. package/template/packages/shared/package.json +1 -1
  81. package/template/packages/shared/src/license-catalog.test.ts +73 -1
  82. package/template/packages/shared/src/license-catalog.ts +95 -0
  83. package/template/packages/{database/src/bundle-validation.ts → shared/src/migration-bundle-validation.ts} +2 -1
  84. package/template/apps/server/src/work-order/task.ts +0 -30
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 模块命名空间约束:注册的 Key 必须等于模块名,或以 `<moduleName>.` 开头。
3
+ * 宿主在所有注册入口统一调用,越界注册在 Server 监听前抛错。
4
+ */
5
+ export function requireModuleNamespace(
6
+ kind: string,
7
+ value: string,
8
+ moduleName: string,
9
+ ): void {
10
+ if (value !== moduleName && !value.startsWith(`${moduleName}.`)) {
11
+ throw new Error(`${kind}不属于模块 ${moduleName}:${value}`);
12
+ }
13
+ }
@@ -0,0 +1,21 @@
1
+ import type { ModuleActor } from "./actor.js";
2
+ import type { ModuleAuditEmitter } from "./audit-port.js";
3
+ import type { ModuleJobSubmitter } from "./jobs-port.js";
4
+ import type { ModuleStoragePort } from "./storage-port.js";
5
+
6
+ /**
7
+ * 模块路由 Handler 的受限请求上下文。
8
+ *
9
+ * 不暴露 Elysia 内部对象、数据库连接或任何 Repository;
10
+ * 模块只能经由 Actor 视图与受限 Port(Audit / Jobs / Storage)与平台交互。
11
+ */
12
+ export interface ModuleRequestContext {
13
+ readonly requestId: string;
14
+ readonly actor: ModuleActor;
15
+ readonly params: Record<string, string | undefined>;
16
+ readonly query: Record<string, string | undefined>;
17
+ readonly body?: unknown;
18
+ readonly audit: ModuleAuditEmitter;
19
+ readonly jobs: ModuleJobSubmitter;
20
+ readonly storage: ModuleStoragePort;
21
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 模块贡献的角色预设。
3
+ *
4
+ * `key` 作为角色 Code,必须使用模块命名空间;`permissionKeys` 必须全部属于
5
+ * 本模块 Manifest `permissions` 声明(宿主在启动时校验)。预设随内置角色一起
6
+ * 同步入库:不存在则创建,权限或菜单漂移则收敛,不改变 4 个内置角色语义。
7
+ */
8
+ export interface ModuleRolePreset {
9
+ readonly key: string;
10
+ readonly label: string;
11
+ readonly description?: string;
12
+ readonly dataScope?:
13
+ | "all"
14
+ | "department"
15
+ | "department-and-children"
16
+ | "self";
17
+ readonly permissionKeys: readonly string[];
18
+ }
@@ -0,0 +1,26 @@
1
+ import type { ModuleRequestContext } from "./request-context.js";
2
+
3
+ export type ModuleRouteMethod = "GET" | "POST" | "PUT" | "DELETE";
4
+
5
+ export type ModuleRouteHandler = (
6
+ context: ModuleRequestContext,
7
+ ) => Promise<unknown> | unknown;
8
+
9
+ /**
10
+ * 模块贡献的 API 路由定义。
11
+ *
12
+ * `method` + `path` 必须与本模块 Manifest `apiPermissions` 中的声明一致
13
+ * (path 含 `/api` 前缀);宿主据此绑定 Guard(权限 / Feature / License),
14
+ * 无需模块手工接线。`permissionKey` 必须与 Manifest 绑定一致并属于本模块命名空间。
15
+ *
16
+ * 声明 `auditAction` 时,宿主在 Handler 成功返回后自动写入一条该动作的审计事件;
17
+ * 动作必须是本模块 Manifest `auditActions` 已注册的动作。
18
+ */
19
+ export interface ModuleRouteDefinition {
20
+ readonly method: ModuleRouteMethod;
21
+ readonly path: string;
22
+ readonly permissionKey: string;
23
+ readonly featureKey?: string;
24
+ readonly auditAction?: string;
25
+ readonly handler: ModuleRouteHandler;
26
+ }
@@ -0,0 +1,48 @@
1
+ import type { ModuleActor } from "./actor.js";
2
+
3
+ export interface ModuleSearchQuery {
4
+ readonly text: string;
5
+ readonly limit: number;
6
+ readonly cursor?: string;
7
+ readonly signal: AbortSignal;
8
+ }
9
+
10
+ export interface ModuleSearchContext {
11
+ readonly actor: ModuleActor;
12
+ readonly requestId: string;
13
+ }
14
+
15
+ export interface ModuleSearchHit {
16
+ readonly id: string;
17
+ readonly resourceType: string;
18
+ readonly maskedTitle: string;
19
+ readonly maskedExcerpt?: string;
20
+ readonly score?: number;
21
+ }
22
+
23
+ export interface ModuleSearchPage {
24
+ readonly hits: ModuleSearchHit[];
25
+ readonly nextCursor?: string;
26
+ }
27
+
28
+ export interface ModuleSearchHealth {
29
+ readonly status: "healthy" | "degraded" | "unavailable";
30
+ readonly checkedAt: string;
31
+ readonly message?: string;
32
+ }
33
+
34
+ /**
35
+ * 模块搜索 Provider。
36
+ *
37
+ * `key` 必须在本模块 Manifest `providers` 中声明;返回的标题与摘要必须是
38
+ * 已脱敏文本,明文访问与导出由目标资源与治理导出走各自 Guard。
39
+ */
40
+ export interface ModuleSearchProvider {
41
+ readonly key: string;
42
+ readonly providerVersion: string;
43
+ search(
44
+ query: ModuleSearchQuery,
45
+ context: ModuleSearchContext,
46
+ ): Promise<ModuleSearchPage>;
47
+ health(signal: AbortSignal): Promise<ModuleSearchHealth>;
48
+ }
@@ -0,0 +1,25 @@
1
+ import type { OpenedFile, UploadSession } from "@southwind-ai/storage";
2
+
3
+ export interface ModuleUploadSessionInput {
4
+ readonly purpose: string;
5
+ readonly filename: string;
6
+ readonly mimeType: string;
7
+ readonly expectedByteSize: number;
8
+ readonly expectedSha256?: string;
9
+ readonly fileExpiresAt?: Date;
10
+ }
11
+
12
+ /**
13
+ * 模块受限 Upload / BlobRead Port。
14
+ *
15
+ * - `createUploadSession` 的 purpose 必须是本模块经 Host 注册的 Upload Policy,
16
+ * 跨模块或平台用途一律拒绝;Owner 由宿主以当前 Actor 身份补齐。
17
+ * - `openFile` 走 FileService 的 Owner 约束,且文件用途必须属于本模块。
18
+ * - `openPublicFile` 走 FileService 的 Purpose 约束,用途同样必须属于本模块。
19
+ * - 模块不允许直接接触 BlobStore 或 Upload Repository。
20
+ */
21
+ export interface ModuleStoragePort {
22
+ createUploadSession(input: ModuleUploadSessionInput): Promise<UploadSession>;
23
+ openFile(fileId: string): Promise<OpenedFile>;
24
+ openPublicFile(fileId: string, purpose: string): Promise<OpenedFile>;
25
+ }
@@ -0,0 +1,18 @@
1
+ export type ModuleTaskResult = Record<string, unknown> | void;
2
+
3
+ export type ModuleTaskHandler = () =>
4
+ | Promise<ModuleTaskResult>
5
+ | ModuleTaskResult;
6
+
7
+ /**
8
+ * 模块定时任务的 Handler 注册。
9
+ *
10
+ * `taskKey` 必须在本模块 Manifest `tasks` 中声明;调度元数据(间隔、重试、
11
+ * Feature、权限)一律以 Manifest 为准,这里只提供执行体。
12
+ */
13
+ export interface ModuleTaskHandlerRegistration {
14
+ readonly taskKey: string;
15
+ readonly handler: ModuleTaskHandler;
16
+ /** 判断执行结果是否为空转(用于调度指标统计),与平台任务语义一致。 */
17
+ readonly isIdleResult?: (value: ModuleTaskResult) => boolean;
18
+ }
@@ -0,0 +1,29 @@
1
+ import type { ModuleActor } from "./actor.js";
2
+
3
+ export interface ModuleToolContext {
4
+ readonly actor: ModuleActor;
5
+ readonly requestId: string;
6
+ }
7
+
8
+ export interface ModuleToolResult {
9
+ readonly items: unknown[];
10
+ readonly total: number;
11
+ readonly page: number;
12
+ readonly pageSize: number;
13
+ }
14
+
15
+ /**
16
+ * 模块 Agent Tool 的 Handler 注册。
17
+ *
18
+ * `toolKey` 必须在本模块 Manifest `tools` 中声明;Guard(权限 / Feature /
19
+ * 数据范围)由宿主在 Agent 路由层统一执行。`scopeMode: "request"` 的 Handler
20
+ * 只会在持有服务端数据范围上下文的 Actor 下执行。
21
+ */
22
+ export interface ModuleToolHandlerRegistration {
23
+ readonly toolKey: string;
24
+ readonly scopeMode: "platform" | "request";
25
+ execute(
26
+ query: Record<string, string | undefined>,
27
+ context: ModuleToolContext,
28
+ ): Promise<ModuleToolResult> | ModuleToolResult;
29
+ }
@@ -5,6 +5,7 @@ export * from "./src/feature.js";
5
5
  export * from "./src/license.js";
6
6
  export * from "./src/license-catalog.js";
7
7
  export * from "./src/migration.js";
8
+ export * from "./src/migration-bundle-validation.js";
8
9
  export * from "./src/module-configuration.js";
9
10
  export * from "./src/correlation.js";
10
11
  export * from "./src/notification.js";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/shared",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,5 +1,13 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { LICENSE_CATALOG } from "./license-catalog.js";
2
+ import {
3
+ LICENSE_CATALOG,
4
+ parseLicenseCatalog,
5
+ parseLicenseCatalogJson,
6
+ resolveLicenseCatalog,
7
+ validateLicenseCatalog,
8
+ type LicenseCatalogDefinition,
9
+ } from "./license-catalog.js";
10
+ import { licenseVersionKey } from "./license.js";
3
11
 
4
12
  describe("License Catalog", () => {
5
13
  test("提供稳定且有序的默认产品版本", () => {
@@ -14,4 +22,68 @@ describe("License Catalog", () => {
14
22
  ),
15
23
  ).toBe(true);
16
24
  });
25
+
26
+ test("默认目录自身通过校验", () => {
27
+ expect(() => validateLicenseCatalog(LICENSE_CATALOG)).not.toThrow();
28
+ });
29
+
30
+ test("重复 Key 或重复 order 时校验报错", () => {
31
+ const duplicatedKey: LicenseCatalogDefinition[] = [
32
+ entry("basic", 10),
33
+ entry("basic", 20),
34
+ ];
35
+ const duplicatedOrder: LicenseCatalogDefinition[] = [
36
+ entry("basic", 10),
37
+ entry("pro", 10),
38
+ ];
39
+
40
+ expect(() => validateLicenseCatalog(duplicatedKey)).toThrow(
41
+ "License 版本目录 Key 重复:basic",
42
+ );
43
+ expect(() => validateLicenseCatalog(duplicatedOrder)).toThrow(
44
+ "License 版本目录 order 重复:10",
45
+ );
46
+ expect(() => validateLicenseCatalog([])).toThrow(
47
+ "License 版本目录不能为空",
48
+ );
49
+ expect(() => validateLicenseCatalog([entry("", 10)])).toThrow(
50
+ "License 版本目录的 Key 与名称不能为空",
51
+ );
52
+ });
53
+
54
+ test("resolveLicenseCatalog 未配置时回退默认目录,配置后校验并返回", () => {
55
+ expect(resolveLicenseCatalog()).toBe(LICENSE_CATALOG);
56
+
57
+ const custom = [entry("oem-basic", 10), entry("oem-pro", 20)];
58
+ expect(resolveLicenseCatalog(custom)).toBe(custom);
59
+ expect(() =>
60
+ resolveLicenseCatalog([entry("dup", 1), entry("dup", 2)]),
61
+ ).toThrow("License 版本目录 Key 重复:dup");
62
+ });
63
+
64
+ test("共享解析器统一归一化字段并拒绝结构漂移", () => {
65
+ expect(
66
+ parseLicenseCatalog([{ key: " oem ", label: " OEM 版 ", order: 10 }]),
67
+ ).toEqual([{ key: "oem", label: "OEM 版", description: "", order: 10 }]);
68
+ expect(() =>
69
+ parseLicenseCatalog([{ key: "oem", label: "OEM", order: 1.5 }]),
70
+ ).toThrow("order 必须是安全整数");
71
+ expect(() =>
72
+ parseLicenseCatalog([
73
+ { key: "oem", label: "OEM", order: 10, legacy: true },
74
+ ]),
75
+ ).toThrow("License 版本目录包含未知字段:legacy");
76
+ expect(() => parseLicenseCatalogJson("invalid", "宿主目录")).toThrow(
77
+ "宿主目录 必须是合法 JSON 数组",
78
+ );
79
+ });
17
80
  });
81
+
82
+ function entry(key: string, order: number): LicenseCatalogDefinition {
83
+ return {
84
+ key: licenseVersionKey(key),
85
+ label: key || "占位",
86
+ description: `${key} 版本`,
87
+ order,
88
+ };
89
+ }
@@ -7,6 +7,8 @@ export interface LicenseCatalogDefinition {
7
7
  order: number;
8
8
  }
9
9
 
10
+ const licenseCatalogFields = new Set(["key", "label", "description", "order"]);
11
+
10
12
  export const LICENSE_CATALOG = [
11
13
  {
12
14
  key: licenseVersionKey("free-trial"),
@@ -31,3 +33,96 @@ export const LICENSE_CATALOG = [
31
33
  export function licenseCatalogKeys(): ReadonlySet<LicenseVersionKey> {
32
34
  return new Set(LICENSE_CATALOG.map(({ key }) => key));
33
35
  }
36
+
37
+ /** 校验宿主配置的 License 版本目录:Key 与 order 均唯一,目录非空。 */
38
+ export function validateLicenseCatalog(
39
+ definitions: readonly LicenseCatalogDefinition[],
40
+ ): void {
41
+ if (definitions.length === 0) {
42
+ throw new Error("License 版本目录不能为空");
43
+ }
44
+ const keys = new Set<string>();
45
+ const orders = new Set<number>();
46
+ for (const definition of definitions) {
47
+ if (!definition.key.trim() || !definition.label.trim()) {
48
+ throw new Error("License 版本目录的 Key 与名称不能为空");
49
+ }
50
+ if (!Number.isSafeInteger(definition.order)) {
51
+ throw new Error(`License 版本 ${definition.key} 的 order 必须是安全整数`);
52
+ }
53
+ if (keys.has(definition.key)) {
54
+ throw new Error(`License 版本目录 Key 重复:${definition.key}`);
55
+ }
56
+ if (orders.has(definition.order)) {
57
+ throw new Error(
58
+ `License 版本目录 order 重复:${definition.order}(${definition.key})`,
59
+ );
60
+ }
61
+ keys.add(definition.key);
62
+ orders.add(definition.order);
63
+ }
64
+ }
65
+
66
+ /** 从未知输入解析并归一化宿主 License 版本目录。 */
67
+ export function parseLicenseCatalog(
68
+ input: unknown,
69
+ ): LicenseCatalogDefinition[] {
70
+ if (!Array.isArray(input)) {
71
+ throw new Error("License 版本目录必须是数组");
72
+ }
73
+ const catalog = input.map((entry, index) => {
74
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
75
+ throw new Error(`License 版本目录第 ${index + 1} 项必须是对象`);
76
+ }
77
+ const record = entry as Record<string, unknown>;
78
+ const unknownField = Object.keys(record).find(
79
+ (key) => !licenseCatalogFields.has(key),
80
+ );
81
+ if (unknownField) {
82
+ throw new Error(`License 版本目录包含未知字段:${unknownField}`);
83
+ }
84
+ if (
85
+ typeof record.key !== "string" ||
86
+ typeof record.label !== "string" ||
87
+ (record.description !== undefined &&
88
+ typeof record.description !== "string") ||
89
+ typeof record.order !== "number"
90
+ ) {
91
+ throw new Error(`License 版本目录第 ${index + 1} 项字段类型无效`);
92
+ }
93
+ return {
94
+ key: licenseVersionKey(record.key.trim()),
95
+ label: record.label.trim(),
96
+ description: record.description?.trim() ?? "",
97
+ order: record.order,
98
+ };
99
+ });
100
+ validateLicenseCatalog(catalog);
101
+ return catalog;
102
+ }
103
+
104
+ /** 解析环境变量 JSON,错误消息不包含原始配置内容。 */
105
+ export function parseLicenseCatalogJson(
106
+ raw: string,
107
+ source = "License 版本目录",
108
+ ): LicenseCatalogDefinition[] {
109
+ let parsed: unknown;
110
+ try {
111
+ parsed = JSON.parse(raw);
112
+ } catch {
113
+ throw new Error(`${source} 必须是合法 JSON 数组`);
114
+ }
115
+ if (!Array.isArray(parsed)) {
116
+ throw new Error(`${source} 必须是合法 JSON 数组`);
117
+ }
118
+ return parseLicenseCatalog(parsed);
119
+ }
120
+
121
+ /** 有宿主配置时校验并返回,未配置时回退平台默认三级目录。 */
122
+ export function resolveLicenseCatalog(
123
+ configured?: readonly LicenseCatalogDefinition[],
124
+ ): readonly LicenseCatalogDefinition[] {
125
+ if (!configured) return LICENSE_CATALOG;
126
+ validateLicenseCatalog(configured);
127
+ return configured;
128
+ }
@@ -4,7 +4,7 @@ import {
4
4
  type MigrationBundle,
5
5
  type MigrationDefinition,
6
6
  type MigrationManifestSource,
7
- } from "@southwind-ai/shared";
7
+ } from "./migration.js";
8
8
 
9
9
  export function normalizeManifestBundles(
10
10
  modules: readonly MigrationManifestSource[],
@@ -126,6 +126,7 @@ export function validateAndOrderBundles(
126
126
  }
127
127
  }
128
128
  validateMigrationDependencies(bundle, migrationOwner);
129
+ orderBundleMigrations(bundle);
129
130
  }
130
131
  return topologicalBundles(byModule);
131
132
  }
@@ -1,30 +0,0 @@
1
- import { workOrderModule } from "@southwind-ai/example-work-order";
2
- import type { FeatureFlagDefinition } from "@southwind-ai/shared";
3
- import type { ScheduledTaskDefinition } from "../scheduler/types.js";
4
- import type { WorkOrderService } from "./service.js";
5
- import { platformDataActor } from "../data-access/platform-actor.js";
6
-
7
- export function createWorkOrderTaskDefinitions(
8
- features: FeatureFlagDefinition[],
9
- service: WorkOrderService,
10
- ): ScheduledTaskDefinition[] {
11
- const manifest = workOrderModule();
12
- const feature = features.find(({ key }) => key === "work-order.ticket");
13
- if (!feature) return [];
14
- const actor = platformDataActor(
15
- "task_work_order_snapshot",
16
- "工单运营快照任务",
17
- );
18
-
19
- return manifest.tasks.map((task) => ({
20
- key: task.key,
21
- label: task.label,
22
- description: task.description,
23
- intervalSeconds: task.intervalSeconds,
24
- maxAttempts: task.maxAttempts,
25
- retryDelaySeconds: task.retryDelaySeconds,
26
- permissionKey: task.permissionKey,
27
- feature,
28
- handler: () => service.operationalSnapshot(actor),
29
- }));
30
- }