@nextclaw/app-runtime 0.11.0 → 0.12.0
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/README.md +19 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/package.js +1 -1
- package/dist/services/app-instance-inventory.service.d.ts +31 -0
- package/dist/services/app-instance-inventory.service.d.ts.map +1 -0
- package/dist/services/app-instance-inventory.service.js +136 -0
- package/dist/services/app-instance-inventory.service.js.map +1 -0
- package/dist/services/app-instance-storage.service.d.ts +6 -0
- package/dist/services/app-instance-storage.service.d.ts.map +1 -1
- package/dist/services/app-instance-storage.service.js +14 -1
- package/dist/services/app-instance-storage.service.js.map +1 -1
- package/dist/types/app-storage.types.d.ts +28 -1
- package/dist/types/app-storage.types.d.ts.map +1 -1
- package/dist/types/app-storage.types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- `napp publish <app-dir>`:把应用目录发布到官方 apps registry;独立应用默认使用 `source`,组合包默认使用 `bundle`
|
|
16
16
|
- `napp install <app-dir|bundle.napp|app-id[@version]>`:从本地或 registry 安装应用
|
|
17
17
|
- `napp update <app-id>`:更新已安装应用
|
|
18
|
-
- `napp uninstall <app-id
|
|
18
|
+
- `napp uninstall <app-id>`:卸载已安装应用,默认保留受管实例数据;增加 `--purge-data` 才会同时删除
|
|
19
19
|
- `napp list`:列出已安装应用
|
|
20
20
|
- `napp info <app-id>`:查看已安装应用详情
|
|
21
21
|
- `napp registry [get|set|reset]`:查看或切换 registry
|
|
@@ -61,7 +61,9 @@ assets/
|
|
|
61
61
|
- 当前 Wasm 执行底座:Node 原生 `WebAssembly` 与 Wasmtime `serve`
|
|
62
62
|
- 分发包形态:`.napp`(底层为 zip,支持 `source` / `bundle` 双模式)
|
|
63
63
|
- 安装目录:`~/.nextclaw/apps/packages/<app-id>/<version>/`
|
|
64
|
-
-
|
|
64
|
+
- 受管实例目录:`~/.nextclaw/apps/instances/<app-id>/default/`
|
|
65
|
+
- 受管实例分类:`data/`、`config/`、`state/`、`cache/`、`tmp/`、`logs/`,并由 `metadata.json` 记录稳定身份
|
|
66
|
+
- 旧用户数据目录:`~/.nextclaw/apps/data/<app-id>/`(首次使用时原子迁移到受管实例)
|
|
65
67
|
- 本地 registry:`~/.nextclaw/apps/registry.json`
|
|
66
68
|
- 本地 config:`~/.nextclaw/apps/config.json`
|
|
67
69
|
- 默认 registry:`https://apps-registry.nextclaw.io/api/v1/apps/registry/`
|
|
@@ -119,6 +121,21 @@ napp uninstall nextclaw.my-first-napp
|
|
|
119
121
|
napp uninstall nextclaw.my-first-napp --purge-data
|
|
120
122
|
```
|
|
121
123
|
|
|
124
|
+
不带 `--purge-data` 时只卸载应用代码和注册记录,受管实例会保留,以便重装后继续使用。带上该选项后,卸载事务才会同时移除该 App 的默认实例。宿主只管理上述实例目录;通过文档授权访问的外部文件或目录不属于 App 实例,卸载和清理数据都不会删除它们。
|
|
125
|
+
|
|
126
|
+
运行中的 NextClaw 还提供统一的数据清单和残留数据清理入口:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
nextclaw app data list --json
|
|
130
|
+
nextclaw app data delete <data-id> --confirm <app-id> --json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`data-id` 必须取自最新的 `list` 结果;删除命令只接受已经卸载或已经从 workspace 移除的 `retained` 实例,并要求 `--confirm` 与目标 App id 完全一致。开发 Service App 时,可以显式重置隔离的开发实例:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
nextclaw app dev <service-app-dir> --reset-data --confirm <app-id> --json
|
|
137
|
+
```
|
|
138
|
+
|
|
122
139
|
## 示例
|
|
123
140
|
|
|
124
141
|
```bash
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { AppRuntimeOptionsService, BuildCliOptions, CreateCliOptions, GrantCliOp
|
|
|
18
18
|
import { AppRuntimeCliService } from "./services/app-runtime-cli.service.js";
|
|
19
19
|
import { CreateCommand } from "./controllers/create.controller.js";
|
|
20
20
|
import { AppHomeService } from "./services/app-home.service.js";
|
|
21
|
-
import { APP_STORAGE_LAYOUT_VERSION, AppInstanceRecord, AppStorageContext, AppStorageLayout, AppStorageUsage, DEFAULT_APP_INSTANCE_ID } from "./types/app-storage.types.js";
|
|
21
|
+
import { APP_STORAGE_LAYOUT_VERSION, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceMetadata, AppInstanceRecord, AppStorageContext, AppStorageLayout, AppStorageUsage, DEFAULT_APP_INSTANCE_ID } from "./types/app-storage.types.js";
|
|
22
22
|
import { AppPublisher, AppRegistryConfig, AppRegistryConfigSnapshot, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, DEFAULT_APP_REGISTRY_URL } from "./types/app-remote-registry.types.js";
|
|
23
23
|
import { AppInstallSourceKind, AppRegistry, AppRegistryAppRecord, AppRegistryInstalledVersion } from "./types/app-registry.types.js";
|
|
24
24
|
import { AppRegistryService } from "./services/app-registry.service.js";
|
|
@@ -51,6 +51,7 @@ import { ValidatePublishCommand } from "./controllers/validate-publish.controlle
|
|
|
51
51
|
import { WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions } from "./services/wasmtime-wasi-http-component.service.js";
|
|
52
52
|
import { AppHostHandle, AppHostService, AppHostStartOptions } from "./services/app-host.service.js";
|
|
53
53
|
import { AppInstallationIntegrityService } from "./services/app-installation-integrity.service.js";
|
|
54
|
+
import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
|
|
54
55
|
import { FileLockService } from "./services/file-lock.service.js";
|
|
55
56
|
import { UiServerService } from "./services/ui-server.service.js";
|
|
56
|
-
export { APP_STORAGE_LAYOUT_VERSION, AppActivationResult, AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService, AppBuildResult, AppBuildService, AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDistributionMode, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallProgressHandler, AppInstallProgressPhase, AppInstallResult, AppInstallSourceKind, AppInstallationIntegrityService, AppInstallationService, AppInstalledPermissionState, AppInstanceRecord, AppInstanceService, AppInstanceStorageService, AppLaunchResolution, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppMarketplaceVisuals, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPlatformSecuritySummary, AppPublishFile, AppPublishPayload, AppPublishResult, AppPublishService, AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppResolvedComponent, AppRollbackResult, AppRunResult, AppRuntimeCliService, AppRuntimeCommandResult, AppRuntimeDeclaration, AppRuntimeDoctorResult, AppRuntimeIsolation, AppRuntimeOptionsService, AppRuntimeProfile, AppRuntimeToolStatus, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageContext, AppStorageDeclaration, AppStorageLayout, AppStorageUsage, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUpdateResult, AppWasiHttpComponentMainManifest, BuildCliOptions, CreateCliOptions, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockService, GrantCliOptions, GrantCommand, HostBridgeServer, InfoCommand, InstallCliOptions, InstallCommand, InstalledAppListItem, JsonOnlyCliOptions, ListCommand, MainRunRequest, MainRunResult, MainRunnerService, PackCliOptions, PackCommand, PermissionsCommand, PlatformAuthStateService, PlatformPublishAuthState, PublishCliOptions, PublishCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, ValidatePublishCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
|
57
|
+
export { APP_STORAGE_LAYOUT_VERSION, AppActivationResult, AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService, AppBuildResult, AppBuildService, AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDistributionMode, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallProgressHandler, AppInstallProgressPhase, AppInstallResult, AppInstallSourceKind, AppInstallationIntegrityService, AppInstallationService, AppInstalledPermissionState, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceInventoryService, AppInstanceMetadata, AppInstanceRecord, AppInstanceService, AppInstanceStorageService, AppLaunchResolution, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppMarketplaceVisuals, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPlatformSecuritySummary, AppPublishFile, AppPublishPayload, AppPublishResult, AppPublishService, AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppResolvedComponent, AppRollbackResult, AppRunResult, AppRuntimeCliService, AppRuntimeCommandResult, AppRuntimeDeclaration, AppRuntimeDoctorResult, AppRuntimeIsolation, AppRuntimeOptionsService, AppRuntimeProfile, AppRuntimeToolStatus, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageContext, AppStorageDeclaration, AppStorageLayout, AppStorageUsage, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUpdateResult, AppWasiHttpComponentMainManifest, BuildCliOptions, CreateCliOptions, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockService, GrantCliOptions, GrantCommand, HostBridgeServer, InfoCommand, InstallCliOptions, InstallCommand, InstalledAppListItem, JsonOnlyCliOptions, ListCommand, MainRunRequest, MainRunResult, MainRunnerService, PackCliOptions, PackCommand, PermissionsCommand, PlatformAuthStateService, PlatformPublishAuthState, PublishCliOptions, PublishCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, ValidatePublishCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
package/dist/index.js
CHANGED
|
@@ -48,4 +48,5 @@ import { AppPublishValidationService } from "./services/app-publish-validation.s
|
|
|
48
48
|
import { ValidatePublishCommand } from "./controllers/validate-publish.controller.js";
|
|
49
49
|
import { AppRuntimeOptionsService } from "./services/app-runtime-options.service.js";
|
|
50
50
|
import { AppRuntimeCliService } from "./services/app-runtime-cli.service.js";
|
|
51
|
-
|
|
51
|
+
import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
|
|
52
|
+
export { APP_STORAGE_LAYOUT_VERSION, AppArtifactValidationService, AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationIntegrityService, AppInstallationService, AppInstanceInventoryService, AppInstanceService, AppInstanceStorageService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPublishService, AppPublishValidationService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, AppRuntimeToolchainService, AppScaffoldService, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockService, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, PlatformAuthStateService, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, ValidatePublishCommand, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentService, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
package/dist/package.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AppInstanceInventory } from "../types/app-storage.types.js";
|
|
2
|
+
import { AppInstanceStorageService } from "./app-instance-storage.service.js";
|
|
3
|
+
|
|
4
|
+
//#region src/services/app-instance-inventory.service.d.ts
|
|
5
|
+
declare class AppInstanceInventoryService {
|
|
6
|
+
private readonly storageService;
|
|
7
|
+
private readonly fileLockService;
|
|
8
|
+
constructor(storageService?: AppInstanceStorageService);
|
|
9
|
+
list: (instancesRoot: string) => Promise<AppInstanceInventory>;
|
|
10
|
+
purge: (params: {
|
|
11
|
+
instancesRoot: string;
|
|
12
|
+
appId: string;
|
|
13
|
+
instanceId: string;
|
|
14
|
+
assertCanPurge?: () => Promise<void>;
|
|
15
|
+
}) => Promise<void>;
|
|
16
|
+
purgeNested: (params: {
|
|
17
|
+
instancesRoot: string;
|
|
18
|
+
pathSegments: string[];
|
|
19
|
+
appId: string;
|
|
20
|
+
instanceId: string;
|
|
21
|
+
assertCanPurge?: () => Promise<void>;
|
|
22
|
+
}) => Promise<void>;
|
|
23
|
+
private listDirectories;
|
|
24
|
+
private assertSafeId;
|
|
25
|
+
private assertWithinRoot;
|
|
26
|
+
private pathExists;
|
|
27
|
+
private isMissingFileError;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { AppInstanceInventoryService };
|
|
31
|
+
//# sourceMappingURL=app-instance-inventory.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-instance-inventory.service.d.ts","names":[],"sources":["../../src/services/app-instance-inventory.service.ts"],"mappings":";;;;cAYa,2BAAA;EAAA,iBAIQ,cAAA;EAAA,iBAHF,eAAA;cAGE,cAAA,GAAc,yBAAA;EAGjC,IAAA,GAAc,aAAA,aAAwB,OAAA,CAAQ,oBAAA;EAuD9C,KAAA,GAAe,MAAA;IACb,aAAA;IACA,KAAA;IACA,UAAA;IACA,cAAA,SAAuB,OAAA;EAAA,MACrB,OAAA;EAWJ,WAAA,GAAqB,MAAA;IACnB,aAAA;IACA,YAAA;IACA,KAAA;IACA,UAAA;IACA,cAAA,SAAuB,OAAA;EAAA,MACrB,OAAA;EAAA,QAuCI,eAAA;EAAA,QAYA,YAAA;EAAA,QAMA,gBAAA;EAAA,QAOA,UAAA;EAAA,QASA,kBAAA;AAAA"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { FileLockService } from "./file-lock.service.js";
|
|
2
|
+
import { AppInstanceStorageService } from "./app-instance-storage.service.js";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { access, readdir, rename, rm } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
//#region src/services/app-instance-inventory.service.ts
|
|
7
|
+
const SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
|
|
8
|
+
var AppInstanceInventoryService = class {
|
|
9
|
+
fileLockService = new FileLockService();
|
|
10
|
+
constructor(storageService = new AppInstanceStorageService()) {
|
|
11
|
+
this.storageService = storageService;
|
|
12
|
+
}
|
|
13
|
+
list = async (instancesRoot) => {
|
|
14
|
+
const root = path.resolve(instancesRoot);
|
|
15
|
+
const entries = [];
|
|
16
|
+
const diagnostics = [];
|
|
17
|
+
for (const appDirectory of await this.listDirectories(root)) {
|
|
18
|
+
const appId = appDirectory.name;
|
|
19
|
+
const appPath = path.join(root, appId);
|
|
20
|
+
if (!SAFE_ID_PATTERN.test(appId)) {
|
|
21
|
+
diagnostics.push({
|
|
22
|
+
instanceDirectory: appPath,
|
|
23
|
+
message: `不安全的 App Instance appId:${appId}`
|
|
24
|
+
});
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
for (const instanceDirectory of await this.listDirectories(appPath)) {
|
|
28
|
+
const instanceId = instanceDirectory.name;
|
|
29
|
+
const instancePath = path.join(appPath, instanceId);
|
|
30
|
+
if (!SAFE_ID_PATTERN.test(instanceId)) {
|
|
31
|
+
diagnostics.push({
|
|
32
|
+
instanceDirectory: instancePath,
|
|
33
|
+
message: `不安全的 App Instance instanceId:${instanceId}`
|
|
34
|
+
});
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const instance = await this.storageService.inspect({
|
|
39
|
+
appId,
|
|
40
|
+
instanceId,
|
|
41
|
+
instanceDirectory: instancePath
|
|
42
|
+
});
|
|
43
|
+
const usage = await this.storageService.measureUsage(instance.storage);
|
|
44
|
+
entries.push({
|
|
45
|
+
appId,
|
|
46
|
+
instanceId,
|
|
47
|
+
publisherId: instance.publisherId,
|
|
48
|
+
storage: instance.storage,
|
|
49
|
+
usage,
|
|
50
|
+
createdAt: instance.createdAt,
|
|
51
|
+
migratedAt: instance.migratedAt
|
|
52
|
+
});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
diagnostics.push({
|
|
55
|
+
instanceDirectory: instancePath,
|
|
56
|
+
message: error instanceof Error ? error.message : String(error)
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
entries.sort((left, right) => left.appId.localeCompare(right.appId) || left.instanceId.localeCompare(right.instanceId));
|
|
62
|
+
diagnostics.sort((left, right) => left.instanceDirectory.localeCompare(right.instanceDirectory));
|
|
63
|
+
return {
|
|
64
|
+
entries,
|
|
65
|
+
diagnostics
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
purge = async (params) => {
|
|
69
|
+
const { appId, assertCanPurge, instanceId, instancesRoot } = params;
|
|
70
|
+
return await this.purgeNested({
|
|
71
|
+
instancesRoot,
|
|
72
|
+
pathSegments: [appId, instanceId],
|
|
73
|
+
appId,
|
|
74
|
+
instanceId,
|
|
75
|
+
assertCanPurge
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
purgeNested = async (params) => {
|
|
79
|
+
const { appId, assertCanPurge, instanceId, instancesRoot, pathSegments } = params;
|
|
80
|
+
this.assertSafeId(appId, "appId");
|
|
81
|
+
this.assertSafeId(instanceId, "instanceId");
|
|
82
|
+
if (pathSegments.length === 0) throw new Error("App Instance 相对路径不能为空。");
|
|
83
|
+
pathSegments.forEach((segment, index) => this.assertSafeId(segment, `pathSegments[${index}]`));
|
|
84
|
+
const root = path.resolve(instancesRoot);
|
|
85
|
+
const instanceDirectory = path.resolve(root, ...pathSegments);
|
|
86
|
+
this.assertWithinRoot(root, instanceDirectory);
|
|
87
|
+
await this.fileLockService.withLock(`${instanceDirectory}.lock`, async () => {
|
|
88
|
+
await assertCanPurge?.();
|
|
89
|
+
await this.storageService.inspect({
|
|
90
|
+
appId,
|
|
91
|
+
instanceId,
|
|
92
|
+
instanceDirectory
|
|
93
|
+
});
|
|
94
|
+
const stagingDirectory = `${instanceDirectory}.deleting-${randomUUID()}`;
|
|
95
|
+
await rename(instanceDirectory, stagingDirectory);
|
|
96
|
+
try {
|
|
97
|
+
await rm(stagingDirectory, { recursive: true });
|
|
98
|
+
} catch (error) {
|
|
99
|
+
try {
|
|
100
|
+
if (!await this.pathExists(instanceDirectory)) await rename(stagingDirectory, instanceDirectory);
|
|
101
|
+
} catch (restoreError) {
|
|
102
|
+
throw new AggregateError([error, restoreError], `App Instance 删除失败且恢复失败:${appId}/${instanceId}`);
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
listDirectories = async (directory) => {
|
|
109
|
+
try {
|
|
110
|
+
return (await readdir(directory, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (this.isMissingFileError(error)) return [];
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
assertSafeId = (value, field) => {
|
|
117
|
+
if (!SAFE_ID_PATTERN.test(value)) throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);
|
|
118
|
+
};
|
|
119
|
+
assertWithinRoot = (root, target) => {
|
|
120
|
+
const relative = path.relative(root, target);
|
|
121
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`App Instance 路径不在受管根目录内:${target}`);
|
|
122
|
+
};
|
|
123
|
+
pathExists = async (targetPath) => {
|
|
124
|
+
try {
|
|
125
|
+
await access(targetPath);
|
|
126
|
+
return true;
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
export { AppInstanceInventoryService };
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=app-instance-inventory.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-instance-inventory.service.js","names":[],"sources":["../../src/services/app-instance-inventory.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, readdir, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport type {\n AppInstanceInventory,\n AppInstanceInventoryEntry,\n} from \"#app-runtime/types/app-storage.types.js\";\n\nconst SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\n\nexport class AppInstanceInventoryService {\n private readonly fileLockService = new FileLockService();\n\n constructor(\n private readonly storageService = new AppInstanceStorageService(),\n ) {}\n\n list = async (instancesRoot: string): Promise<AppInstanceInventory> => {\n const root = path.resolve(instancesRoot);\n const entries: AppInstanceInventoryEntry[] = [];\n const diagnostics: AppInstanceInventory[\"diagnostics\"] = [];\n for (const appDirectory of await this.listDirectories(root)) {\n const appId = appDirectory.name;\n const appPath = path.join(root, appId);\n if (!SAFE_ID_PATTERN.test(appId)) {\n diagnostics.push({\n instanceDirectory: appPath,\n message: `不安全的 App Instance appId:${appId}`,\n });\n continue;\n }\n for (const instanceDirectory of await this.listDirectories(appPath)) {\n const instanceId = instanceDirectory.name;\n const instancePath = path.join(appPath, instanceId);\n if (!SAFE_ID_PATTERN.test(instanceId)) {\n diagnostics.push({\n instanceDirectory: instancePath,\n message: `不安全的 App Instance instanceId:${instanceId}`,\n });\n continue;\n }\n try {\n const instance = await this.storageService.inspect({\n appId,\n instanceId,\n instanceDirectory: instancePath,\n });\n const usage = await this.storageService.measureUsage(instance.storage);\n entries.push({\n appId,\n instanceId,\n publisherId: instance.publisherId,\n storage: instance.storage,\n usage,\n createdAt: instance.createdAt,\n migratedAt: instance.migratedAt,\n });\n } catch (error) {\n diagnostics.push({\n instanceDirectory: instancePath,\n message: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n entries.sort((left, right) =>\n left.appId.localeCompare(right.appId) || left.instanceId.localeCompare(right.instanceId));\n diagnostics.sort((left, right) =>\n left.instanceDirectory.localeCompare(right.instanceDirectory));\n return { entries, diagnostics };\n };\n\n purge = async (params: {\n instancesRoot: string;\n appId: string;\n instanceId: string;\n assertCanPurge?: () => Promise<void>;\n }): Promise<void> => {\n const { appId, assertCanPurge, instanceId, instancesRoot } = params;\n return await this.purgeNested({\n instancesRoot,\n pathSegments: [appId, instanceId],\n appId,\n instanceId,\n assertCanPurge,\n });\n };\n\n purgeNested = async (params: {\n instancesRoot: string;\n pathSegments: string[];\n appId: string;\n instanceId: string;\n assertCanPurge?: () => Promise<void>;\n }): Promise<void> => {\n const { appId, assertCanPurge, instanceId, instancesRoot, pathSegments } = params;\n this.assertSafeId(appId, \"appId\");\n this.assertSafeId(instanceId, \"instanceId\");\n if (pathSegments.length === 0) {\n throw new Error(\"App Instance 相对路径不能为空。\");\n }\n pathSegments.forEach((segment, index) =>\n this.assertSafeId(segment, `pathSegments[${index}]`));\n const root = path.resolve(instancesRoot);\n const instanceDirectory = path.resolve(root, ...pathSegments);\n this.assertWithinRoot(root, instanceDirectory);\n await this.fileLockService.withLock(`${instanceDirectory}.lock`, async () => {\n await assertCanPurge?.();\n await this.storageService.inspect({\n appId,\n instanceId,\n instanceDirectory,\n });\n const stagingDirectory = `${instanceDirectory}.deleting-${randomUUID()}`;\n await rename(instanceDirectory, stagingDirectory);\n try {\n await rm(stagingDirectory, { recursive: true });\n } catch (error) {\n try {\n if (!await this.pathExists(instanceDirectory)) {\n await rename(stagingDirectory, instanceDirectory);\n }\n } catch (restoreError) {\n throw new AggregateError(\n [error, restoreError],\n `App Instance 删除失败且恢复失败:${appId}/${instanceId}`,\n );\n }\n throw error;\n }\n });\n };\n\n private listDirectories = async (directory: string) => {\n try {\n return (await readdir(directory, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory() && !entry.name.startsWith(\".\"));\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return [];\n }\n throw error;\n }\n };\n\n private assertSafeId = (value: string, field: string): void => {\n if (!SAFE_ID_PATTERN.test(value)) {\n throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);\n }\n };\n\n private assertWithinRoot = (root: string, target: string): void => {\n const relative = path.relative(root, target);\n if (!relative || relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n throw new Error(`App Instance 路径不在受管根目录内:${target}`);\n }\n };\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":"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"}
|
|
@@ -28,6 +28,11 @@ declare class AppInstanceStorageService {
|
|
|
28
28
|
createdAt: string;
|
|
29
29
|
}) => AppInstanceRecord;
|
|
30
30
|
measureUsage: (storage: AppStorageContext) => Promise<AppStorageUsage>;
|
|
31
|
+
inspect: (params: {
|
|
32
|
+
appId: string;
|
|
33
|
+
instanceId: string;
|
|
34
|
+
instanceDirectory: string;
|
|
35
|
+
}) => Promise<AppInstanceRecord>;
|
|
31
36
|
rollbackNewInstance: (params: {
|
|
32
37
|
instance: AppInstanceRecord;
|
|
33
38
|
legacyDataDirectory?: string;
|
|
@@ -38,6 +43,7 @@ declare class AppInstanceStorageService {
|
|
|
38
43
|
private readMetadata;
|
|
39
44
|
private toInstanceRecord;
|
|
40
45
|
private assertMetadataIdentity;
|
|
46
|
+
private assertMetadataCoordinates;
|
|
41
47
|
private bindPublisher;
|
|
42
48
|
private assertSafeId;
|
|
43
49
|
private measureDirectory;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-instance-storage.service.d.ts","names":[],"sources":["../../src/services/app-instance-storage.service.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"app-instance-storage.service.d.ts","names":[],"sources":["../../src/services/app-instance-storage.service.ts"],"mappings":";;;;cAiBa,yBAAA;EAAA,iBAGkB,cAAA;EAAA,iBAFZ,eAAA;cAEY,cAAA,GAAgB,cAAA;EAE7C,0BAAA,GAAoC,MAAA;IAClC,KAAA;IACA,WAAA;IACA,mBAAA;IACA,iBAAA;EAAA,MACE,OAAA,CAAQ,iBAAA;EAWZ,WAAA,GAAqB,MAAA;IACnB,KAAA;IACA,UAAA;IACA,iBAAA;IACA,WAAA;IACA,mBAAA;IACA,iBAAA;EAAA,MACE,OAAA,CAAQ,iBAAA;EAAA,QAUJ,mBAAA;EAAA,QA6BA,sBAAA;EA+ER,0BAAA,GAA8B,MAAA;IAC5B,KAAA;IACA,aAAA;IACA,SAAA;EAAA,MACE,iBAAA;EAoBJ,YAAA,GAAsB,OAAA,EAAS,iBAAA,KAAoB,OAAA,CAAQ,eAAA;EAqB3D,OAAA,GAAiB,MAAA;IACf,KAAA;IACA,UAAA;IACA,iBAAA;EAAA,MACE,OAAA,CAAQ,iBAAA;EAgBZ,mBAAA,GAA6B,MAAA;IAC3B,QAAA,EAAU,iBAAA;IACV,mBAAA;EAAA,MACE,OAAA;EAAA,QAqBI,uBAAA;EAAA,QAqBA,YAAA;EAAA,QAgBA,wBAAA;EAAA,QAWA,YAAA;EAAA,QA0BA,gBAAA;EAAA,QAcA,sBAAA;EAAA,QAcA,yBAAA;EAAA,QAYA,aAAA;EAAA,QAqBA,YAAA;EAAA,QAMA,gBAAA;EAAA,QA0BA,UAAA;EAAA,QASA,kBAAA;AAAA"}
|
|
@@ -130,6 +130,16 @@ var AppInstanceStorageService = class {
|
|
|
130
130
|
totalBytes: dataBytes + configBytes + stateBytes + cacheBytes + temporaryBytes + logsBytes
|
|
131
131
|
};
|
|
132
132
|
};
|
|
133
|
+
inspect = async (params) => {
|
|
134
|
+
const { appId, instanceId, instanceDirectory: instancePath } = params;
|
|
135
|
+
this.assertSafeId(appId, "appId");
|
|
136
|
+
this.assertSafeId(instanceId, "instanceId");
|
|
137
|
+
const instanceDirectory = path.resolve(instancePath);
|
|
138
|
+
const metadata = await this.readMetadata(instanceDirectory);
|
|
139
|
+
if (!metadata) throw new Error(`App Instance metadata 不存在:${instanceDirectory}`);
|
|
140
|
+
this.assertMetadataCoordinates(metadata, appId, instanceId);
|
|
141
|
+
return this.toInstanceRecord(metadata, this.buildContext(instanceDirectory, instanceId));
|
|
142
|
+
};
|
|
133
143
|
rollbackNewInstance = async (params) => {
|
|
134
144
|
const { instance, legacyDataDirectory: legacyDataPath } = params;
|
|
135
145
|
const instanceDirectory = instance.storage.instanceDirectory;
|
|
@@ -196,9 +206,12 @@ var AppInstanceStorageService = class {
|
|
|
196
206
|
legacyDataDirectory: metadata.legacyDataDirectory
|
|
197
207
|
});
|
|
198
208
|
assertMetadataIdentity = (metadata, appId, instanceId, publisherId) => {
|
|
199
|
-
|
|
209
|
+
this.assertMetadataCoordinates(metadata, appId, instanceId);
|
|
200
210
|
if (metadata.publisherId && metadata.publisherId !== publisherId) throw new Error(`App Instance ${appId}/${instanceId} 已绑定发布者 ${metadata.publisherId},拒绝由 ${publisherId ?? "未验证本地来源"} 接管。`);
|
|
201
211
|
};
|
|
212
|
+
assertMetadataCoordinates = (metadata, appId, instanceId) => {
|
|
213
|
+
if (metadata.appId !== appId || metadata.instanceId !== instanceId) throw new Error(`App Instance identity 不匹配:期望 ${appId}/${instanceId},实际 ${metadata.appId}/${metadata.instanceId}`);
|
|
214
|
+
};
|
|
202
215
|
bindPublisher = async (instanceDirectory, metadata, publisherId) => {
|
|
203
216
|
const nextMetadata = {
|
|
204
217
|
...metadata,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-instance-storage.service.js","names":[],"sources":["../../src/services/app-instance-storage.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, mkdir, readdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport {\n APP_STORAGE_LAYOUT_VERSION,\n DEFAULT_APP_INSTANCE_ID,\n type AppInstanceRecord,\n type AppStorageContext,\n type AppStorageUsage,\n} from \"#app-runtime/types/app-storage.types.js\";\n\ntype StorageMetadata = {\n schemaVersion: 1;\n appId: string;\n instanceId: string;\n publisherId?: string;\n layoutVersion: 1;\n createdAt: string;\n migratedAt?: string;\n legacyDataDirectory?: string;\n};\n\nconst STORAGE_METADATA_FILE = \"metadata.json\";\nconst SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\n\nexport class AppInstanceStorageService {\n private readonly fileLockService = new FileLockService();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {}\n\n materializeDefaultInstance = async (params: {\n appId: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n return await this.materialize({\n ...params,\n instanceId: DEFAULT_APP_INSTANCE_ID,\n instanceDirectory: this.appHomeService.getAppInstanceDirectory(\n params.appId,\n DEFAULT_APP_INSTANCE_ID,\n ),\n });\n };\n\n materialize = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n this.assertSafeId(params.appId, \"appId\");\n this.assertSafeId(params.instanceId, \"instanceId\");\n const instanceDirectory = path.resolve(params.instanceDirectory);\n return await this.fileLockService.withLock(\n `${instanceDirectory}.lock`,\n async () => await this.materializeUnlocked({ ...params, instanceDirectory }),\n );\n };\n\n private materializeUnlocked = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n const {\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n publisherId,\n } = params;\n const existingInstance = await this.resolveExistingInstance({\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n publisherId,\n });\n if (existingInstance) {\n return existingInstance;\n }\n\n return await this.materializeNewInstance(params);\n };\n\n private materializeNewInstance = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n const {\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n legacyDataDirectory: legacyDataPath,\n publisherId,\n } = params;\n\n await mkdir(path.dirname(instanceDirectory), { recursive: true });\n const stagingDirectory = `${instanceDirectory}.migrating-${randomUUID()}`;\n const legacyDataDirectory = legacyDataPath\n ? path.resolve(legacyDataPath)\n : undefined;\n const createdAt = new Date().toISOString();\n let movedLegacyData = false;\n try {\n const stagingStorage = this.buildContext(stagingDirectory, instanceId);\n await mkdir(stagingDirectory, { recursive: false });\n if (legacyDataDirectory && await this.pathExists(legacyDataDirectory)) {\n await rename(legacyDataDirectory, stagingStorage.dataDirectory);\n movedLegacyData = true;\n }\n await this.ensureStorageDirectories(stagingStorage);\n const metadata: StorageMetadata = {\n schemaVersion: 1,\n appId,\n instanceId,\n publisherId,\n layoutVersion: APP_STORAGE_LAYOUT_VERSION,\n createdAt,\n ...(movedLegacyData\n ? { migratedAt: new Date().toISOString(), legacyDataDirectory }\n : {}),\n };\n await writeFile(\n path.join(stagingDirectory, STORAGE_METADATA_FILE),\n `${JSON.stringify(metadata, null, 2)}\\n`,\n { encoding: \"utf8\", mode: 0o600, flag: \"wx\" },\n );\n await rename(stagingDirectory, instanceDirectory);\n return this.toInstanceRecord(\n metadata,\n this.buildContext(instanceDirectory, instanceId),\n dataSchemaVersion,\n );\n } catch (error) {\n if (\n movedLegacyData &&\n legacyDataDirectory &&\n !await this.pathExists(legacyDataDirectory)\n ) {\n const stagedDataDirectory = path.join(stagingDirectory, \"data\");\n if (await this.pathExists(stagedDataDirectory)) {\n await rename(stagedDataDirectory, legacyDataDirectory);\n }\n }\n await rm(stagingDirectory, { recursive: true, force: true });\n if (await this.pathExists(instanceDirectory)) {\n const racedMetadata = await this.readMetadata(instanceDirectory);\n if (racedMetadata) {\n this.assertMetadataIdentity(racedMetadata, appId, instanceId, publisherId);\n const storage = this.buildContext(instanceDirectory, instanceId);\n await this.ensureStorageDirectories(storage);\n return this.toInstanceRecord(racedMetadata, storage, dataSchemaVersion);\n }\n }\n throw error;\n }\n };\n\n buildLegacyDefaultInstance = (params: {\n appId: string;\n dataDirectory: string;\n createdAt: string;\n }): AppInstanceRecord => {\n const { appId, createdAt, dataDirectory } = params;\n const instanceDirectory = this.appHomeService.getAppInstanceDirectory(\n appId,\n DEFAULT_APP_INSTANCE_ID,\n );\n return {\n id: DEFAULT_APP_INSTANCE_ID,\n publisherId: undefined,\n storage: {\n ...this.buildContext(instanceDirectory, DEFAULT_APP_INSTANCE_ID),\n layout: \"legacy\",\n dataDirectory: path.resolve(dataDirectory),\n },\n dataSchemaVersion: 1,\n createdAt,\n legacyDataDirectory: path.resolve(dataDirectory),\n };\n };\n\n measureUsage = async (storage: AppStorageContext): Promise<AppStorageUsage> => {\n const [dataBytes, configBytes, stateBytes, cacheBytes, temporaryBytes, logsBytes] =\n await Promise.all([\n this.measureDirectory(storage.dataDirectory),\n this.measureDirectory(storage.configDirectory),\n this.measureDirectory(storage.stateDirectory),\n this.measureDirectory(storage.cacheDirectory),\n this.measureDirectory(storage.temporaryDirectory),\n this.measureDirectory(storage.logsDirectory),\n ]);\n return {\n dataBytes,\n configBytes,\n stateBytes,\n cacheBytes,\n temporaryBytes,\n logsBytes,\n totalBytes: dataBytes + configBytes + stateBytes + cacheBytes + temporaryBytes + logsBytes,\n };\n };\n\n rollbackNewInstance = async (params: {\n instance: AppInstanceRecord;\n legacyDataDirectory?: string;\n }): Promise<void> => {\n const { instance, legacyDataDirectory: legacyDataPath } = params;\n const instanceDirectory = instance.storage.instanceDirectory;\n const metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n return;\n }\n const legacyDataDirectory = legacyDataPath\n ? path.resolve(legacyDataPath)\n : undefined;\n if (\n legacyDataDirectory &&\n !await this.pathExists(legacyDataDirectory) &&\n await this.pathExists(instance.storage.dataDirectory)\n ) {\n await mkdir(path.dirname(legacyDataDirectory), { recursive: true });\n await rename(instance.storage.dataDirectory, legacyDataDirectory);\n }\n await rm(instanceDirectory, { recursive: true, force: true });\n };\n\n private resolveExistingInstance = async (params: {\n appId: string;\n dataSchemaVersion?: number;\n instanceDirectory: string;\n instanceId: string;\n publisherId?: string;\n }): Promise<AppInstanceRecord | undefined> => {\n const { appId, dataSchemaVersion, instanceDirectory, instanceId, publisherId } = params;\n let metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n return undefined;\n }\n this.assertMetadataIdentity(metadata, appId, instanceId, publisherId);\n if (publisherId && !metadata.publisherId) {\n metadata = await this.bindPublisher(instanceDirectory, metadata, publisherId);\n }\n const storage = this.buildContext(instanceDirectory, instanceId);\n await this.ensureStorageDirectories(storage);\n return this.toInstanceRecord(metadata, storage, dataSchemaVersion);\n };\n\n private buildContext = (\n instanceDirectory: string,\n instanceId: string,\n ): AppStorageContext => ({\n layout: \"instance-v1\",\n layoutVersion: APP_STORAGE_LAYOUT_VERSION,\n instanceId,\n instanceDirectory,\n dataDirectory: path.join(instanceDirectory, \"data\"),\n configDirectory: path.join(instanceDirectory, \"config\"),\n stateDirectory: path.join(instanceDirectory, \"state\"),\n cacheDirectory: path.join(instanceDirectory, \"cache\"),\n temporaryDirectory: path.join(instanceDirectory, \"tmp\"),\n logsDirectory: path.join(instanceDirectory, \"logs\"),\n });\n\n private ensureStorageDirectories = async (storage: AppStorageContext): Promise<void> => {\n await Promise.all([\n mkdir(storage.dataDirectory, { recursive: true }),\n mkdir(storage.configDirectory, { recursive: true }),\n mkdir(storage.stateDirectory, { recursive: true }),\n mkdir(storage.cacheDirectory, { recursive: true }),\n mkdir(storage.temporaryDirectory, { recursive: true }),\n mkdir(storage.logsDirectory, { recursive: true }),\n ]);\n };\n\n private readMetadata = async (instanceDirectory: string): Promise<StorageMetadata | undefined> => {\n try {\n const raw = JSON.parse(\n await readFile(path.join(instanceDirectory, STORAGE_METADATA_FILE), \"utf8\"),\n ) as Partial<StorageMetadata>;\n if (\n raw.schemaVersion !== 1 ||\n raw.layoutVersion !== APP_STORAGE_LAYOUT_VERSION ||\n typeof raw.appId !== \"string\" ||\n typeof raw.instanceId !== \"string\" ||\n (raw.publisherId !== undefined && typeof raw.publisherId !== \"string\") ||\n typeof raw.createdAt !== \"string\"\n ) {\n throw new Error(`无效的 App Instance metadata:${instanceDirectory}`);\n }\n return raw as StorageMetadata;\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return undefined;\n }\n throw error;\n }\n };\n\n private toInstanceRecord = (\n metadata: StorageMetadata,\n storage: AppStorageContext,\n dataSchemaVersion = 1,\n ): AppInstanceRecord => ({\n id: metadata.instanceId,\n publisherId: metadata.publisherId,\n storage,\n dataSchemaVersion,\n createdAt: metadata.createdAt,\n migratedAt: metadata.migratedAt,\n legacyDataDirectory: metadata.legacyDataDirectory,\n });\n\n private assertMetadataIdentity = (\n metadata: StorageMetadata,\n appId: string,\n instanceId: string,\n publisherId?: string,\n ): void => {\n if (metadata.appId !== appId || metadata.instanceId !== instanceId) {\n throw new Error(\n `App Instance identity 不匹配:期望 ${appId}/${instanceId},实际 ${metadata.appId}/${metadata.instanceId}`,\n );\n }\n if (metadata.publisherId && metadata.publisherId !== publisherId) {\n throw new Error(\n `App Instance ${appId}/${instanceId} 已绑定发布者 ${metadata.publisherId},拒绝由 ${publisherId ?? \"未验证本地来源\"} 接管。`,\n );\n }\n };\n\n private bindPublisher = async (\n instanceDirectory: string,\n metadata: StorageMetadata,\n publisherId: string,\n ): Promise<StorageMetadata> => {\n const nextMetadata = { ...metadata, publisherId };\n const metadataPath = path.join(instanceDirectory, STORAGE_METADATA_FILE);\n const temporaryPath = `${metadataPath}.${randomUUID()}.tmp`;\n try {\n await writeFile(\n temporaryPath,\n `${JSON.stringify(nextMetadata, null, 2)}\\n`,\n { encoding: \"utf8\", mode: 0o600, flag: \"wx\" },\n );\n await rename(temporaryPath, metadataPath);\n return nextMetadata;\n } finally {\n await rm(temporaryPath, { force: true });\n }\n };\n\n private assertSafeId = (value: string, field: string): void => {\n if (!SAFE_ID_PATTERN.test(value)) {\n throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);\n }\n };\n\n private measureDirectory = async (directory: string): Promise<number> => {\n try {\n const entries = await readdir(directory, { withFileTypes: true });\n let bytes = 0;\n for (const entry of entries) {\n const entryPath = path.join(directory, entry.name);\n if (entry.isSymbolicLink()) {\n continue;\n }\n if (entry.isDirectory()) {\n bytes += await this.measureDirectory(entryPath);\n continue;\n }\n if (entry.isFile()) {\n bytes += (await stat(entryPath)).size;\n }\n }\n return bytes;\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return 0;\n }\n throw error;\n }\n };\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":";;;;;;;AAwBA,MAAM,wBAAwB;AAC9B,MAAM,kBAAkB;AAExB,IAAa,4BAAb,MAAuC;CACrC,kBAAmC,IAAI,iBAAiB;CAExD,YAAY,iBAAkD,IAAI,gBAAgB,EAAE;AAAvD,OAAA,iBAAA;;CAE7B,6BAA6B,OAAO,WAKF;AAChC,SAAO,MAAM,KAAK,YAAY;GAC5B,GAAG;GACH,YAAY;GACZ,mBAAmB,KAAK,eAAe,wBACrC,OAAO,OACP,wBACD;GACF,CAAC;;CAGJ,cAAc,OAAO,WAOa;AAChC,OAAK,aAAa,OAAO,OAAO,QAAQ;AACxC,OAAK,aAAa,OAAO,YAAY,aAAa;EAClD,MAAM,oBAAoB,KAAK,QAAQ,OAAO,kBAAkB;AAChE,SAAO,MAAM,KAAK,gBAAgB,SAChC,GAAG,kBAAkB,QACrB,YAAY,MAAM,KAAK,oBAAoB;GAAE,GAAG;GAAQ;GAAmB,CAAC,CAC7E;;CAGH,sBAA8B,OAAO,WAOH;EAChC,MAAM,EACJ,OACA,mBACA,mBACA,YACA,gBACE;EACJ,MAAM,mBAAmB,MAAM,KAAK,wBAAwB;GAC1D;GACA;GACA;GACA;GACA;GACD,CAAC;AACF,MAAI,iBACF,QAAO;AAGT,SAAO,MAAM,KAAK,uBAAuB,OAAO;;CAGlD,yBAAiC,OAAO,WAON;EAChC,MAAM,EACJ,OACA,mBACA,mBACA,YACA,qBAAqB,gBACrB,gBACE;AAEJ,QAAM,MAAM,KAAK,QAAQ,kBAAkB,EAAE,EAAE,WAAW,MAAM,CAAC;EACjE,MAAM,mBAAmB,GAAG,kBAAkB,aAAa,YAAY;EACvE,MAAM,sBAAsB,iBACxB,KAAK,QAAQ,eAAe,GAC5B,KAAA;EACJ,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;EAC1C,IAAI,kBAAkB;AACtB,MAAI;GACF,MAAM,iBAAiB,KAAK,aAAa,kBAAkB,WAAW;AACtE,SAAM,MAAM,kBAAkB,EAAE,WAAW,OAAO,CAAC;AACnD,OAAI,uBAAuB,MAAM,KAAK,WAAW,oBAAoB,EAAE;AACrE,UAAM,OAAO,qBAAqB,eAAe,cAAc;AAC/D,sBAAkB;;AAEpB,SAAM,KAAK,yBAAyB,eAAe;GACnD,MAAM,WAA4B;IAChC,eAAe;IACf;IACA;IACA;IACA,eAAA;IACA;IACA,GAAI,kBACA;KAAE,6BAAY,IAAI,MAAM,EAAC,aAAa;KAAE;KAAqB,GAC7D,EAAE;IACP;AACD,SAAM,UACJ,KAAK,KAAK,kBAAkB,sBAAsB,EAClD,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,KACrC;IAAE,UAAU;IAAQ,MAAM;IAAO,MAAM;IAAM,CAC9C;AACD,SAAM,OAAO,kBAAkB,kBAAkB;AACjD,UAAO,KAAK,iBACV,UACA,KAAK,aAAa,mBAAmB,WAAW,EAChD,kBACD;WACM,OAAO;AACd,OACE,mBACA,uBACA,CAAC,MAAM,KAAK,WAAW,oBAAoB,EAC3C;IACA,MAAM,sBAAsB,KAAK,KAAK,kBAAkB,OAAO;AAC/D,QAAI,MAAM,KAAK,WAAW,oBAAoB,CAC5C,OAAM,OAAO,qBAAqB,oBAAoB;;AAG1D,SAAM,GAAG,kBAAkB;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;AAC5D,OAAI,MAAM,KAAK,WAAW,kBAAkB,EAAE;IAC5C,MAAM,gBAAgB,MAAM,KAAK,aAAa,kBAAkB;AAChE,QAAI,eAAe;AACjB,UAAK,uBAAuB,eAAe,OAAO,YAAY,YAAY;KAC1E,MAAM,UAAU,KAAK,aAAa,mBAAmB,WAAW;AAChE,WAAM,KAAK,yBAAyB,QAAQ;AAC5C,YAAO,KAAK,iBAAiB,eAAe,SAAS,kBAAkB;;;AAG3E,SAAM;;;CAIV,8BAA8B,WAIL;EACvB,MAAM,EAAE,OAAO,WAAW,kBAAkB;EAC5C,MAAM,oBAAoB,KAAK,eAAe,wBAC5C,OACA,wBACD;AACD,SAAO;GACL,IAAI;GACJ,aAAa,KAAA;GACb,SAAS;IACP,GAAG,KAAK,aAAa,mBAAmB,wBAAwB;IAChE,QAAQ;IACR,eAAe,KAAK,QAAQ,cAAc;IAC3C;GACD,mBAAmB;GACnB;GACA,qBAAqB,KAAK,QAAQ,cAAc;GACjD;;CAGH,eAAe,OAAO,YAAyD;EAC7E,MAAM,CAAC,WAAW,aAAa,YAAY,YAAY,gBAAgB,aACrE,MAAM,QAAQ,IAAI;GAChB,KAAK,iBAAiB,QAAQ,cAAc;GAC5C,KAAK,iBAAiB,QAAQ,gBAAgB;GAC9C,KAAK,iBAAiB,QAAQ,eAAe;GAC7C,KAAK,iBAAiB,QAAQ,eAAe;GAC7C,KAAK,iBAAiB,QAAQ,mBAAmB;GACjD,KAAK,iBAAiB,QAAQ,cAAc;GAC7C,CAAC;AACJ,SAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA,YAAY,YAAY,cAAc,aAAa,aAAa,iBAAiB;GAClF;;CAGH,sBAAsB,OAAO,WAGR;EACnB,MAAM,EAAE,UAAU,qBAAqB,mBAAmB;EAC1D,MAAM,oBAAoB,SAAS,QAAQ;AAE3C,MAAI,CADa,MAAM,KAAK,aAAa,kBAAkB,CAEzD;EAEF,MAAM,sBAAsB,iBACxB,KAAK,QAAQ,eAAe,GAC5B,KAAA;AACJ,MACE,uBACA,CAAC,MAAM,KAAK,WAAW,oBAAoB,IAC3C,MAAM,KAAK,WAAW,SAAS,QAAQ,cAAc,EACrD;AACA,SAAM,MAAM,KAAK,QAAQ,oBAAoB,EAAE,EAAE,WAAW,MAAM,CAAC;AACnE,SAAM,OAAO,SAAS,QAAQ,eAAe,oBAAoB;;AAEnE,QAAM,GAAG,mBAAmB;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;;CAG/D,0BAAkC,OAAO,WAMK;EAC5C,MAAM,EAAE,OAAO,mBAAmB,mBAAmB,YAAY,gBAAgB;EACjF,IAAI,WAAW,MAAM,KAAK,aAAa,kBAAkB;AACzD,MAAI,CAAC,SACH;AAEF,OAAK,uBAAuB,UAAU,OAAO,YAAY,YAAY;AACrE,MAAI,eAAe,CAAC,SAAS,YAC3B,YAAW,MAAM,KAAK,cAAc,mBAAmB,UAAU,YAAY;EAE/E,MAAM,UAAU,KAAK,aAAa,mBAAmB,WAAW;AAChE,QAAM,KAAK,yBAAyB,QAAQ;AAC5C,SAAO,KAAK,iBAAiB,UAAU,SAAS,kBAAkB;;CAGpE,gBACE,mBACA,gBACuB;EACvB,QAAQ;EACR,eAAA;EACA;EACA;EACA,eAAe,KAAK,KAAK,mBAAmB,OAAO;EACnD,iBAAiB,KAAK,KAAK,mBAAmB,SAAS;EACvD,gBAAgB,KAAK,KAAK,mBAAmB,QAAQ;EACrD,gBAAgB,KAAK,KAAK,mBAAmB,QAAQ;EACrD,oBAAoB,KAAK,KAAK,mBAAmB,MAAM;EACvD,eAAe,KAAK,KAAK,mBAAmB,OAAO;EACpD;CAED,2BAAmC,OAAO,YAA8C;AACtF,QAAM,QAAQ,IAAI;GAChB,MAAM,QAAQ,eAAe,EAAE,WAAW,MAAM,CAAC;GACjD,MAAM,QAAQ,iBAAiB,EAAE,WAAW,MAAM,CAAC;GACnD,MAAM,QAAQ,gBAAgB,EAAE,WAAW,MAAM,CAAC;GAClD,MAAM,QAAQ,gBAAgB,EAAE,WAAW,MAAM,CAAC;GAClD,MAAM,QAAQ,oBAAoB,EAAE,WAAW,MAAM,CAAC;GACtD,MAAM,QAAQ,eAAe,EAAE,WAAW,MAAM,CAAC;GAClD,CAAC;;CAGJ,eAAuB,OAAO,sBAAoE;AAChG,MAAI;GACF,MAAM,MAAM,KAAK,MACf,MAAM,SAAS,KAAK,KAAK,mBAAmB,sBAAsB,EAAE,OAAO,CAC5E;AACD,OACE,IAAI,kBAAkB,KACtB,IAAI,kBAAA,KACJ,OAAO,IAAI,UAAU,YACrB,OAAO,IAAI,eAAe,YACzB,IAAI,gBAAgB,KAAA,KAAa,OAAO,IAAI,gBAAgB,YAC7D,OAAO,IAAI,cAAc,SAEzB,OAAM,IAAI,MAAM,6BAA6B,oBAAoB;AAEnE,UAAO;WACA,OAAO;AACd,OAAI,KAAK,mBAAmB,MAAM,CAChC;AAEF,SAAM;;;CAIV,oBACE,UACA,SACA,oBAAoB,OACG;EACvB,IAAI,SAAS;EACb,aAAa,SAAS;EACtB;EACA;EACA,WAAW,SAAS;EACpB,YAAY,SAAS;EACrB,qBAAqB,SAAS;EAC/B;CAED,0BACE,UACA,OACA,YACA,gBACS;AACT,MAAI,SAAS,UAAU,SAAS,SAAS,eAAe,WACtD,OAAM,IAAI,MACR,gCAAgC,MAAM,GAAG,WAAW,MAAM,SAAS,MAAM,GAAG,SAAS,aACtF;AAEH,MAAI,SAAS,eAAe,SAAS,gBAAgB,YACnD,OAAM,IAAI,MACR,gBAAgB,MAAM,GAAG,WAAW,UAAU,SAAS,YAAY,OAAO,eAAe,UAAU,MACpG;;CAIL,gBAAwB,OACtB,mBACA,UACA,gBAC6B;EAC7B,MAAM,eAAe;GAAE,GAAG;GAAU;GAAa;EACjD,MAAM,eAAe,KAAK,KAAK,mBAAmB,sBAAsB;EACxE,MAAM,gBAAgB,GAAG,aAAa,GAAG,YAAY,CAAC;AACtD,MAAI;AACF,SAAM,UACJ,eACA,GAAG,KAAK,UAAU,cAAc,MAAM,EAAE,CAAC,KACzC;IAAE,UAAU;IAAQ,MAAM;IAAO,MAAM;IAAM,CAC9C;AACD,SAAM,OAAO,eAAe,aAAa;AACzC,UAAO;YACC;AACR,SAAM,GAAG,eAAe,EAAE,OAAO,MAAM,CAAC;;;CAI5C,gBAAwB,OAAe,UAAwB;AAC7D,MAAI,CAAC,gBAAgB,KAAK,MAAM,CAC9B,OAAM,IAAI,MAAM,GAAG,MAAM,yBAAyB,QAAQ;;CAI9D,mBAA2B,OAAO,cAAuC;AACvE,MAAI;GACF,MAAM,UAAU,MAAM,QAAQ,WAAW,EAAE,eAAe,MAAM,CAAC;GACjE,IAAI,QAAQ;AACZ,QAAK,MAAM,SAAS,SAAS;IAC3B,MAAM,YAAY,KAAK,KAAK,WAAW,MAAM,KAAK;AAClD,QAAI,MAAM,gBAAgB,CACxB;AAEF,QAAI,MAAM,aAAa,EAAE;AACvB,cAAS,MAAM,KAAK,iBAAiB,UAAU;AAC/C;;AAEF,QAAI,MAAM,QAAQ,CAChB,WAAU,MAAM,KAAK,UAAU,EAAE;;AAGrC,UAAO;WACA,OAAO;AACd,OAAI,KAAK,mBAAmB,MAAM,CAChC,QAAO;AAET,SAAM;;;CAIV,aAAqB,OAAO,eAAyC;AACnE,MAAI;AACF,SAAM,OAAO,WAAW;AACxB,UAAO;UACD;AACN,UAAO;;;CAIX,sBAA8B,UAC5B,OAAO,UAAU,YAAY,UAAU,QACvC,UAAU,SAAU,MAA6B,SAAS"}
|
|
1
|
+
{"version":3,"file":"app-instance-storage.service.js","names":[],"sources":["../../src/services/app-instance-storage.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, mkdir, readdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport {\n APP_STORAGE_LAYOUT_VERSION,\n DEFAULT_APP_INSTANCE_ID,\n type AppInstanceMetadata,\n type AppInstanceRecord,\n type AppStorageContext,\n type AppStorageUsage,\n} from \"#app-runtime/types/app-storage.types.js\";\n\nconst STORAGE_METADATA_FILE = \"metadata.json\";\nconst SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\n\nexport class AppInstanceStorageService {\n private readonly fileLockService = new FileLockService();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {}\n\n materializeDefaultInstance = async (params: {\n appId: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n return await this.materialize({\n ...params,\n instanceId: DEFAULT_APP_INSTANCE_ID,\n instanceDirectory: this.appHomeService.getAppInstanceDirectory(\n params.appId,\n DEFAULT_APP_INSTANCE_ID,\n ),\n });\n };\n\n materialize = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n this.assertSafeId(params.appId, \"appId\");\n this.assertSafeId(params.instanceId, \"instanceId\");\n const instanceDirectory = path.resolve(params.instanceDirectory);\n return await this.fileLockService.withLock(\n `${instanceDirectory}.lock`,\n async () => await this.materializeUnlocked({ ...params, instanceDirectory }),\n );\n };\n\n private materializeUnlocked = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n const {\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n publisherId,\n } = params;\n const existingInstance = await this.resolveExistingInstance({\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n publisherId,\n });\n if (existingInstance) {\n return existingInstance;\n }\n\n return await this.materializeNewInstance(params);\n };\n\n private materializeNewInstance = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n const {\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n legacyDataDirectory: legacyDataPath,\n publisherId,\n } = params;\n\n await mkdir(path.dirname(instanceDirectory), { recursive: true });\n const stagingDirectory = `${instanceDirectory}.migrating-${randomUUID()}`;\n const legacyDataDirectory = legacyDataPath\n ? path.resolve(legacyDataPath)\n : undefined;\n const createdAt = new Date().toISOString();\n let movedLegacyData = false;\n try {\n const stagingStorage = this.buildContext(stagingDirectory, instanceId);\n await mkdir(stagingDirectory, { recursive: false });\n if (legacyDataDirectory && await this.pathExists(legacyDataDirectory)) {\n await rename(legacyDataDirectory, stagingStorage.dataDirectory);\n movedLegacyData = true;\n }\n await this.ensureStorageDirectories(stagingStorage);\n const metadata: AppInstanceMetadata = {\n schemaVersion: 1,\n appId,\n instanceId,\n publisherId,\n layoutVersion: APP_STORAGE_LAYOUT_VERSION,\n createdAt,\n ...(movedLegacyData\n ? { migratedAt: new Date().toISOString(), legacyDataDirectory }\n : {}),\n };\n await writeFile(\n path.join(stagingDirectory, STORAGE_METADATA_FILE),\n `${JSON.stringify(metadata, null, 2)}\\n`,\n { encoding: \"utf8\", mode: 0o600, flag: \"wx\" },\n );\n await rename(stagingDirectory, instanceDirectory);\n return this.toInstanceRecord(\n metadata,\n this.buildContext(instanceDirectory, instanceId),\n dataSchemaVersion,\n );\n } catch (error) {\n if (\n movedLegacyData &&\n legacyDataDirectory &&\n !await this.pathExists(legacyDataDirectory)\n ) {\n const stagedDataDirectory = path.join(stagingDirectory, \"data\");\n if (await this.pathExists(stagedDataDirectory)) {\n await rename(stagedDataDirectory, legacyDataDirectory);\n }\n }\n await rm(stagingDirectory, { recursive: true, force: true });\n if (await this.pathExists(instanceDirectory)) {\n const racedMetadata = await this.readMetadata(instanceDirectory);\n if (racedMetadata) {\n this.assertMetadataIdentity(racedMetadata, appId, instanceId, publisherId);\n const storage = this.buildContext(instanceDirectory, instanceId);\n await this.ensureStorageDirectories(storage);\n return this.toInstanceRecord(racedMetadata, storage, dataSchemaVersion);\n }\n }\n throw error;\n }\n };\n\n buildLegacyDefaultInstance = (params: {\n appId: string;\n dataDirectory: string;\n createdAt: string;\n }): AppInstanceRecord => {\n const { appId, createdAt, dataDirectory } = params;\n const instanceDirectory = this.appHomeService.getAppInstanceDirectory(\n appId,\n DEFAULT_APP_INSTANCE_ID,\n );\n return {\n id: DEFAULT_APP_INSTANCE_ID,\n publisherId: undefined,\n storage: {\n ...this.buildContext(instanceDirectory, DEFAULT_APP_INSTANCE_ID),\n layout: \"legacy\",\n dataDirectory: path.resolve(dataDirectory),\n },\n dataSchemaVersion: 1,\n createdAt,\n legacyDataDirectory: path.resolve(dataDirectory),\n };\n };\n\n measureUsage = async (storage: AppStorageContext): Promise<AppStorageUsage> => {\n const [dataBytes, configBytes, stateBytes, cacheBytes, temporaryBytes, logsBytes] =\n await Promise.all([\n this.measureDirectory(storage.dataDirectory),\n this.measureDirectory(storage.configDirectory),\n this.measureDirectory(storage.stateDirectory),\n this.measureDirectory(storage.cacheDirectory),\n this.measureDirectory(storage.temporaryDirectory),\n this.measureDirectory(storage.logsDirectory),\n ]);\n return {\n dataBytes,\n configBytes,\n stateBytes,\n cacheBytes,\n temporaryBytes,\n logsBytes,\n totalBytes: dataBytes + configBytes + stateBytes + cacheBytes + temporaryBytes + logsBytes,\n };\n };\n\n inspect = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n }): Promise<AppInstanceRecord> => {\n const { appId, instanceId, instanceDirectory: instancePath } = params;\n this.assertSafeId(appId, \"appId\");\n this.assertSafeId(instanceId, \"instanceId\");\n const instanceDirectory = path.resolve(instancePath);\n const metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n throw new Error(`App Instance metadata 不存在:${instanceDirectory}`);\n }\n this.assertMetadataCoordinates(metadata, appId, instanceId);\n return this.toInstanceRecord(\n metadata,\n this.buildContext(instanceDirectory, instanceId),\n );\n };\n\n rollbackNewInstance = async (params: {\n instance: AppInstanceRecord;\n legacyDataDirectory?: string;\n }): Promise<void> => {\n const { instance, legacyDataDirectory: legacyDataPath } = params;\n const instanceDirectory = instance.storage.instanceDirectory;\n const metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n return;\n }\n const legacyDataDirectory = legacyDataPath\n ? path.resolve(legacyDataPath)\n : undefined;\n if (\n legacyDataDirectory &&\n !await this.pathExists(legacyDataDirectory) &&\n await this.pathExists(instance.storage.dataDirectory)\n ) {\n await mkdir(path.dirname(legacyDataDirectory), { recursive: true });\n await rename(instance.storage.dataDirectory, legacyDataDirectory);\n }\n await rm(instanceDirectory, { recursive: true, force: true });\n };\n\n private resolveExistingInstance = async (params: {\n appId: string;\n dataSchemaVersion?: number;\n instanceDirectory: string;\n instanceId: string;\n publisherId?: string;\n }): Promise<AppInstanceRecord | undefined> => {\n const { appId, dataSchemaVersion, instanceDirectory, instanceId, publisherId } = params;\n let metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n return undefined;\n }\n this.assertMetadataIdentity(metadata, appId, instanceId, publisherId);\n if (publisherId && !metadata.publisherId) {\n metadata = await this.bindPublisher(instanceDirectory, metadata, publisherId);\n }\n const storage = this.buildContext(instanceDirectory, instanceId);\n await this.ensureStorageDirectories(storage);\n return this.toInstanceRecord(metadata, storage, dataSchemaVersion);\n };\n\n private buildContext = (\n instanceDirectory: string,\n instanceId: string,\n ): AppStorageContext => ({\n layout: \"instance-v1\",\n layoutVersion: APP_STORAGE_LAYOUT_VERSION,\n instanceId,\n instanceDirectory,\n dataDirectory: path.join(instanceDirectory, \"data\"),\n configDirectory: path.join(instanceDirectory, \"config\"),\n stateDirectory: path.join(instanceDirectory, \"state\"),\n cacheDirectory: path.join(instanceDirectory, \"cache\"),\n temporaryDirectory: path.join(instanceDirectory, \"tmp\"),\n logsDirectory: path.join(instanceDirectory, \"logs\"),\n });\n\n private ensureStorageDirectories = async (storage: AppStorageContext): Promise<void> => {\n await Promise.all([\n mkdir(storage.dataDirectory, { recursive: true }),\n mkdir(storage.configDirectory, { recursive: true }),\n mkdir(storage.stateDirectory, { recursive: true }),\n mkdir(storage.cacheDirectory, { recursive: true }),\n mkdir(storage.temporaryDirectory, { recursive: true }),\n mkdir(storage.logsDirectory, { recursive: true }),\n ]);\n };\n\n private readMetadata = async (\n instanceDirectory: string,\n ): Promise<AppInstanceMetadata | undefined> => {\n try {\n const raw = JSON.parse(\n await readFile(path.join(instanceDirectory, STORAGE_METADATA_FILE), \"utf8\"),\n ) as Partial<AppInstanceMetadata>;\n if (\n raw.schemaVersion !== 1 ||\n raw.layoutVersion !== APP_STORAGE_LAYOUT_VERSION ||\n typeof raw.appId !== \"string\" ||\n typeof raw.instanceId !== \"string\" ||\n (raw.publisherId !== undefined && typeof raw.publisherId !== \"string\") ||\n typeof raw.createdAt !== \"string\"\n ) {\n throw new Error(`无效的 App Instance metadata:${instanceDirectory}`);\n }\n return raw as AppInstanceMetadata;\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return undefined;\n }\n throw error;\n }\n };\n\n private toInstanceRecord = (\n metadata: AppInstanceMetadata,\n storage: AppStorageContext,\n dataSchemaVersion = 1,\n ): AppInstanceRecord => ({\n id: metadata.instanceId,\n publisherId: metadata.publisherId,\n storage,\n dataSchemaVersion,\n createdAt: metadata.createdAt,\n migratedAt: metadata.migratedAt,\n legacyDataDirectory: metadata.legacyDataDirectory,\n });\n\n private assertMetadataIdentity = (\n metadata: AppInstanceMetadata,\n appId: string,\n instanceId: string,\n publisherId?: string,\n ): void => {\n this.assertMetadataCoordinates(metadata, appId, instanceId);\n if (metadata.publisherId && metadata.publisherId !== publisherId) {\n throw new Error(\n `App Instance ${appId}/${instanceId} 已绑定发布者 ${metadata.publisherId},拒绝由 ${publisherId ?? \"未验证本地来源\"} 接管。`,\n );\n }\n };\n\n private assertMetadataCoordinates = (\n metadata: AppInstanceMetadata,\n appId: string,\n instanceId: string,\n ): void => {\n if (metadata.appId !== appId || metadata.instanceId !== instanceId) {\n throw new Error(\n `App Instance identity 不匹配:期望 ${appId}/${instanceId},实际 ${metadata.appId}/${metadata.instanceId}`,\n );\n }\n };\n\n private bindPublisher = async (\n instanceDirectory: string,\n metadata: AppInstanceMetadata,\n publisherId: string,\n ): Promise<AppInstanceMetadata> => {\n const nextMetadata = { ...metadata, publisherId };\n const metadataPath = path.join(instanceDirectory, STORAGE_METADATA_FILE);\n const temporaryPath = `${metadataPath}.${randomUUID()}.tmp`;\n try {\n await writeFile(\n temporaryPath,\n `${JSON.stringify(nextMetadata, null, 2)}\\n`,\n { encoding: \"utf8\", mode: 0o600, flag: \"wx\" },\n );\n await rename(temporaryPath, metadataPath);\n return nextMetadata;\n } finally {\n await rm(temporaryPath, { force: true });\n }\n };\n\n private assertSafeId = (value: string, field: string): void => {\n if (!SAFE_ID_PATTERN.test(value)) {\n throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);\n }\n };\n\n private measureDirectory = async (directory: string): Promise<number> => {\n try {\n const entries = await readdir(directory, { withFileTypes: true });\n let bytes = 0;\n for (const entry of entries) {\n const entryPath = path.join(directory, entry.name);\n if (entry.isSymbolicLink()) {\n continue;\n }\n if (entry.isDirectory()) {\n bytes += await this.measureDirectory(entryPath);\n continue;\n }\n if (entry.isFile()) {\n bytes += (await stat(entryPath)).size;\n }\n }\n return bytes;\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return 0;\n }\n throw error;\n }\n };\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":"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"}
|
|
@@ -32,6 +32,33 @@ type AppInstanceRecord = {
|
|
|
32
32
|
migratedAt?: string;
|
|
33
33
|
legacyDataDirectory?: string;
|
|
34
34
|
};
|
|
35
|
+
type AppInstanceMetadata = {
|
|
36
|
+
schemaVersion: 1;
|
|
37
|
+
appId: string;
|
|
38
|
+
instanceId: string;
|
|
39
|
+
publisherId?: string;
|
|
40
|
+
layoutVersion: typeof APP_STORAGE_LAYOUT_VERSION;
|
|
41
|
+
createdAt: string;
|
|
42
|
+
migratedAt?: string;
|
|
43
|
+
legacyDataDirectory?: string;
|
|
44
|
+
};
|
|
45
|
+
type AppInstanceInventoryEntry = {
|
|
46
|
+
appId: string;
|
|
47
|
+
instanceId: string;
|
|
48
|
+
publisherId?: string;
|
|
49
|
+
storage: AppStorageContext;
|
|
50
|
+
usage: AppStorageUsage;
|
|
51
|
+
createdAt: string;
|
|
52
|
+
migratedAt?: string;
|
|
53
|
+
};
|
|
54
|
+
type AppInstanceInventoryDiagnostic = {
|
|
55
|
+
instanceDirectory: string;
|
|
56
|
+
message: string;
|
|
57
|
+
};
|
|
58
|
+
type AppInstanceInventory = {
|
|
59
|
+
entries: AppInstanceInventoryEntry[];
|
|
60
|
+
diagnostics: AppInstanceInventoryDiagnostic[];
|
|
61
|
+
};
|
|
35
62
|
//#endregion
|
|
36
|
-
export { APP_STORAGE_LAYOUT_VERSION, AppInstanceRecord, AppStorageContext, AppStorageLayout, AppStorageUsage, DEFAULT_APP_INSTANCE_ID };
|
|
63
|
+
export { APP_STORAGE_LAYOUT_VERSION, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceMetadata, AppInstanceRecord, AppStorageContext, AppStorageLayout, AppStorageUsage, DEFAULT_APP_INSTANCE_ID };
|
|
37
64
|
//# sourceMappingURL=app-storage.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-storage.types.d.ts","names":[],"sources":["../../src/types/app-storage.types.ts"],"mappings":";cAAa,uBAAA;AAAA,cACA,0BAAA;AAAA,KAED,gBAAA;AAAA,KAEA,iBAAA;EACV,MAAA,EAAQ,gBAAA;EACR,aAAA,SAAsB,0BAAA;EACtB,UAAA;EACA,iBAAA;EACA,aAAA;EACA,eAAA;EACA,cAAA;EACA,cAAA;EACA,kBAAA;EACA,aAAA;AAAA;AAAA,KAGU,eAAA;EACV,SAAA;EACA,WAAA;EACA,UAAA;EACA,UAAA;EACA,cAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,KAGU,iBAAA;EACV,EAAA;EACA,WAAA;EACA,OAAA,EAAS,iBAAA;EACT,iBAAA;EACA,SAAA;EACA,UAAA;EACA,mBAAA;AAAA"}
|
|
1
|
+
{"version":3,"file":"app-storage.types.d.ts","names":[],"sources":["../../src/types/app-storage.types.ts"],"mappings":";cAAa,uBAAA;AAAA,cACA,0BAAA;AAAA,KAED,gBAAA;AAAA,KAEA,iBAAA;EACV,MAAA,EAAQ,gBAAA;EACR,aAAA,SAAsB,0BAAA;EACtB,UAAA;EACA,iBAAA;EACA,aAAA;EACA,eAAA;EACA,cAAA;EACA,cAAA;EACA,kBAAA;EACA,aAAA;AAAA;AAAA,KAGU,eAAA;EACV,SAAA;EACA,WAAA;EACA,UAAA;EACA,UAAA;EACA,cAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,KAGU,iBAAA;EACV,EAAA;EACA,WAAA;EACA,OAAA,EAAS,iBAAA;EACT,iBAAA;EACA,SAAA;EACA,UAAA;EACA,mBAAA;AAAA;AAAA,KAGU,mBAAA;EACV,aAAA;EACA,KAAA;EACA,UAAA;EACA,WAAA;EACA,aAAA,SAAsB,0BAAA;EACtB,SAAA;EACA,UAAA;EACA,mBAAA;AAAA;AAAA,KAGU,yBAAA;EACV,KAAA;EACA,UAAA;EACA,WAAA;EACA,OAAA,EAAS,iBAAA;EACT,KAAA,EAAO,eAAA;EACP,SAAA;EACA,UAAA;AAAA;AAAA,KAGU,8BAAA;EACV,iBAAA;EACA,OAAA;AAAA;AAAA,KAGU,oBAAA;EACV,OAAA,EAAS,yBAAA;EACT,WAAA,EAAa,8BAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-storage.types.js","names":[],"sources":["../../src/types/app-storage.types.ts"],"sourcesContent":["export const DEFAULT_APP_INSTANCE_ID = \"default\";\nexport const APP_STORAGE_LAYOUT_VERSION = 1;\n\nexport type AppStorageLayout = \"legacy\" | \"instance-v1\";\n\nexport type AppStorageContext = {\n layout: AppStorageLayout;\n layoutVersion: typeof APP_STORAGE_LAYOUT_VERSION;\n instanceId: string;\n instanceDirectory: string;\n dataDirectory: string;\n configDirectory: string;\n stateDirectory: string;\n cacheDirectory: string;\n temporaryDirectory: string;\n logsDirectory: string;\n};\n\nexport type AppStorageUsage = {\n dataBytes: number;\n configBytes: number;\n stateBytes: number;\n cacheBytes: number;\n temporaryBytes: number;\n logsBytes: number;\n totalBytes: number;\n};\n\nexport type AppInstanceRecord = {\n id: string;\n publisherId?: string;\n storage: AppStorageContext;\n dataSchemaVersion: number;\n createdAt: string;\n migratedAt?: string;\n legacyDataDirectory?: string;\n};\n"],"mappings":";AAAA,MAAa,0BAA0B;AACvC,MAAa,6BAA6B"}
|
|
1
|
+
{"version":3,"file":"app-storage.types.js","names":[],"sources":["../../src/types/app-storage.types.ts"],"sourcesContent":["export const DEFAULT_APP_INSTANCE_ID = \"default\";\nexport const APP_STORAGE_LAYOUT_VERSION = 1;\n\nexport type AppStorageLayout = \"legacy\" | \"instance-v1\";\n\nexport type AppStorageContext = {\n layout: AppStorageLayout;\n layoutVersion: typeof APP_STORAGE_LAYOUT_VERSION;\n instanceId: string;\n instanceDirectory: string;\n dataDirectory: string;\n configDirectory: string;\n stateDirectory: string;\n cacheDirectory: string;\n temporaryDirectory: string;\n logsDirectory: string;\n};\n\nexport type AppStorageUsage = {\n dataBytes: number;\n configBytes: number;\n stateBytes: number;\n cacheBytes: number;\n temporaryBytes: number;\n logsBytes: number;\n totalBytes: number;\n};\n\nexport type AppInstanceRecord = {\n id: string;\n publisherId?: string;\n storage: AppStorageContext;\n dataSchemaVersion: number;\n createdAt: string;\n migratedAt?: string;\n legacyDataDirectory?: string;\n};\n\nexport type AppInstanceMetadata = {\n schemaVersion: 1;\n appId: string;\n instanceId: string;\n publisherId?: string;\n layoutVersion: typeof APP_STORAGE_LAYOUT_VERSION;\n createdAt: string;\n migratedAt?: string;\n legacyDataDirectory?: string;\n};\n\nexport type AppInstanceInventoryEntry = {\n appId: string;\n instanceId: string;\n publisherId?: string;\n storage: AppStorageContext;\n usage: AppStorageUsage;\n createdAt: string;\n migratedAt?: string;\n};\n\nexport type AppInstanceInventoryDiagnostic = {\n instanceDirectory: string;\n message: string;\n};\n\nexport type AppInstanceInventory = {\n entries: AppInstanceInventoryEntry[];\n diagnostics: AppInstanceInventoryDiagnostic[];\n};\n"],"mappings":";AAAA,MAAa,0BAA0B;AACvC,MAAa,6BAA6B"}
|