@nextclaw/app-runtime 0.9.15 → 0.11.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.
Files changed (68) hide show
  1. package/dist/controllers/publish.controller.js +7 -0
  2. package/dist/controllers/publish.controller.js.map +1 -1
  3. package/dist/index.d.ts +8 -3
  4. package/dist/index.js +6 -1
  5. package/dist/package.js +1 -1
  6. package/dist/services/app-artifact-validation.service.d.ts +46 -0
  7. package/dist/services/app-artifact-validation.service.d.ts.map +1 -0
  8. package/dist/services/app-artifact-validation.service.js +250 -0
  9. package/dist/services/app-artifact-validation.service.js.map +1 -0
  10. package/dist/services/app-bundle.service.d.ts +4 -8
  11. package/dist/services/app-bundle.service.d.ts.map +1 -1
  12. package/dist/services/app-bundle.service.js +6 -65
  13. package/dist/services/app-bundle.service.js.map +1 -1
  14. package/dist/services/app-home.service.d.ts +4 -0
  15. package/dist/services/app-home.service.d.ts.map +1 -1
  16. package/dist/services/app-home.service.js +15 -1
  17. package/dist/services/app-home.service.js.map +1 -1
  18. package/dist/services/app-install-source.service.js +14 -1
  19. package/dist/services/app-install-source.service.js.map +1 -1
  20. package/dist/services/app-installation-filesystem.service.js +68 -0
  21. package/dist/services/app-installation-filesystem.service.js.map +1 -0
  22. package/dist/services/app-installation-integrity.service.d.ts +20 -0
  23. package/dist/services/app-installation-integrity.service.d.ts.map +1 -0
  24. package/dist/services/app-installation-integrity.service.js +84 -0
  25. package/dist/services/app-installation-integrity.service.js.map +1 -0
  26. package/dist/services/app-installation-lifecycle.service.js +110 -0
  27. package/dist/services/app-installation-lifecycle.service.js.map +1 -0
  28. package/dist/services/app-installation.service.d.ts +21 -11
  29. package/dist/services/app-installation.service.d.ts.map +1 -1
  30. package/dist/services/app-installation.service.js +262 -280
  31. package/dist/services/app-installation.service.js.map +1 -1
  32. package/dist/services/app-instance-storage.service.d.ts +49 -0
  33. package/dist/services/app-instance-storage.service.d.ts.map +1 -0
  34. package/dist/services/app-instance-storage.service.js +256 -0
  35. package/dist/services/app-instance-storage.service.js.map +1 -0
  36. package/dist/services/app-manifest.service.d.ts +5 -1
  37. package/dist/services/app-manifest.service.d.ts.map +1 -1
  38. package/dist/services/app-manifest.service.js +59 -3
  39. package/dist/services/app-manifest.service.js.map +1 -1
  40. package/dist/services/app-publish-validation.service.d.ts +1 -1
  41. package/dist/services/app-publish.service.d.ts +2 -1
  42. package/dist/services/app-publish.service.d.ts.map +1 -1
  43. package/dist/services/app-publish.service.js +3 -2
  44. package/dist/services/app-publish.service.js.map +1 -1
  45. package/dist/services/app-registry.service.d.ts +13 -3
  46. package/dist/services/app-registry.service.d.ts.map +1 -1
  47. package/dist/services/app-registry.service.js +143 -13
  48. package/dist/services/app-registry.service.js.map +1 -1
  49. package/dist/services/file-lock.service.d.ts +20 -0
  50. package/dist/services/file-lock.service.d.ts.map +1 -0
  51. package/dist/services/file-lock.service.js +156 -0
  52. package/dist/services/file-lock.service.js.map +1 -0
  53. package/dist/types/app-installation.types.d.ts +8 -1
  54. package/dist/types/app-installation.types.d.ts.map +1 -1
  55. package/dist/types/app-manifest.types.d.ts +22 -1
  56. package/dist/types/app-manifest.types.d.ts.map +1 -1
  57. package/dist/types/app-manifest.types.js.map +1 -1
  58. package/dist/types/app-publish.types.d.ts +4 -1
  59. package/dist/types/app-publish.types.d.ts.map +1 -1
  60. package/dist/types/app-publish.types.js.map +1 -1
  61. package/dist/types/app-registry.types.d.ts +7 -1
  62. package/dist/types/app-registry.types.d.ts.map +1 -1
  63. package/dist/types/app-remote-registry.types.d.ts +1 -1
  64. package/dist/types/app-storage.types.d.ts +37 -0
  65. package/dist/types/app-storage.types.d.ts.map +1 -0
  66. package/dist/types/app-storage.types.js +7 -0
  67. package/dist/types/app-storage.types.js.map +1 -0
  68. package/package.json +6 -1
