create-windy 0.2.19 → 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 +312 -154
  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
@@ -1,5 +1,8 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { workOrderModule, type WorkOrder } from "@southwind-ai/example-work-order";
2
+ import {
3
+ workOrderModule,
4
+ type WorkOrder,
5
+ } from "@southwind-ai/example-work-order";
3
6
  import type { ModuleManifest } from "@southwind-ai/modules";
4
7
  import { licenseVersionKey } from "@southwind-ai/shared";
5
8
  import { buildScopeContext } from "../data-access/context.js";
@@ -11,6 +14,7 @@ import type { SearchProvider } from "../search/contracts.js";
11
14
  import { SearchService } from "../search/service.js";
12
15
  import type { ManagedDepartment } from "../system/entities.js";
13
16
  import { InMemoryEntityRepository } from "../system/repository.js";
17
+ import { adaptModuleSearchProvider } from "../module-host.js";
14
18
  import { workOrderDataScopeBinding } from "./repository.js";
15
19
  import {
16
20
  WorkOrderSearchProvider,
@@ -40,10 +44,13 @@ describe("示例工单 SearchProvider", () => {
40
44
  throw new Error("搜索不得调用裸 Repository.list");
41
45
  };
42
46
  const service = searchService([
43
- new WorkOrderSearchProvider(
44
- new WorkOrderService(
45
- repository,
46
- new InMemoryEntityRepository<ManagedDepartment>(),
47
+ adaptModuleSearchProvider(
48
+ new WorkOrderSearchProvider(
49
+ () =>
50
+ new WorkOrderService(
51
+ repository,
52
+ new InMemoryEntityRepository<ManagedDepartment>(),
53
+ ),
47
54
  ),
48
55
  ),
49
56
  ]);
@@ -84,17 +91,18 @@ describe("示例工单 SearchProvider", () => {
84
91
  });
85
92
 
86
93
  test("Provider 游标映射到 scoped Repository 分页", async () => {
87
- const provider = new WorkOrderSearchProvider(
88
- new WorkOrderService(
89
- new InMemoryScopedEntityRepository<WorkOrder>(
90
- [
91
- ticket("ticket_1", "user_self", "故障一"),
92
- ticket("ticket_2", "user_self", "故障二"),
93
- ],
94
- workOrderDataScopeBinding,
95
- ),
96
- new InMemoryEntityRepository<ManagedDepartment>(),
94
+ const workOrders = new WorkOrderService(
95
+ new InMemoryScopedEntityRepository<WorkOrder>(
96
+ [
97
+ ticket("ticket_1", "user_self", "故障一"),
98
+ ticket("ticket_2", "user_self", "故障二"),
99
+ ],
100
+ workOrderDataScopeBinding,
97
101
  ),
102
+ new InMemoryEntityRepository<ManagedDepartment>(),
103
+ );
104
+ const provider = adaptModuleSearchProvider(
105
+ new WorkOrderSearchProvider(() => workOrders),
98
106
  );
99
107
  const service = searchService([provider]);
100
108
  const first = await service.search(
@@ -1,9 +1,10 @@
1
1
  import type {
2
- SearchExecutionContext,
3
- SearchProvider,
4
- SearchProviderPage,
5
- SearchProviderQuery,
6
- } from "../search/contracts.js";
2
+ ModuleSearchContext,
3
+ ModuleSearchPage,
4
+ ModuleSearchProvider,
5
+ ModuleSearchQuery,
6
+ } from "@southwind-ai/server-sdk";
7
+ import { requestActorFromModule } from "../module-host/request-context.js";
7
8
  import { SearchProtocolError } from "../search/errors.js";
8
9
  import { maskSearchText } from "../search/masking.js";
9
10
  import type { WorkOrderService } from "./service.js";
@@ -11,21 +12,25 @@ import type { WorkOrderService } from "./service.js";
11
12
  export const WORK_ORDER_SEARCH_PROVIDER_KEY = "work-order.ticket.search";
12
13
  export const WORK_ORDER_SEARCH_PROVIDER_VERSION = "0.1.0";
13
14
 
14
- export class WorkOrderSearchProvider implements SearchProvider {
15
+ /**
16
+ * 示例工单搜索 Provider,实现 server-sdk 的受限 Provider 协议。
17
+ * `requireService` 延迟解析宿主装配完成后才存在的模块服务。
18
+ */
19
+ export class WorkOrderSearchProvider implements ModuleSearchProvider {
15
20
  readonly key = WORK_ORDER_SEARCH_PROVIDER_KEY;
16
21
  readonly providerVersion = WORK_ORDER_SEARCH_PROVIDER_VERSION;
17
22
 
18
- constructor(private readonly service: WorkOrderService) {}
23
+ constructor(private readonly requireService: () => WorkOrderService) {}
19
24
 
20
25
  async search(
21
- request: SearchProviderQuery,
22
- context: SearchExecutionContext,
23
- ): Promise<SearchProviderPage> {
26
+ request: ModuleSearchQuery,
27
+ context: ModuleSearchContext,
28
+ ): Promise<ModuleSearchPage> {
24
29
  if (request.signal.aborted) throw new Error("search aborted");
25
30
  const page = parseProviderCursor(request.cursor);
26
- const result = await this.service.list(
31
+ const result = await this.requireService().list(
27
32
  { q: request.text, page, pageSize: request.limit },
28
- context.actor,
33
+ requestActorFromModule(context.actor),
29
34
  );
30
35
  if (request.signal.aborted) throw new Error("search aborted");
31
36
  return {
@@ -1,21 +1,30 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import type { WorkOrder } from "@southwind-ai/example-work-order";
2
+ import { type WorkOrder } from "@southwind-ai/example-work-order";
3
+ import { createWorkOrderModule } from "../example-modules.js";
3
4
  import { createFoundationSnapshot } from "../foundation.js";
5
+ import {
6
+ createModuleTaskDefinitions,
7
+ initializeModuleHosts,
8
+ } from "../module-host.js";
4
9
  import { createServerRuntime } from "../runtime.js";
5
10
  import { InMemorySchedulerRepository } from "../scheduler/repository.js";
6
11
  import { SchedulerService } from "../scheduler/service.js";
7
12
  import type { ManagedDepartment } from "../system/entities.js";
8
13
  import { InMemoryEntityRepository } from "../system/repository.js";
9
14
  import { InMemoryScopedEntityRepository } from "../data-access/scoped-repository.js";
10
- import { WorkOrderService } from "./service.js";
11
15
  import { workOrderDataScopeBinding } from "./repository.js";
12
- import { createWorkOrderTaskDefinitions } from "./task.js";
13
16
 
14
17
  describe("示例工单后台任务", () => {
15
18
  test("通过统一调度 Guard 执行并把业务快照写入历史", async () => {
16
19
  const runtime = createServerRuntime({ WINDY_DEV_ADMIN_TOKEN: "token" });
17
- const service = new WorkOrderService(
18
- new InMemoryScopedEntityRepository<WorkOrder>(
20
+ const module = createWorkOrderModule();
21
+ const hosts = await initializeModuleHosts({
22
+ modules: [module.manifest],
23
+ installations: [module],
24
+ });
25
+ module.attach({
26
+ departments: new InMemoryEntityRepository<ManagedDepartment>(),
27
+ repository: new InMemoryScopedEntityRepository<WorkOrder>(
19
28
  [
20
29
  item("ticket_high", "high", "open"),
21
30
  item("ticket_progress", "medium", "in-progress"),
@@ -23,14 +32,10 @@ describe("示例工单后台任务", () => {
23
32
  ],
24
33
  workOrderDataScopeBinding,
25
34
  ),
26
- new InMemoryEntityRepository<ManagedDepartment>(),
27
- );
35
+ });
28
36
  const scheduler = new SchedulerService(
29
37
  new InMemorySchedulerRepository(),
30
- createWorkOrderTaskDefinitions(
31
- createFoundationSnapshot().features,
32
- service,
33
- ),
38
+ createModuleTaskDefinitions(hosts, createFoundationSnapshot().features),
34
39
  runtime,
35
40
  );
36
41
  await scheduler.initialize();
@@ -4,6 +4,7 @@ FROM oven/bun:1.3.14-alpine AS deps
4
4
  WORKDIR /app
5
5
 
6
6
  COPY package.json bun.lock ./
7
+ # windy-copy:deps begin
7
8
  COPY apps/license/package.json apps/license/package.json
8
9
  COPY apps/server/package.json apps/server/package.json
9
10
  COPY apps/signing-service/package.json apps/signing-service/package.json
@@ -20,14 +21,16 @@ COPY packages/modules/package.json packages/modules/package.json
20
21
  COPY packages/server-sdk/package.json packages/server-sdk/package.json
21
22
  COPY packages/shared/package.json packages/shared/package.json
22
23
  COPY packages/storage/package.json packages/storage/package.json
24
+ # windy-copy:deps end
23
25
  RUN bun install --frozen-lockfile
24
26
 
25
27
  FROM deps AS build
28
+ # windy-copy:build begin
26
29
  COPY apps/web apps/web
27
- COPY packages/example-work-order packages/example-work-order
28
30
  COPY packages/license-sdk packages/license-sdk
29
31
  COPY packages/modules packages/modules
30
32
  COPY packages/shared packages/shared
33
+ # windy-copy:build end
31
34
  RUN bun run --cwd apps/web build
32
35
 
33
36
  FROM deps AS runner
@@ -42,7 +42,10 @@ const state = vi.hoisted(() => {
42
42
  });
43
43
 
44
44
  vi.mock("@/composables/useAccessSnapshot", () => ({
45
- useAccessSnapshot: () => ({ snapshot: state.access }),
45
+ useAccessSnapshot: () => ({
46
+ snapshot: state.access,
47
+ ensureLoaded: vi.fn(async () => state.access.value),
48
+ }),
46
49
  }));
47
50
 
48
51
  vi.mock("@/composables/useAuthSession", () => ({
@@ -18,6 +18,8 @@ const access = useAccessSnapshot();
18
18
  const watermark = useWatermarkSettings(auth.actor);
19
19
  onMounted(() => {
20
20
  void watermark.ensureLoaded();
21
+ // 业务页路由守卫不会预取访问快照,这里兜底加载,失败时保持水印隐藏。
22
+ void access.ensureLoaded().catch(() => undefined);
21
23
  });
22
24
  const allowed = computed(() => {
23
25
  const snapshot = access.snapshot.value;
@@ -45,7 +45,10 @@ const state = vi.hoisted(() => ({
45
45
  }));
46
46
 
47
47
  vi.mock("@/composables/useAccessSnapshot", () => ({
48
- useAccessSnapshot: () => ({ snapshot: state.access }),
48
+ useAccessSnapshot: () => ({
49
+ snapshot: state.access,
50
+ ensureLoaded: vi.fn(async () => state.access.value),
51
+ }),
49
52
  }));
50
53
 
51
54
  vi.mock("@/composables/useAuthSession", () => ({
@@ -58,8 +58,20 @@ Logo 使用 `branding-logo` 上传策略。匿名 `/api/platform/files/:id` 只
58
58
  - 删除为幂等操作;第一次存在时返回 `true`,重复删除返回 `false`。
59
59
  - 进程在 Blob 发布与元数据发布之间退出时可能产生孤儿内容;上传生命周期模块基于过期时间、引用集合与 grace period 清理。
60
60
 
61
+ ## 模块受限 Upload/BlobRead Port
62
+
63
+ 业务模块不直接使用 `UploadService` / `FileService`,而是经 `ModuleRequestContext.storage` 获得 `ModuleStoragePort`(宿主实现见 `apps/server/src/module-host/storage-port.ts`):
64
+
65
+ - `createUploadSession` 的 purpose 强制属于本模块经 Host 注册的 Upload Policy;跨模块用途和平台基础用途(如 `bulk-data`、`branding-logo`)一律以 `UPLOAD_FORBIDDEN` 拒绝。Owner 由宿主以当前 Actor 补齐,模块无法代他人创建会话。
66
+ - `openFile` 走 `FileService.open` 的 Owner 约束,并在返回前校验文件用途属于本模块——即使是本人文件,用途越界也拒绝。
67
+ - `openPublicFile` 走 `FileService.openPublic` 的 Purpose 约束,且只允许本模块已注册用途。
68
+ - 模块不直接接触 `BlobStore` 或 Upload Repository;宿主未挂载存储服务时,任何访问都以明确错误失败,不静默绕过。
69
+
70
+ 用途唯一性仍在 Host 注册时全局校验:命名空间重叠的模块(如 `shop` 与 `shop.sub`)注册同一 purpose 会在启动时失败。
71
+
61
72
  ## 当前切片
62
73
 
63
74
  - F1:Blob 端口、SHA-256、随机引用和本地流式 Adapter 已完成。
64
75
  - F2:上传会话、分片续传、文件元数据、校验和清理已完成。
65
76
  - F3:服务端授权下载、审计、bulk artifact 与 Logo 接入已完成。
77
+ - F4:模块受限 Upload/BlobRead Port 已完成,业务模块经 Host 注册用途并受命名空间约束。
@@ -49,6 +49,63 @@ test("strict loader 对 unknown 与错误类型 fail-fast 且不回显输入值"
49
49
  }
50
50
  });
51
51
 
52
+ test("WINDY_LICENSE_VERSIONS 解析宿主自定义 License 版本目录", () => {
53
+ const config = loadPlatformConfig({
54
+ WINDY_LICENSE_VERSIONS: JSON.stringify([
55
+ { key: "oem-basic", label: "OEM 基础版", order: 10 },
56
+ {
57
+ key: "oem-pro",
58
+ label: "OEM 专业版",
59
+ description: "含高级治理能力",
60
+ order: 20,
61
+ },
62
+ ]),
63
+ });
64
+
65
+ expect(config.license.versions).toEqual([
66
+ { key: "oem-basic", label: "OEM 基础版", description: "", order: 10 },
67
+ {
68
+ key: "oem-pro",
69
+ label: "OEM 专业版",
70
+ description: "含高级治理能力",
71
+ order: 20,
72
+ },
73
+ ]);
74
+ expect(loadPlatformConfig({}).license.versions).toBeUndefined();
75
+ });
76
+
77
+ test("WINDY_LICENSE_VERSIONS 非法 JSON 或非法结构时 fail-fast", () => {
78
+ expect(() =>
79
+ loadPlatformConfig({ WINDY_LICENSE_VERSIONS: "not-a-json" }),
80
+ ).toThrow(
81
+ expect.objectContaining<Partial<ConfigLoadError>>({
82
+ code: "CONFIG_INVALID_VALUE",
83
+ fieldPath: "license.versions",
84
+ }),
85
+ );
86
+ expect(() =>
87
+ loadPlatformConfig({
88
+ WINDY_LICENSE_VERSIONS: JSON.stringify([{ key: "missing-order" }]),
89
+ }),
90
+ ).toThrow(
91
+ expect.objectContaining<Partial<ConfigLoadError>>({
92
+ code: "CONFIG_INVALID_VALUE",
93
+ }),
94
+ );
95
+ expect(() =>
96
+ loadPlatformConfig({
97
+ WINDY_LICENSE_VERSIONS: JSON.stringify([
98
+ { key: "fractional", label: "小数版本", order: 1.5 },
99
+ ]),
100
+ }),
101
+ ).toThrow(
102
+ expect.objectContaining<Partial<ConfigLoadError>>({
103
+ code: "CONFIG_INVALID_VALUE",
104
+ fieldPath: "license.versions",
105
+ }),
106
+ );
107
+ });
108
+
52
109
  test("必填 Secret 缺失时 fail-fast 且 registry 不暴露环境引用", () => {
53
110
  const definition = defineModuleConfig({
54
111
  key: "example.secure",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/config",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,4 +1,11 @@
1
- import type { DatabaseDialect } from "@southwind-ai/shared";
1
+ import type {
2
+ DatabaseDialect,
3
+ LicenseCatalogDefinition,
4
+ } from "@southwind-ai/shared";
5
+ import {
6
+ parseLicenseCatalog,
7
+ parseLicenseCatalogJson,
8
+ } from "@southwind-ai/shared";
2
9
  import { z } from "zod";
3
10
  import { defineModuleConfig, ModuleConfigRegistry } from "./definition.js";
4
11
  import {
@@ -33,6 +40,8 @@ export interface PlatformConfig {
33
40
  publicKeyId?: string;
34
41
  publicKey?: string;
35
42
  machineCode?: string;
43
+ /** 宿主自定义 License 版本目录;未配置时回退 shared 默认三级目录。 */
44
+ versions?: LicenseCatalogDefinition[];
36
45
  };
37
46
  audit: { enabled: boolean; redactFields: string[] };
38
47
  compatibility: {
@@ -75,6 +84,20 @@ const platformSchema: z.ZodType<PlatformConfig> = z.strictObject({
75
84
  publicKeyId: z.string().trim().min(1).optional(),
76
85
  publicKey: z.string().trim().min(1).optional(),
77
86
  machineCode: z.string().trim().min(1).optional(),
87
+ versions: z
88
+ .unknown()
89
+ .transform((value, context) => {
90
+ try {
91
+ return parseLicenseCatalog(value);
92
+ } catch (error) {
93
+ context.addIssue({
94
+ code: "custom",
95
+ message: error instanceof Error ? error.message : "版本目录无效",
96
+ });
97
+ return z.NEVER;
98
+ }
99
+ })
100
+ .optional(),
78
101
  }),
79
102
  audit: z.strictObject({
80
103
  enabled: z.boolean(),
@@ -105,6 +128,7 @@ export const platformConfigDefinition = defineModuleConfig<PlatformConfig>({
105
128
  "LICENSE_PUBLIC_KEY_ID",
106
129
  "LICENSE_PUBLIC_KEY",
107
130
  "LICENSE_MACHINE_CODE",
131
+ "WINDY_LICENSE_VERSIONS",
108
132
  ],
109
133
  environment: [
110
134
  value("PLATFORM_NAME", "name", "Windy Platform"),
@@ -133,6 +157,9 @@ export const platformConfigDefinition = defineModuleConfig<PlatformConfig>({
133
157
  value("LICENSE_PUBLIC_KEY_ID", "license.publicKeyId"),
134
158
  value("LICENSE_PUBLIC_KEY", "license.publicKey"),
135
159
  secret("LICENSE_MACHINE_CODE", "license.machineCode"),
160
+ value("WINDY_LICENSE_VERSIONS", "license.versions", undefined, (raw) =>
161
+ parseLicenseCatalogJson(raw, "WINDY_LICENSE_VERSIONS"),
162
+ ),
136
163
  value("AUDIT_ENABLED", "audit.enabled", true, strictBoolean),
137
164
  value(
138
165
  "AUDIT_REDACT_FIELDS",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/database",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,16 +1,14 @@
1
- import type {
2
- BundleMigrationDefinition,
3
- DatabaseCompatibilityAdapter,
4
- DatabaseDialect,
5
- MigrationBundle,
6
- MigrationManifestSource,
7
- MigrationStatement,
8
- } from "@southwind-ai/shared";
9
1
  import {
10
2
  normalizeManifestBundles,
11
3
  orderBundleMigrations,
12
4
  validateAndOrderBundles,
13
- } from "./bundle-validation.js";
5
+ type BundleMigrationDefinition,
6
+ type DatabaseCompatibilityAdapter,
7
+ type DatabaseDialect,
8
+ type MigrationBundle,
9
+ type MigrationManifestSource,
10
+ type MigrationStatement,
11
+ } from "@southwind-ai/shared";
14
12
  import { createDialectAdapter } from "./dialects.js";
15
13
  import type { MigrationHistoryEntry, MigrationHistoryStore } from "./store.js";
16
14
 
@@ -2,3 +2,4 @@ export * from "./src/contract.js";
2
2
  export * from "./src/manifest.js";
3
3
  export * from "./src/migration-bundle.js";
4
4
  export * from "./src/schema.js";
5
+ export * from "./src/server-module.js";
@@ -4,6 +4,8 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "dependencies": {
7
+ "@southwind-ai/modules": "workspace:*",
8
+ "@southwind-ai/server-sdk": "workspace:*",
7
9
  "@southwind-ai/shared": "workspace:*",
8
10
  "zod": "^4.4.3"
9
11
  },
@@ -0,0 +1,61 @@
1
+ import type { ModuleManifest } from "@southwind-ai/modules";
2
+ import type {
3
+ InstallableServerModule,
4
+ ModuleRouteDefinition,
5
+ ModuleSearchProvider,
6
+ ModuleTaskHandler,
7
+ } from "@southwind-ai/server-sdk";
8
+ import { workOrderModule } from "./manifest.js";
9
+
10
+ export interface WorkOrderServerBindings {
11
+ readonly routes: readonly ModuleRouteDefinition[];
12
+ readonly snapshot: ModuleTaskHandler;
13
+ readonly searchProvider: ModuleSearchProvider;
14
+ }
15
+
16
+ /**
17
+ * 工单业务包拥有 Manifest 与 Host 注册过程;宿主只注入具体业务实现。
18
+ */
19
+ export function createWorkOrderServerModule(
20
+ bindings: WorkOrderServerBindings,
21
+ ): InstallableServerModule<ModuleManifest> {
22
+ return {
23
+ manifest: workOrderModule(),
24
+ setup(host) {
25
+ host.registerUploadPolicy({
26
+ purpose: "work-order.ticket.attachment",
27
+ maxByteSize: 20 * 1024 * 1024,
28
+ maxPartByteSize: 4 * 1024 * 1024,
29
+ sessionTtlMs: 2 * 60 * 60_000,
30
+ allowedTypes: {
31
+ "application/pdf": ["pdf"],
32
+ "image/png": ["png"],
33
+ "image/jpeg": ["jpg", "jpeg"],
34
+ "text/plain": ["txt"],
35
+ },
36
+ signature: "none",
37
+ });
38
+ host.registerRoutes(bindings.routes);
39
+ host.registerTaskHandlers([
40
+ {
41
+ taskKey: "work-order.ticket.snapshot",
42
+ handler: bindings.snapshot,
43
+ },
44
+ ]);
45
+ host.registerSearchProviders([bindings.searchProvider]);
46
+ host.registerRolePresets([
47
+ {
48
+ key: "work-order.ticket-manager",
49
+ label: "工单管理员",
50
+ description: "管理本部门及下级部门工单的模块角色预设。",
51
+ dataScope: "department-and-children",
52
+ permissionKeys: [
53
+ "work-order.ticket.create",
54
+ "work-order.ticket.manage",
55
+ "work-order.ticket.read",
56
+ ],
57
+ },
58
+ ]);
59
+ },
60
+ };
61
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/jobs",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {
@@ -179,8 +179,14 @@ export interface SubmitDurableJobResult {
179
179
  created: boolean;
180
180
  }
181
181
 
182
- export interface DurableJobs {
182
+ /**
183
+ * 只保留提交能力的窄接口,用于向模块等受限调用方暴露 Durable Jobs。
184
+ */
185
+ export interface DurableJobSubmitter {
183
186
  submit(input: SubmitDurableJob): Promise<SubmitDurableJobResult>;
187
+ }
188
+
189
+ export interface DurableJobs extends DurableJobSubmitter {
184
190
  get(id: string): Promise<DurableJobSnapshot | undefined>;
185
191
  list(requestedBy: string, limit?: number): Promise<DurableJob[]>;
186
192
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/modules",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {