@undefineds.co/xpod 0.2.6 → 0.2.7

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 (53) hide show
  1. package/config/cloud.json +1 -1
  2. package/dist/api/chatkit/ai-provider.js +4 -3
  3. package/dist/api/chatkit/ai-provider.js.map +1 -1
  4. package/dist/api/chatkit/default-agent.js +8 -7
  5. package/dist/api/chatkit/default-agent.js.map +1 -1
  6. package/dist/api/chatkit/runtime/PtyThreadRuntime.js +2 -1
  7. package/dist/api/chatkit/runtime/PtyThreadRuntime.js.map +1 -1
  8. package/dist/api/handlers/AdminHandler.js +1 -1
  9. package/dist/api/handlers/AdminHandler.js.map +1 -1
  10. package/dist/api/service/VercelChatService.js +23 -23
  11. package/dist/api/service/VercelChatService.js.map +1 -1
  12. package/dist/api/service/platform-ai-config.d.ts +8 -0
  13. package/dist/api/service/platform-ai-config.js +64 -0
  14. package/dist/api/service/platform-ai-config.js.map +1 -0
  15. package/dist/cli/commands/start.js +1 -2
  16. package/dist/cli/commands/start.js.map +1 -1
  17. package/dist/components/context.jsonld +3 -0
  18. package/dist/http/TracingHandler.js +2 -0
  19. package/dist/http/TracingHandler.js.map +1 -1
  20. package/dist/identity/drizzle/DrizzleIndexedStorage.d.ts +10 -1
  21. package/dist/identity/drizzle/DrizzleIndexedStorage.js +99 -6
  22. package/dist/identity/drizzle/DrizzleIndexedStorage.js.map +1 -1
  23. package/dist/identity/drizzle/DrizzleIndexedStorage.jsonld +36 -0
  24. package/dist/main.js +1 -2
  25. package/dist/main.js.map +1 -1
  26. package/dist/provision/ProvisionPodCreator.d.ts +1 -0
  27. package/dist/provision/ProvisionPodCreator.js +25 -2
  28. package/dist/provision/ProvisionPodCreator.js.map +1 -1
  29. package/dist/provision/ProvisionPodCreator.jsonld +4 -0
  30. package/dist/runtime/bootstrap.js +4 -4
  31. package/dist/runtime/bootstrap.js.map +1 -1
  32. package/dist/storage/MetadataRequestContext.d.ts +13 -0
  33. package/dist/storage/MetadataRequestContext.js +6 -0
  34. package/dist/storage/MetadataRequestContext.js.map +1 -0
  35. package/dist/storage/NodeSqliteDrizzle.js +19 -5
  36. package/dist/storage/NodeSqliteDrizzle.js.map +1 -1
  37. package/dist/storage/accessors/MinioDataAccessor.d.ts +1 -0
  38. package/dist/storage/accessors/MinioDataAccessor.js +25 -2
  39. package/dist/storage/accessors/MinioDataAccessor.js.map +1 -1
  40. package/dist/storage/accessors/MinioDataAccessor.jsonld +4 -0
  41. package/dist/storage/accessors/MixDataAccessor.d.ts +3 -1
  42. package/dist/storage/accessors/MixDataAccessor.js +61 -23
  43. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  44. package/dist/storage/accessors/MixDataAccessor.jsonld +23 -0
  45. package/dist/supervisor/Supervisor.js +1 -1
  46. package/dist/supervisor/Supervisor.js.map +1 -1
  47. package/package.json +1 -1
  48. package/static/app/assets/index.css +1 -1
  49. package/static/app/assets/main.js +50 -10
  50. package/static/dashboard/assets/{dashboard-BfmT5Vc8.js → dashboard-CTk9cn1-.js} +8 -8
  51. package/static/dashboard/assets/dashboard-VQRsvXx9.css +1 -0
  52. package/static/dashboard/dashboard.html +2 -2
  53. package/static/dashboard/assets/dashboard-DkZOr7bV.css +0 -1
@@ -53,8 +53,10 @@ class MinioDataAccessor {
53
53
  * @param identifier - Identifier for which the data is requested.
54
54
  */
55
55
  async getData(identifier) {
56
+ const started = Date.now();
56
57
  const url = new URL(identifier.path);
57
58
  const stream = await this.client.getObject(this.bucketName, url.pathname);
59
+ this.logDuration('getData', identifier.path, started);
58
60
  return (0, community_server_1.guardStream)(stream);
59
61
  }
60
62
  /**
@@ -75,6 +77,7 @@ class MinioDataAccessor {
75
77
  * @param identifier - Identifier for which the metadata is requested.
76
78
  */
77
79
  async getMetadata(identifier) {
80
+ const started = Date.now();
78
81
  const url = new URL(identifier.path);
79
82
  const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);
80
83
  const isDirectory = identifier.path.endsWith('/');
@@ -87,10 +90,14 @@ class MinioDataAccessor {
87
90
  throw new community_server_1.NotFoundHttpError();
88
91
  }
89
92
  if (!(0, community_server_1.isContainerIdentifier)(identifier) && !isDirectory) {
90
- return this.getFileMetadata(link, stats);
93
+ const metadata = await this.getFileMetadata(link, stats);
94
+ this.logDuration('getMetadata', identifier.path, started);
95
+ return metadata;
91
96
  }
92
97
  if ((0, community_server_1.isContainerIdentifier)(identifier) && isDirectory) {
93
- return this.getDirectoryMetadata(link, stats);
98
+ const metadata = await this.getDirectoryMetadata(link, stats);
99
+ this.logDuration('getMetadata', identifier.path, started);
100
+ return metadata;
94
101
  }
95
102
  throw new community_server_1.NotFoundHttpError();
96
103
  }
@@ -123,11 +130,13 @@ class MinioDataAccessor {
123
130
  * @param metadata - Metadata to store.
124
131
  */
125
132
  async writeDocument(identifier, data, metadata) {
133
+ const started = Date.now();
126
134
  const url = new URL(identifier.path);
127
135
  const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);
128
136
  const itemMetadata = this.encodeMetadata(link, metadata);
129
137
  try {
130
138
  await this.client.putObject(this.bucketName, url.pathname, data, metadata.contentLength, itemMetadata || undefined);
139
+ this.logDuration('writeDocument', identifier.path, started);
131
140
  }
132
141
  catch (error) {
133
142
  this.logger.error(`Error writing document: ${identifier.path} ${error}`);
@@ -143,9 +152,11 @@ class MinioDataAccessor {
143
152
  * @param metadata - Metadata to store.
144
153
  */
145
154
  async writeContainer(identifier, metadata) {
155
+ const started = Date.now();
146
156
  const url = new URL(identifier.path);
147
157
  const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);
148
158
  await this.client.putObject(this.bucketName, `${url.pathname}/.container`, Buffer.from(''), metadata.contentLength, this.encodeMetadata(link, metadata) || undefined);
159
+ this.logDuration('writeContainer', identifier.path, started);
149
160
  }
150
161
  /**
151
162
  * Writes metadata for a resource.
@@ -254,6 +265,18 @@ class MinioDataAccessor {
254
265
  decodeMetadata(link, metadata) {
255
266
  return new community_server_1.RepresentationMetadata(link.identifier, metadata);
256
267
  }
268
+ logDuration(operation, identifierPath, started, slowThresholdMs = 100, warnThresholdMs = 1000) {
269
+ const elapsedMs = Date.now() - started;
270
+ if (elapsedMs < slowThresholdMs) {
271
+ return;
272
+ }
273
+ const message = `[timing] MinioDataAccessor.${operation} path=${identifierPath} took=${elapsedMs}ms`;
274
+ if (elapsedMs >= warnThresholdMs) {
275
+ this.logger.warn(message);
276
+ return;
277
+ }
278
+ this.logger.info(message);
279
+ }
257
280
  }
258
281
  exports.MinioDataAccessor = MinioDataAccessor;
259
282
  //# sourceMappingURL=MinioDataAccessor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MinioDataAccessor.js","sourceRoot":"","sources":["../../../src/storage/accessors/MinioDataAccessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { Client, BucketItemStat } from 'minio';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { DataAccessor } from '@solid/community-server';\nimport type { Readable } from 'node:stream';\nimport { DataFactory } from 'n3';\nimport {\n RepresentationMetadata,\n \n NotFoundHttpError,\n guardStream,\n isContainerIdentifier,\n isContainerPath,\n joinFilePath,\n UnsupportedMediaTypeHttpError,\n CONTENT_TYPE_TERM,\n DC,\n IANA,\n LDP,\n POSIX,\n RDF,\n SOLID_META,\n XSD,\n parseQuads,\n serializeQuads,\n addResourceMetadata,\n updateModifiedDate,\n toLiteral,\n toNamedTerm,\n} from '@solid/community-server';\nimport type { Guarded } from '@solid/community-server';\nimport type { FileIdentifierMapper, ResourceLink } from '@solid/community-server';\nimport type { \n ResourceIdentifier,\n Representation,\n MetadataRecord\n} from '@solid/community-server';\n\nexport class MinioDataAccessor implements DataAccessor {\n protected readonly logger = getLoggerFor(this);\n protected readonly resourceMapper: FileIdentifierMapper;\n private readonly client: Client;\n private readonly bucketName: string;\n\n public constructor(\n resourceMapper: FileIdentifierMapper,\n accessKey: string,\n secretKey: string,\n endpoint: string,\n bucketName: string,\n ) {\n this.resourceMapper = resourceMapper;\n\n // Parse endpoint - supports both URL format (http://host:port) and simple format (host:port)\n let endPoint: string;\n let port: number | undefined;\n let useSSL = false;\n\n if (endpoint.startsWith('http://') || endpoint.startsWith('https://')) {\n const url = new URL(endpoint);\n endPoint = url.hostname;\n port = url.port ? parseInt(url.port, 10) : undefined;\n useSSL = url.protocol === 'https:';\n } else {\n const parts = endpoint.split(':');\n endPoint = parts[0];\n port = parts[1] ? parseInt(parts[1], 10) : undefined;\n }\n\n this.client = new Client({\n accessKey,\n secretKey,\n endPoint,\n port,\n useSSL,\n });\n this.bucketName = bucketName;\n this.logger.info(`MinioDataAccessor initialized with endpoint: ${endPoint}:${port} (SSL: ${useSSL})`)\n }\n\n /**\n * Should throw a NotImplementedHttpError if the DataAccessor does not support storing the given Representation.\n *\n * @param representation - Incoming Representation.\n *\n * @throws BadRequestHttpError\n * If it does not support the incoming data.\n */\n public async canHandle(representation: Representation): Promise<void> {\n if (!representation.binary) {\n throw new UnsupportedMediaTypeHttpError('Only binary data is supported.');\n }\n }\n\n /**\n * Returns a data stream stored for the given identifier.\n * It can be assumed that the incoming identifier will always correspond to a document.\n *\n * @param identifier - Identifier for which the data is requested.\n */\n public async getData(identifier: ResourceIdentifier): Promise<Guarded<Readable>> {\n const url = new URL(identifier.path)\n const stream = await this.client.getObject(this.bucketName, url.pathname);\n return guardStream(stream);\n }\n\n /**\n * Generate a presigned GET URL for the given resource.\n * @param identifier - Resource identifier.\n * @param expires - URL expiry in seconds (default 3600).\n */\n public async getPresignedUrl(identifier: ResourceIdentifier, expires = 3600): Promise<string> {\n const url = new URL(identifier.path);\n const objectKey = url.pathname.replace(/^\\//, '');\n return this.client.presignedGetObject(this.bucketName, objectKey, expires);\n }\n\n /**\n * Returns the metadata corresponding to the identifier.\n * If possible, it is suggested to add a `posix:size` triple to the metadata indicating the binary size.\n * This is necessary for range requests.\n *\n * @param identifier - Identifier for which the metadata is requested.\n */\n public async getMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> {\n const url = new URL(identifier.path)\n const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);\n const isDirectory = identifier.path.endsWith('/');\n const objectName = isDirectory ? `${url.pathname}/.container` : url.pathname;\n let stats: BucketItemStat;\n try {\n stats = await this.client.statObject(this.bucketName, objectName);\n } catch (error) {\n throw new NotFoundHttpError();\n }\n if (!isContainerIdentifier(identifier) && !isDirectory) {\n return this.getFileMetadata(link, stats);\n }\n if (isContainerIdentifier(identifier) && isDirectory) {\n return this.getDirectoryMetadata(link, stats);\n }\n throw new NotFoundHttpError();\n }\n\n /**\n * Returns metadata for all resources in the requested container.\n * This should not be all metadata of those resources (but it can be),\n * but instead the main metadata you want to show in situations\n * where all these resources are presented simultaneously.\n * Generally this would be metadata that is present for all of these resources,\n * such as resource type or last modified date.\n *\n * It can be safely assumed that the incoming identifier will always correspond to a container.\n *\n * @param identifier - Identifier of the parent container.\n */\n public async* getChildren(identifier: ResourceIdentifier): AsyncIterableIterator<RepresentationMetadata> {\n const url = new URL(identifier.path)\n const objects = this.client.listObjectsV2(this.bucketName, url.pathname);\n for await (const object of objects) {\n const metadata = await this.getMetadata(object);\n yield metadata;\n }\n }\n\n /**\n * Writes data and metadata for a document.\n * If any data and/or metadata exist for the given identifier, it should be overwritten.\n *\n * @param identifier - Identifier of the resource.\n * @param data - Data to store.\n * @param metadata - Metadata to store.\n */\n public async writeDocument(identifier: ResourceIdentifier, data: Guarded<Readable>, metadata: RepresentationMetadata): Promise<void> {\n const url = new URL(identifier.path);\n const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);\n const itemMetadata = this.encodeMetadata(link, metadata);\n try {\n await this.client.putObject(\n this.bucketName,\n url.pathname,\n data,\n metadata.contentLength,\n itemMetadata || undefined,\n );\n } catch (error) {\n this.logger.error(`Error writing document: ${identifier.path} ${error}`)\n throw error;\n }\n }\n\n /**\n * Writes metadata for a container.\n * If the container does not exist yet it should be created,\n * if it does its metadata should be overwritten, except for the containment triples.\n *\n * @param identifier - Identifier of the container.\n * @param metadata - Metadata to store.\n */\n public async writeContainer(identifier: ResourceIdentifier, metadata: RepresentationMetadata): Promise<void> {\n const url = new URL(identifier.path)\n const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);\n await this.client.putObject(\n this.bucketName,\n `${url.pathname}/.container`,\n Buffer.from(''),\n metadata.contentLength,\n this.encodeMetadata(link, metadata) || undefined,\n );\n }\n\n /**\n * Writes metadata for a resource.\n * It can safely be assumed that the subject resource already exists.\n *\n * @param identifier - Identifier of the subject resource.\n * @param metadata - Metadata to store.\n */\n public async writeMetadata(identifier: ResourceIdentifier, metadata: RepresentationMetadata): Promise<void> {\n throw new Error('Minio does not support writing metadata for a resource.');\n }\n\n /**\n * Deletes the resource and its corresponding metadata.\n *\n * Solid, §5.4: \"When a contained resource is deleted, the server MUST also remove the corresponding containment\n * triple, which has the effect of removing the deleted resource from the containing container.\"\n * https://solid.github.io/specification/protocol#deleting-resources\n *\n * @param identifier - Resource to delete.\n */\n public async deleteResource(identifier: ResourceIdentifier): Promise<void> {\n const link = new URL(identifier.path)\n await this.client.removeObject(this.bucketName, link.pathname);\n }\n\n /**\n * Reads and generates all metadata relevant for the given file,\n * ingesting it into a RepresentationMetadata object.\n *\n * @param link - Path related metadata.\n * @param stats - Stats object of the corresponding file.\n */\n private async getFileMetadata(link: ResourceLink, stats: BucketItemStat): Promise<RepresentationMetadata> {\n const metadata = await this.getBaseMetadata(link, stats, false);\n // If the resource is using an unsupported contentType, the original contentType was written to the metadata file.\n // As a result, we should only set the contentType derived from the file path,\n // when no previous metadata entry for contentType is present.\n if (typeof metadata.contentType === 'undefined') {\n metadata.set(CONTENT_TYPE_TERM, link.contentType);\n }\n return metadata;\n }\n\n /**\n * Reads and generates all metadata relevant for the given directory,\n * ingesting it into a RepresentationMetadata object.\n *\n * @param link - Path related metadata.\n * @param stats - Stats object of the corresponding directory.\n */\n private async getDirectoryMetadata(link: ResourceLink, stats: BucketItemStat): Promise<RepresentationMetadata> {\n return this.getBaseMetadata(link, stats, true);\n }\n \n /**\n * Generates metadata relevant for any resources stored by this accessor.\n *\n * @param link - Path related metadata.\n * @param stats - Stats objects of the corresponding directory.\n * @param isContainer - If the path points to a container (directory) or not.\n */\n private async getBaseMetadata(link: ResourceLink, stats: BucketItemStat, isContainer: boolean): Promise<RepresentationMetadata> {\n const metadata = this.decodeMetadata(link, stats.metaData);\n addResourceMetadata(metadata, isContainer);\n this.addPosixMetadata(metadata, stats, isContainer);\n return metadata;\n }\n \n /**\n * Helper function to add file system related metadata.\n *\n * @param metadata - metadata object to add to\n * @param stats - Stats of the file/directory corresponding to the resource.\n */\n private addPosixMetadata(metadata: RepresentationMetadata, stats: BucketItemStat, isDirectory: boolean): void {\n updateModifiedDate(metadata, stats.lastModified);\n metadata.add(\n POSIX.terms.mtime,\n toLiteral(Math.floor(stats.lastModified.getTime() / 1000), XSD.terms.integer),\n SOLID_META.terms.ResponseMetadata,\n );\n if (!isDirectory) {\n metadata.add(\n POSIX.terms.size,\n toLiteral(stats.size, XSD.terms.integer),\n SOLID_META.terms.ResponseMetadata,\n );\n }\n }\n\n /**\n * encode the metadata of the resource to string.\n *\n * @param link - Path related metadata of the resource.\n * @param metadata - Metadata to write.\n *\n * @returns string of metadata.\n */\n protected encodeMetadata(link: ResourceLink, metadata: RepresentationMetadata): object | null {\n // These are stored by file system conventions\n metadata.remove(RDF.terms.type, LDP.terms.Resource);\n metadata.remove(RDF.terms.type, LDP.terms.Container);\n metadata.remove(RDF.terms.type, LDP.terms.BasicContainer);\n metadata.removeAll(DC.terms.modified);\n // When writing metadata for a document, only remove the content-type when dealing with a supported media type.\n // A media type is supported if the FileIdentifierMapper can correctly store it.\n // This allows restoring the appropriate content-type on data read (see getFileMetadata).\n if (isContainerPath(link.filePath) || typeof metadata.contentType !== 'undefined') {\n metadata.removeAll(CONTENT_TYPE_TERM);\n }\n const contentTypeObject = metadata.contentTypeObject\n if (contentTypeObject === undefined\n || Object.keys(contentTypeObject.parameters).length === 0) {\n return null;\n }\n // Write metadata to file if there are quads remaining\n return contentTypeObject.parameters;\n }\n\n protected decodeMetadata(link: ResourceLink, metadata: MetadataRecord): RepresentationMetadata {\n return new RepresentationMetadata(link.identifier, metadata);\n }\n}\n"]}
1
+ {"version":3,"file":"MinioDataAccessor.js","sourceRoot":"","sources":["../../../src/storage/accessors/MinioDataAccessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { Client, BucketItemStat } from 'minio';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { DataAccessor } from '@solid/community-server';\nimport type { Readable } from 'node:stream';\nimport { DataFactory } from 'n3';\nimport {\n RepresentationMetadata,\n \n NotFoundHttpError,\n guardStream,\n isContainerIdentifier,\n isContainerPath,\n joinFilePath,\n UnsupportedMediaTypeHttpError,\n CONTENT_TYPE_TERM,\n DC,\n IANA,\n LDP,\n POSIX,\n RDF,\n SOLID_META,\n XSD,\n parseQuads,\n serializeQuads,\n addResourceMetadata,\n updateModifiedDate,\n toLiteral,\n toNamedTerm,\n} from '@solid/community-server';\nimport type { Guarded } from '@solid/community-server';\nimport type { FileIdentifierMapper, ResourceLink } from '@solid/community-server';\nimport type { \n ResourceIdentifier,\n Representation,\n MetadataRecord\n} from '@solid/community-server';\n\nexport class MinioDataAccessor implements DataAccessor {\n protected readonly logger = getLoggerFor(this);\n protected readonly resourceMapper: FileIdentifierMapper;\n private readonly client: Client;\n private readonly bucketName: string;\n\n public constructor(\n resourceMapper: FileIdentifierMapper,\n accessKey: string,\n secretKey: string,\n endpoint: string,\n bucketName: string,\n ) {\n this.resourceMapper = resourceMapper;\n\n // Parse endpoint - supports both URL format (http://host:port) and simple format (host:port)\n let endPoint: string;\n let port: number | undefined;\n let useSSL = false;\n\n if (endpoint.startsWith('http://') || endpoint.startsWith('https://')) {\n const url = new URL(endpoint);\n endPoint = url.hostname;\n port = url.port ? parseInt(url.port, 10) : undefined;\n useSSL = url.protocol === 'https:';\n } else {\n const parts = endpoint.split(':');\n endPoint = parts[0];\n port = parts[1] ? parseInt(parts[1], 10) : undefined;\n }\n\n this.client = new Client({\n accessKey,\n secretKey,\n endPoint,\n port,\n useSSL,\n });\n this.bucketName = bucketName;\n this.logger.info(`MinioDataAccessor initialized with endpoint: ${endPoint}:${port} (SSL: ${useSSL})`)\n }\n\n /**\n * Should throw a NotImplementedHttpError if the DataAccessor does not support storing the given Representation.\n *\n * @param representation - Incoming Representation.\n *\n * @throws BadRequestHttpError\n * If it does not support the incoming data.\n */\n public async canHandle(representation: Representation): Promise<void> {\n if (!representation.binary) {\n throw new UnsupportedMediaTypeHttpError('Only binary data is supported.');\n }\n }\n\n /**\n * Returns a data stream stored for the given identifier.\n * It can be assumed that the incoming identifier will always correspond to a document.\n *\n * @param identifier - Identifier for which the data is requested.\n */\n public async getData(identifier: ResourceIdentifier): Promise<Guarded<Readable>> {\n const started = Date.now();\n const url = new URL(identifier.path)\n const stream = await this.client.getObject(this.bucketName, url.pathname);\n this.logDuration('getData', identifier.path, started);\n return guardStream(stream);\n }\n\n /**\n * Generate a presigned GET URL for the given resource.\n * @param identifier - Resource identifier.\n * @param expires - URL expiry in seconds (default 3600).\n */\n public async getPresignedUrl(identifier: ResourceIdentifier, expires = 3600): Promise<string> {\n const url = new URL(identifier.path);\n const objectKey = url.pathname.replace(/^\\//, '');\n return this.client.presignedGetObject(this.bucketName, objectKey, expires);\n }\n\n /**\n * Returns the metadata corresponding to the identifier.\n * If possible, it is suggested to add a `posix:size` triple to the metadata indicating the binary size.\n * This is necessary for range requests.\n *\n * @param identifier - Identifier for which the metadata is requested.\n */\n public async getMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> {\n const started = Date.now();\n const url = new URL(identifier.path)\n const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);\n const isDirectory = identifier.path.endsWith('/');\n const objectName = isDirectory ? `${url.pathname}/.container` : url.pathname;\n let stats: BucketItemStat;\n try {\n stats = await this.client.statObject(this.bucketName, objectName);\n } catch (error) {\n throw new NotFoundHttpError();\n }\n if (!isContainerIdentifier(identifier) && !isDirectory) {\n const metadata = await this.getFileMetadata(link, stats);\n this.logDuration('getMetadata', identifier.path, started);\n return metadata;\n }\n if (isContainerIdentifier(identifier) && isDirectory) {\n const metadata = await this.getDirectoryMetadata(link, stats);\n this.logDuration('getMetadata', identifier.path, started);\n return metadata;\n }\n throw new NotFoundHttpError();\n }\n\n /**\n * Returns metadata for all resources in the requested container.\n * This should not be all metadata of those resources (but it can be),\n * but instead the main metadata you want to show in situations\n * where all these resources are presented simultaneously.\n * Generally this would be metadata that is present for all of these resources,\n * such as resource type or last modified date.\n *\n * It can be safely assumed that the incoming identifier will always correspond to a container.\n *\n * @param identifier - Identifier of the parent container.\n */\n public async* getChildren(identifier: ResourceIdentifier): AsyncIterableIterator<RepresentationMetadata> {\n const url = new URL(identifier.path)\n const objects = this.client.listObjectsV2(this.bucketName, url.pathname);\n for await (const object of objects) {\n const metadata = await this.getMetadata(object);\n yield metadata;\n }\n }\n\n /**\n * Writes data and metadata for a document.\n * If any data and/or metadata exist for the given identifier, it should be overwritten.\n *\n * @param identifier - Identifier of the resource.\n * @param data - Data to store.\n * @param metadata - Metadata to store.\n */\n public async writeDocument(identifier: ResourceIdentifier, data: Guarded<Readable>, metadata: RepresentationMetadata): Promise<void> {\n const started = Date.now();\n const url = new URL(identifier.path);\n const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);\n const itemMetadata = this.encodeMetadata(link, metadata);\n try {\n await this.client.putObject(\n this.bucketName,\n url.pathname,\n data,\n metadata.contentLength,\n itemMetadata || undefined,\n );\n this.logDuration('writeDocument', identifier.path, started);\n } catch (error) {\n this.logger.error(`Error writing document: ${identifier.path} ${error}`)\n throw error;\n }\n }\n\n /**\n * Writes metadata for a container.\n * If the container does not exist yet it should be created,\n * if it does its metadata should be overwritten, except for the containment triples.\n *\n * @param identifier - Identifier of the container.\n * @param metadata - Metadata to store.\n */\n public async writeContainer(identifier: ResourceIdentifier, metadata: RepresentationMetadata): Promise<void> {\n const started = Date.now();\n const url = new URL(identifier.path)\n const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);\n await this.client.putObject(\n this.bucketName,\n `${url.pathname}/.container`,\n Buffer.from(''),\n metadata.contentLength,\n this.encodeMetadata(link, metadata) || undefined,\n );\n this.logDuration('writeContainer', identifier.path, started);\n }\n\n /**\n * Writes metadata for a resource.\n * It can safely be assumed that the subject resource already exists.\n *\n * @param identifier - Identifier of the subject resource.\n * @param metadata - Metadata to store.\n */\n public async writeMetadata(identifier: ResourceIdentifier, metadata: RepresentationMetadata): Promise<void> {\n throw new Error('Minio does not support writing metadata for a resource.');\n }\n\n /**\n * Deletes the resource and its corresponding metadata.\n *\n * Solid, §5.4: \"When a contained resource is deleted, the server MUST also remove the corresponding containment\n * triple, which has the effect of removing the deleted resource from the containing container.\"\n * https://solid.github.io/specification/protocol#deleting-resources\n *\n * @param identifier - Resource to delete.\n */\n public async deleteResource(identifier: ResourceIdentifier): Promise<void> {\n const link = new URL(identifier.path)\n await this.client.removeObject(this.bucketName, link.pathname);\n }\n\n /**\n * Reads and generates all metadata relevant for the given file,\n * ingesting it into a RepresentationMetadata object.\n *\n * @param link - Path related metadata.\n * @param stats - Stats object of the corresponding file.\n */\n private async getFileMetadata(link: ResourceLink, stats: BucketItemStat): Promise<RepresentationMetadata> {\n const metadata = await this.getBaseMetadata(link, stats, false);\n // If the resource is using an unsupported contentType, the original contentType was written to the metadata file.\n // As a result, we should only set the contentType derived from the file path,\n // when no previous metadata entry for contentType is present.\n if (typeof metadata.contentType === 'undefined') {\n metadata.set(CONTENT_TYPE_TERM, link.contentType);\n }\n return metadata;\n }\n\n /**\n * Reads and generates all metadata relevant for the given directory,\n * ingesting it into a RepresentationMetadata object.\n *\n * @param link - Path related metadata.\n * @param stats - Stats object of the corresponding directory.\n */\n private async getDirectoryMetadata(link: ResourceLink, stats: BucketItemStat): Promise<RepresentationMetadata> {\n return this.getBaseMetadata(link, stats, true);\n }\n \n /**\n * Generates metadata relevant for any resources stored by this accessor.\n *\n * @param link - Path related metadata.\n * @param stats - Stats objects of the corresponding directory.\n * @param isContainer - If the path points to a container (directory) or not.\n */\n private async getBaseMetadata(link: ResourceLink, stats: BucketItemStat, isContainer: boolean): Promise<RepresentationMetadata> {\n const metadata = this.decodeMetadata(link, stats.metaData);\n addResourceMetadata(metadata, isContainer);\n this.addPosixMetadata(metadata, stats, isContainer);\n return metadata;\n }\n \n /**\n * Helper function to add file system related metadata.\n *\n * @param metadata - metadata object to add to\n * @param stats - Stats of the file/directory corresponding to the resource.\n */\n private addPosixMetadata(metadata: RepresentationMetadata, stats: BucketItemStat, isDirectory: boolean): void {\n updateModifiedDate(metadata, stats.lastModified);\n metadata.add(\n POSIX.terms.mtime,\n toLiteral(Math.floor(stats.lastModified.getTime() / 1000), XSD.terms.integer),\n SOLID_META.terms.ResponseMetadata,\n );\n if (!isDirectory) {\n metadata.add(\n POSIX.terms.size,\n toLiteral(stats.size, XSD.terms.integer),\n SOLID_META.terms.ResponseMetadata,\n );\n }\n }\n\n /**\n * encode the metadata of the resource to string.\n *\n * @param link - Path related metadata of the resource.\n * @param metadata - Metadata to write.\n *\n * @returns string of metadata.\n */\n protected encodeMetadata(link: ResourceLink, metadata: RepresentationMetadata): object | null {\n // These are stored by file system conventions\n metadata.remove(RDF.terms.type, LDP.terms.Resource);\n metadata.remove(RDF.terms.type, LDP.terms.Container);\n metadata.remove(RDF.terms.type, LDP.terms.BasicContainer);\n metadata.removeAll(DC.terms.modified);\n // When writing metadata for a document, only remove the content-type when dealing with a supported media type.\n // A media type is supported if the FileIdentifierMapper can correctly store it.\n // This allows restoring the appropriate content-type on data read (see getFileMetadata).\n if (isContainerPath(link.filePath) || typeof metadata.contentType !== 'undefined') {\n metadata.removeAll(CONTENT_TYPE_TERM);\n }\n const contentTypeObject = metadata.contentTypeObject\n if (contentTypeObject === undefined\n || Object.keys(contentTypeObject.parameters).length === 0) {\n return null;\n }\n // Write metadata to file if there are quads remaining\n return contentTypeObject.parameters;\n }\n\n protected decodeMetadata(link: ResourceLink, metadata: MetadataRecord): RepresentationMetadata {\n return new RepresentationMetadata(link.identifier, metadata);\n }\n\n private logDuration(\n operation: string,\n identifierPath: string,\n started: number,\n slowThresholdMs = 100,\n warnThresholdMs = 1000,\n ): void {\n const elapsedMs = Date.now() - started;\n if (elapsedMs < slowThresholdMs) {\n return;\n }\n\n const message = `[timing] MinioDataAccessor.${operation} path=${identifierPath} took=${elapsedMs}ms`;\n if (elapsedMs >= warnThresholdMs) {\n this.logger.warn(message);\n return;\n }\n\n this.logger.info(message);\n }\n}\n"]}
@@ -118,6 +118,10 @@
118
118
  {
119
119
  "@id": "undefineds:dist/storage/accessors/MinioDataAccessor.jsonld#MinioDataAccessor__member_decodeMetadata",
120
120
  "memberFieldName": "decodeMetadata"
121
+ },
122
+ {
123
+ "@id": "undefineds:dist/storage/accessors/MinioDataAccessor.jsonld#MinioDataAccessor__member_logDuration",
124
+ "memberFieldName": "logDuration"
121
125
  }
122
126
  ],
123
127
  "constructorArguments": [
@@ -15,7 +15,8 @@ export declare class MixDataAccessor implements DataAccessor {
15
15
  private readonly structuredDataAccessor;
16
16
  private readonly unstructuredDataAccessor;
17
17
  private readonly presignedRedirectEnabled;
18
- constructor(structuredDataAccessor: DataAccessor, unstructuredDataAccessor: DataAccessor, presignedRedirectEnabled?: boolean);
18
+ private readonly mirrorContainersToUnstructured;
19
+ constructor(structuredDataAccessor: DataAccessor, unstructuredDataAccessor: DataAccessor, presignedRedirectEnabled?: boolean, mirrorContainersToUnstructured?: boolean);
19
20
  /**
20
21
  * This accessor supports all types of data.
21
22
  */
@@ -41,4 +42,5 @@ export declare class MixDataAccessor implements DataAccessor {
41
42
  * then save metadata in structured accessor.
42
43
  */
43
44
  private writeUnstructuredDocument;
45
+ private invalidateMetadataCache;
44
46
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MixDataAccessor = void 0;
4
4
  const global_logger_factory_1 = require("global-logger-factory");
5
5
  const community_server_1 = require("@solid/community-server");
6
+ const MetadataRequestContext_1 = require("../MetadataRequestContext");
6
7
  /**
7
8
  * MixDataAccessor - Routes data to appropriate storage based on content type
8
9
  *
@@ -13,11 +14,12 @@ const community_server_1 = require("@solid/community-server");
13
14
  * to be used as the RDF storage backend.
14
15
  */
15
16
  class MixDataAccessor {
16
- constructor(structuredDataAccessor, unstructuredDataAccessor, presignedRedirectEnabled = false) {
17
+ constructor(structuredDataAccessor, unstructuredDataAccessor, presignedRedirectEnabled = false, mirrorContainersToUnstructured = true) {
17
18
  this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
18
19
  this.structuredDataAccessor = structuredDataAccessor;
19
20
  this.unstructuredDataAccessor = unstructuredDataAccessor;
20
21
  this.presignedRedirectEnabled = presignedRedirectEnabled;
22
+ this.mirrorContainersToUnstructured = mirrorContainersToUnstructured;
21
23
  }
22
24
  /**
23
25
  * This accessor supports all types of data.
@@ -49,36 +51,54 @@ class MixDataAccessor {
49
51
  return await this.structuredDataAccessor.getData(identifier);
50
52
  }
51
53
  async getMetadata(identifier) {
52
- // Metadata is always stored in the structured accessor
53
- const metadata = await this.structuredDataAccessor.getMetadata(identifier);
54
- // For resources without explicit content type, default to RDF
55
- // This includes containers (which are always RDF) and documents without contentType
56
- if (!metadata.contentType) {
57
- metadata.contentType = community_server_1.INTERNAL_QUADS;
54
+ const cache = MetadataRequestContext_1.metadataRequestContext.getStore()?.metadataCache;
55
+ const cacheKey = identifier.path;
56
+ const cached = cache?.get(cacheKey);
57
+ if (cached) {
58
+ if (cached.kind === 'miss') {
59
+ throw new community_server_1.NotFoundHttpError();
60
+ }
61
+ return new community_server_1.RepresentationMetadata(cached.metadata);
62
+ }
63
+ try {
64
+ const metadata = await this.structuredDataAccessor.getMetadata(identifier);
65
+ if (!metadata.contentType) {
66
+ metadata.contentType = community_server_1.INTERNAL_QUADS;
67
+ }
68
+ cache?.set(cacheKey, { kind: 'hit', metadata: new community_server_1.RepresentationMetadata(metadata) });
69
+ return metadata;
70
+ }
71
+ catch (error) {
72
+ if (community_server_1.NotFoundHttpError.isInstance(error)) {
73
+ cache?.set(cacheKey, { kind: 'miss' });
74
+ }
75
+ throw error;
58
76
  }
59
- return metadata;
60
77
  }
61
78
  async *getChildren(identifier) {
62
79
  // Children metadata is stored in the structured accessor
63
80
  yield* this.structuredDataAccessor.getChildren(identifier);
64
81
  }
65
82
  async writeContainer(identifier, metadata) {
66
- // Container metadata goes to structured storage
67
- // Also create in unstructured if it needs to store files
68
- if (this.isUnstructured(metadata)) {
83
+ if (this.mirrorContainersToUnstructured && this.isUnstructured(metadata)) {
69
84
  await this.unstructuredDataAccessor.writeContainer(identifier, metadata);
70
85
  }
71
86
  await this.structuredDataAccessor.writeContainer(identifier, metadata);
87
+ this.invalidateMetadataCache(identifier);
72
88
  }
73
89
  async writeDocument(identifier, data, metadata) {
74
90
  if (this.isUnstructured(metadata)) {
75
- return await this.writeUnstructuredDocument(identifier, data, metadata);
91
+ await this.writeUnstructuredDocument(identifier, data, metadata);
92
+ this.invalidateMetadataCache(identifier);
93
+ return;
76
94
  }
77
- return await this.structuredDataAccessor.writeDocument(identifier, data, metadata);
95
+ await this.structuredDataAccessor.writeDocument(identifier, data, metadata);
96
+ this.invalidateMetadataCache(identifier);
78
97
  }
79
98
  async writeMetadata(identifier, metadata) {
80
99
  // Metadata always goes to structured storage
81
- return await this.structuredDataAccessor.writeMetadata(identifier, metadata);
100
+ await this.structuredDataAccessor.writeMetadata(identifier, metadata);
101
+ this.invalidateMetadataCache(identifier);
82
102
  }
83
103
  async deleteResource(identifier) {
84
104
  const metadata = await this.getMetadata(identifier);
@@ -95,7 +115,8 @@ class MixDataAccessor {
95
115
  }
96
116
  }
97
117
  // Always delete from structured storage (contains metadata)
98
- return await this.structuredDataAccessor.deleteResource(identifier);
118
+ await this.structuredDataAccessor.deleteResource(identifier);
119
+ this.invalidateMetadataCache(identifier);
99
120
  }
100
121
  /**
101
122
  * Execute SPARQL UPDATE on structured data accessor.
@@ -115,16 +136,21 @@ class MixDataAccessor {
115
136
  async writeUnstructuredDocument(identifier, data, metadata) {
116
137
  // Write the actual data to unstructured storage
117
138
  await this.unstructuredDataAccessor.writeDocument(identifier, data, metadata);
118
- // Get the metadata from unstructured storage (includes size, etc.)
119
- let updatedMetadata = await this.unstructuredDataAccessor.getMetadata(identifier);
120
- // Filter out invalid quads
121
- const removing = [];
122
- for (const quad of updatedMetadata.quads()) {
123
- if (!/^http/.test(quad.predicate.value)) {
124
- removing.push(quad);
139
+ let updatedMetadata;
140
+ if (typeof metadata.contentLength === 'number') {
141
+ updatedMetadata = new community_server_1.RepresentationMetadata(metadata);
142
+ updatedMetadata.add(community_server_1.POSIX.terms.size, (0, community_server_1.toLiteral)(metadata.contentLength, community_server_1.XSD.terms.integer), community_server_1.SOLID_META.terms.ResponseMetadata);
143
+ }
144
+ else {
145
+ updatedMetadata = await this.unstructuredDataAccessor.getMetadata(identifier);
146
+ const removing = [];
147
+ for (const quad of updatedMetadata.quads()) {
148
+ if (!/^http/.test(quad.predicate.value)) {
149
+ removing.push(quad);
150
+ }
125
151
  }
152
+ updatedMetadata.removeQuads(removing);
126
153
  }
127
- updatedMetadata.removeQuads(removing);
128
154
  // Save metadata to structured storage
129
155
  try {
130
156
  await this.structuredDataAccessor.writeMetadata(identifier, updatedMetadata);
@@ -136,6 +162,18 @@ class MixDataAccessor {
136
162
  throw error;
137
163
  }
138
164
  }
165
+ invalidateMetadataCache(identifier) {
166
+ const cache = MetadataRequestContext_1.metadataRequestContext.getStore()?.metadataCache;
167
+ if (!cache) {
168
+ return;
169
+ }
170
+ const exact = identifier.path;
171
+ const trimmed = exact.endsWith('/') ? exact.replace(/\/+$/u, '') : exact;
172
+ const withSlash = exact.endsWith('/') ? exact : `${exact}/`;
173
+ cache.delete(exact);
174
+ cache.delete(trimmed);
175
+ cache.delete(withSlash);
176
+ }
139
177
  }
140
178
  exports.MixDataAccessor = MixDataAccessor;
141
179
  //# sourceMappingURL=MixDataAccessor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MixDataAccessor.js","sourceRoot":"","sources":["../../../src/storage/accessors/MixDataAccessor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Readable } from 'stream';\nimport { getLoggerFor } from 'global-logger-factory';\n\nimport type { Quad } from '@rdfjs/types';\nimport {\n isContainerIdentifier,\n RepresentationMetadata,\n INTERNAL_QUADS,\n FoundHttpError,\n} from '@solid/community-server';\nimport type {\n Representation,\n ResourceIdentifier,\n Guarded,\n DataAccessor,\n} from '@solid/community-server';\n\n/**\n * MixDataAccessor - Routes data to appropriate storage based on content type\n * \n * - RDF data (internal/quads) -> structuredDataAccessor (Quadstore or QuintStore)\n * - Other data (binary, text, etc.) -> unstructuredDataAccessor (FileSystem, Minio, etc.)\n * \n * This uses composition instead of inheritance, allowing any DataAccessor\n * to be used as the RDF storage backend.\n */\nexport class MixDataAccessor implements DataAccessor {\n protected readonly logger = getLoggerFor(this);\n \n private readonly structuredDataAccessor: DataAccessor;\n private readonly unstructuredDataAccessor: DataAccessor;\n private readonly presignedRedirectEnabled: boolean;\n\n constructor(\n structuredDataAccessor: DataAccessor,\n unstructuredDataAccessor: DataAccessor,\n presignedRedirectEnabled = false,\n ) {\n this.structuredDataAccessor = structuredDataAccessor;\n this.unstructuredDataAccessor = unstructuredDataAccessor;\n this.presignedRedirectEnabled = presignedRedirectEnabled;\n }\n\n /**\n * This accessor supports all types of data.\n */\n public async canHandle(representation: Representation): Promise<void> {\n return void 0;\n }\n\n /**\n * Checks if the given representation is unstructured (non-RDF).\n */\n private isUnstructured(metadata: RepresentationMetadata): boolean {\n return metadata.contentType !== INTERNAL_QUADS;\n }\n\n public async getData(identifier: ResourceIdentifier): Promise<Guarded<Readable>> {\n const metadata = await this.getMetadata(identifier);\n if (this.isUnstructured(metadata)) {\n // When presigned redirect is enabled and the unstructured accessor supports it,\n // generate a presigned URL and throw FoundHttpError to trigger a 302 redirect.\n if (this.presignedRedirectEnabled) {\n const accessor = this.unstructuredDataAccessor as { getPresignedUrl?: (id: ResourceIdentifier, expires?: number) => Promise<string> };\n if (typeof accessor.getPresignedUrl === 'function') {\n const presignedUrl = await accessor.getPresignedUrl(identifier);\n this.logger.debug(`Presigned redirect: ${identifier.path}`);\n throw new FoundHttpError(presignedUrl);\n }\n }\n return await this.unstructuredDataAccessor.getData(identifier);\n }\n return await this.structuredDataAccessor.getData(identifier);\n }\n\n public async getMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> {\n // Metadata is always stored in the structured accessor\n const metadata = await this.structuredDataAccessor.getMetadata(identifier);\n\n // For resources without explicit content type, default to RDF\n // This includes containers (which are always RDF) and documents without contentType\n if (!metadata.contentType) {\n metadata.contentType = INTERNAL_QUADS;\n }\n\n return metadata;\n }\n\n public async* getChildren(identifier: ResourceIdentifier): AsyncIterableIterator<RepresentationMetadata> {\n // Children metadata is stored in the structured accessor\n yield* this.structuredDataAccessor.getChildren(identifier);\n }\n\n public async writeContainer(\n identifier: ResourceIdentifier,\n metadata: RepresentationMetadata,\n ): Promise<void> {\n // Container metadata goes to structured storage\n // Also create in unstructured if it needs to store files\n if (this.isUnstructured(metadata)) {\n await this.unstructuredDataAccessor.writeContainer(identifier, metadata);\n }\n await this.structuredDataAccessor.writeContainer(identifier, metadata);\n }\n\n public async writeDocument(\n identifier: ResourceIdentifier,\n data: Guarded<Readable>,\n metadata: RepresentationMetadata,\n ): Promise<void> {\n if (this.isUnstructured(metadata)) {\n return await this.writeUnstructuredDocument(identifier, data, metadata);\n }\n return await this.structuredDataAccessor.writeDocument(identifier, data, metadata);\n }\n\n public async writeMetadata(identifier: ResourceIdentifier, metadata: RepresentationMetadata): Promise<void> {\n // Metadata always goes to structured storage\n return await this.structuredDataAccessor.writeMetadata(identifier, metadata);\n }\n\n public async deleteResource(identifier: ResourceIdentifier): Promise<void> {\n const metadata = await this.getMetadata(identifier);\n \n // Try to delete from unstructured storage if applicable\n if (this.isUnstructured(metadata)) {\n try {\n await this.unstructuredDataAccessor.deleteResource(identifier);\n } catch (error: any) {\n // Ignore file not found errors\n if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR') {\n throw error;\n }\n }\n }\n \n // Always delete from structured storage (contains metadata)\n return await this.structuredDataAccessor.deleteResource(identifier);\n }\n\n /**\n * Execute SPARQL UPDATE on structured data accessor.\n * Delegates to the underlying structuredDataAccessor if it supports SPARQL.\n */\n public async executeSparqlUpdate(query: string, baseIri?: string): Promise<void> {\n const accessor = this.structuredDataAccessor as { executeSparqlUpdate?: (query: string, baseIri?: string) => Promise<void> };\n if (typeof accessor.executeSparqlUpdate !== 'function') {\n throw new Error('Structured data accessor does not support SPARQL UPDATE');\n }\n return accessor.executeSparqlUpdate(query, baseIri);\n }\n\n /**\n * Write unstructured document: store data in unstructured accessor,\n * then save metadata in structured accessor.\n */\n private async writeUnstructuredDocument(\n identifier: ResourceIdentifier,\n data: Guarded<Readable>,\n metadata: RepresentationMetadata,\n ): Promise<void> {\n // Write the actual data to unstructured storage\n await this.unstructuredDataAccessor.writeDocument(identifier, data, metadata);\n \n // Get the metadata from unstructured storage (includes size, etc.)\n let updatedMetadata = await this.unstructuredDataAccessor.getMetadata(identifier);\n \n // Filter out invalid quads\n const removing: Quad[] = [];\n for (const quad of updatedMetadata.quads()) {\n if (!/^http/.test(quad.predicate.value)) {\n removing.push(quad);\n }\n }\n updatedMetadata.removeQuads(removing);\n \n // Save metadata to structured storage\n try {\n await this.structuredDataAccessor.writeMetadata(identifier, updatedMetadata);\n } catch (error) {\n this.logger.error(`Error writing metadata for ${identifier.path}: ${error}`);\n // Rollback: delete the unstructured data\n await this.unstructuredDataAccessor.deleteResource(identifier);\n throw error;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"MixDataAccessor.js","sourceRoot":"","sources":["../../../src/storage/accessors/MixDataAccessor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Readable } from 'stream';\nimport { getLoggerFor } from 'global-logger-factory';\n\nimport type { Quad } from '@rdfjs/types';\nimport {\n isContainerIdentifier,\n RepresentationMetadata,\n INTERNAL_QUADS,\n FoundHttpError,\n NotFoundHttpError,\n POSIX,\n SOLID_META,\n XSD,\n toLiteral,\n} from '@solid/community-server';\nimport type {\n Representation,\n ResourceIdentifier,\n Guarded,\n DataAccessor,\n} from '@solid/community-server';\nimport { metadataRequestContext } from '../MetadataRequestContext';\n\n/**\n * MixDataAccessor - Routes data to appropriate storage based on content type\n * \n * - RDF data (internal/quads) -> structuredDataAccessor (Quadstore or QuintStore)\n * - Other data (binary, text, etc.) -> unstructuredDataAccessor (FileSystem, Minio, etc.)\n * \n * This uses composition instead of inheritance, allowing any DataAccessor\n * to be used as the RDF storage backend.\n */\nexport class MixDataAccessor implements DataAccessor {\n protected readonly logger = getLoggerFor(this);\n \n private readonly structuredDataAccessor: DataAccessor;\n private readonly unstructuredDataAccessor: DataAccessor;\n private readonly presignedRedirectEnabled: boolean;\n private readonly mirrorContainersToUnstructured: boolean;\n\n constructor(\n structuredDataAccessor: DataAccessor,\n unstructuredDataAccessor: DataAccessor,\n presignedRedirectEnabled = false,\n mirrorContainersToUnstructured = true,\n ) {\n this.structuredDataAccessor = structuredDataAccessor;\n this.unstructuredDataAccessor = unstructuredDataAccessor;\n this.presignedRedirectEnabled = presignedRedirectEnabled;\n this.mirrorContainersToUnstructured = mirrorContainersToUnstructured;\n }\n\n /**\n * This accessor supports all types of data.\n */\n public async canHandle(representation: Representation): Promise<void> {\n return void 0;\n }\n\n /**\n * Checks if the given representation is unstructured (non-RDF).\n */\n private isUnstructured(metadata: RepresentationMetadata): boolean {\n return metadata.contentType !== INTERNAL_QUADS;\n }\n\n public async getData(identifier: ResourceIdentifier): Promise<Guarded<Readable>> {\n const metadata = await this.getMetadata(identifier);\n if (this.isUnstructured(metadata)) {\n // When presigned redirect is enabled and the unstructured accessor supports it,\n // generate a presigned URL and throw FoundHttpError to trigger a 302 redirect.\n if (this.presignedRedirectEnabled) {\n const accessor = this.unstructuredDataAccessor as { getPresignedUrl?: (id: ResourceIdentifier, expires?: number) => Promise<string> };\n if (typeof accessor.getPresignedUrl === 'function') {\n const presignedUrl = await accessor.getPresignedUrl(identifier);\n this.logger.debug(`Presigned redirect: ${identifier.path}`);\n throw new FoundHttpError(presignedUrl);\n }\n }\n return await this.unstructuredDataAccessor.getData(identifier);\n }\n return await this.structuredDataAccessor.getData(identifier);\n }\n\n public async getMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> {\n const cache = metadataRequestContext.getStore()?.metadataCache;\n const cacheKey = identifier.path;\n const cached = cache?.get(cacheKey);\n if (cached) {\n if (cached.kind === 'miss') {\n throw new NotFoundHttpError();\n }\n return new RepresentationMetadata(cached.metadata);\n }\n\n try {\n const metadata = await this.structuredDataAccessor.getMetadata(identifier);\n\n if (!metadata.contentType) {\n metadata.contentType = INTERNAL_QUADS;\n }\n\n cache?.set(cacheKey, { kind: 'hit', metadata: new RepresentationMetadata(metadata) });\n return metadata;\n } catch (error) {\n if (NotFoundHttpError.isInstance(error)) {\n cache?.set(cacheKey, { kind: 'miss' });\n }\n throw error;\n }\n }\n\n public async* getChildren(identifier: ResourceIdentifier): AsyncIterableIterator<RepresentationMetadata> {\n // Children metadata is stored in the structured accessor\n yield* this.structuredDataAccessor.getChildren(identifier);\n }\n\n public async writeContainer(\n identifier: ResourceIdentifier,\n metadata: RepresentationMetadata,\n ): Promise<void> {\n if (this.mirrorContainersToUnstructured && this.isUnstructured(metadata)) {\n await this.unstructuredDataAccessor.writeContainer(identifier, metadata);\n }\n await this.structuredDataAccessor.writeContainer(identifier, metadata);\n this.invalidateMetadataCache(identifier);\n }\n\n public async writeDocument(\n identifier: ResourceIdentifier,\n data: Guarded<Readable>,\n metadata: RepresentationMetadata,\n ): Promise<void> {\n if (this.isUnstructured(metadata)) {\n await this.writeUnstructuredDocument(identifier, data, metadata);\n this.invalidateMetadataCache(identifier);\n return;\n }\n await this.structuredDataAccessor.writeDocument(identifier, data, metadata);\n this.invalidateMetadataCache(identifier);\n }\n\n public async writeMetadata(identifier: ResourceIdentifier, metadata: RepresentationMetadata): Promise<void> {\n // Metadata always goes to structured storage\n await this.structuredDataAccessor.writeMetadata(identifier, metadata);\n this.invalidateMetadataCache(identifier);\n }\n\n public async deleteResource(identifier: ResourceIdentifier): Promise<void> {\n const metadata = await this.getMetadata(identifier);\n \n // Try to delete from unstructured storage if applicable\n if (this.isUnstructured(metadata)) {\n try {\n await this.unstructuredDataAccessor.deleteResource(identifier);\n } catch (error: any) {\n // Ignore file not found errors\n if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR') {\n throw error;\n }\n }\n }\n \n // Always delete from structured storage (contains metadata)\n await this.structuredDataAccessor.deleteResource(identifier);\n this.invalidateMetadataCache(identifier);\n }\n\n /**\n * Execute SPARQL UPDATE on structured data accessor.\n * Delegates to the underlying structuredDataAccessor if it supports SPARQL.\n */\n public async executeSparqlUpdate(query: string, baseIri?: string): Promise<void> {\n const accessor = this.structuredDataAccessor as { executeSparqlUpdate?: (query: string, baseIri?: string) => Promise<void> };\n if (typeof accessor.executeSparqlUpdate !== 'function') {\n throw new Error('Structured data accessor does not support SPARQL UPDATE');\n }\n return accessor.executeSparqlUpdate(query, baseIri);\n }\n\n /**\n * Write unstructured document: store data in unstructured accessor,\n * then save metadata in structured accessor.\n */\n private async writeUnstructuredDocument(\n identifier: ResourceIdentifier,\n data: Guarded<Readable>,\n metadata: RepresentationMetadata,\n ): Promise<void> {\n // Write the actual data to unstructured storage\n await this.unstructuredDataAccessor.writeDocument(identifier, data, metadata);\n \n let updatedMetadata: RepresentationMetadata;\n if (typeof metadata.contentLength === 'number') {\n updatedMetadata = new RepresentationMetadata(metadata);\n updatedMetadata.add(\n POSIX.terms.size,\n toLiteral(metadata.contentLength, XSD.terms.integer),\n SOLID_META.terms.ResponseMetadata,\n );\n } else {\n updatedMetadata = await this.unstructuredDataAccessor.getMetadata(identifier);\n\n const removing: Quad[] = [];\n for (const quad of updatedMetadata.quads()) {\n if (!/^http/.test(quad.predicate.value)) {\n removing.push(quad);\n }\n }\n updatedMetadata.removeQuads(removing);\n }\n \n // Save metadata to structured storage\n try {\n await this.structuredDataAccessor.writeMetadata(identifier, updatedMetadata);\n } catch (error) {\n this.logger.error(`Error writing metadata for ${identifier.path}: ${error}`);\n // Rollback: delete the unstructured data\n await this.unstructuredDataAccessor.deleteResource(identifier);\n throw error;\n }\n }\n\n private invalidateMetadataCache(identifier: ResourceIdentifier): void {\n const cache = metadataRequestContext.getStore()?.metadataCache;\n if (!cache) {\n return;\n }\n\n const exact = identifier.path;\n const trimmed = exact.endsWith('/') ? exact.replace(/\\/+$/u, '') : exact;\n const withSlash = exact.endsWith('/') ? exact : `${exact}/`;\n cache.delete(exact);\n cache.delete(trimmed);\n cache.delete(withSlash);\n }\n}\n"]}
@@ -26,6 +26,18 @@
26
26
  ]
27
27
  }
28
28
  },
29
+ {
30
+ "@id": "undefineds:dist/storage/accessors/MixDataAccessor.jsonld#MixDataAccessor_mirrorContainersToUnstructured",
31
+ "range": {
32
+ "@type": "ParameterRangeUnion",
33
+ "parameterRangeElements": [
34
+ "xsd:boolean",
35
+ {
36
+ "@type": "ParameterRangeUndefined"
37
+ }
38
+ ]
39
+ }
40
+ },
29
41
  {
30
42
  "@id": "undefineds:dist/storage/accessors/MixDataAccessor.jsonld#MixDataAccessor_structuredDataAccessor",
31
43
  "range": "css:dist/storage/accessors/DataAccessor.jsonld#DataAccessor"
@@ -55,6 +67,10 @@
55
67
  "@id": "undefineds:dist/storage/accessors/MixDataAccessor.jsonld#MixDataAccessor__member_presignedRedirectEnabled",
56
68
  "memberFieldName": "presignedRedirectEnabled"
57
69
  },
70
+ {
71
+ "@id": "undefineds:dist/storage/accessors/MixDataAccessor.jsonld#MixDataAccessor__member_mirrorContainersToUnstructured",
72
+ "memberFieldName": "mirrorContainersToUnstructured"
73
+ },
58
74
  {
59
75
  "@id": "undefineds:dist/storage/accessors/MixDataAccessor.jsonld#MixDataAccessor__member_constructor",
60
76
  "memberFieldName": "constructor"
@@ -102,6 +118,10 @@
102
118
  {
103
119
  "@id": "undefineds:dist/storage/accessors/MixDataAccessor.jsonld#MixDataAccessor__member_writeUnstructuredDocument",
104
120
  "memberFieldName": "writeUnstructuredDocument"
121
+ },
122
+ {
123
+ "@id": "undefineds:dist/storage/accessors/MixDataAccessor.jsonld#MixDataAccessor__member_invalidateMetadataCache",
124
+ "memberFieldName": "invalidateMetadataCache"
105
125
  }
106
126
  ],
107
127
  "constructorArguments": [
@@ -113,6 +133,9 @@
113
133
  },
114
134
  {
115
135
  "@id": "undefineds:dist/storage/accessors/MixDataAccessor.jsonld#MixDataAccessor_presignedRedirectEnabled"
136
+ },
137
+ {
138
+ "@id": "undefineds:dist/storage/accessors/MixDataAccessor.jsonld#MixDataAccessor_mirrorContainersToUnstructured"
116
139
  }
117
140
  ]
118
141
  }
@@ -132,7 +132,7 @@ class Supervisor {
132
132
  console.log(`[Supervisor] Starting ${name}...`);
133
133
  this.addLog(name, 'info', 'Service starting');
134
134
  this.updateState(name, { status: 'starting', startTime: Date.now() });
135
- const env = { ...process.env, ...config.env };
135
+ const env = config.env ?? process.env;
136
136
  const child = (0, node_child_process_1.spawn)(config.command, config.args, {
137
137
  stdio: ['ignore', 'pipe', 'pipe'],
138
138
  env,
@@ -1 +1 @@
1
- {"version":3,"file":"Supervisor.js","sourceRoot":"","sources":["../../src/supervisor/Supervisor.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process';\nimport kill from 'tree-kill';\nimport type { ServiceConfig, ServiceState, ServiceStatus, StatusChangeHandler } from './types';\n\nconst MAX_RESTARTS = 5;\nconst MAX_LOGS = 500;\n\nexport interface SupervisorLog {\n timestamp: string;\n level: 'info' | 'warn' | 'error';\n source: string;\n message: string;\n}\n\nexport class Supervisor {\n private processes: Map<string, ChildProcess> = new Map();\n private managedStops: Map<string, () => Promise<void>> = new Map();\n private states: Map<string, ServiceState> = new Map();\n private configs: Map<string, ServiceConfig> = new Map();\n private logs: SupervisorLog[] = [];\n private onStatusChange?: StatusChangeHandler;\n private isShuttingDown = false;\n\n constructor(options: { handleProcessSignals?: boolean } = {}) {\n if (options.handleProcessSignals === false) {\n return;\n }\n\n // 确保父进程退出时清理所有子进程\n process.on('exit', () => this.killAll());\n process.on('SIGINT', () => this.shutdown('SIGINT'));\n process.on('SIGTERM', () => this.shutdown('SIGTERM'));\n }\n\n private async shutdown(signal: string): Promise<void> {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n console.log(`[Supervisor] Received ${signal}, stopping all services...`);\n await this.stopAll();\n process.exit(0);\n }\n\n private killAll(): void {\n // 同步杀掉所有子进程(用于 process.on('exit'))\n for (const [, child] of this.processes) {\n if (child.pid) {\n try {\n process.kill(child.pid, 'SIGKILL');\n } catch {\n // 进程可能已经退出\n }\n }\n }\n }\n\n public setStatusChangeHandler(handler: StatusChangeHandler): void {\n this.onStatusChange = handler;\n }\n\n public register(config: ServiceConfig): void {\n this.configs.set(config.name, config);\n this.states.set(config.name, {\n name: config.name,\n status: 'stopped',\n restartCount: 0,\n });\n }\n\n public registerManaged(name: string, stop?: () => Promise<void> | void): void {\n if (!this.states.has(name)) {\n this.states.set(name, {\n name,\n status: 'stopped',\n restartCount: 0,\n });\n }\n\n if (stop) {\n this.managedStops.set(name, async() => {\n await stop();\n });\n }\n }\n\n public setStatus(name: string, status: ServiceStatus, extra?: Partial<ServiceState>): void {\n if (!this.states.has(name)) {\n this.registerManaged(name);\n }\n\n this.updateState(name, {\n status,\n ...extra,\n });\n }\n\n public addLog(source: string, level: SupervisorLog['level'], message: string): void {\n this.logs.push({\n timestamp: new Date().toISOString(),\n level,\n source,\n message,\n });\n\n if (this.logs.length > MAX_LOGS) {\n this.logs.splice(0, this.logs.length - MAX_LOGS);\n }\n }\n\n public getLogs(filters?: {\n level?: string;\n source?: string;\n limit?: number;\n }): SupervisorLog[] {\n let rows = this.logs;\n\n if (filters?.level) {\n rows = rows.filter((item) => item.level === filters.level);\n }\n if (filters?.source) {\n rows = rows.filter((item) => item.source === filters.source);\n }\n\n const limit = filters?.limit;\n if (typeof limit === 'number' && Number.isFinite(limit) && limit > 0) {\n return rows.slice(-limit);\n }\n\n return rows;\n }\n\n public async startAll(): Promise<void> {\n for (const name of this.configs.keys()) {\n this.start(name);\n }\n }\n\n public async stopAll(): Promise<void> {\n this.isShuttingDown = true;\n const promises: Promise<void>[] = [];\n for (const name of this.processes.keys()) {\n promises.push(this.stop(name));\n }\n for (const [ name, stop ] of this.managedStops) {\n promises.push(stop().catch((error) => {\n this.addLog(name, 'error', `Failed to stop managed service: ${String(error)}`);\n }));\n }\n await Promise.all(promises);\n }\n\n public start(name: string): void {\n const config = this.configs.get(name);\n const state = this.states.get(name);\n if (!config || !state) return;\n\n if (state.status === 'running' || state.status === 'starting') return;\n\n console.log(`[Supervisor] Starting ${name}...`);\n this.addLog(name, 'info', 'Service starting');\n this.updateState(name, { status: 'starting', startTime: Date.now() });\n\n const env = { ...process.env, ...config.env };\n\n const child = spawn(config.command, config.args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n env,\n cwd: config.cwd || process.cwd(),\n detached: false,\n });\n\n this.processes.set(name, child);\n this.updateState(name, { status: 'running', pid: child.pid });\n\n const prefixLog = (source: string, data: Buffer, isError = false): void => {\n const output = data.toString();\n const lines = output.split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) {\n continue;\n }\n\n if (isError) {\n console.error(`[${source}] ${trimmed}`);\n this.addLog(source, 'error', trimmed);\n } else {\n console.log(`[${source}] ${trimmed}`);\n this.addLog(source, 'info', trimmed);\n }\n }\n };\n\n child.stdout?.on('data', (data) => {\n prefixLog(name, data, false);\n });\n\n child.stderr?.on('data', (data) => {\n prefixLog(name, data, true);\n });\n\n child.on('error', (err) => {\n console.error(`[Supervisor] Error spawning ${name}:`, err);\n this.addLog(name, 'error', `Spawn error: ${String(err)}`);\n this.updateState(name, { status: 'crashed' });\n });\n\n child.on('exit', (code, signal) => {\n console.log(`[Supervisor] ${name} exited with code ${code} signal ${signal}`);\n this.addLog(name, code === 0 ? 'info' : 'error', `Exited with code ${code ?? 'null'} signal ${signal ?? 'null'}`);\n const currentState = this.states.get(name);\n const wasManualStop = currentState?.status === 'stopped';\n\n this.updateState(name, {\n status: 'stopped',\n lastExitCode: code ?? undefined,\n pid: undefined,\n });\n this.processes.delete(name);\n\n // Auto-restart on crash (not on manual stop or shutdown)\n if (code !== 0 && !wasManualStop && !this.isShuttingDown) {\n const newState = this.states.get(name);\n const restartCount = (newState?.restartCount || 0) + 1;\n\n if (restartCount <= MAX_RESTARTS) {\n this.updateState(name, { restartCount });\n console.log(`[Supervisor] Restarting ${name} in 2s... (attempt ${restartCount}/${MAX_RESTARTS})`);\n this.addLog(name, 'warn', `Restarting in 2s (attempt ${restartCount}/${MAX_RESTARTS})`);\n setTimeout(() => this.start(name), 2000);\n } else {\n console.error(`[Supervisor] ${name} exceeded max restarts (${MAX_RESTARTS}), giving up`);\n this.addLog(name, 'error', `Exceeded max restarts (${MAX_RESTARTS})`);\n }\n }\n });\n }\n\n public stop(name: string): Promise<void> {\n return new Promise((resolve) => {\n const child = this.processes.get(name);\n if (!child || !child.pid) {\n resolve();\n return;\n }\n\n // Mark as stopped first to prevent auto-restart\n this.updateState(name, { status: 'stopped' });\n this.addLog(name, 'info', 'Stopping service');\n\n kill(child.pid, 'SIGTERM', (err) => {\n if (err) {\n console.error(`[Supervisor] Failed to kill ${name}:`, err);\n this.addLog(name, 'error', `Failed to stop: ${String(err)}`);\n }\n resolve();\n });\n });\n }\n\n public getStatus(name: string): ServiceState | undefined {\n const state = this.states.get(name);\n if (state && state.status === 'running' && state.startTime) {\n return { ...state, uptime: Date.now() - state.startTime };\n }\n return state;\n }\n\n public getAllStatus(): ServiceState[] {\n return Array.from(this.states.values()).map((s) => {\n if (s.status === 'running' && s.startTime) {\n return { ...s, uptime: Date.now() - s.startTime };\n }\n return s;\n });\n }\n\n private updateState(name: string, update: Partial<ServiceState>): void {\n const state = this.states.get(name);\n if (state) {\n Object.assign(state, update);\n this.onStatusChange?.(name, { ...state });\n }\n }\n}\n"]}
1
+ {"version":3,"file":"Supervisor.js","sourceRoot":"","sources":["../../src/supervisor/Supervisor.ts"],"names":[],"mappings":";;;;;;AAAA,2DAA8D;AAC9D,0DAA6B;AAG7B,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,MAAM,QAAQ,GAAG,GAAG,CAAC;AASrB,MAAa,UAAU;IASrB,YAAY,UAA8C,EAAE;QARpD,cAAS,GAA8B,IAAI,GAAG,EAAE,CAAC;QACjD,iBAAY,GAAqC,IAAI,GAAG,EAAE,CAAC;QAC3D,WAAM,GAA8B,IAAI,GAAG,EAAE,CAAC;QAC9C,YAAO,GAA+B,IAAI,GAAG,EAAE,CAAC;QAChD,SAAI,GAAoB,EAAE,CAAC;QAE3B,mBAAc,GAAG,KAAK,CAAC;QAG7B,IAAI,OAAO,CAAC,oBAAoB,KAAK,KAAK,EAAE,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,kBAAkB;QAClB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QACpD,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IACxD,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,MAAc;QACnC,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO;QAChC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,yBAAyB,MAAM,4BAA4B,CAAC,CAAC;QACzE,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAEO,OAAO;QACb,mCAAmC;QACnC,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACvC,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;gBACd,IAAI,CAAC;oBACH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;gBACrC,CAAC;gBAAC,MAAM,CAAC;oBACP,WAAW;gBACb,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAEM,sBAAsB,CAAC,OAA4B;QACxD,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;IAChC,CAAC;IAEM,QAAQ,CAAC,MAAqB;QACnC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE;YAC3B,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,SAAS;YACjB,YAAY,EAAE,CAAC;SAChB,CAAC,CAAC;IACL,CAAC;IAEM,eAAe,CAAC,IAAY,EAAE,IAAiC;QACpE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE;gBACpB,IAAI;gBACJ,MAAM,EAAE,SAAS;gBACjB,YAAY,EAAE,CAAC;aAChB,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,IAAG,EAAE;gBACpC,MAAM,IAAI,EAAE,CAAC;YACf,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAEM,SAAS,CAAC,IAAY,EAAE,MAAqB,EAAE,KAA6B;QACjF,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE;YACrB,MAAM;YACN,GAAG,KAAK;SACT,CAAC,CAAC;IACL,CAAC;IAEM,MAAM,CAAC,MAAc,EAAE,KAA6B,EAAE,OAAe;QAC1E,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,KAAK;YACL,MAAM;YACN,OAAO;SACR,CAAC,CAAC;QAEH,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;YAChC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAEM,OAAO,CAAC,OAId;QACC,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAErB,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;YACnB,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;YACpB,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/D,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,CAAC;QAC7B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACrE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,KAAK,CAAC,QAAQ;QACnB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YACvC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,OAAO;QAClB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,MAAM,QAAQ,GAAoB,EAAE,CAAC;QACrC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;YACzC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACjC,CAAC;QACD,KAAK,MAAM,CAAE,IAAI,EAAE,IAAI,CAAE,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC/C,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACnC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,mCAAmC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACjF,CAAC,CAAC,CAAC,CAAC;QACN,CAAC;QACD,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAEM,KAAK,CAAC,IAAY;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK;YAAE,OAAO;QAE9B,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU;YAAE,OAAO;QAEtE,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,KAAK,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,kBAAkB,CAAC,CAAC;QAC9C,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAEtE,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;QAEtC,MAAM,KAAK,GAAG,IAAA,0BAAK,EAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE;YAC/C,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;YACjC,GAAG;YACH,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;YAChC,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAChC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAE9D,MAAM,SAAS,GAAG,CAAC,MAAc,EAAE,IAAY,EAAE,OAAO,GAAG,KAAK,EAAQ,EAAE;YACxE,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC5B,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,SAAS;gBACX,CAAC;gBAED,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO,CAAC,KAAK,CAAC,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC,CAAC;oBACxC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;gBACxC,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,GAAG,CAAC,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC,CAAC;oBACtC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;gBACvC,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QAEF,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAChC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAChC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,OAAO,CAAC,KAAK,CAAC,+BAA+B,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC;YAC3D,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,gBAAgB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC1D,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YAChC,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,qBAAqB,IAAI,WAAW,MAAM,EAAE,CAAC,CAAC;YAC9E,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,oBAAoB,IAAI,IAAI,MAAM,WAAW,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC;YAClH,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC3C,MAAM,aAAa,GAAG,YAAY,EAAE,MAAM,KAAK,SAAS,CAAC;YAEzD,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE;gBACrB,MAAM,EAAE,SAAS;gBACjB,YAAY,EAAE,IAAI,IAAI,SAAS;gBAC/B,GAAG,EAAE,SAAS;aACf,CAAC,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAE5B,yDAAyD;YACzD,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;gBACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBACvC,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,YAAY,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;gBAEvD,IAAI,YAAY,IAAI,YAAY,EAAE,CAAC;oBACjC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC;oBACzC,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,sBAAsB,YAAY,IAAI,YAAY,GAAG,CAAC,CAAC;oBAClG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,6BAA6B,YAAY,IAAI,YAAY,GAAG,CAAC,CAAC;oBACxF,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;gBAC3C,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,KAAK,CAAC,gBAAgB,IAAI,2BAA2B,YAAY,cAAc,CAAC,CAAC;oBACzF,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,0BAA0B,YAAY,GAAG,CAAC,CAAC;gBACxE,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,IAAI,CAAC,IAAY;QACtB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;gBACzB,OAAO,EAAE,CAAC;gBACV,OAAO;YACT,CAAC;YAED,gDAAgD;YAChD,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YAC9C,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,kBAAkB,CAAC,CAAC;YAE9C,IAAA,mBAAI,EAAC,KAAK,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;gBACjC,IAAI,GAAG,EAAE,CAAC;oBACR,OAAO,CAAC,KAAK,CAAC,+BAA+B,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC;oBAC3D,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,mBAAmB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC/D,CAAC;gBACD,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,SAAS,CAAC,IAAY;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YAC3D,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;QAC5D,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEM,YAAY;QACjB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAChD,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;gBAC1C,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC;YACpD,CAAC;YACD,OAAO,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,WAAW,CAAC,IAAY,EAAE,MAA6B;QAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC7B,IAAI,CAAC,cAAc,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;CACF;AA7QD,gCA6QC","sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process';\nimport kill from 'tree-kill';\nimport type { ServiceConfig, ServiceState, ServiceStatus, StatusChangeHandler } from './types';\n\nconst MAX_RESTARTS = 5;\nconst MAX_LOGS = 500;\n\nexport interface SupervisorLog {\n timestamp: string;\n level: 'info' | 'warn' | 'error';\n source: string;\n message: string;\n}\n\nexport class Supervisor {\n private processes: Map<string, ChildProcess> = new Map();\n private managedStops: Map<string, () => Promise<void>> = new Map();\n private states: Map<string, ServiceState> = new Map();\n private configs: Map<string, ServiceConfig> = new Map();\n private logs: SupervisorLog[] = [];\n private onStatusChange?: StatusChangeHandler;\n private isShuttingDown = false;\n\n constructor(options: { handleProcessSignals?: boolean } = {}) {\n if (options.handleProcessSignals === false) {\n return;\n }\n\n // 确保父进程退出时清理所有子进程\n process.on('exit', () => this.killAll());\n process.on('SIGINT', () => this.shutdown('SIGINT'));\n process.on('SIGTERM', () => this.shutdown('SIGTERM'));\n }\n\n private async shutdown(signal: string): Promise<void> {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n console.log(`[Supervisor] Received ${signal}, stopping all services...`);\n await this.stopAll();\n process.exit(0);\n }\n\n private killAll(): void {\n // 同步杀掉所有子进程(用于 process.on('exit'))\n for (const [, child] of this.processes) {\n if (child.pid) {\n try {\n process.kill(child.pid, 'SIGKILL');\n } catch {\n // 进程可能已经退出\n }\n }\n }\n }\n\n public setStatusChangeHandler(handler: StatusChangeHandler): void {\n this.onStatusChange = handler;\n }\n\n public register(config: ServiceConfig): void {\n this.configs.set(config.name, config);\n this.states.set(config.name, {\n name: config.name,\n status: 'stopped',\n restartCount: 0,\n });\n }\n\n public registerManaged(name: string, stop?: () => Promise<void> | void): void {\n if (!this.states.has(name)) {\n this.states.set(name, {\n name,\n status: 'stopped',\n restartCount: 0,\n });\n }\n\n if (stop) {\n this.managedStops.set(name, async() => {\n await stop();\n });\n }\n }\n\n public setStatus(name: string, status: ServiceStatus, extra?: Partial<ServiceState>): void {\n if (!this.states.has(name)) {\n this.registerManaged(name);\n }\n\n this.updateState(name, {\n status,\n ...extra,\n });\n }\n\n public addLog(source: string, level: SupervisorLog['level'], message: string): void {\n this.logs.push({\n timestamp: new Date().toISOString(),\n level,\n source,\n message,\n });\n\n if (this.logs.length > MAX_LOGS) {\n this.logs.splice(0, this.logs.length - MAX_LOGS);\n }\n }\n\n public getLogs(filters?: {\n level?: string;\n source?: string;\n limit?: number;\n }): SupervisorLog[] {\n let rows = this.logs;\n\n if (filters?.level) {\n rows = rows.filter((item) => item.level === filters.level);\n }\n if (filters?.source) {\n rows = rows.filter((item) => item.source === filters.source);\n }\n\n const limit = filters?.limit;\n if (typeof limit === 'number' && Number.isFinite(limit) && limit > 0) {\n return rows.slice(-limit);\n }\n\n return rows;\n }\n\n public async startAll(): Promise<void> {\n for (const name of this.configs.keys()) {\n this.start(name);\n }\n }\n\n public async stopAll(): Promise<void> {\n this.isShuttingDown = true;\n const promises: Promise<void>[] = [];\n for (const name of this.processes.keys()) {\n promises.push(this.stop(name));\n }\n for (const [ name, stop ] of this.managedStops) {\n promises.push(stop().catch((error) => {\n this.addLog(name, 'error', `Failed to stop managed service: ${String(error)}`);\n }));\n }\n await Promise.all(promises);\n }\n\n public start(name: string): void {\n const config = this.configs.get(name);\n const state = this.states.get(name);\n if (!config || !state) return;\n\n if (state.status === 'running' || state.status === 'starting') return;\n\n console.log(`[Supervisor] Starting ${name}...`);\n this.addLog(name, 'info', 'Service starting');\n this.updateState(name, { status: 'starting', startTime: Date.now() });\n\n const env = config.env ?? process.env;\n\n const child = spawn(config.command, config.args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n env,\n cwd: config.cwd || process.cwd(),\n detached: false,\n });\n\n this.processes.set(name, child);\n this.updateState(name, { status: 'running', pid: child.pid });\n\n const prefixLog = (source: string, data: Buffer, isError = false): void => {\n const output = data.toString();\n const lines = output.split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) {\n continue;\n }\n\n if (isError) {\n console.error(`[${source}] ${trimmed}`);\n this.addLog(source, 'error', trimmed);\n } else {\n console.log(`[${source}] ${trimmed}`);\n this.addLog(source, 'info', trimmed);\n }\n }\n };\n\n child.stdout?.on('data', (data) => {\n prefixLog(name, data, false);\n });\n\n child.stderr?.on('data', (data) => {\n prefixLog(name, data, true);\n });\n\n child.on('error', (err) => {\n console.error(`[Supervisor] Error spawning ${name}:`, err);\n this.addLog(name, 'error', `Spawn error: ${String(err)}`);\n this.updateState(name, { status: 'crashed' });\n });\n\n child.on('exit', (code, signal) => {\n console.log(`[Supervisor] ${name} exited with code ${code} signal ${signal}`);\n this.addLog(name, code === 0 ? 'info' : 'error', `Exited with code ${code ?? 'null'} signal ${signal ?? 'null'}`);\n const currentState = this.states.get(name);\n const wasManualStop = currentState?.status === 'stopped';\n\n this.updateState(name, {\n status: 'stopped',\n lastExitCode: code ?? undefined,\n pid: undefined,\n });\n this.processes.delete(name);\n\n // Auto-restart on crash (not on manual stop or shutdown)\n if (code !== 0 && !wasManualStop && !this.isShuttingDown) {\n const newState = this.states.get(name);\n const restartCount = (newState?.restartCount || 0) + 1;\n\n if (restartCount <= MAX_RESTARTS) {\n this.updateState(name, { restartCount });\n console.log(`[Supervisor] Restarting ${name} in 2s... (attempt ${restartCount}/${MAX_RESTARTS})`);\n this.addLog(name, 'warn', `Restarting in 2s (attempt ${restartCount}/${MAX_RESTARTS})`);\n setTimeout(() => this.start(name), 2000);\n } else {\n console.error(`[Supervisor] ${name} exceeded max restarts (${MAX_RESTARTS}), giving up`);\n this.addLog(name, 'error', `Exceeded max restarts (${MAX_RESTARTS})`);\n }\n }\n });\n }\n\n public stop(name: string): Promise<void> {\n return new Promise((resolve) => {\n const child = this.processes.get(name);\n if (!child || !child.pid) {\n resolve();\n return;\n }\n\n // Mark as stopped first to prevent auto-restart\n this.updateState(name, { status: 'stopped' });\n this.addLog(name, 'info', 'Stopping service');\n\n kill(child.pid, 'SIGTERM', (err) => {\n if (err) {\n console.error(`[Supervisor] Failed to kill ${name}:`, err);\n this.addLog(name, 'error', `Failed to stop: ${String(err)}`);\n }\n resolve();\n });\n });\n }\n\n public getStatus(name: string): ServiceState | undefined {\n const state = this.states.get(name);\n if (state && state.status === 'running' && state.startTime) {\n return { ...state, uptime: Date.now() - state.startTime };\n }\n return state;\n }\n\n public getAllStatus(): ServiceState[] {\n return Array.from(this.states.values()).map((s) => {\n if (s.status === 'running' && s.startTime) {\n return { ...s, uptime: Date.now() - s.startTime };\n }\n return s;\n });\n }\n\n private updateState(name: string, update: Partial<ServiceState>): void {\n const state = this.states.get(name);\n if (state) {\n Object.assign(state, update);\n this.onStatusChange?.(name, { ...state });\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undefineds.co/xpod",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Xpod is an extended Community Solid Server, offering rich-feature, production-level Solid Pod and identity management.",
5
5
  "repository": "https://github.com/undefinedsco/xpod",
6
6
  "author": "developer@undefineds.co",