@@ -1 +1 @@
1
- {"version":3,"file":"app-registry.service.js","names":[],"sources":["../../src/services/app-registry.service.ts"],"sourcesContent":["import { open, readFile, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppPermissions, AppResolvedComponent } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDocumentGrantMap } from \"#app-runtime/types/app-permissions.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport type {\n AppInstallSourceKind,\n AppRegistry,\n AppRegistryAppRecord,\n AppRegistryInstalledVersion,\n} from \"#app-runtime/types/app-registry.types.js\";\n\nexport class AppRegistryService {\n private static readonly mutationQueues = new Map<string, Promise<unknown>>();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {}\n\n load = async (): Promise<AppRegistry> => {\n await this.appHomeService.ensureBaseDirectories();\n try {\n const raw = await readFile(this.appHomeService.getRegistryPath(), \"utf-8\");\n return this.parseRegistry(JSON.parse(raw) as unknown);\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return { schemaVersion: 1, apps: {}, suppressedBuiltIns: {} };\n }\n throw error;\n }\n };\n\n save = async (registry: AppRegistry): Promise<void> => {\n await this.withMutation(async () => await this.saveUnlocked(registry));\n };\n\n private saveUnlocked = async (registry: AppRegistry): Promise<void> => {\n await this.appHomeService.ensureBaseDirectories();\n const registryPath = this.appHomeService.getRegistryPath();\n const temporaryPath = path.join(\n path.dirname(registryPath),\n `.${path.basename(registryPath)}.${process.pid}.${Date.now()}.tmp`,\n );\n const handle = await open(temporaryPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(registry, null, 2)}\\n`, \"utf-8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n try {\n await rename(temporaryPath, registryPath);\n } catch (error) {\n await rm(temporaryPath, { force: true });\n throw error;\n }\n };\n\n listApps = async (): Promise<AppRegistryAppRecord[]> => {\n const registry = await this.load();\n return Object.values(registry.apps).sort((left, right) => left.appId.localeCompare(right.appId));\n };\n\n getApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n const registry = await this.load();\n return registry.apps[appId];\n };\n\n getActiveVersion = async (appId: string): Promise<AppRegistryInstalledVersion | undefined> => {\n const appRecord = await this.getApp(appId);\n return appRecord?.installedVersions[appRecord.activeVersion];\n };\n\n upsertInstallation = async (params: {\n appId: string;\n name: string;\n description?: string;\n version: string;\n installDirectory: string;\n dataDirectory: string;\n sourceKind: AppInstallSourceKind;\n sourceRef: string;\n installedAt: string;\n distributionMode?: AppRegistryInstalledVersion[\"distributionMode\"];\n permissions: AppPermissions;\n registryUrl?: string;\n bundleUrl?: string;\n sha256?: string;\n publisher?: AppRegistryInstalledVersion[\"publisher\"];\n manifestSchemaVersion: 1 | 2;\n components?: AppResolvedComponent[];\n primaryPanelId?: string;\n enabled?: boolean;\n }): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const currentRecord = registry.apps[params.appId];\n const nextRecord: AppRegistryAppRecord = {\n appId: params.appId,\n name: params.name,\n description: params.description,\n activeVersion: params.version,\n enabled: params.enabled ?? currentRecord?.enabled ?? params.manifestSchemaVersion === 1,\n dataDirectory: params.dataDirectory,\n installedVersions: {\n ...(currentRecord?.installedVersions ?? {}),\n [params.version]: {\n version: params.version,\n installDirectory: params.installDirectory,\n sourceKind: params.sourceKind,\n sourceRef: params.sourceRef,\n installedAt: params.installedAt,\n distributionMode: params.distributionMode,\n permissions: params.permissions,\n registryUrl: params.registryUrl,\n bundleUrl: params.bundleUrl,\n sha256: params.sha256,\n publisher: params.publisher,\n manifestSchemaVersion: params.manifestSchemaVersion,\n components: params.components,\n primaryPanelId: params.primaryPanelId,\n },\n },\n grants: currentRecord?.grants ?? {},\n };\n registry.apps[params.appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n setEnabled = async (appId: string, enabled: boolean): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({ ...record, enabled }));\n };\n\n activateVersion = async (\n appId: string,\n version: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n if (!record.installedVersions[version]) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n return { ...record, activeVersion: version };\n });\n };\n\n updateGrants = async (\n appId: string,\n grants: AppDocumentGrantMap,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({\n ...record,\n grants: { ...record.grants, ...grants },\n }));\n };\n\n setDocumentGrant = async (\n appId: string,\n scopeId: string,\n directoryPath: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateGrants(appId, { [scopeId]: directoryPath });\n };\n\n removeDocumentGrant = async (appId: string, scopeId: string): Promise<boolean> => {\n let removed = false;\n await this.updateApp(appId, (record) => {\n if (!(scopeId in record.grants)) {\n return record;\n }\n const grants = { ...record.grants };\n delete grants[scopeId];\n removed = true;\n return { ...record, grants };\n });\n return removed;\n };\n\n removeApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n return undefined;\n }\n delete registry.apps[appId];\n await this.saveUnlocked(registry);\n return appRecord;\n });\n };\n\n isBuiltInSuppressed = async (appId: string): Promise<boolean> => {\n const registry = await this.load();\n return Boolean(registry.suppressedBuiltIns[appId]);\n };\n\n setBuiltInSuppressed = async (appId: string, suppressed: boolean): Promise<void> => {\n await this.withMutation(async () => {\n const registry = await this.load();\n if (suppressed) {\n registry.suppressedBuiltIns[appId] = { suppressedAt: new Date().toISOString() };\n } else {\n delete registry.suppressedBuiltIns[appId];\n }\n await this.saveUnlocked(registry);\n });\n };\n\n private updateApp = async (\n appId: string,\n update: (record: AppRegistryAppRecord) => AppRegistryAppRecord,\n ): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const nextRecord = update(appRecord);\n registry.apps[appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n private withMutation = async <T>(operation: () => Promise<T>): Promise<T> => {\n const registryPath = path.resolve(this.appHomeService.getRegistryPath());\n const previous = AppRegistryService.mutationQueues.get(registryPath) ?? Promise.resolve();\n const current = previous.catch(() => undefined).then(operation);\n AppRegistryService.mutationQueues.set(registryPath, current);\n try {\n return await current;\n } finally {\n if (AppRegistryService.mutationQueues.get(registryPath) === current) {\n AppRegistryService.mutationQueues.delete(registryPath);\n }\n }\n };\n\n private parseRegistry = (rawRegistry: unknown): AppRegistry => {\n const candidate = this.assertRecord(rawRegistry, \"registry.json\");\n if (candidate.schemaVersion !== 1) {\n throw new Error(\"当前只支持 registry schemaVersion = 1。\");\n }\n const rawApps = this.assertRecord(candidate.apps, \"registry.apps\");\n const apps: Record<string, AppRegistryAppRecord> = {};\n for (const [appId, rawApp] of Object.entries(rawApps)) {\n const app = this.assertRecord(rawApp, `registry.apps.${appId}`) as Partial<AppRegistryAppRecord>;\n const installedVersions: Record<string, AppRegistryInstalledVersion> = {};\n const rawVersions = this.assertRecord(\n app.installedVersions,\n `registry.apps.${appId}.installedVersions`,\n );\n for (const [version, rawVersion] of Object.entries(rawVersions)) {\n const versionRecord = this.assertRecord(\n rawVersion,\n `registry.apps.${appId}.installedVersions.${version}`,\n ) as Partial<AppRegistryInstalledVersion>;\n installedVersions[version] = {\n ...(versionRecord as AppRegistryInstalledVersion),\n version,\n manifestSchemaVersion: versionRecord.manifestSchemaVersion === 2 ? 2 : 1,\n };\n }\n const activeVersion = this.requireString(app.activeVersion, `registry.apps.${appId}.activeVersion`);\n if (!installedVersions[activeVersion]) {\n throw new Error(`registry.apps.${appId} 缺少 activeVersion ${activeVersion}。`);\n }\n apps[appId] = {\n ...(app as AppRegistryAppRecord),\n appId,\n enabled: typeof app.enabled === \"boolean\" ? app.enabled : true,\n activeVersion,\n installedVersions,\n grants: app.grants && typeof app.grants === \"object\" ? app.grants : {},\n };\n }\n const suppressedBuiltIns = candidate.suppressedBuiltIns &&\n typeof candidate.suppressedBuiltIns === \"object\" &&\n !Array.isArray(candidate.suppressedBuiltIns)\n ? Object.fromEntries(Object.entries(candidate.suppressedBuiltIns).flatMap(([appId, raw]) => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return [];\n }\n const suppressedAt = (raw as { suppressedAt?: unknown }).suppressedAt;\n return typeof suppressedAt === \"string\" ? [[appId, { suppressedAt }]] : [];\n }))\n : {};\n return { schemaVersion: 1, apps, suppressedBuiltIns };\n };\n\n private assertRecord = (value: unknown, field: string): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${field} 必须是对象。`);\n }\n return value as Record<string, unknown>;\n };\n\n private requireString = (value: unknown, field: string): string => {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${field} 必须是非空字符串。`);\n }\n return value;\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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"}
1
+ {"version":3,"file":"app-registry.service.js","names":[],"sources":["../../src/services/app-registry.service.ts"],"sourcesContent":["import { open, readFile, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type {\n AppPermissions,\n AppPlatformSecuritySummary,\n AppResolvedComponent,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppDocumentGrantMap } from \"#app-runtime/types/app-permissions.types.js\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { AppInstanceStorageService } from \"#app-runtime/services/app-instance-storage.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport type { AppInstanceRecord } from \"#app-runtime/types/app-storage.types.js\";\nimport type {\n AppInstallSourceKind,\n AppRegistry,\n AppRegistryAppRecord,\n AppRegistryInstalledVersion,\n} from \"#app-runtime/types/app-registry.types.js\";\n\nconst SAFE_APP_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\nconst SAFE_VERSION_PATTERN = /^[0-9A-Za-z]+(?:[._+-][0-9A-Za-z]+)*$/;\nconst SAFE_COMPONENT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport class AppRegistryService {\n private readonly instanceStorageService: AppInstanceStorageService;\n private readonly fileLockService = new FileLockService();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {\n this.instanceStorageService = new AppInstanceStorageService(appHomeService);\n }\n\n load = async (): Promise<AppRegistry> => {\n await this.appHomeService.ensureBaseDirectories();\n try {\n const raw = await readFile(this.appHomeService.getRegistryPath(), \"utf-8\");\n return this.parseRegistry(JSON.parse(raw) as unknown);\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return { schemaVersion: 1, apps: {}, suppressedBuiltIns: {} };\n }\n throw error;\n }\n };\n\n save = async (registry: AppRegistry): Promise<void> => {\n await this.withMutation(async () => await this.saveUnlocked(registry));\n };\n\n private saveUnlocked = async (registry: AppRegistry): Promise<void> => {\n await this.appHomeService.ensureBaseDirectories();\n const registryPath = this.appHomeService.getRegistryPath();\n const temporaryPath = path.join(\n path.dirname(registryPath),\n `.${path.basename(registryPath)}.${process.pid}.${Date.now()}.tmp`,\n );\n const handle = await open(temporaryPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(registry, null, 2)}\\n`, \"utf-8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n try {\n await rename(temporaryPath, registryPath);\n } catch (error) {\n await rm(temporaryPath, { force: true });\n throw error;\n }\n };\n\n listApps = async (): Promise<AppRegistryAppRecord[]> => {\n const registry = await this.load();\n return Object.values(registry.apps).sort((left, right) => left.appId.localeCompare(right.appId));\n };\n\n getApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n const registry = await this.load();\n return registry.apps[appId];\n };\n\n getActiveVersion = async (appId: string): Promise<AppRegistryInstalledVersion | undefined> => {\n const appRecord = await this.getApp(appId);\n return appRecord?.installedVersions[appRecord.activeVersion];\n };\n\n upsertInstallation = async (params: {\n appId: string;\n name: string;\n description?: string;\n version: string;\n installDirectory: string;\n defaultInstance: AppInstanceRecord;\n sourceKind: AppInstallSourceKind;\n sourceRef: string;\n installedAt: string;\n distributionMode?: AppRegistryInstalledVersion[\"distributionMode\"];\n permissions: AppPermissions;\n registryUrl?: string;\n bundleUrl?: string;\n sha256?: string;\n publisher?: AppRegistryInstalledVersion[\"publisher\"];\n manifestSchemaVersion: 1 | 2;\n components?: AppResolvedComponent[];\n primaryPanelId?: string;\n security?: AppPlatformSecuritySummary;\n dataSchemaVersion: number;\n contentSha256?: string;\n enabled?: boolean;\n activate?: boolean;\n }): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const currentRecord = registry.apps[params.appId];\n const currentPublisher = currentRecord?.publisher ??\n currentRecord?.installedVersions[currentRecord.activeVersion]?.publisher;\n if (currentPublisher && currentPublisher.id !== params.publisher?.id) {\n throw new Error(\n `应用 ${params.appId} 已绑定发布者 ${currentPublisher.id},拒绝由 ${params.publisher?.id ?? \"未验证本地来源\"} 覆盖。`,\n );\n }\n const nextRecord: AppRegistryAppRecord = {\n appId: params.appId,\n name: params.name,\n description: params.description,\n publisher: currentPublisher ?? params.publisher,\n activeVersion: params.activate === false && currentRecord\n ? currentRecord.activeVersion\n : params.version,\n enabled: params.enabled ?? currentRecord?.enabled ?? params.manifestSchemaVersion === 1,\n dataDirectory: params.defaultInstance.storage.dataDirectory,\n defaultInstance: params.defaultInstance,\n installedVersions: {\n ...(currentRecord?.installedVersions ?? {}),\n [params.version]: {\n version: params.version,\n installDirectory: params.installDirectory,\n sourceKind: params.sourceKind,\n sourceRef: params.sourceRef,\n installedAt: params.installedAt,\n distributionMode: params.distributionMode,\n permissions: params.permissions,\n registryUrl: params.registryUrl,\n bundleUrl: params.bundleUrl,\n sha256: params.sha256,\n publisher: params.publisher,\n manifestSchemaVersion: params.manifestSchemaVersion,\n components: params.components,\n primaryPanelId: params.primaryPanelId,\n security: params.security,\n dataSchemaVersion: params.dataSchemaVersion,\n contentSha256: params.contentSha256,\n },\n },\n grants: currentRecord?.grants ?? {},\n };\n registry.apps[params.appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n setEnabled = async (appId: string, enabled: boolean): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({ ...record, enabled }));\n };\n\n activateVersion = async (\n appId: string,\n version: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n if (!record.installedVersions[version]) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n return { ...record, activeVersion: version };\n });\n };\n\n setVersionContentDigest = async (\n appId: string,\n version: string,\n contentSha256: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => {\n const installedVersion = record.installedVersions[version];\n if (!installedVersion) {\n throw new Error(`应用 ${appId} 未安装版本 ${version}。`);\n }\n if (installedVersion.contentSha256 && installedVersion.contentSha256 !== contentSha256) {\n throw new Error(`应用 ${appId}@${version} 已存在不同的代码完整性摘要。`);\n }\n return {\n ...record,\n installedVersions: {\n ...record.installedVersions,\n [version]: { ...installedVersion, contentSha256 },\n },\n };\n });\n };\n\n updateGrants = async (\n appId: string,\n grants: AppDocumentGrantMap,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateApp(appId, (record) => ({\n ...record,\n grants: { ...record.grants, ...grants },\n }));\n };\n\n setDocumentGrant = async (\n appId: string,\n scopeId: string,\n directoryPath: string,\n ): Promise<AppRegistryAppRecord> => {\n return await this.updateGrants(appId, { [scopeId]: directoryPath });\n };\n\n removeDocumentGrant = async (appId: string, scopeId: string): Promise<boolean> => {\n let removed = false;\n await this.updateApp(appId, (record) => {\n if (!(scopeId in record.grants)) {\n return record;\n }\n const grants = { ...record.grants };\n delete grants[scopeId];\n removed = true;\n return { ...record, grants };\n });\n return removed;\n };\n\n removeApp = async (appId: string): Promise<AppRegistryAppRecord | undefined> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n return undefined;\n }\n delete registry.apps[appId];\n await this.saveUnlocked(registry);\n return appRecord;\n });\n };\n\n isBuiltInSuppressed = async (appId: string): Promise<boolean> => {\n const registry = await this.load();\n return Boolean(registry.suppressedBuiltIns[appId]);\n };\n\n setBuiltInSuppressed = async (appId: string, suppressed: boolean): Promise<void> => {\n await this.withMutation(async () => {\n const registry = await this.load();\n if (suppressed) {\n registry.suppressedBuiltIns[appId] = { suppressedAt: new Date().toISOString() };\n } else {\n delete registry.suppressedBuiltIns[appId];\n }\n await this.saveUnlocked(registry);\n });\n };\n\n private updateApp = async (\n appId: string,\n update: (record: AppRegistryAppRecord) => AppRegistryAppRecord,\n ): Promise<AppRegistryAppRecord> => {\n return await this.withMutation(async () => {\n const registry = await this.load();\n const appRecord = registry.apps[appId];\n if (!appRecord) {\n throw new Error(`未找到已安装应用:${appId}`);\n }\n const nextRecord = update(appRecord);\n registry.apps[appId] = nextRecord;\n await this.saveUnlocked(registry);\n return nextRecord;\n });\n };\n\n private withMutation = async <T>(operation: () => Promise<T>): Promise<T> => {\n const registryPath = path.resolve(this.appHomeService.getRegistryPath());\n return await this.fileLockService.withLock(`${registryPath}.lock`, operation);\n };\n\n private parseRegistry = (rawRegistry: unknown): AppRegistry => {\n const candidate = this.assertRecord(rawRegistry, \"registry.json\");\n if (candidate.schemaVersion !== 1) {\n throw new Error(\"当前只支持 registry schemaVersion = 1。\");\n }\n const rawApps = this.assertRecord(candidate.apps, \"registry.apps\");\n const apps: Record<string, AppRegistryAppRecord> = {};\n for (const [appId, rawApp] of Object.entries(rawApps)) {\n if (!SAFE_APP_ID_PATTERN.test(appId)) {\n throw new Error(`registry.apps 包含不安全的 appId:${appId}`);\n }\n const app = this.assertRecord(rawApp, `registry.apps.${appId}`) as Partial<AppRegistryAppRecord>;\n const installedVersions: Record<string, AppRegistryInstalledVersion> = {};\n const rawVersions = this.assertRecord(\n app.installedVersions,\n `registry.apps.${appId}.installedVersions`,\n );\n for (const [version, rawVersion] of Object.entries(rawVersions)) {\n if (!SAFE_VERSION_PATTERN.test(version)) {\n throw new Error(`registry.apps.${appId} 包含不安全的版本:${version}`);\n }\n const versionRecord = this.assertRecord(\n rawVersion,\n `registry.apps.${appId}.installedVersions.${version}`,\n ) as Partial<AppRegistryInstalledVersion>;\n const installDirectory = this.requireString(\n versionRecord.installDirectory,\n `registry.apps.${appId}.installedVersions.${version}.installDirectory`,\n );\n this.assertExactPath(\n installDirectory,\n this.appHomeService.getInstallDirectory(appId, version),\n `registry.apps.${appId}.installedVersions.${version}.installDirectory`,\n );\n installedVersions[version] = {\n ...(versionRecord as AppRegistryInstalledVersion),\n version,\n installDirectory: path.resolve(installDirectory),\n manifestSchemaVersion: versionRecord.manifestSchemaVersion === 2 ? 2 : 1,\n components: this.parseComponents(\n versionRecord.components,\n installDirectory,\n `registry.apps.${appId}.installedVersions.${version}.components`,\n ),\n dataSchemaVersion: typeof versionRecord.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(versionRecord.dataSchemaVersion) &&\n versionRecord.dataSchemaVersion > 0\n ? versionRecord.dataSchemaVersion\n : 1,\n };\n }\n const activeVersion = this.requireString(app.activeVersion, `registry.apps.${appId}.activeVersion`);\n if (!installedVersions[activeVersion]) {\n throw new Error(`registry.apps.${appId} 缺少 activeVersion ${activeVersion}。`);\n }\n const dataDirectory = this.requireString(\n app.dataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n const firstInstalledAt = Object.values(installedVersions)\n .map((version) => version.installedAt)\n .filter((value): value is string => typeof value === \"string\")\n .sort()[0] ?? new Date(0).toISOString();\n const defaultInstance = this.parseDefaultInstance(\n app.defaultInstance,\n appId,\n dataDirectory,\n firstInstalledAt,\n );\n apps[appId] = {\n ...(app as AppRegistryAppRecord),\n appId,\n publisher: app.publisher ?? installedVersions[activeVersion]?.publisher,\n enabled: typeof app.enabled === \"boolean\" ? app.enabled : true,\n activeVersion,\n dataDirectory: defaultInstance.storage.dataDirectory,\n defaultInstance,\n installedVersions,\n grants: app.grants && typeof app.grants === \"object\" ? app.grants : {},\n };\n }\n const suppressedBuiltIns = candidate.suppressedBuiltIns &&\n typeof candidate.suppressedBuiltIns === \"object\" &&\n !Array.isArray(candidate.suppressedBuiltIns)\n ? Object.fromEntries(Object.entries(candidate.suppressedBuiltIns).flatMap(([appId, raw]) => {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return [];\n }\n const suppressedAt = (raw as { suppressedAt?: unknown }).suppressedAt;\n return typeof suppressedAt === \"string\" ? [[appId, { suppressedAt }]] : [];\n }))\n : {};\n return { schemaVersion: 1, apps, suppressedBuiltIns };\n };\n\n private parseDefaultInstance = (\n rawInstance: unknown,\n appId: string,\n dataDirectory: string,\n createdAt: string,\n ): AppInstanceRecord => {\n if (!rawInstance || typeof rawInstance !== \"object\" || Array.isArray(rawInstance)) {\n this.assertExactPath(\n dataDirectory,\n this.appHomeService.getAppDataDirectory(appId),\n `registry.apps.${appId}.dataDirectory`,\n );\n return this.instanceStorageService.buildLegacyDefaultInstance({\n appId,\n dataDirectory,\n createdAt,\n });\n }\n const instance = rawInstance as Partial<AppInstanceRecord>;\n if (\n instance.id !== \"default\" ||\n !instance.storage ||\n typeof instance.storage !== \"object\" ||\n (instance.storage.layout !== \"legacy\" && instance.storage.layout !== \"instance-v1\")\n ) {\n throw new Error(`registry.apps.${appId}.defaultInstance 无效。`);\n }\n if (instance.publisherId !== undefined && typeof instance.publisherId !== \"string\") {\n throw new Error(`registry.apps.${appId}.defaultInstance.publisherId 无效。`);\n }\n const dataSchemaVersion = typeof instance.dataSchemaVersion === \"number\" &&\n Number.isSafeInteger(instance.dataSchemaVersion) &&\n instance.dataSchemaVersion > 0\n ? instance.dataSchemaVersion\n : 1;\n const instanceCreatedAt = typeof instance.createdAt === \"string\"\n ? instance.createdAt\n : createdAt;\n const instanceDirectory = path.resolve(\n this.appHomeService.getAppInstanceDirectory(appId, \"default\"),\n );\n const expectedStorage = {\n layout: \"instance-v1\" as const,\n layoutVersion: 1 as const,\n instanceId: \"default\",\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 if (instance.storage.layout === \"legacy\") {\n const expectedLegacyDataDirectory = this.appHomeService.getAppDataDirectory(appId);\n this.assertExactPath(\n dataDirectory,\n expectedLegacyDataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n return {\n id: \"default\",\n publisherId: instance.publisherId,\n storage: {\n ...expectedStorage,\n layout: \"legacy\",\n dataDirectory: path.resolve(expectedLegacyDataDirectory),\n },\n dataSchemaVersion,\n createdAt: instanceCreatedAt,\n migratedAt: instance.migratedAt,\n legacyDataDirectory: path.resolve(expectedLegacyDataDirectory),\n };\n }\n for (const [field, expectedPath] of Object.entries({\n instanceDirectory: expectedStorage.instanceDirectory,\n dataDirectory: expectedStorage.dataDirectory,\n configDirectory: expectedStorage.configDirectory,\n stateDirectory: expectedStorage.stateDirectory,\n cacheDirectory: expectedStorage.cacheDirectory,\n temporaryDirectory: expectedStorage.temporaryDirectory,\n logsDirectory: expectedStorage.logsDirectory,\n })) {\n this.assertExactPath(\n instance.storage[field as keyof typeof instance.storage] as string,\n expectedPath,\n `registry.apps.${appId}.defaultInstance.storage.${field}`,\n );\n }\n this.assertExactPath(\n dataDirectory,\n expectedStorage.dataDirectory,\n `registry.apps.${appId}.dataDirectory`,\n );\n return {\n id: \"default\",\n publisherId: instance.publisherId,\n storage: expectedStorage,\n dataSchemaVersion,\n createdAt: instanceCreatedAt,\n migratedAt: instance.migratedAt,\n legacyDataDirectory: instance.legacyDataDirectory,\n };\n };\n\n private assertExactPath = (actual: unknown, expected: string, field: string): void => {\n if (typeof actual !== \"string\" || path.resolve(actual) !== path.resolve(expected)) {\n throw new Error(`${field} 必须位于受管路径 ${path.resolve(expected)}。`);\n }\n };\n\n private parseComponents = (\n rawComponents: unknown,\n installDirectory: string,\n field: string,\n ): AppResolvedComponent[] | undefined => {\n if (rawComponents === undefined) {\n return undefined;\n }\n if (!Array.isArray(rawComponents)) {\n throw new Error(`${field} 必须是数组。`);\n }\n return rawComponents.map((rawComponent, index) => {\n const component = this.assertRecord(rawComponent, `${field}[${index}]`);\n const kind = component.kind;\n if (kind !== \"panel\" && kind !== \"service\") {\n throw new Error(`${field}[${index}].kind 无效。`);\n }\n const id = this.requireString(component.id, `${field}[${index}].id`);\n if (!SAFE_COMPONENT_ID_PATTERN.test(id)) {\n throw new Error(`${field}[${index}].id 不安全。`);\n }\n const relativeComponentPath = this.requireString(\n component.path,\n `${field}[${index}].path`,\n );\n const expectedComponentDirectory = path.resolve(installDirectory, relativeComponentPath);\n const relativeToInstall = path.relative(\n path.resolve(installDirectory),\n expectedComponentDirectory,\n );\n if (\n !relativeToInstall ||\n relativeToInstall.startsWith(\"..\") ||\n path.isAbsolute(relativeToInstall)\n ) {\n throw new Error(`${field}[${index}].path 必须位于版本目录内。`);\n }\n const expectedManifestPath = path.join(\n expectedComponentDirectory,\n kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\",\n );\n this.assertExactPath(\n component.componentDirectory,\n expectedComponentDirectory,\n `${field}[${index}].componentDirectory`,\n );\n this.assertExactPath(\n component.manifestPath,\n expectedManifestPath,\n `${field}[${index}].manifestPath`,\n );\n return {\n kind,\n id,\n path: relativeComponentPath,\n componentDirectory: expectedComponentDirectory,\n manifestPath: expectedManifestPath,\n };\n });\n };\n\n private assertRecord = (value: unknown, field: string): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${field} 必须是对象。`);\n }\n return value as Record<string, unknown>;\n };\n\n private requireString = (value: unknown, field: string): string => {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${field} 必须是非空字符串。`);\n }\n return value;\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":";;;;;;AAmBA,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,4BAA4B;AAElC,IAAa,qBAAb,MAAgC;CAC9B;CACA,kBAAmC,IAAI,iBAAiB;CAExD,YAAY,iBAAkD,IAAI,gBAAgB,EAAE;AAAvD,OAAA,iBAAA;AAC3B,OAAK,yBAAyB,IAAI,0BAA0B,eAAe;;CAG7E,OAAO,YAAkC;AACvC,QAAM,KAAK,eAAe,uBAAuB;AACjD,MAAI;GACF,MAAM,MAAM,MAAM,SAAS,KAAK,eAAe,iBAAiB,EAAE,QAAQ;AAC1E,UAAO,KAAK,cAAc,KAAK,MAAM,IAAI,CAAY;WAC9C,OAAO;AACd,OAAI,KAAK,mBAAmB,MAAM,CAChC,QAAO;IAAE,eAAe;IAAG,MAAM,EAAE;IAAE,oBAAoB,EAAE;IAAE;AAE/D,SAAM;;;CAIV,OAAO,OAAO,aAAyC;AACrD,QAAM,KAAK,aAAa,YAAY,MAAM,KAAK,aAAa,SAAS,CAAC;;CAGxE,eAAuB,OAAO,aAAyC;AACrE,QAAM,KAAK,eAAe,uBAAuB;EACjD,MAAM,eAAe,KAAK,eAAe,iBAAiB;EAC1D,MAAM,gBAAgB,KAAK,KACzB,KAAK,QAAQ,aAAa,EAC1B,IAAI,KAAK,SAAS,aAAa,CAAC,GAAG,QAAQ,IAAI,GAAG,KAAK,KAAK,CAAC,MAC9D;EACD,MAAM,SAAS,MAAM,KAAK,eAAe,MAAM,IAAM;AACrD,MAAI;AACF,SAAM,OAAO,UAAU,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,KAAK,QAAQ;AACzE,SAAM,OAAO,MAAM;YACX;AACR,SAAM,OAAO,OAAO;;AAEtB,MAAI;AACF,SAAM,OAAO,eAAe,aAAa;WAClC,OAAO;AACd,SAAM,GAAG,eAAe,EAAE,OAAO,MAAM,CAAC;AACxC,SAAM;;;CAIV,WAAW,YAA6C;EACtD,MAAM,WAAW,MAAM,KAAK,MAAM;AAClC,SAAO,OAAO,OAAO,SAAS,KAAK,CAAC,MAAM,MAAM,UAAU,KAAK,MAAM,cAAc,MAAM,MAAM,CAAC;;CAGlG,SAAS,OAAO,UAA6D;AAE3E,UADiB,MAAM,KAAK,MAAM,EAClB,KAAK;;CAGvB,mBAAmB,OAAO,UAAoE;EAC5F,MAAM,YAAY,MAAM,KAAK,OAAO,MAAM;AAC1C,SAAO,WAAW,kBAAkB,UAAU;;CAGhD,qBAAqB,OAAO,WAwBS;AACnC,SAAO,MAAM,KAAK,aAAa,YAAY;GACzC,MAAM,WAAW,MAAM,KAAK,MAAM;GAClC,MAAM,gBAAgB,SAAS,KAAK,OAAO;GAC3C,MAAM,mBAAmB,eAAe,aACtC,eAAe,kBAAkB,cAAc,gBAAgB;AACjE,OAAI,oBAAoB,iBAAiB,OAAO,OAAO,WAAW,GAChE,OAAM,IAAI,MACR,MAAM,OAAO,MAAM,UAAU,iBAAiB,GAAG,OAAO,OAAO,WAAW,MAAM,UAAU,MAC3F;GAEH,MAAM,aAAmC;IACvC,OAAO,OAAO;IACd,MAAM,OAAO;IACb,aAAa,OAAO;IACpB,WAAW,oBAAoB,OAAO;IACtC,eAAe,OAAO,aAAa,SAAS,gBACxC,cAAc,gBACd,OAAO;IACX,SAAS,OAAO,WAAW,eAAe,WAAW,OAAO,0BAA0B;IACtF,eAAe,OAAO,gBAAgB,QAAQ;IAC9C,iBAAiB,OAAO;IACxB,mBAAmB;KACjB,GAAI,eAAe,qBAAqB,EAAE;MACzC,OAAO,UAAU;MAChB,SAAS,OAAO;MAChB,kBAAkB,OAAO;MACzB,YAAY,OAAO;MACnB,WAAW,OAAO;MAClB,aAAa,OAAO;MACpB,kBAAkB,OAAO;MACzB,aAAa,OAAO;MACpB,aAAa,OAAO;MACpB,WAAW,OAAO;MAClB,QAAQ,OAAO;MACf,WAAW,OAAO;MAClB,uBAAuB,OAAO;MAC9B,YAAY,OAAO;MACnB,gBAAgB,OAAO;MACvB,UAAU,OAAO;MACjB,mBAAmB,OAAO;MAC1B,eAAe,OAAO;MACvB;KACF;IACD,QAAQ,eAAe,UAAU,EAAE;IACpC;AACD,YAAS,KAAK,OAAO,SAAS;AAC9B,SAAM,KAAK,aAAa,SAAS;AACjC,UAAO;IACP;;CAGJ,aAAa,OAAO,OAAe,YAAoD;AACrF,SAAO,MAAM,KAAK,UAAU,QAAQ,YAAY;GAAE,GAAG;GAAQ;GAAS,EAAE;;CAG1E,kBAAkB,OAChB,OACA,YACkC;AAClC,SAAO,MAAM,KAAK,UAAU,QAAQ,WAAW;AAC7C,OAAI,CAAC,OAAO,kBAAkB,SAC5B,OAAM,IAAI,MAAM,MAAM,MAAM,SAAS,QAAQ,GAAG;AAElD,UAAO;IAAE,GAAG;IAAQ,eAAe;IAAS;IAC5C;;CAGJ,0BAA0B,OACxB,OACA,SACA,kBACkC;AAClC,SAAO,MAAM,KAAK,UAAU,QAAQ,WAAW;GAC7C,MAAM,mBAAmB,OAAO,kBAAkB;AAClD,OAAI,CAAC,iBACH,OAAM,IAAI,MAAM,MAAM,MAAM,SAAS,QAAQ,GAAG;AAElD,OAAI,iBAAiB,iBAAiB,iBAAiB,kBAAkB,cACvE,OAAM,IAAI,MAAM,MAAM,MAAM,GAAG,QAAQ,iBAAiB;AAE1D,UAAO;IACL,GAAG;IACH,mBAAmB;KACjB,GAAG,OAAO;MACT,UAAU;MAAE,GAAG;MAAkB;MAAe;KAClD;IACF;IACD;;CAGJ,eAAe,OACb,OACA,WACkC;AAClC,SAAO,MAAM,KAAK,UAAU,QAAQ,YAAY;GAC9C,GAAG;GACH,QAAQ;IAAE,GAAG,OAAO;IAAQ,GAAG;IAAQ;GACxC,EAAE;;CAGL,mBAAmB,OACjB,OACA,SACA,kBACkC;AAClC,SAAO,MAAM,KAAK,aAAa,OAAO,GAAG,UAAU,eAAe,CAAC;;CAGrE,sBAAsB,OAAO,OAAe,YAAsC;EAChF,IAAI,UAAU;AACd,QAAM,KAAK,UAAU,QAAQ,WAAW;AACtC,OAAI,EAAE,WAAW,OAAO,QACtB,QAAO;GAET,MAAM,SAAS,EAAE,GAAG,OAAO,QAAQ;AACnC,UAAO,OAAO;AACd,aAAU;AACV,UAAO;IAAE,GAAG;IAAQ;IAAQ;IAC5B;AACF,SAAO;;CAGT,YAAY,OAAO,UAA6D;AAC9E,SAAO,MAAM,KAAK,aAAa,YAAY;GACzC,MAAM,WAAW,MAAM,KAAK,MAAM;GAClC,MAAM,YAAY,SAAS,KAAK;AAChC,OAAI,CAAC,UACH;AAEF,UAAO,SAAS,KAAK;AACrB,SAAM,KAAK,aAAa,SAAS;AACjC,UAAO;IACP;;CAGJ,sBAAsB,OAAO,UAAoC;EAC/D,MAAM,WAAW,MAAM,KAAK,MAAM;AAClC,SAAO,QAAQ,SAAS,mBAAmB,OAAO;;CAGpD,uBAAuB,OAAO,OAAe,eAAuC;AAClF,QAAM,KAAK,aAAa,YAAY;GAClC,MAAM,WAAW,MAAM,KAAK,MAAM;AAClC,OAAI,WACF,UAAS,mBAAmB,SAAS,EAAE,+BAAc,IAAI,MAAM,EAAC,aAAa,EAAE;OAE/E,QAAO,SAAS,mBAAmB;AAErC,SAAM,KAAK,aAAa,SAAS;IACjC;;CAGJ,YAAoB,OAClB,OACA,WACkC;AAClC,SAAO,MAAM,KAAK,aAAa,YAAY;GACzC,MAAM,WAAW,MAAM,KAAK,MAAM;GAClC,MAAM,YAAY,SAAS,KAAK;AAChC,OAAI,CAAC,UACH,OAAM,IAAI,MAAM,YAAY,QAAQ;GAEtC,MAAM,aAAa,OAAO,UAAU;AACpC,YAAS,KAAK,SAAS;AACvB,SAAM,KAAK,aAAa,SAAS;AACjC,UAAO;IACP;;CAGJ,eAAuB,OAAU,cAA4C;EAC3E,MAAM,eAAe,KAAK,QAAQ,KAAK,eAAe,iBAAiB,CAAC;AACxE,SAAO,MAAM,KAAK,gBAAgB,SAAS,GAAG,aAAa,QAAQ,UAAU;;CAG/E,iBAAyB,gBAAsC;EAC7D,MAAM,YAAY,KAAK,aAAa,aAAa,gBAAgB;AACjE,MAAI,UAAU,kBAAkB,EAC9B,OAAM,IAAI,MAAM,oCAAoC;EAEtD,MAAM,UAAU,KAAK,aAAa,UAAU,MAAM,gBAAgB;EAClE,MAAM,OAA6C,EAAE;AACrD,OAAK,MAAM,CAAC,OAAO,WAAW,OAAO,QAAQ,QAAQ,EAAE;AACrD,OAAI,CAAC,oBAAoB,KAAK,MAAM,CAClC,OAAM,IAAI,MAAM,8BAA8B,QAAQ;GAExD,MAAM,MAAM,KAAK,aAAa,QAAQ,iBAAiB,QAAQ;GAC/D,MAAM,oBAAiE,EAAE;GACzE,MAAM,cAAc,KAAK,aACvB,IAAI,mBACJ,iBAAiB,MAAM,oBACxB;AACD,QAAK,MAAM,CAAC,SAAS,eAAe,OAAO,QAAQ,YAAY,EAAE;AAC/D,QAAI,CAAC,qBAAqB,KAAK,QAAQ,CACrC,OAAM,IAAI,MAAM,iBAAiB,MAAM,YAAY,UAAU;IAE/D,MAAM,gBAAgB,KAAK,aACzB,YACA,iBAAiB,MAAM,qBAAqB,UAC7C;IACD,MAAM,mBAAmB,KAAK,cAC5B,cAAc,kBACd,iBAAiB,MAAM,qBAAqB,QAAQ,mBACrD;AACD,SAAK,gBACH,kBACA,KAAK,eAAe,oBAAoB,OAAO,QAAQ,EACvD,iBAAiB,MAAM,qBAAqB,QAAQ,mBACrD;AACD,sBAAkB,WAAW;KAC3B,GAAI;KACJ;KACA,kBAAkB,KAAK,QAAQ,iBAAiB;KAChD,uBAAuB,cAAc,0BAA0B,IAAI,IAAI;KACvE,YAAY,KAAK,gBACf,cAAc,YACd,kBACA,iBAAiB,MAAM,qBAAqB,QAAQ,aACrD;KACD,mBAAmB,OAAO,cAAc,sBAAsB,YAC5D,OAAO,cAAc,cAAc,kBAAkB,IACrD,cAAc,oBAAoB,IAChC,cAAc,oBACd;KACL;;GAEH,MAAM,gBAAgB,KAAK,cAAc,IAAI,eAAe,iBAAiB,MAAM,gBAAgB;AACnG,OAAI,CAAC,kBAAkB,eACrB,OAAM,IAAI,MAAM,iBAAiB,MAAM,oBAAoB,cAAc,GAAG;GAE9E,MAAM,gBAAgB,KAAK,cACzB,IAAI,eACJ,iBAAiB,MAAM,gBACxB;GACD,MAAM,mBAAmB,OAAO,OAAO,kBAAkB,CACtD,KAAK,YAAY,QAAQ,YAAY,CACrC,QAAQ,UAA2B,OAAO,UAAU,SAAS,CAC7D,MAAM,CAAC,uBAAM,IAAI,KAAK,EAAE,EAAC,aAAa;GACzC,MAAM,kBAAkB,KAAK,qBAC3B,IAAI,iBACJ,OACA,eACA,iBACD;AACD,QAAK,SAAS;IACZ,GAAI;IACJ;IACA,WAAW,IAAI,aAAa,kBAAkB,gBAAgB;IAC9D,SAAS,OAAO,IAAI,YAAY,YAAY,IAAI,UAAU;IAC1D;IACA,eAAe,gBAAgB,QAAQ;IACvC;IACA;IACA,QAAQ,IAAI,UAAU,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS,EAAE;IACvE;;AAaH,SAAO;GAAE,eAAe;GAAG;GAAM,oBAXN,UAAU,sBACnC,OAAO,UAAU,uBAAuB,YACxC,CAAC,MAAM,QAAQ,UAAU,mBAAmB,GAC1C,OAAO,YAAY,OAAO,QAAQ,UAAU,mBAAmB,CAAC,SAAS,CAAC,OAAO,SAAS;AACxF,QAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAAI,CACvD,QAAO,EAAE;IAEX,MAAM,eAAgB,IAAmC;AACzD,WAAO,OAAO,iBAAiB,WAAW,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,GAAG,EAAE;KAC1E,CAAC,GACH,EAAE;GAC+C;;CAGvD,wBACE,aACA,OACA,eACA,cACsB;AACtB,MAAI,CAAC,eAAe,OAAO,gBAAgB,YAAY,MAAM,QAAQ,YAAY,EAAE;AACjF,QAAK,gBACH,eACA,KAAK,eAAe,oBAAoB,MAAM,EAC9C,iBAAiB,MAAM,gBACxB;AACD,UAAO,KAAK,uBAAuB,2BAA2B;IAC5D;IACA;IACA;IACD,CAAC;;EAEJ,MAAM,WAAW;AACjB,MACE,SAAS,OAAO,aAChB,CAAC,SAAS,WACV,OAAO,SAAS,YAAY,YAC3B,SAAS,QAAQ,WAAW,YAAY,SAAS,QAAQ,WAAW,cAErE,OAAM,IAAI,MAAM,iBAAiB,MAAM,sBAAsB;AAE/D,MAAI,SAAS,gBAAgB,KAAA,KAAa,OAAO,SAAS,gBAAgB,SACxE,OAAM,IAAI,MAAM,iBAAiB,MAAM,kCAAkC;EAE3E,MAAM,oBAAoB,OAAO,SAAS,sBAAsB,YAC9D,OAAO,cAAc,SAAS,kBAAkB,IAChD,SAAS,oBAAoB,IAC3B,SAAS,oBACT;EACJ,MAAM,oBAAoB,OAAO,SAAS,cAAc,WACpD,SAAS,YACT;EACJ,MAAM,oBAAoB,KAAK,QAC7B,KAAK,eAAe,wBAAwB,OAAO,UAAU,CAC9D;EACD,MAAM,kBAAkB;GACtB,QAAQ;GACR,eAAe;GACf,YAAY;GACZ;GACA,eAAe,KAAK,KAAK,mBAAmB,OAAO;GACnD,iBAAiB,KAAK,KAAK,mBAAmB,SAAS;GACvD,gBAAgB,KAAK,KAAK,mBAAmB,QAAQ;GACrD,gBAAgB,KAAK,KAAK,mBAAmB,QAAQ;GACrD,oBAAoB,KAAK,KAAK,mBAAmB,MAAM;GACvD,eAAe,KAAK,KAAK,mBAAmB,OAAO;GACpD;AACD,MAAI,SAAS,QAAQ,WAAW,UAAU;GACxC,MAAM,8BAA8B,KAAK,eAAe,oBAAoB,MAAM;AAClF,QAAK,gBACH,eACA,6BACA,iBAAiB,MAAM,gBACxB;AACD,UAAO;IACL,IAAI;IACJ,aAAa,SAAS;IACtB,SAAS;KACP,GAAG;KACH,QAAQ;KACR,eAAe,KAAK,QAAQ,4BAA4B;KACzD;IACD;IACA,WAAW;IACX,YAAY,SAAS;IACrB,qBAAqB,KAAK,QAAQ,4BAA4B;IAC/D;;AAEH,OAAK,MAAM,CAAC,OAAO,iBAAiB,OAAO,QAAQ;GACjD,mBAAmB,gBAAgB;GACnC,eAAe,gBAAgB;GAC/B,iBAAiB,gBAAgB;GACjC,gBAAgB,gBAAgB;GAChC,gBAAgB,gBAAgB;GAChC,oBAAoB,gBAAgB;GACpC,eAAe,gBAAgB;GAChC,CAAC,CACA,MAAK,gBACH,SAAS,QAAQ,QACjB,cACA,iBAAiB,MAAM,2BAA2B,QACnD;AAEH,OAAK,gBACH,eACA,gBAAgB,eAChB,iBAAiB,MAAM,gBACxB;AACD,SAAO;GACL,IAAI;GACJ,aAAa,SAAS;GACtB,SAAS;GACT;GACA,WAAW;GACX,YAAY,SAAS;GACrB,qBAAqB,SAAS;GAC/B;;CAGH,mBAA2B,QAAiB,UAAkB,UAAwB;AACpF,MAAI,OAAO,WAAW,YAAY,KAAK,QAAQ,OAAO,KAAK,KAAK,QAAQ,SAAS,CAC/E,OAAM,IAAI,MAAM,GAAG,MAAM,YAAY,KAAK,QAAQ,SAAS,CAAC,GAAG;;CAInE,mBACE,eACA,kBACA,UACuC;AACvC,MAAI,kBAAkB,KAAA,EACpB;AAEF,MAAI,CAAC,MAAM,QAAQ,cAAc,CAC/B,OAAM,IAAI,MAAM,GAAG,MAAM,SAAS;AAEpC,SAAO,cAAc,KAAK,cAAc,UAAU;GAChD,MAAM,YAAY,KAAK,aAAa,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG;GACvE,MAAM,OAAO,UAAU;AACvB,OAAI,SAAS,WAAW,SAAS,UAC/B,OAAM,IAAI,MAAM,GAAG,MAAM,GAAG,MAAM,YAAY;GAEhD,MAAM,KAAK,KAAK,cAAc,UAAU,IAAI,GAAG,MAAM,GAAG,MAAM,MAAM;AACpE,OAAI,CAAC,0BAA0B,KAAK,GAAG,CACrC,OAAM,IAAI,MAAM,GAAG,MAAM,GAAG,MAAM,WAAW;GAE/C,MAAM,wBAAwB,KAAK,cACjC,UAAU,MACV,GAAG,MAAM,GAAG,MAAM,QACnB;GACD,MAAM,6BAA6B,KAAK,QAAQ,kBAAkB,sBAAsB;GACxF,MAAM,oBAAoB,KAAK,SAC7B,KAAK,QAAQ,iBAAiB,EAC9B,2BACD;AACD,OACE,CAAC,qBACD,kBAAkB,WAAW,KAAK,IAClC,KAAK,WAAW,kBAAkB,CAElC,OAAM,IAAI,MAAM,GAAG,MAAM,GAAG,MAAM,mBAAmB;GAEvD,MAAM,uBAAuB,KAAK,KAChC,4BACA,SAAS,UAAU,mBAAmB,mBACvC;AACD,QAAK,gBACH,UAAU,oBACV,4BACA,GAAG,MAAM,GAAG,MAAM,sBACnB;AACD,QAAK,gBACH,UAAU,cACV,sBACA,GAAG,MAAM,GAAG,MAAM,gBACnB;AACD,UAAO;IACL;IACA;IACA,MAAM;IACN,oBAAoB;IACpB,cAAc;IACf;IACD;;CAGJ,gBAAwB,OAAgB,UAA2C;AACjF,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CAC7D,OAAM,IAAI,MAAM,GAAG,MAAM,SAAS;AAEpC,SAAO;;CAGT,iBAAyB,OAAgB,UAA0B;AACjE,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,MAAM,CAC5C,OAAM,IAAI,MAAM,GAAG,MAAM,YAAY;AAEvC,SAAO;;CAGT,sBAA8B,UAC5B,OAAO,UAAU,YAAY,UAAU,QACvC,UAAU,SAAU,MAA6B,SAAS"}
@@ -0,0 +1,20 @@
1
+ //#region src/services/file-lock.service.d.ts
2
+ declare class FileLockService {
3
+ private static readonly localQueues;
4
+ withLock: <T>(lockPath: string, operation: () => Promise<T>, options?: {
5
+ waitTimeoutMs?: number;
6
+ }) => Promise<T>;
7
+ private acquire;
8
+ private recoverAndAcquire;
9
+ private waitOrThrow;
10
+ private removeOwnedLock;
11
+ private canRecoverLock;
12
+ private parseMetadata;
13
+ private isProcessAlive;
14
+ private pathExists;
15
+ private isAlreadyExistsError;
16
+ private isMissingFileError;
17
+ }
18
+ //#endregion
19
+ export { FileLockService };
20
+ //# sourceMappingURL=file-lock.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock.service.d.ts","names":[],"sources":["../../src/services/file-lock.service.ts"],"mappings":";cAea,eAAA;EAAA,wBACa,WAAA;EAExB,QAAA,MACE,QAAA,UACA,SAAA,QAAiB,OAAA,CAAQ,CAAA,GACzB,OAAA;IAAW,aAAA;EAAA,MACV,OAAA,CAAQ,CAAA;EAAA,QAqBH,OAAA;EAAA,QA8CA,iBAAA;EAAA,QA6CA,WAAA;EAAA,QAWA,eAAA;EAAA,QAcA,cAAA;EAAA,QAgBA,aAAA;EAAA,QAmBA,cAAA;EAAA,QAUA,UAAA;EAAA,QASA,oBAAA;EAAA,QAIA,kBAAA;AAAA"}
@@ -0,0 +1,156 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { access, mkdir, open, readFile, rm, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { setTimeout } from "node:timers/promises";
5
+ //#region src/services/file-lock.service.ts
6
+ const DEFAULT_WAIT_TIMEOUT_MS = 15e3;
7
+ const MALFORMED_LOCK_STALE_AFTER_MS = 3e4;
8
+ const RETRY_DELAY_MS = 25;
9
+ var FileLockService = class FileLockService {
10
+ static localQueues = /* @__PURE__ */ new Map();
11
+ withLock = async (lockPath, operation, options = {}) => {
12
+ const resolvedLockPath = path.resolve(lockPath);
13
+ const current = (FileLockService.localQueues.get(resolvedLockPath) ?? Promise.resolve()).catch(() => void 0).then(async () => {
14
+ const release = await this.acquire(resolvedLockPath, options.waitTimeoutMs);
15
+ try {
16
+ return await operation();
17
+ } finally {
18
+ await release();
19
+ }
20
+ });
21
+ FileLockService.localQueues.set(resolvedLockPath, current);
22
+ try {
23
+ return await current;
24
+ } finally {
25
+ if (FileLockService.localQueues.get(resolvedLockPath) === current) FileLockService.localQueues.delete(resolvedLockPath);
26
+ }
27
+ };
28
+ acquire = async (lockPath, waitTimeoutMs = DEFAULT_WAIT_TIMEOUT_MS) => {
29
+ await mkdir(path.dirname(lockPath), { recursive: true });
30
+ const startedAt = Date.now();
31
+ const recoveryLockPath = `${lockPath}.recovery`;
32
+ while (true) {
33
+ if (await this.pathExists(recoveryLockPath)) {
34
+ await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);
35
+ continue;
36
+ }
37
+ const metadata = {
38
+ token: randomUUID(),
39
+ pid: process.pid,
40
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
41
+ };
42
+ try {
43
+ const handle = await open(lockPath, "wx", 384);
44
+ try {
45
+ await handle.writeFile(`${JSON.stringify(metadata)}\n`, "utf8");
46
+ await handle.sync();
47
+ } catch (error) {
48
+ await handle.close().catch(() => void 0);
49
+ await rm(lockPath, { force: true }).catch(() => void 0);
50
+ throw error;
51
+ }
52
+ return async () => {
53
+ await handle.close();
54
+ await this.removeOwnedLock(lockPath, metadata.token);
55
+ };
56
+ } catch (error) {
57
+ if (!this.isAlreadyExistsError(error)) throw error;
58
+ if (await this.canRecoverLock(lockPath)) {
59
+ const recovered = await this.recoverAndAcquire(lockPath, recoveryLockPath);
60
+ if (recovered) return recovered;
61
+ }
62
+ await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);
63
+ }
64
+ }
65
+ };
66
+ recoverAndAcquire = async (lockPath, recoveryLockPath) => {
67
+ let recoveryHandle;
68
+ try {
69
+ recoveryHandle = await open(recoveryLockPath, "wx", 384);
70
+ await recoveryHandle.writeFile(`${process.pid}\n`, "utf8");
71
+ await recoveryHandle.sync();
72
+ } catch (error) {
73
+ await recoveryHandle?.close().catch(() => void 0);
74
+ if (this.isAlreadyExistsError(error)) return;
75
+ throw error;
76
+ }
77
+ try {
78
+ if (!await this.canRecoverLock(lockPath)) return;
79
+ await rm(lockPath, { force: true });
80
+ const metadata = {
81
+ token: randomUUID(),
82
+ pid: process.pid,
83
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
84
+ };
85
+ const handle = await open(lockPath, "wx", 384);
86
+ try {
87
+ await handle.writeFile(`${JSON.stringify(metadata)}\n`, "utf8");
88
+ await handle.sync();
89
+ } catch (error) {
90
+ await handle.close().catch(() => void 0);
91
+ await rm(lockPath, { force: true }).catch(() => void 0);
92
+ throw error;
93
+ }
94
+ return async () => {
95
+ await handle.close();
96
+ await this.removeOwnedLock(lockPath, metadata.token);
97
+ };
98
+ } finally {
99
+ await recoveryHandle.close();
100
+ await rm(recoveryLockPath, { force: true });
101
+ }
102
+ };
103
+ waitOrThrow = async (lockPath, startedAt, waitTimeoutMs) => {
104
+ if (Date.now() - startedAt >= waitTimeoutMs) throw new Error(`等待文件锁超时:${lockPath}`);
105
+ await setTimeout(RETRY_DELAY_MS + Math.floor(Math.random() * RETRY_DELAY_MS));
106
+ };
107
+ removeOwnedLock = async (lockPath, token) => {
108
+ try {
109
+ if (this.parseMetadata(await readFile(lockPath, "utf8"))?.token !== token) throw new Error(`文件锁 owner 已变化,拒绝删除:${lockPath}`);
110
+ await rm(lockPath);
111
+ } catch (error) {
112
+ if (!this.isMissingFileError(error)) throw error;
113
+ }
114
+ };
115
+ canRecoverLock = async (lockPath) => {
116
+ try {
117
+ const [raw, lockStats] = await Promise.all([readFile(lockPath, "utf8"), stat(lockPath)]);
118
+ const metadata = this.parseMetadata(raw);
119
+ if (metadata) return !this.isProcessAlive(metadata.pid);
120
+ return Date.now() - lockStats.mtimeMs >= MALFORMED_LOCK_STALE_AFTER_MS;
121
+ } catch (error) {
122
+ return this.isMissingFileError(error);
123
+ }
124
+ };
125
+ parseMetadata = (raw) => {
126
+ try {
127
+ const candidate = JSON.parse(raw);
128
+ if (typeof candidate.token !== "string" || !candidate.token || typeof candidate.pid !== "number" || !Number.isSafeInteger(candidate.pid) || candidate.pid <= 0 || typeof candidate.createdAt !== "string") return;
129
+ return candidate;
130
+ } catch {
131
+ return;
132
+ }
133
+ };
134
+ isProcessAlive = (pid) => {
135
+ try {
136
+ process.kill(pid, 0);
137
+ return true;
138
+ } catch (error) {
139
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EPERM";
140
+ }
141
+ };
142
+ pathExists = async (targetPath) => {
143
+ try {
144
+ await access(targetPath);
145
+ return true;
146
+ } catch {
147
+ return false;
148
+ }
149
+ };
150
+ isAlreadyExistsError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
151
+ isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
152
+ };
153
+ //#endregion
154
+ export { FileLockService };
155
+
156
+ //# sourceMappingURL=file-lock.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock.service.js","names":["delay"],"sources":["../../src/services/file-lock.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, mkdir, open, readFile, rm, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\ntype FileLockMetadata = {\n token: string;\n pid: number;\n createdAt: string;\n};\n\nconst DEFAULT_WAIT_TIMEOUT_MS = 15_000;\nconst MALFORMED_LOCK_STALE_AFTER_MS = 30_000;\nconst RETRY_DELAY_MS = 25;\n\nexport class FileLockService {\n private static readonly localQueues = new Map<string, Promise<unknown>>();\n\n withLock = async <T>(\n lockPath: string,\n operation: () => Promise<T>,\n options: { waitTimeoutMs?: number } = {},\n ): Promise<T> => {\n const resolvedLockPath = path.resolve(lockPath);\n const previous = FileLockService.localQueues.get(resolvedLockPath) ?? Promise.resolve();\n const current = previous.catch(() => undefined).then(async () => {\n const release = await this.acquire(resolvedLockPath, options.waitTimeoutMs);\n try {\n return await operation();\n } finally {\n await release();\n }\n });\n FileLockService.localQueues.set(resolvedLockPath, current);\n try {\n return await current;\n } finally {\n if (FileLockService.localQueues.get(resolvedLockPath) === current) {\n FileLockService.localQueues.delete(resolvedLockPath);\n }\n }\n };\n\n private acquire = async (\n lockPath: string,\n waitTimeoutMs = DEFAULT_WAIT_TIMEOUT_MS,\n ): Promise<() => Promise<void>> => {\n await mkdir(path.dirname(lockPath), { recursive: true });\n const startedAt = Date.now();\n const recoveryLockPath = `${lockPath}.recovery`;\n while (true) {\n if (await this.pathExists(recoveryLockPath)) {\n await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);\n continue;\n }\n const metadata: FileLockMetadata = {\n token: randomUUID(),\n pid: process.pid,\n createdAt: new Date().toISOString(),\n };\n try {\n const handle = await open(lockPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(metadata)}\\n`, \"utf8\");\n await handle.sync();\n } catch (error) {\n await handle.close().catch(() => undefined);\n await rm(lockPath, { force: true }).catch(() => undefined);\n throw error;\n }\n return async () => {\n await handle.close();\n await this.removeOwnedLock(lockPath, metadata.token);\n };\n } catch (error) {\n if (!this.isAlreadyExistsError(error)) {\n throw error;\n }\n if (await this.canRecoverLock(lockPath)) {\n const recovered = await this.recoverAndAcquire(lockPath, recoveryLockPath);\n if (recovered) {\n return recovered;\n }\n }\n await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);\n }\n }\n };\n\n private recoverAndAcquire = async (\n lockPath: string,\n recoveryLockPath: string,\n ): Promise<(() => Promise<void>) | undefined> => {\n let recoveryHandle;\n try {\n recoveryHandle = await open(recoveryLockPath, \"wx\", 0o600);\n await recoveryHandle.writeFile(`${process.pid}\\n`, \"utf8\");\n await recoveryHandle.sync();\n } catch (error) {\n await recoveryHandle?.close().catch(() => undefined);\n if (this.isAlreadyExistsError(error)) {\n return undefined;\n }\n throw error;\n }\n try {\n if (!await this.canRecoverLock(lockPath)) {\n return undefined;\n }\n await rm(lockPath, { force: true });\n const metadata: FileLockMetadata = {\n token: randomUUID(),\n pid: process.pid,\n createdAt: new Date().toISOString(),\n };\n const handle = await open(lockPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(metadata)}\\n`, \"utf8\");\n await handle.sync();\n } catch (error) {\n await handle.close().catch(() => undefined);\n await rm(lockPath, { force: true }).catch(() => undefined);\n throw error;\n }\n return async () => {\n await handle.close();\n await this.removeOwnedLock(lockPath, metadata.token);\n };\n } finally {\n await recoveryHandle.close();\n await rm(recoveryLockPath, { force: true });\n }\n };\n\n private waitOrThrow = async (\n lockPath: string,\n startedAt: number,\n waitTimeoutMs: number,\n ): Promise<void> => {\n if (Date.now() - startedAt >= waitTimeoutMs) {\n throw new Error(`等待文件锁超时:${lockPath}`);\n }\n await delay(RETRY_DELAY_MS + Math.floor(Math.random() * RETRY_DELAY_MS));\n };\n\n private removeOwnedLock = async (lockPath: string, token: string): Promise<void> => {\n try {\n const metadata = this.parseMetadata(await readFile(lockPath, \"utf8\"));\n if (metadata?.token !== token) {\n throw new Error(`文件锁 owner 已变化,拒绝删除:${lockPath}`);\n }\n await rm(lockPath);\n } catch (error) {\n if (!this.isMissingFileError(error)) {\n throw error;\n }\n }\n };\n\n private canRecoverLock = async (lockPath: string): Promise<boolean> => {\n try {\n const [raw, lockStats] = await Promise.all([\n readFile(lockPath, \"utf8\"),\n stat(lockPath),\n ]);\n const metadata = this.parseMetadata(raw);\n if (metadata) {\n return !this.isProcessAlive(metadata.pid);\n }\n return Date.now() - lockStats.mtimeMs >= MALFORMED_LOCK_STALE_AFTER_MS;\n } catch (error) {\n return this.isMissingFileError(error);\n }\n };\n\n private parseMetadata = (raw: string): FileLockMetadata | undefined => {\n try {\n const candidate = JSON.parse(raw) as Partial<FileLockMetadata>;\n if (\n typeof candidate.token !== \"string\" ||\n !candidate.token ||\n typeof candidate.pid !== \"number\" ||\n !Number.isSafeInteger(candidate.pid) ||\n candidate.pid <= 0 ||\n typeof candidate.createdAt !== \"string\"\n ) {\n return undefined;\n }\n return candidate as FileLockMetadata;\n } catch {\n return undefined;\n }\n };\n\n private isProcessAlive = (pid: number): boolean => {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"EPERM\";\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 isAlreadyExistsError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"EEXIST\";\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":";;;;;AAWA,MAAM,0BAA0B;AAChC,MAAM,gCAAgC;AACtC,MAAM,iBAAiB;AAEvB,IAAa,kBAAb,MAAa,gBAAgB;CAC3B,OAAwB,8BAAc,IAAI,KAA+B;CAEzE,WAAW,OACT,UACA,WACA,UAAsC,EAAE,KACzB;EACf,MAAM,mBAAmB,KAAK,QAAQ,SAAS;EAE/C,MAAM,WADW,gBAAgB,YAAY,IAAI,iBAAiB,IAAI,QAAQ,SAAS,EAC9D,YAAY,KAAA,EAAU,CAAC,KAAK,YAAY;GAC/D,MAAM,UAAU,MAAM,KAAK,QAAQ,kBAAkB,QAAQ,cAAc;AAC3E,OAAI;AACF,WAAO,MAAM,WAAW;aAChB;AACR,UAAM,SAAS;;IAEjB;AACF,kBAAgB,YAAY,IAAI,kBAAkB,QAAQ;AAC1D,MAAI;AACF,UAAO,MAAM;YACL;AACR,OAAI,gBAAgB,YAAY,IAAI,iBAAiB,KAAK,QACxD,iBAAgB,YAAY,OAAO,iBAAiB;;;CAK1D,UAAkB,OAChB,UACA,gBAAgB,4BACiB;AACjC,QAAM,MAAM,KAAK,QAAQ,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;EACxD,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,mBAAmB,GAAG,SAAS;AACrC,SAAO,MAAM;AACX,OAAI,MAAM,KAAK,WAAW,iBAAiB,EAAE;AAC3C,UAAM,KAAK,YAAY,UAAU,WAAW,cAAc;AAC1D;;GAEF,MAAM,WAA6B;IACjC,OAAO,YAAY;IACnB,KAAK,QAAQ;IACb,4BAAW,IAAI,MAAM,EAAC,aAAa;IACpC;AACD,OAAI;IACF,MAAM,SAAS,MAAM,KAAK,UAAU,MAAM,IAAM;AAChD,QAAI;AACF,WAAM,OAAO,UAAU,GAAG,KAAK,UAAU,SAAS,CAAC,KAAK,OAAO;AAC/D,WAAM,OAAO,MAAM;aACZ,OAAO;AACd,WAAM,OAAO,OAAO,CAAC,YAAY,KAAA,EAAU;AAC3C,WAAM,GAAG,UAAU,EAAE,OAAO,MAAM,CAAC,CAAC,YAAY,KAAA,EAAU;AAC1D,WAAM;;AAER,WAAO,YAAY;AACjB,WAAM,OAAO,OAAO;AACpB,WAAM,KAAK,gBAAgB,UAAU,SAAS,MAAM;;YAE/C,OAAO;AACd,QAAI,CAAC,KAAK,qBAAqB,MAAM,CACnC,OAAM;AAER,QAAI,MAAM,KAAK,eAAe,SAAS,EAAE;KACvC,MAAM,YAAY,MAAM,KAAK,kBAAkB,UAAU,iBAAiB;AAC1E,SAAI,UACF,QAAO;;AAGX,UAAM,KAAK,YAAY,UAAU,WAAW,cAAc;;;;CAKhE,oBAA4B,OAC1B,UACA,qBAC+C;EAC/C,IAAI;AACJ,MAAI;AACF,oBAAiB,MAAM,KAAK,kBAAkB,MAAM,IAAM;AAC1D,SAAM,eAAe,UAAU,GAAG,QAAQ,IAAI,KAAK,OAAO;AAC1D,SAAM,eAAe,MAAM;WACpB,OAAO;AACd,SAAM,gBAAgB,OAAO,CAAC,YAAY,KAAA,EAAU;AACpD,OAAI,KAAK,qBAAqB,MAAM,CAClC;AAEF,SAAM;;AAER,MAAI;AACF,OAAI,CAAC,MAAM,KAAK,eAAe,SAAS,CACtC;AAEF,SAAM,GAAG,UAAU,EAAE,OAAO,MAAM,CAAC;GACnC,MAAM,WAA6B;IACjC,OAAO,YAAY;IACnB,KAAK,QAAQ;IACb,4BAAW,IAAI,MAAM,EAAC,aAAa;IACpC;GACD,MAAM,SAAS,MAAM,KAAK,UAAU,MAAM,IAAM;AAChD,OAAI;AACF,UAAM,OAAO,UAAU,GAAG,KAAK,UAAU,SAAS,CAAC,KAAK,OAAO;AAC/D,UAAM,OAAO,MAAM;YACZ,OAAO;AACd,UAAM,OAAO,OAAO,CAAC,YAAY,KAAA,EAAU;AAC3C,UAAM,GAAG,UAAU,EAAE,OAAO,MAAM,CAAC,CAAC,YAAY,KAAA,EAAU;AAC1D,UAAM;;AAER,UAAO,YAAY;AACjB,UAAM,OAAO,OAAO;AACpB,UAAM,KAAK,gBAAgB,UAAU,SAAS,MAAM;;YAE9C;AACR,SAAM,eAAe,OAAO;AAC5B,SAAM,GAAG,kBAAkB,EAAE,OAAO,MAAM,CAAC;;;CAI/C,cAAsB,OACpB,UACA,WACA,kBACkB;AAClB,MAAI,KAAK,KAAK,GAAG,aAAa,cAC5B,OAAM,IAAI,MAAM,WAAW,WAAW;AAExC,QAAMA,WAAM,iBAAiB,KAAK,MAAM,KAAK,QAAQ,GAAG,eAAe,CAAC;;CAG1E,kBAA0B,OAAO,UAAkB,UAAiC;AAClF,MAAI;AAEF,OADiB,KAAK,cAAc,MAAM,SAAS,UAAU,OAAO,CAAC,EACvD,UAAU,MACtB,OAAM,IAAI,MAAM,sBAAsB,WAAW;AAEnD,SAAM,GAAG,SAAS;WACX,OAAO;AACd,OAAI,CAAC,KAAK,mBAAmB,MAAM,CACjC,OAAM;;;CAKZ,iBAAyB,OAAO,aAAuC;AACrE,MAAI;GACF,MAAM,CAAC,KAAK,aAAa,MAAM,QAAQ,IAAI,CACzC,SAAS,UAAU,OAAO,EAC1B,KAAK,SAAS,CACf,CAAC;GACF,MAAM,WAAW,KAAK,cAAc,IAAI;AACxC,OAAI,SACF,QAAO,CAAC,KAAK,eAAe,SAAS,IAAI;AAE3C,UAAO,KAAK,KAAK,GAAG,UAAU,WAAW;WAClC,OAAO;AACd,UAAO,KAAK,mBAAmB,MAAM;;;CAIzC,iBAAyB,QAA8C;AACrE,MAAI;GACF,MAAM,YAAY,KAAK,MAAM,IAAI;AACjC,OACE,OAAO,UAAU,UAAU,YAC3B,CAAC,UAAU,SACX,OAAO,UAAU,QAAQ,YACzB,CAAC,OAAO,cAAc,UAAU,IAAI,IACpC,UAAU,OAAO,KACjB,OAAO,UAAU,cAAc,SAE/B;AAEF,UAAO;UACD;AACN;;;CAIJ,kBAA0B,QAAyB;AACjD,MAAI;AACF,WAAQ,KAAK,KAAK,EAAE;AACpB,UAAO;WACA,OAAO;AACd,UAAO,OAAO,UAAU,YAAY,UAAU,QAC5C,UAAU,SAAU,MAA6B,SAAS;;;CAIhE,aAAqB,OAAO,eAAyC;AACnE,MAAI;AACF,SAAM,OAAO,WAAW;AACxB,UAAO;UACD;AACN,UAAO;;;CAIX,wBAAgC,UAC9B,OAAO,UAAU,YAAY,UAAU,QACvC,UAAU,SAAU,MAA6B,SAAS;CAE5D,sBAA8B,UAC5B,OAAO,UAAU,YAAY,UAAU,QACvC,UAAU,SAAU,MAA6B,SAAS"}
@@ -1,6 +1,7 @@
1
- import { AppPermissions, AppResolvedComponent } from "./app-manifest.types.js";
2
1
  import { AppDistributionMode } from "./app-bundle.types.js";
2
+ import { AppPermissions, AppResolvedComponent } from "./app-manifest.types.js";
3
3
  import { AppDocumentGrantMap } from "./app-permissions.types.js";
4
+ import { AppInstanceRecord, AppStorageContext, AppStorageUsage } from "./app-storage.types.js";
4
5
  import { AppPublisher } from "./app-remote-registry.types.js";
5
6
  import { AppInstallSourceKind } from "./app-registry.types.js";
6
7
 
@@ -13,6 +14,7 @@ type AppInstallResult = {
13
14
  version: string;
14
15
  installDirectory: string;
15
16
  dataDirectory: string;
17
+ instance: AppInstanceRecord;
16
18
  sourceKind: AppInstallSourceKind;
17
19
  distributionMode?: AppDistributionMode;
18
20
  sourceRef: string;
@@ -33,6 +35,9 @@ type AppInfoResult = {
33
35
  activeVersion: string;
34
36
  enabled: boolean;
35
37
  dataDirectory: string;
38
+ instance: AppInstanceRecord;
39
+ storage: AppStorageContext;
40
+ storageUsage: AppStorageUsage;
36
41
  installedVersions: Array<{
37
42
  version: string;
38
43
  installDirectory: string;
@@ -48,6 +53,7 @@ type AppInfoResult = {
48
53
  manifestSchemaVersion: 1 | 2;
49
54
  components?: AppResolvedComponent[];
50
55
  primaryPanelId?: string;
56
+ contentSha256?: string;
51
57
  }>;
52
58
  grants: AppDocumentGrantMap;
53
59
  };
@@ -70,6 +76,7 @@ type AppLaunchResolution = {
70
76
  appDirectory: string;
71
77
  appId?: string;
72
78
  dataDirectory?: string;
79
+ storage?: AppStorageContext;
73
80
  documentGrantMap: AppDocumentGrantMap;
74
81
  };
75
82
  type AppUpdateResult = AppInstallResult & {
@@ -1 +1 @@
1
- {"version":3,"file":"app-installation.types.d.ts","names":[],"sources":["../../src/types/app-installation.types.ts"],"mappings":";;;;;;;KAMY,uBAAA;AAAA,KAOA,yBAAA,IACV,KAAA,EAAO,uBAAA,YACG,OAAA;AAAA,KAEA,gBAAA;EACV,KAAA;EACA,IAAA;EACA,OAAA;EACA,gBAAA;EACA,aAAA;EACA,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,SAAA;EACA,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA,GAAY,YAAA;EACZ,OAAA;EACA,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;AAAA;AAAA,KAGU,aAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,aAAA;EACA,OAAA;EACA,aAAA;EACA,iBAAA,EAAmB,KAAA;IACjB,OAAA;IACA,gBAAA;IACA,UAAA,EAAY,oBAAA;IACZ,gBAAA,GAAmB,mBAAA;IACnB,SAAA;IACA,WAAA;IACA,WAAA,EAAa,cAAA;IACb,WAAA;IACA,SAAA;IACA,MAAA;IACA,SAAA,GAAY,YAAA;IACZ,qBAAA;IACA,UAAA,GAAa,oBAAA;IACb,cAAA;EAAA;EAEF,MAAA,EAAQ,mBAAA;AAAA;AAAA,KAGE,oBAAA;EACV,KAAA;EACA,IAAA;EACA,aAAA;EACA,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,OAAA;EACA,qBAAA;EACA,cAAA;AAAA;AAAA,KAGU,kBAAA;EACV,KAAA;EACA,eAAA;EACA,WAAA;AAAA;AAAA,KAGU,mBAAA;EACV,YAAA;EACA,KAAA;EACA,aAAA;EACA,gBAAA,EAAkB,mBAAA;AAAA;AAAA,KAGR,eAAA,GAAkB,gBAAA;EAC5B,eAAA;EACA,OAAA;AAAA;AAAA,KAGU,mBAAA;EACV,KAAA;EACA,aAAA;EACA,OAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,mBAAA;EAC9B,eAAA;EACA,UAAA;AAAA"}
1
+ {"version":3,"file":"app-installation.types.d.ts","names":[],"sources":["../../src/types/app-installation.types.ts"],"mappings":";;;;;;;;KAWY,uBAAA;AAAA,KAOA,yBAAA,IACV,KAAA,EAAO,uBAAA,YACG,OAAA;AAAA,KAEA,gBAAA;EACV,KAAA;EACA,IAAA;EACA,OAAA;EACA,gBAAA;EACA,aAAA;EACA,QAAA,EAAU,iBAAA;EACV,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,SAAA;EACA,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA,GAAY,YAAA;EACZ,OAAA;EACA,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;AAAA;AAAA,KAGU,aAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,aAAA;EACA,OAAA;EACA,aAAA;EACA,QAAA,EAAU,iBAAA;EACV,OAAA,EAAS,iBAAA;EACT,YAAA,EAAc,eAAA;EACd,iBAAA,EAAmB,KAAA;IACjB,OAAA;IACA,gBAAA;IACA,UAAA,EAAY,oBAAA;IACZ,gBAAA,GAAmB,mBAAA;IACnB,SAAA;IACA,WAAA;IACA,WAAA,EAAa,cAAA;IACb,WAAA;IACA,SAAA;IACA,MAAA;IACA,SAAA,GAAY,YAAA;IACZ,qBAAA;IACA,UAAA,GAAa,oBAAA;IACb,cAAA;IACA,aAAA;EAAA;EAEF,MAAA,EAAQ,mBAAA;AAAA;AAAA,KAGE,oBAAA;EACV,KAAA;EACA,IAAA;EACA,aAAA;EACA,UAAA,EAAY,oBAAA;EACZ,gBAAA,GAAmB,mBAAA;EACnB,OAAA;EACA,qBAAA;EACA,cAAA;AAAA;AAAA,KAGU,kBAAA;EACV,KAAA;EACA,eAAA;EACA,WAAA;AAAA;AAAA,KAGU,mBAAA;EACV,YAAA;EACA,KAAA;EACA,aAAA;EACA,OAAA,GAAU,iBAAA;EACV,gBAAA,EAAkB,mBAAA;AAAA;AAAA,KAGR,eAAA,GAAkB,gBAAA;EAC5B,eAAA;EACA,OAAA;AAAA;AAAA,KAGU,mBAAA;EACV,KAAA;EACA,aAAA;EACA,OAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,mBAAA;EAC9B,eAAA;EACA,UAAA;AAAA"}
@@ -13,8 +13,25 @@ type AppPermissions = {
13
13
  };
14
14
  capabilities?: {
15
15
  hostBridge?: boolean;
16
+ nativeProcess?: boolean;
16
17
  };
17
18
  };
19
+ type AppRuntimeProfile = "panel-only" | "wasi" | "native-process";
20
+ type AppRuntimeIsolation = "sandboxed" | "host-mediated" | "full-user";
21
+ type AppRuntimeDeclaration = {
22
+ profile: AppRuntimeProfile;
23
+ };
24
+ type AppStorageDeclaration = {
25
+ scope: "global";
26
+ schemaVersion: number;
27
+ };
28
+ type AppPlatformSecuritySummary = {
29
+ runtimeProfile: AppRuntimeProfile;
30
+ isolation: AppRuntimeIsolation;
31
+ hasServiceComponents: boolean;
32
+ inferred: boolean;
33
+ permissions: AppPermissions;
34
+ };
18
35
  type AppCoreWasmMainManifest = {
19
36
  kind: "wasm";
20
37
  entry: string;
@@ -58,6 +75,9 @@ type AppComponentManifest = {
58
75
  presentation?: {
59
76
  primaryPanel?: string;
60
77
  };
78
+ runtime?: AppRuntimeDeclaration;
79
+ storage?: AppStorageDeclaration;
80
+ permissions?: AppPermissions;
61
81
  components: AppComponentReference[];
62
82
  };
63
83
  type AppManifest = AppStandaloneManifest | AppComponentManifest;
@@ -110,10 +130,11 @@ type AppComponentManifestSummary = {
110
130
  iconPath?: string;
111
131
  primaryPanelId?: string;
112
132
  components: AppResolvedComponent[];
133
+ security: AppPlatformSecuritySummary;
113
134
  };
114
135
  type AppManifestSummary = AppStandaloneManifestSummary | AppComponentManifestSummary;
115
136
  declare function isAppStandaloneManifestBundle(bundle: AppManifestBundle): bundle is AppStandaloneManifestBundle;
116
137
  declare function isAppComponentManifestBundle(bundle: AppManifestBundle): bundle is AppComponentManifestBundle;
117
138
  //#endregion
118
- export { AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDocumentAccessMode, AppDocumentAccessScope, AppMainManifest, AppManifest, AppManifestBundle, AppManifestSummary, AppPermissions, AppResolvedComponent, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppUiManifest, AppWasiHttpComponentMainManifest, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
139
+ export { AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDocumentAccessMode, AppDocumentAccessScope, AppMainManifest, AppManifest, AppManifestBundle, AppManifestSummary, AppPermissions, AppPlatformSecuritySummary, AppResolvedComponent, AppRuntimeDeclaration, AppRuntimeIsolation, AppRuntimeProfile, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageDeclaration, AppUiManifest, AppWasiHttpComponentMainManifest, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
119
140
  //# sourceMappingURL=app-manifest.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-manifest.types.d.ts","names":[],"sources":["../../src/types/app-manifest.types.ts"],"mappings":";KAAY,qBAAA;AAAA,KAEA,sBAAA;EACV,EAAA;EACA,IAAA,EAAM,qBAAA;EACN,WAAA;AAAA;AAAA,KAGU,cAAA;EACV,cAAA,GAAiB,sBAAA;EACjB,cAAA;EACA,OAAA;IAAsB,SAAA;EAAA;EACtB,YAAA;IACE,UAAA;EAAA;AAAA;AAAA,KAIQ,uBAAA;EACV,IAAA;EACA,KAAA;EACA,MAAA;EACA,MAAA;AAAA;AAAA,KAGU,gCAAA;EACV,IAAA;EACA,KAAA;AAAA;AAAA,KAGU,eAAA,GAAkB,uBAAA,GAA0B,gCAAA;AAAA,KAE5C,aAAA;EACV,KAAA;AAAA;AAAA,KAGU,qBAAA;EACV,aAAA;EACA,EAAA;EACA,IAAA;EACA,OAAA;EACA,WAAA;EACA,IAAA;EACA,IAAA,EAAM,eAAA;EACN,EAAA,EAAI,aAAA;EACJ,WAAA,GAAc,cAAA;AAAA;AAAA,KAGJ,gBAAA;AAAA,KAEA,qBAAA;EACV,IAAA,EAAM,gBAAA;EACN,IAAA;AAAA;AAAA,KAGU,oBAAA;EACV,aAAA;EACA,EAAA;EACA,IAAA;EACA,OAAA;EACA,WAAA;EACA,IAAA;EACA,OAAA;IACE,QAAA;EAAA;EAEF,YAAA;IACE,YAAA;EAAA;EAEF,UAAA,EAAY,qBAAA;AAAA;AAAA,KAGF,WAAA,GAAc,qBAAA,GAAwB,oBAAA;AAAA,KAEtC,oBAAA,GAAuB,qBAAA;EACjC,EAAA;EACA,kBAAA;EACA,YAAA;AAAA;AAAA,KAGU,2BAAA;EACV,YAAA;EACA,YAAA;EACA,QAAA,EAAU,qBAAA;EACV,aAAA;EACA,WAAA;EACA,eAAA;EACA,mBAAA;EACA,QAAA;AAAA;AAAA,KAGU,0BAAA;EACV,YAAA;EACA,YAAA;EACA,QAAA,EAAU,oBAAA;EACV,UAAA,EAAY,oBAAA;EACZ,mBAAA;EACA,QAAA;EACA,cAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,2BAAA,GAA8B,0BAAA;AAAA,KAElD,4BAAA;EACV,aAAA;EACA,EAAA;EACA,IAAA;EACA,OAAA;EACA,WAAA;EACA,QAAA,EAAU,eAAA;EACV,MAAA;EACA,YAAA;EACA,aAAA;EACA,WAAA;EACA,QAAA;EACA,WAAA,EAAa,cAAA;AAAA;AAAA,KAGH,2BAAA;EACV,aAAA;EACA,EAAA;EACA,IAAA;EACA,OAAA;EACA,WAAA;EACA,YAAA;EACA,QAAA;EACA,cAAA;EACA,UAAA,EAAY,oBAAA;AAAA;AAAA,KAGF,kBAAA,GAAqB,4BAAA,GAA+B,2BAAA;AAAA,iBAEhD,6BAAA,CACd,MAAA,EAAQ,iBAAA,GACP,MAAA,IAAU,2BAAA;AAAA,iBAIG,4BAAA,CACd,MAAA,EAAQ,iBAAA,GACP,MAAA,IAAU,0BAAA"}
1
+ {"version":3,"file":"app-manifest.types.d.ts","names":[],"sources":["../../src/types/app-manifest.types.ts"],"mappings":";KAAY,qBAAA;AAAA,KAEA,sBAAA;EACV,EAAA;EACA,IAAA,EAAM,qBAAA;EACN,WAAA;AAAA;AAAA,KAGU,cAAA;EACV,cAAA,GAAiB,sBAAA;EACjB,cAAA;EACA,OAAA;IAAsB,SAAA;EAAA;EACtB,YAAA;IACE,UAAA;IACA,aAAA;EAAA;AAAA;AAAA,KAIQ,iBAAA;AAAA,KACA,mBAAA;AAAA,KAEA,qBAAA;EACV,OAAA,EAAS,iBAAA;AAAA;AAAA,KAGC,qBAAA;EACV,KAAA;EACA,aAAA;AAAA;AAAA,KAGU,0BAAA;EACV,cAAA,EAAgB,iBAAA;EAChB,SAAA,EAAW,mBAAA;EACX,oBAAA;EACA,QAAA;EACA,WAAA,EAAa,cAAA;AAAA;AAAA,KAGH,uBAAA;EACV,IAAA;EACA,KAAA;EACA,MAAA;EACA,MAAA;AAAA;AAAA,KAGU,gCAAA;EACV,IAAA;EACA,KAAA;AAAA;AAAA,KAGU,eAAA,GAAkB,uBAAA,GAA0B,gCAAA;AAAA,KAE5C,aAAA;EACV,KAAA;AAAA;AAAA,KAGU,qBAAA;EACV,aAAA;EACA,EAAA;EACA,IAAA;EACA,OAAA;EACA,WAAA;EACA,IAAA;EACA,IAAA,EAAM,eAAA;EACN,EAAA,EAAI,aAAA;EACJ,WAAA,GAAc,cAAA;AAAA;AAAA,KAGJ,gBAAA;AAAA,KAEA,qBAAA;EACV,IAAA,EAAM,gBAAA;EACN,IAAA;AAAA;AAAA,KAGU,oBAAA;EACV,aAAA;EACA,EAAA;EACA,IAAA;EACA,OAAA;EACA,WAAA;EACA,IAAA;EACA,OAAA;IACE,QAAA;EAAA;EAEF,YAAA;IACE,YAAA;EAAA;EAEF,OAAA,GAAU,qBAAA;EACV,OAAA,GAAU,qBAAA;EACV,WAAA,GAAc,cAAA;EACd,UAAA,EAAY,qBAAA;AAAA;AAAA,KAGF,WAAA,GAAc,qBAAA,GAAwB,oBAAA;AAAA,KAEtC,oBAAA,GAAuB,qBAAA;EACjC,EAAA;EACA,kBAAA;EACA,YAAA;AAAA;AAAA,KAGU,2BAAA;EACV,YAAA;EACA,YAAA;EACA,QAAA,EAAU,qBAAA;EACV,aAAA;EACA,WAAA;EACA,eAAA;EACA,mBAAA;EACA,QAAA;AAAA;AAAA,KAGU,0BAAA;EACV,YAAA;EACA,YAAA;EACA,QAAA,EAAU,oBAAA;EACV,UAAA,EAAY,oBAAA;EACZ,mBAAA;EACA,QAAA;EACA,cAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,2BAAA,GAA8B,0BAAA;AAAA,KAElD,4BAAA;EACV,aAAA;EACA,EAAA;EACA,IAAA;EACA,OAAA;EACA,WAAA;EACA,QAAA,EAAU,eAAA;EACV,MAAA;EACA,YAAA;EACA,aAAA;EACA,WAAA;EACA,QAAA;EACA,WAAA,EAAa,cAAA;AAAA;AAAA,KAGH,2BAAA;EACV,aAAA;EACA,EAAA;EACA,IAAA;EACA,OAAA;EACA,WAAA;EACA,YAAA;EACA,QAAA;EACA,cAAA;EACA,UAAA,EAAY,oBAAA;EACZ,QAAA,EAAU,0BAAA;AAAA;AAAA,KAGA,kBAAA,GAAqB,4BAAA,GAA+B,2BAAA;AAAA,iBAEhD,6BAAA,CACd,MAAA,EAAQ,iBAAA,GACP,MAAA,IAAU,2BAAA;AAAA,iBAIG,4BAAA,CACd,MAAA,EAAQ,iBAAA,GACP,MAAA,IAAU,0BAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"app-manifest.types.js","names":[],"sources":["../../src/types/app-manifest.types.ts"],"sourcesContent":["export type AppDocumentAccessMode = \"read\" | \"read-write\";\n\nexport type AppDocumentAccessScope = {\n id: string;\n mode: AppDocumentAccessMode;\n description?: string;\n};\n\nexport type AppPermissions = {\n documentAccess?: AppDocumentAccessScope[];\n allowedDomains?: string[];\n storage?: boolean | { namespace?: string };\n capabilities?: {\n hostBridge?: boolean;\n };\n};\n\nexport type AppCoreWasmMainManifest = {\n kind: \"wasm\";\n entry: string;\n export: string;\n action: string;\n};\n\nexport type AppWasiHttpComponentMainManifest = {\n kind: \"wasi-http-component\";\n entry: string;\n};\n\nexport type AppMainManifest = AppCoreWasmMainManifest | AppWasiHttpComponentMainManifest;\n\nexport type AppUiManifest = {\n entry: string;\n};\n\nexport type AppStandaloneManifest = {\n schemaVersion: 1;\n id: string;\n name: string;\n version: string;\n description?: string;\n icon?: string;\n main: AppMainManifest;\n ui: AppUiManifest;\n permissions?: AppPermissions;\n};\n\nexport type AppComponentKind = \"panel\" | \"service\";\n\nexport type AppComponentReference = {\n kind: AppComponentKind;\n path: string;\n};\n\nexport type AppComponentManifest = {\n schemaVersion: 2;\n id: string;\n name: string;\n version: string;\n description?: string;\n icon?: string;\n engines?: {\n nextclaw?: string;\n };\n presentation?: {\n primaryPanel?: string;\n };\n components: AppComponentReference[];\n};\n\nexport type AppManifest = AppStandaloneManifest | AppComponentManifest;\n\nexport type AppResolvedComponent = AppComponentReference & {\n id: string;\n componentDirectory: string;\n manifestPath: string;\n};\n\nexport type AppStandaloneManifestBundle = {\n appDirectory: string;\n manifestPath: string;\n manifest: AppStandaloneManifest;\n mainEntryPath: string;\n uiEntryPath: string;\n uiDirectoryPath: string;\n assetsDirectoryPath: string;\n iconPath?: string;\n};\n\nexport type AppComponentManifestBundle = {\n appDirectory: string;\n manifestPath: string;\n manifest: AppComponentManifest;\n components: AppResolvedComponent[];\n assetsDirectoryPath: string;\n iconPath?: string;\n primaryPanelId?: string;\n};\n\nexport type AppManifestBundle = AppStandaloneManifestBundle | AppComponentManifestBundle;\n\nexport type AppStandaloneManifestSummary = {\n schemaVersion: 1;\n id: string;\n name: string;\n version: string;\n description?: string;\n mainKind: AppMainManifest[\"kind\"];\n action?: string;\n manifestPath: string;\n mainEntryPath: string;\n uiEntryPath: string;\n iconPath?: string;\n permissions: AppPermissions;\n};\n\nexport type AppComponentManifestSummary = {\n schemaVersion: 2;\n id: string;\n name: string;\n version: string;\n description?: string;\n manifestPath: string;\n iconPath?: string;\n primaryPanelId?: string;\n components: AppResolvedComponent[];\n};\n\nexport type AppManifestSummary = AppStandaloneManifestSummary | AppComponentManifestSummary;\n\nexport function isAppStandaloneManifestBundle(\n bundle: AppManifestBundle,\n): bundle is AppStandaloneManifestBundle {\n return bundle.manifest.schemaVersion === 1;\n}\n\nexport function isAppComponentManifestBundle(\n bundle: AppManifestBundle,\n): bundle is AppComponentManifestBundle {\n return bundle.manifest.schemaVersion === 2;\n}\n"],"mappings":";AAkIA,SAAgB,8BACd,QACuC;AACvC,QAAO,OAAO,SAAS,kBAAkB;;AAG3C,SAAgB,6BACd,QACsC;AACtC,QAAO,OAAO,SAAS,kBAAkB"}
1
+ {"version":3,"file":"app-manifest.types.js","names":[],"sources":["../../src/types/app-manifest.types.ts"],"sourcesContent":["export type AppDocumentAccessMode = \"read\" | \"read-write\";\n\nexport type AppDocumentAccessScope = {\n id: string;\n mode: AppDocumentAccessMode;\n description?: string;\n};\n\nexport type AppPermissions = {\n documentAccess?: AppDocumentAccessScope[];\n allowedDomains?: string[];\n storage?: boolean | { namespace?: string };\n capabilities?: {\n hostBridge?: boolean;\n nativeProcess?: boolean;\n };\n};\n\nexport type AppRuntimeProfile = \"panel-only\" | \"wasi\" | \"native-process\";\nexport type AppRuntimeIsolation = \"sandboxed\" | \"host-mediated\" | \"full-user\";\n\nexport type AppRuntimeDeclaration = {\n profile: AppRuntimeProfile;\n};\n\nexport type AppStorageDeclaration = {\n scope: \"global\";\n schemaVersion: number;\n};\n\nexport type AppPlatformSecuritySummary = {\n runtimeProfile: AppRuntimeProfile;\n isolation: AppRuntimeIsolation;\n hasServiceComponents: boolean;\n inferred: boolean;\n permissions: AppPermissions;\n};\n\nexport type AppCoreWasmMainManifest = {\n kind: \"wasm\";\n entry: string;\n export: string;\n action: string;\n};\n\nexport type AppWasiHttpComponentMainManifest = {\n kind: \"wasi-http-component\";\n entry: string;\n};\n\nexport type AppMainManifest = AppCoreWasmMainManifest | AppWasiHttpComponentMainManifest;\n\nexport type AppUiManifest = {\n entry: string;\n};\n\nexport type AppStandaloneManifest = {\n schemaVersion: 1;\n id: string;\n name: string;\n version: string;\n description?: string;\n icon?: string;\n main: AppMainManifest;\n ui: AppUiManifest;\n permissions?: AppPermissions;\n};\n\nexport type AppComponentKind = \"panel\" | \"service\";\n\nexport type AppComponentReference = {\n kind: AppComponentKind;\n path: string;\n};\n\nexport type AppComponentManifest = {\n schemaVersion: 2;\n id: string;\n name: string;\n version: string;\n description?: string;\n icon?: string;\n engines?: {\n nextclaw?: string;\n };\n presentation?: {\n primaryPanel?: string;\n };\n runtime?: AppRuntimeDeclaration;\n storage?: AppStorageDeclaration;\n permissions?: AppPermissions;\n components: AppComponentReference[];\n};\n\nexport type AppManifest = AppStandaloneManifest | AppComponentManifest;\n\nexport type AppResolvedComponent = AppComponentReference & {\n id: string;\n componentDirectory: string;\n manifestPath: string;\n};\n\nexport type AppStandaloneManifestBundle = {\n appDirectory: string;\n manifestPath: string;\n manifest: AppStandaloneManifest;\n mainEntryPath: string;\n uiEntryPath: string;\n uiDirectoryPath: string;\n assetsDirectoryPath: string;\n iconPath?: string;\n};\n\nexport type AppComponentManifestBundle = {\n appDirectory: string;\n manifestPath: string;\n manifest: AppComponentManifest;\n components: AppResolvedComponent[];\n assetsDirectoryPath: string;\n iconPath?: string;\n primaryPanelId?: string;\n};\n\nexport type AppManifestBundle = AppStandaloneManifestBundle | AppComponentManifestBundle;\n\nexport type AppStandaloneManifestSummary = {\n schemaVersion: 1;\n id: string;\n name: string;\n version: string;\n description?: string;\n mainKind: AppMainManifest[\"kind\"];\n action?: string;\n manifestPath: string;\n mainEntryPath: string;\n uiEntryPath: string;\n iconPath?: string;\n permissions: AppPermissions;\n};\n\nexport type AppComponentManifestSummary = {\n schemaVersion: 2;\n id: string;\n name: string;\n version: string;\n description?: string;\n manifestPath: string;\n iconPath?: string;\n primaryPanelId?: string;\n components: AppResolvedComponent[];\n security: AppPlatformSecuritySummary;\n};\n\nexport type AppManifestSummary = AppStandaloneManifestSummary | AppComponentManifestSummary;\n\nexport function isAppStandaloneManifestBundle(\n bundle: AppManifestBundle,\n): bundle is AppStandaloneManifestBundle {\n return bundle.manifest.schemaVersion === 1;\n}\n\nexport function isAppComponentManifestBundle(\n bundle: AppManifestBundle,\n): bundle is AppComponentManifestBundle {\n return bundle.manifest.schemaVersion === 2;\n}\n"],"mappings":";AA2JA,SAAgB,8BACd,QACuC;AACvC,QAAO,OAAO,SAAS,kBAAkB;;AAG3C,SAAgB,6BACd,QACsC;AACtC,QAAO,OAAO,SAAS,kBAAkB"}
@@ -1,5 +1,5 @@
1
- import { AppManifest, AppPermissions } from "./app-manifest.types.js";
2
1
  import { AppDistributionMode } from "./app-bundle.types.js";
2
+ import { AppManifest, AppPermissions } from "./app-manifest.types.js";
3
3
  import { AppPublisher } from "./app-remote-registry.types.js";
4
4
 
5
5
  //#region src/types/app-publish.types.d.ts
@@ -54,6 +54,9 @@ type AppPublishResult = {
54
54
  item: {
55
55
  slug: string;
56
56
  appId: string;
57
+ ownerScope: string;
58
+ appName: string;
59
+ publishStatus: "pending" | "published";
57
60
  name: string;
58
61
  latestVersion: string;
59
62
  webUrl?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"app-publish.types.d.ts","names":[],"sources":["../../src/types/app-publish.types.ts"],"mappings":";;;;;cAIa,gCAAA;AAAA,KAED,qBAAA;EACV,KAAA;EACA,WAAA;AAAA;AAAA,KAGU,sBAAA;EACV,IAAA;EACA,OAAA;EACA,WAAA,EAAa,MAAA;EACb,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,MAAA;EACA,IAAA;EACA,UAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA,GAAY,YAAA;EACZ,OAAA,GAAU,qBAAA;AAAA;AAAA,KAGA,cAAA;EACV,IAAA;EACA,aAAA;AAAA;AAAA,KAGU,iBAAA;EACV,IAAA;EACA,KAAA;EACA,IAAA;EACA,OAAA;EACA,OAAA;EACA,WAAA,EAAa,MAAA;EACb,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,MAAA;EACA,IAAA;EACA,UAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA,EAAW,YAAA;EACX,OAAA,GAAU,qBAAA;EACV,gBAAA,EAAkB,mBAAA;EAClB,QAAA,EAAU,WAAA;EACV,WAAA,EAAa,cAAA;EACb,YAAA;EACA,YAAA;EACA,KAAA,EAAO,cAAA;AAAA;AAAA,KAGG,gBAAA;EACV,OAAA;EACA,IAAA;IACE,IAAA;IACA,KAAA;IACA,IAAA;IACA,aAAA;IACA,MAAA;IACA,OAAA;MACE,IAAA;MACA,IAAA;MACA,OAAA;MACA,QAAA;IAAA;EAAA;EAGJ,YAAA;IACE,IAAA;IACA,MAAA;IACA,IAAA,EAAM,mBAAA;EAAA;EAER,SAAA;AAAA"}
1
+ {"version":3,"file":"app-publish.types.d.ts","names":[],"sources":["../../src/types/app-publish.types.ts"],"mappings":";;;;;cAOa,gCAAA;AAAA,KAGD,qBAAA;EACV,KAAA;EACA,WAAA;AAAA;AAAA,KAGU,sBAAA;EACV,IAAA;EACA,OAAA;EACA,WAAA,EAAa,MAAA;EACb,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,MAAA;EACA,IAAA;EACA,UAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA,GAAY,YAAA;EACZ,OAAA,GAAU,qBAAA;AAAA;AAAA,KAGA,cAAA;EACV,IAAA;EACA,aAAA;AAAA;AAAA,KAGU,iBAAA;EACV,IAAA;EACA,KAAA;EACA,IAAA;EACA,OAAA;EACA,OAAA;EACA,WAAA,EAAa,MAAA;EACb,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,MAAA;EACA,IAAA;EACA,UAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA,EAAW,YAAA;EACX,OAAA,GAAU,qBAAA;EACV,gBAAA,EAAkB,mBAAA;EAClB,QAAA,EAAU,WAAA;EACV,WAAA,EAAa,cAAA;EACb,YAAA;EACA,YAAA;EACA,KAAA,EAAO,cAAA;AAAA;AAAA,KAGG,gBAAA;EACV,OAAA;EACA,IAAA;IACE,IAAA;IACA,KAAA;IACA,UAAA;IACA,OAAA;IACA,aAAA;IACA,IAAA;IACA,aAAA;IACA,MAAA;IACA,OAAA;MACE,IAAA;MACA,IAAA;MACA,OAAA;MACA,QAAA;IAAA;EAAA;EAGJ,YAAA;IACE,IAAA;IACA,MAAA;IACA,IAAA,EAAM,mBAAA;EAAA;EAER,SAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"app-publish.types.js","names":[],"sources":["../../src/types/app-publish.types.ts"],"sourcesContent":["import type { AppDistributionMode } from \"#app-runtime/types/app-bundle.types.js\";\nimport type { AppManifest, AppPermissions } from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppPublisher } from \"#app-runtime/types/app-remote-registry.types.js\";\n\nexport const DEFAULT_APP_MARKETPLACE_API_BASE = \"https://apps-registry.nextclaw.io\";\n\nexport type AppMarketplaceVisuals = {\n cover: string;\n accentColor: string;\n};\n\nexport type AppMarketplaceMetadata = {\n slug: string;\n summary: string;\n summaryI18n: Record<string, string>;\n description?: string;\n descriptionI18n?: Record<string, string>;\n author: string;\n tags: string[];\n sourceRepo?: string;\n homepage?: string;\n featured?: boolean;\n publisher?: AppPublisher;\n visuals?: AppMarketplaceVisuals;\n};\n\nexport type AppPublishFile = {\n path: string;\n contentBase64: string;\n};\n\nexport type AppPublishPayload = {\n slug: string;\n appId: string;\n name: string;\n version: string;\n summary: string;\n summaryI18n: Record<string, string>;\n description?: string;\n descriptionI18n?: Record<string, string>;\n author: string;\n tags: string[];\n sourceRepo?: string;\n homepage?: string;\n featured: boolean;\n publisher: AppPublisher;\n visuals?: AppMarketplaceVisuals;\n distributionMode: AppDistributionMode;\n manifest: AppManifest;\n permissions: AppPermissions;\n bundleBase64: string;\n bundleSha256: string;\n files: AppPublishFile[];\n};\n\nexport type AppPublishResult = {\n created: boolean;\n item: {\n slug: string;\n appId: string;\n name: string;\n latestVersion: string;\n webUrl?: string;\n install: {\n kind: \"registry\";\n spec: string;\n command: string;\n registry: string;\n };\n };\n distribution: {\n path: string;\n sha256: string;\n mode: AppDistributionMode;\n };\n fileCount: number;\n};\n"],"mappings":";AAIA,MAAa,mCAAmC"}
1
+ {"version":3,"file":"app-publish.types.js","names":[],"sources":["../../src/types/app-publish.types.ts"],"sourcesContent":["import type { AppDistributionMode } from \"#app-runtime/types/app-bundle.types.js\";\nimport type {\n AppManifest,\n AppPermissions,\n} from \"#app-runtime/types/app-manifest.types.js\";\nimport type { AppPublisher } from \"#app-runtime/types/app-remote-registry.types.js\";\n\nexport const DEFAULT_APP_MARKETPLACE_API_BASE =\n \"https://apps-registry.nextclaw.io\";\n\nexport type AppMarketplaceVisuals = {\n cover: string;\n accentColor: string;\n};\n\nexport type AppMarketplaceMetadata = {\n slug: string;\n summary: string;\n summaryI18n: Record<string, string>;\n description?: string;\n descriptionI18n?: Record<string, string>;\n author: string;\n tags: string[];\n sourceRepo?: string;\n homepage?: string;\n featured?: boolean;\n publisher?: AppPublisher;\n visuals?: AppMarketplaceVisuals;\n};\n\nexport type AppPublishFile = {\n path: string;\n contentBase64: string;\n};\n\nexport type AppPublishPayload = {\n slug: string;\n appId: string;\n name: string;\n version: string;\n summary: string;\n summaryI18n: Record<string, string>;\n description?: string;\n descriptionI18n?: Record<string, string>;\n author: string;\n tags: string[];\n sourceRepo?: string;\n homepage?: string;\n featured: boolean;\n publisher: AppPublisher;\n visuals?: AppMarketplaceVisuals;\n distributionMode: AppDistributionMode;\n manifest: AppManifest;\n permissions: AppPermissions;\n bundleBase64: string;\n bundleSha256: string;\n files: AppPublishFile[];\n};\n\nexport type AppPublishResult = {\n created: boolean;\n item: {\n slug: string;\n appId: string;\n ownerScope: string;\n appName: string;\n publishStatus: \"pending\" | \"published\";\n name: string;\n latestVersion: string;\n webUrl?: string;\n install: {\n kind: \"registry\";\n spec: string;\n command: string;\n registry: string;\n };\n };\n distribution: {\n path: string;\n sha256: string;\n mode: AppDistributionMode;\n };\n fileCount: number;\n};\n"],"mappings":";AAOA,MAAa,mCACX"}
@@ -1,6 +1,7 @@
1
- import { AppPermissions, AppResolvedComponent } from "./app-manifest.types.js";
2
1
  import { AppDistributionMode } from "./app-bundle.types.js";
2
+ import { AppPermissions, AppPlatformSecuritySummary, AppResolvedComponent } from "./app-manifest.types.js";
3
3
  import { AppDocumentGrantMap } from "./app-permissions.types.js";
4
+ import { AppInstanceRecord } from "./app-storage.types.js";
4
5
  import { AppPublisher } from "./app-remote-registry.types.js";
5
6
 
6
7
  //#region src/types/app-registry.types.d.ts
@@ -20,14 +21,19 @@ type AppRegistryInstalledVersion = {
20
21
  manifestSchemaVersion: 1 | 2;
21
22
  components?: AppResolvedComponent[];
22
23
  primaryPanelId?: string;
24
+ security?: AppPlatformSecuritySummary;
25
+ dataSchemaVersion: number;
26
+ contentSha256?: string;
23
27
  };
24
28
  type AppRegistryAppRecord = {
25
29
  appId: string;
26
30
  name: string;
27
31
  description?: string;
32
+ publisher?: AppPublisher;
28
33
  activeVersion: string;
29
34
  enabled: boolean;
30
35
  dataDirectory: string;
36
+ defaultInstance: AppInstanceRecord;
31
37
  installedVersions: Record<string, AppRegistryInstalledVersion>;
32
38
  grants: AppDocumentGrantMap;
33
39
  };
@@ -1 +1 @@
1
- {"version":3,"file":"app-registry.types.d.ts","names":[],"sources":["../../src/types/app-registry.types.ts"],"mappings":";;;;;;KAKY,oBAAA;AAAA,KAEA,2BAAA;EACV,OAAA;EACA,gBAAA;EACA,UAAA,EAAY,oBAAA;EACZ,SAAA;EACA,WAAA;EACA,gBAAA,GAAmB,mBAAA;EACnB,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA,GAAY,YAAA;EACZ,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;AAAA;AAAA,KAGU,oBAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,aAAA;EACA,OAAA;EACA,aAAA;EACA,iBAAA,EAAmB,MAAA,SAAe,2BAAA;EAClC,MAAA,EAAQ,mBAAA;AAAA;AAAA,KAGE,WAAA;EACV,aAAA;EACA,IAAA,EAAM,MAAA,SAAe,oBAAA;EACrB,kBAAA,EAAoB,MAAA;IAClB,YAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"app-registry.types.d.ts","names":[],"sources":["../../src/types/app-registry.types.ts"],"mappings":";;;;;;;KAUY,oBAAA;AAAA,KAEA,2BAAA;EACV,OAAA;EACA,gBAAA;EACA,UAAA,EAAY,oBAAA;EACZ,SAAA;EACA,WAAA;EACA,gBAAA,GAAmB,mBAAA;EACnB,WAAA,EAAa,cAAA;EACb,WAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA,GAAY,YAAA;EACZ,qBAAA;EACA,UAAA,GAAa,oBAAA;EACb,cAAA;EACA,QAAA,GAAW,0BAAA;EACX,iBAAA;EACA,aAAA;AAAA;AAAA,KAGU,oBAAA;EACV,KAAA;EACA,IAAA;EACA,WAAA;EACA,SAAA,GAAY,YAAA;EACZ,aAAA;EACA,OAAA;EACA,aAAA;EACA,eAAA,EAAiB,iBAAA;EACjB,iBAAA,EAAmB,MAAA,SAAe,2BAAA;EAClC,MAAA,EAAQ,mBAAA;AAAA;AAAA,KAGE,WAAA;EACV,aAAA;EACA,IAAA,EAAM,MAAA,SAAe,oBAAA;EACrB,kBAAA,EAAoB,MAAA;IAClB,YAAA;EAAA;AAAA"}
@@ -1,5 +1,5 @@
1
- import { AppPermissions } from "./app-manifest.types.js";
2
1
  import { AppDistributionMode } from "./app-bundle.types.js";
2
+ import { AppPermissions } from "./app-manifest.types.js";
3
3
 
4
4
  //#region src/types/app-remote-registry.types.d.ts
5
5
  declare const DEFAULT_APP_REGISTRY_URL = "https://apps-registry.nextclaw.io/api/v1/apps/registry/";
@@ -0,0 +1,37 @@
1
+ //#region src/types/app-storage.types.d.ts
2
+ declare const DEFAULT_APP_INSTANCE_ID = "default";
3
+ declare const APP_STORAGE_LAYOUT_VERSION = 1;
4
+ type AppStorageLayout = "legacy" | "instance-v1";
5
+ type AppStorageContext = {
6
+ layout: AppStorageLayout;
7
+ layoutVersion: typeof APP_STORAGE_LAYOUT_VERSION;
8
+ instanceId: string;
9
+ instanceDirectory: string;
10
+ dataDirectory: string;
11
+ configDirectory: string;
12
+ stateDirectory: string;
13
+ cacheDirectory: string;
14
+ temporaryDirectory: string;
15
+ logsDirectory: string;
16
+ };
17
+ type AppStorageUsage = {
18
+ dataBytes: number;
19
+ configBytes: number;
20
+ stateBytes: number;
21
+ cacheBytes: number;
22
+ temporaryBytes: number;
23
+ logsBytes: number;
24
+ totalBytes: number;
25
+ };
26
+ type AppInstanceRecord = {
27
+ id: string;
28
+ publisherId?: string;
29
+ storage: AppStorageContext;
30
+ dataSchemaVersion: number;
31
+ createdAt: string;
32
+ migratedAt?: string;
33
+ legacyDataDirectory?: string;
34
+ };
35
+ //#endregion
36
+ export { APP_STORAGE_LAYOUT_VERSION, AppInstanceRecord, AppStorageContext, AppStorageLayout, AppStorageUsage, DEFAULT_APP_INSTANCE_ID };
37
+ //# sourceMappingURL=app-storage.types.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,7 @@
1
+ //#region src/types/app-storage.types.ts
2
+ const DEFAULT_APP_INSTANCE_ID = "default";
3
+ const APP_STORAGE_LAYOUT_VERSION = 1;
4
+ //#endregion
5
+ export { APP_STORAGE_LAYOUT_VERSION, DEFAULT_APP_INSTANCE_ID };
6
+
7
+ //# sourceMappingURL=app-storage.types.js.map
@@ -0,0 +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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/app-runtime",
3
- "version": "0.9.15",
3
+ "version": "0.11.0",
4
4
  "private": false,
5
5
  "description": "Standalone micro app runtime and CLI for NextClaw apps.",
6
6
  "type": "module",
@@ -32,6 +32,11 @@
32
32
  "development": "./src/index.ts",
33
33
  "types": "./dist/index.d.ts",
34
34
  "default": "./dist/index.js"
35
+ },
36
+ "./artifact-validation": {
37
+ "development": "./src/services/app-artifact-validation.service.ts",
38
+ "types": "./dist/services/app-artifact-validation.service.d.ts",
39
+ "default": "./dist/services/app-artifact-validation.service.js"
35
40
  }
36
41
  },
37
42
  "imports": {