@twin.org/federated-catalogue-service 0.0.3-next.2 → 0.0.3-next.21

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 (37) hide show
  1. package/README.md +1 -1
  2. package/dist/es/entities/dataset.js +12 -4
  3. package/dist/es/entities/dataset.js.map +1 -1
  4. package/dist/es/federatedCatalogueRoutes.js +241 -53
  5. package/dist/es/federatedCatalogueRoutes.js.map +1 -1
  6. package/dist/es/index.js +2 -0
  7. package/dist/es/index.js.map +1 -1
  8. package/dist/es/models/IFederatedCatalogueServiceConfig.js +4 -0
  9. package/dist/es/models/IFederatedCatalogueServiceConfig.js.map +1 -0
  10. package/dist/es/models/IFederatedCatalogueServiceConstructorOptions.js.map +1 -1
  11. package/dist/es/services/federatedCatalogueService.js +344 -131
  12. package/dist/es/services/federatedCatalogueService.js.map +1 -1
  13. package/dist/es/utils/catalogErrorUtils.js +49 -0
  14. package/dist/es/utils/catalogErrorUtils.js.map +1 -0
  15. package/dist/es/utils/datasetConverters.js +23 -10
  16. package/dist/es/utils/datasetConverters.js.map +1 -1
  17. package/dist/types/entities/dataset.d.ts +37 -33
  18. package/dist/types/index.d.ts +2 -0
  19. package/dist/types/models/IFederatedCatalogueServiceConfig.d.ts +5 -0
  20. package/dist/types/models/IFederatedCatalogueServiceConstructorOptions.d.ts +14 -4
  21. package/dist/types/services/federatedCatalogueService.d.ts +37 -21
  22. package/dist/types/utils/catalogErrorUtils.d.ts +15 -0
  23. package/dist/types/utils/datasetConverters.d.ts +10 -10
  24. package/docs/changelog.md +271 -3
  25. package/docs/examples.md +126 -1
  26. package/docs/open-api/spec.json +256 -2513
  27. package/docs/reference/classes/Dataset.md +70 -62
  28. package/docs/reference/classes/FederatedCatalogueService.md +77 -40
  29. package/docs/reference/functions/datasetEntityToModel.md +7 -7
  30. package/docs/reference/functions/datasetModelToEntity.md +11 -6
  31. package/docs/reference/functions/transformErrorToStatusCode.md +19 -0
  32. package/docs/reference/functions/transformToCatalogError.md +20 -0
  33. package/docs/reference/index.md +3 -0
  34. package/docs/reference/interfaces/IFederatedCatalogueServiceConfig.md +3 -0
  35. package/docs/reference/interfaces/IFederatedCatalogueServiceConstructorOptions.md +33 -5
  36. package/locales/en.json +12 -6
  37. package/package.json +10 -5
@@ -1 +1 @@
1
- {"version":3,"file":"federatedCatalogueService.js","sourceRoot":"","sources":["../../../src/services/federatedCatalogueService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tBaseError,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tNotFoundError,\n\tObjectHelper\n} from \"@twin.org/core\";\nimport { Blake2b } from \"@twin.org/crypto\";\nimport { type IJsonLdContextDefinitionRoot, JsonLdProcessor } from \"@twin.org/data-json-ld\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport type {\n\tIBaseFilter,\n\tIFederatedCatalogueComponent\n} from \"@twin.org/federated-catalogue-models\";\nimport {\n\tFederatedCatalogueContexts,\n\tFederatedCatalogueFilterFactory\n} from \"@twin.org/federated-catalogue-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport {\n\tDataspaceProtocolContexts,\n\tDataspaceProtocolDataTypes\n} from \"@twin.org/standards-dataspace-protocol\";\nimport { DublinCoreContexts, DublinCoreDataTypes } from \"@twin.org/standards-dublin-core\";\nimport { FoafDataTypes } from \"@twin.org/standards-foaf\";\nimport {\n\tDcatClasses,\n\tDcatContexts,\n\ttype DcatContextType,\n\tDcatDataTypes,\n\ttype ICatalog,\n\ttype IDataset\n} from \"@twin.org/standards-w3c-dcat\";\nimport type { Dataset } from \"../entities/dataset.js\";\nimport type { IFederatedCatalogueServiceConstructorOptions } from \"../models/IFederatedCatalogueServiceConstructorOptions.js\";\nimport { datasetEntityToModel, datasetModelToEntity } from \"../utils/datasetConverters.js\";\n\n/**\n * Service for managing federated catalogue operations.\n * Provides Dataspace Protocol-compliant catalog endpoints for dataset registry and query.\n */\nexport class FederatedCatalogueService implements IFederatedCatalogueComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<FederatedCatalogueService>();\n\n\t/**\n\t * The logging component for the federated catalogue service.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * The entity storage connector for datasets.\n\t * @internal\n\t */\n\tprivate readonly _datasetStorage: IEntityStorageConnector<Dataset>;\n\n\t/**\n\t * Create a new instance of FederatedCatalogueService.\n\t * @param options The options for the service.\n\t */\n\tconstructor(options?: IFederatedCatalogueServiceConstructorOptions) {\n\t\tthis._logging = options?.loggingComponent;\n\n\t\tthis._datasetStorage = EntityStorageConnectorFactory.get(\n\t\t\toptions?.datasetStorageConnectorType ?? \"dataset\"\n\t\t);\n\n\t\t// Register JSON-LD redirects for offline processing\n\t\tDcatDataTypes.registerRedirects();\n\t\tDublinCoreDataTypes.registerRedirects();\n\t\tFoafDataTypes.registerRedirects();\n\t\tDataspaceProtocolDataTypes.registerRedirects();\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn FederatedCatalogueService.CLASS_NAME;\n\t}\n\n\t/**\n\t * Retrieve a dataset by its unique identifier.\n\t * @param dataSetId The unique identifier of the dataset.\n\t * @returns The dataset if found.\n\t * @throws NotFoundError if the dataset does not exist.\n\t */\n\tpublic async get(dataSetId: string): Promise<IDataset> {\n\t\tGuards.stringValue(FederatedCatalogueService.CLASS_NAME, nameof(dataSetId), dataSetId);\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\tts: Date.now(),\n\t\t\tmessage: \"datasetRetrieve\",\n\t\t\tdata: { dataSetId }\n\t\t});\n\n\t\tconst datasetEntity = await this._datasetStorage.get(dataSetId);\n\n\t\tif (!datasetEntity) {\n\t\t\tthrow new NotFoundError(FederatedCatalogueService.CLASS_NAME, \"datasetNotFound\", dataSetId);\n\t\t}\n\n\t\treturn datasetEntityToModel(datasetEntity);\n\t}\n\n\t/**\n\t * Insert or update a dataset in the catalogue.\n\t * This method is internal and should not be exposed via REST endpoints.\n\t * @param dataSet The dataset to store.\n\t */\n\tpublic async set(dataSet: IDataset): Promise<void> {\n\t\tGuards.object(FederatedCatalogueService.CLASS_NAME, nameof(dataSet), dataSet);\n\n\t\tconst dataSetId = dataSet[\"@id\"] ?? dataSet[\"dcterms:identifier\"];\n\t\tGuards.stringValue(FederatedCatalogueService.CLASS_NAME, nameof(dataSetId), dataSetId);\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\tts: Date.now(),\n\t\t\tmessage: \"datasetSet\",\n\t\t\tdata: { dataSetId }\n\t\t});\n\n\t\tconst datasetEntity = datasetModelToEntity(dataSet);\n\n\t\tconst allIndexes: { [key: string]: unknown } = {};\n\t\tconst filterNames = FederatedCatalogueFilterFactory.names();\n\t\tfor (const filterType of filterNames) {\n\t\t\ttry {\n\t\t\t\tconst filter = FederatedCatalogueFilterFactory.get(filterType);\n\t\t\t\tconst filterIndexes = await filter.createIndex(dataSet);\n\n\t\t\t\tallIndexes[filterType] = filterIndexes;\n\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\t\t\tts: Date.now(),\n\t\t\t\t\tmessage: \"filterIndexPersisted\",\n\t\t\t\t\tdata: { dataSetId, filterType, indexCount: Object.keys(filterIndexes).length }\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\t\t\tts: Date.now(),\n\t\t\t\t\tmessage: \"filterIndexCreationFailed\",\n\t\t\t\t\tdata: { dataSetId, filterType },\n\t\t\t\t\terror: BaseError.fromError(error)\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tawait this._datasetStorage.set(datasetEntity);\n\t}\n\n\t/**\n\t * Execute a query against the catalogue using registered filter plugins.\n\t * Returns a complete DCAT Catalog object with proper JSON-LD context, metadata, and datasets.\n\t * The filter payload is evaluated by the appropriate filter plugin based on its structure.\n\t * Pagination properties (cursor, limit) and filter type (@type) are extracted from the filter object.\n\t * @param filter The filter criteria containing @type, optional cursor and limit properties.\n\t * @returns Complete ICatalog object with @context, @id, @type, dcat:dataset, and optional cursor.\n\t * @throws NotFoundError if @type is missing or if the filter type is not registered.\n\t */\n\tpublic async query(filter?: IBaseFilter[]): Promise<ICatalog> {\n\t\tlet datasets: IDataset[];\n\t\tlet resultCursor: string | undefined;\n\n\t\tconst isArray = Is.array(filter);\n\t\tif (!filter || (isArray && filter.length === 0)) {\n\t\t\tconst result = await this._datasetStorage.query();\n\t\t\tdatasets = result.entities.map(entity => datasetEntityToModel(entity));\n\t\t} else if (isArray && filter.length > 1) {\n\t\t\tthrow new GeneralError(FederatedCatalogueService.CLASS_NAME, \"multipleFiltersNotSupported\");\n\t\t} else {\n\t\t\tconst singleFilter = filter[0];\n\n\t\t\tconst cursor = singleFilter?.cursor;\n\t\t\tconst limit = singleFilter?.limit;\n\t\t\tconst filterType = singleFilter?.[\"@type\"];\n\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"catalogQuery\",\n\t\t\t\tdata: { filterType: filterType ?? \"\", cursor: cursor ?? \"\", limit: limit ?? \"\" }\n\t\t\t});\n\n\t\t\tGuards.stringValue(FederatedCatalogueService.CLASS_NAME, nameof(filterType), filterType);\n\n\t\t\tconst selectedFilter = FederatedCatalogueFilterFactory.get(filterType);\n\n\t\t\tObjectHelper.propertyDelete(filter, \"cursor\");\n\t\t\tObjectHelper.propertyDelete(filter, \"limit\");\n\t\t\tObjectHelper.propertyDelete(filter, \"@type\");\n\t\t\tconst result = await selectedFilter.query(filter);\n\n\t\t\tdatasets = result.datasets;\n\t\t\tresultCursor = result.cursor;\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\tts: Date.now(),\n\t\t\tmessage: \"catalogQueryComplete\",\n\t\t\tdata: { resultCount: datasets.length, hasMore: Is.stringValue(resultCursor) }\n\t\t});\n\n\t\t// Generate deterministic catalog ID using canonical hash of dataset IDs\n\t\t// Sort dataset IDs to ensure consistent ordering\n\t\tconst datasetIds = datasets\n\t\t\t.map(d => d[\"@id\"])\n\t\t\t.filter(id => Is.stringValue(id))\n\t\t\t.sort();\n\n\t\t// Create deterministic representation for hashing\n\t\tconst canonicalContent = JSON.stringify({\n\t\t\t\"@type\": DcatClasses.Catalog,\n\t\t\tdatasets: datasetIds\n\t\t});\n\n\t\tconst canonicalBytes = Converter.utf8ToBytes(canonicalContent);\n\t\tconst catalogHash = Converter.bytesToHex(Blake2b.sum256(canonicalBytes));\n\t\tconst catalogId = `urn:x-catalog:${catalogHash}`;\n\n\t\t// Return complete catalog with deterministic ID\n\t\tconst catalog: ICatalog = {\n\t\t\t\"@context\": [\n\t\t\t\tDataspaceProtocolContexts.ContextRoot,\n\t\t\t\t{\n\t\t\t\t\tdcat: DcatContexts.ContextRoot,\n\t\t\t\t\tdcterms: DublinCoreContexts.ContextTerms,\n\t\t\t\t\tcursor: `${FederatedCatalogueContexts.ContextRoot}cursor`\n\t\t\t\t}\n\t\t\t] as IJsonLdContextDefinitionRoot as DcatContextType,\n\t\t\t\"@id\": catalogId,\n\t\t\t\"@type\": DcatClasses.Catalog,\n\t\t\t\"dcat:dataset\": datasets\n\t\t};\n\n\t\tif (resultCursor) {\n\t\t\tcatalog.cursor = resultCursor;\n\t\t}\n\n\t\t// Apply JSON-LD compaction to ensure proper context handling\n\t\tconst compactedCatalog = await JsonLdProcessor.compact(catalog, catalog[\"@context\"]);\n\n\t\treturn compactedCatalog;\n\t}\n\n\t/**\n\t * Remove a dataset from the catalogue by its unique identifier.\n\t * Indexes are automatically removed as they are stored with the dataset.\n\t * @param dataSetId The unique identifier of the dataset to remove.\n\t */\n\tpublic async remove(dataSetId: string): Promise<void> {\n\t\tGuards.stringValue(FederatedCatalogueService.CLASS_NAME, nameof(dataSetId), dataSetId);\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\tts: Date.now(),\n\t\t\tmessage: \"datasetRemove\",\n\t\t\tdata: { dataSetId }\n\t\t});\n\n\t\tawait this._datasetStorage.remove(dataSetId);\n\t}\n}\n"]}
1
+ {"version":3,"file":"federatedCatalogueService.js","sourceRoot":"","sources":["../../../src/services/federatedCatalogueService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { HttpUrlHelper } from \"@twin.org/api-models\";\nimport { ContextIdKeys } from \"@twin.org/context\";\nimport {\n\tArrayHelper,\n\tBaseError,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tJsonHelper,\n\tMutex,\n\tNotFoundError,\n\tObjectHelper,\n\tUrl,\n\tUrn,\n\tValidation\n} from \"@twin.org/core\";\nimport { Blake2b } from \"@twin.org/crypto\";\nimport { JsonLdHelper, JsonLdProcessor } from \"@twin.org/data-json-ld\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport {\n\tFederatedCatalogueFilterFactory,\n\ttype IFederatedCatalogueComponent\n} from \"@twin.org/federated-catalogue-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport {\n\tDataspaceProtocolCatalogTypes,\n\tDataspaceProtocolContexts,\n\tDataspaceProtocolDataTypes,\n\tDataspaceProtocolHelper,\n\ttype IDataspaceProtocolCatalog,\n\ttype IDataspaceProtocolCatalogError\n} from \"@twin.org/standards-dataspace-protocol\";\nimport { DublinCoreContexts, DublinCoreDataTypes } from \"@twin.org/standards-dublin-core\";\nimport { FoafDataTypes } from \"@twin.org/standards-foaf\";\nimport {\n\tDcatContexts,\n\tDcatDataTypes,\n\ttype DcatContextType,\n\ttype IDcatDataset\n} from \"@twin.org/standards-w3c-dcat\";\nimport { OdrlContexts } from \"@twin.org/standards-w3c-odrl\";\nimport { TrustHelper, type ITrustComponent } from \"@twin.org/trust-models\";\nimport type { Dataset } from \"../entities/dataset.js\";\nimport type { IFederatedCatalogueServiceConstructorOptions } from \"../models/IFederatedCatalogueServiceConstructorOptions.js\";\nimport { transformToCatalogError } from \"../utils/catalogErrorUtils.js\";\nimport { datasetEntityToModel, datasetModelToEntity } from \"../utils/datasetConverters.js\";\n\n/**\n * Service for managing federated catalogue operations.\n * Provides Dataspace Protocol-compliant catalog endpoints for dataset registry and query.\n */\nexport class FederatedCatalogueService implements IFederatedCatalogueComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<FederatedCatalogueService>();\n\n\t/**\n\t * The logging component for the federated catalogue service.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * The entity storage connector for datasets.\n\t * @internal\n\t */\n\tprivate readonly _datasetStorage: IEntityStorageConnector<Dataset>;\n\n\t/**\n\t * The trust component for token verification and generation.\n\t * @internal\n\t */\n\tprivate readonly _trustComponent: ITrustComponent;\n\n\t/**\n\t * Create a new instance of FederatedCatalogueService.\n\t * @param options The options for the service.\n\t */\n\tconstructor(options?: IFederatedCatalogueServiceConstructorOptions) {\n\t\tthis._logging = ComponentFactory.getIfExists<ILoggingComponent>(options?.loggingComponentType);\n\n\t\tthis._datasetStorage = EntityStorageConnectorFactory.get(\n\t\t\toptions?.datasetEntityStorageType ?? \"dataset\"\n\t\t);\n\n\t\tthis._trustComponent = ComponentFactory.get<ITrustComponent>(\n\t\t\toptions?.trustComponentType ?? \"trust\"\n\t\t);\n\n\t\t// Register JSON-LD redirects for offline processing\n\t\tDcatDataTypes.registerRedirects();\n\t\tDublinCoreDataTypes.registerRedirects();\n\t\tFoafDataTypes.registerRedirects();\n\t\tDataspaceProtocolDataTypes.registerRedirects();\n\n\t\t// Register DS Protocol data types for conformance checking\n\t\tDataspaceProtocolDataTypes.registerTypes();\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn FederatedCatalogueService.CLASS_NAME;\n\t}\n\n\t/**\n\t * Retrieve a dataset by its unique identifier.\n\t * @param datasetId The unique identifier of the dataset.\n\t * @param trustPayload Optional payload for trust evaluation, if applicable.\n\t * @returns The dataset if found, or a CatalogError if not found or an error occurs.\n\t */\n\tpublic async get(\n\t\tdatasetId: string,\n\t\ttrustPayload: unknown\n\t): Promise<IDcatDataset | IDataspaceProtocolCatalogError> {\n\t\ttry {\n\t\t\tGuards.stringValue(FederatedCatalogueService.CLASS_NAME, nameof(datasetId), datasetId);\n\t\t\tawait TrustHelper.verifyTrust(this._trustComponent, trustPayload, \"get\");\n\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"datasetRetrieve\",\n\t\t\t\tdata: { datasetId }\n\t\t\t});\n\n\t\t\tconst datasetEntity = await this._datasetStorage.get(datasetId);\n\n\t\t\tif (!datasetEntity) {\n\t\t\t\tthrow new NotFoundError(FederatedCatalogueService.CLASS_NAME, \"datasetNotFound\", datasetId);\n\t\t\t}\n\n\t\t\tconst dataset = datasetEntityToModel(datasetEntity);\n\n\t\t\t// Normalize to DS Protocol compliant format\n\t\t\t// This ensures the payload matches exactly what the DS Protocol mandates\n\t\t\tconst normalizedDataset = await DataspaceProtocolHelper.normalize(\n\t\t\t\tJsonLdHelper.toNodeObject(dataset)\n\t\t\t);\n\n\t\t\tconst structured = JsonLdHelper.toStructuredObject<IDcatDataset>(normalizedDataset);\n\t\t\treturn structured;\n\t\t} catch (error) {\n\t\t\treturn transformToCatalogError(error);\n\t\t}\n\t}\n\n\t/**\n\t * Insert or update a dataset in the catalogue.\n\t * @param dataset The dataset to store.\n\t * @param trustPayload Optional payload for trust evaluation, if applicable.\n\t * @returns The unique identifier of the stored dataset, or a CatalogError if an error occurs.\n\t */\n\tpublic async set(\n\t\tdataset: IDcatDataset,\n\t\ttrustPayload: unknown\n\t): Promise<string | IDataspaceProtocolCatalogError> {\n\t\ttry {\n\t\t\tGuards.object(FederatedCatalogueService.CLASS_NAME, nameof(dataset), dataset);\n\n\t\t\tconst trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, \"set\");\n\n\t\t\t// Normalize @id from dcterms:identifier if provided\n\t\t\tconst datasetId = dataset[\"@id\"] ?? dataset[\"dcterms:identifier\"];\n\t\t\tGuards.stringValue(FederatedCatalogueService.CLASS_NAME, nameof(datasetId), datasetId);\n\n\t\t\t// Set @id if it was derived from dcterms:identifier\n\t\t\tif (Is.empty(dataset[\"@id\"]) && !Is.empty(dataset[\"dcterms:identifier\"])) {\n\t\t\t\tdataset[\"@id\"] = datasetId;\n\t\t\t}\n\n\t\t\t// Validate @id is a valid URI (URN or URL) per DS Protocol\n\t\t\tconst isValidUrn = !Is.empty(Urn.tryParseExact(datasetId));\n\t\t\tconst isValidUrl = !Is.empty(Url.tryParseExact(datasetId));\n\t\t\tif (!isValidUrn && !isValidUrl) {\n\t\t\t\tthrow new GeneralError(FederatedCatalogueService.CLASS_NAME, \"datasetIdInvalidUri\", {\n\t\t\t\t\tdatasetId\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Validate @type exists\n\t\t\tGuards.stringValue(FederatedCatalogueService.CLASS_NAME, \"@type\", dataset[\"@type\"]);\n\n\t\t\t// Validate dcterms:publisher exists (required for multi-participant catalog)\n\t\t\t// The publisher is used to derive participantId when returning catalog query results\n\t\t\tconst publisher = dataset[\"dcterms:publisher\"];\n\t\t\tif (Is.empty(publisher)) {\n\t\t\t\tthrow new GeneralError(FederatedCatalogueService.CLASS_NAME, \"datasetMissingPublisher\", {\n\t\t\t\t\tdatasetId\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// DS Protocol compliance validation\n\t\t\tconst validationFailures = await DataspaceProtocolHelper.validate(\n\t\t\t\tJsonLdHelper.toNodeObject(dataset)\n\t\t\t);\n\n\t\t\tValidation.asValidationError(\n\t\t\t\tFederatedCatalogueService.CLASS_NAME,\n\t\t\t\t\"dataset\",\n\t\t\t\tvalidationFailures\n\t\t\t);\n\n\t\t\t// Normalize dataset for storage using JSON-LD compaction\n\t\t\t// This ensures the dataset uses prefixed properties that entity storage expects\n\t\t\t// Entity storage schema uses DCAT-prefixed properties (dcat:distribution, not distribution)\n\t\t\t// Use a standard context with prefixes to ensure proper normalization\n\t\t\tconst storageContext: DcatContextType = {\n\t\t\t\tdcat: DcatContexts.Namespace,\n\t\t\t\tdcterms: DublinCoreContexts.NamespaceTerms,\n\t\t\t\todrl: OdrlContexts.Namespace\n\t\t\t};\n\t\t\tconst normalizedDataset = await JsonLdProcessor.compact(dataset, storageContext);\n\n\t\t\tconst datasetEntity = datasetModelToEntity(normalizedDataset, trustInfo.identity);\n\n\t\t\t// Serialise the read-check-write cycle so concurrent requests for the\n\t\t\t// same dataset cannot both pass the ownership check and overwrite each other.\n\t\t\tawait Mutex.lock(datasetId);\n\t\t\ttry {\n\t\t\t\tconst existingEntity = await this._datasetStorage.get(datasetId);\n\t\t\t\tif (!Is.empty(existingEntity) && existingEntity.ownerId !== trustInfo.identity) {\n\t\t\t\t\tthrow new GeneralError(FederatedCatalogueService.CLASS_NAME, \"datasetOwnerMismatch\", {\n\t\t\t\t\t\tdatasetId\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Bake the publishing organization into distribution accessService URLs\n\t\t\t\tawait this.bakeOrganizationIntoDistributions(datasetEntity, trustInfo.identity);\n\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\t\t\tts: Date.now(),\n\t\t\t\t\tmessage: \"datasetSet\",\n\t\t\t\t\tdata: { datasetId }\n\t\t\t\t});\n\n\t\t\t\tconst filterNames = FederatedCatalogueFilterFactory.names();\n\t\t\t\tfor (const filterType of filterNames) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst filter = FederatedCatalogueFilterFactory.get(filterType);\n\t\t\t\t\t\tconst filterIndexes = await filter.createIndex(normalizedDataset);\n\n\t\t\t\t\t\tawait this._logging?.log({\n\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\t\t\t\t\tts: Date.now(),\n\t\t\t\t\t\t\tmessage: \"filterIndexPersisted\",\n\t\t\t\t\t\t\tdata: { datasetId, filterType, indexCount: Object.keys(filterIndexes).length }\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tawait this._logging?.log({\n\t\t\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\t\t\t\t\tts: Date.now(),\n\t\t\t\t\t\t\tmessage: \"filterIndexCreationFailed\",\n\t\t\t\t\t\t\tdata: { datasetId, filterType },\n\t\t\t\t\t\t\terror: BaseError.fromError(error)\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tawait this._datasetStorage.set(datasetEntity);\n\n\t\t\t\treturn datasetId;\n\t\t\t} finally {\n\t\t\t\tMutex.unlock(datasetId);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn transformToCatalogError(error);\n\t\t}\n\t}\n\n\t/**\n\t * Remove a dataset from the catalogue by its unique identifier.\n\t * Indexes are automatically removed as they are stored with the dataset.\n\t * @param datasetId The unique identifier of the dataset to remove.\n\t * @param trustPayload Optional payload for trust evaluation, if applicable.\n\t * @returns Nothing, or a CatalogError if an error occurs.\n\t */\n\tpublic async remove(\n\t\tdatasetId: string,\n\t\ttrustPayload: unknown\n\t): Promise<IDataspaceProtocolCatalogError | undefined> {\n\t\ttry {\n\t\t\tGuards.stringValue(FederatedCatalogueService.CLASS_NAME, nameof(datasetId), datasetId);\n\n\t\t\tconst trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, \"remove\");\n\n\t\t\tawait Mutex.lock(datasetId);\n\t\t\ttry {\n\t\t\t\tconst existingEntity = await this._datasetStorage.get(datasetId);\n\t\t\t\tif (!Is.empty(existingEntity) && existingEntity.ownerId !== trustInfo.identity) {\n\t\t\t\t\tthrow new GeneralError(FederatedCatalogueService.CLASS_NAME, \"datasetRemoveNotOwner\", {\n\t\t\t\t\t\tdatasetId\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\t\t\tts: Date.now(),\n\t\t\t\t\tmessage: \"datasetRemove\",\n\t\t\t\t\tdata: { datasetId }\n\t\t\t\t});\n\n\t\t\t\tawait this._datasetStorage.remove(datasetId);\n\t\t\t} finally {\n\t\t\t\tMutex.unlock(datasetId);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn transformToCatalogError(error);\n\t\t}\n\t}\n\n\t/**\n\t * Execute a query against the catalogue using registered filter plugins.\n\t * Returns a DS Protocol compliant Catalog object with participantId.\n\t *\n\t * The root catalog's participantId is the requesting participant (from context).\n\t * Own datasets (matching requestingParticipantId) go directly in root dataset[].\n\t * Other participants' datasets are grouped in nested catalog[] entries.\n\t *\n\t * For anonymous requests (no context), uses the first publisher found as fallback.\n\t * Returns CatalogError 404 when no datasets exist, CatalogError 400 for invalid requests.\n\t *\n\t * @param filter The filter criteria containing @type, optional cursor and limit properties.\n\t * @param cursor Optional cursor for pagination.\n\t * @param limit Optional limit for pagination.\n\t * @param trustPayload Optional payload for trust evaluation, if applicable.\n\t * @returns Complete IDataspaceProtocolCatalog with @context, @id, @type, participantId, dataset/catalog,\n\t * or CatalogError if validation fails or an error occurs.\n\t */\n\tpublic async query(\n\t\tfilter: unknown[] | undefined,\n\t\tcursor: string | undefined,\n\t\tlimit: number | undefined,\n\t\ttrustPayload: unknown\n\t): Promise<{\n\t\tresult: IDataspaceProtocolCatalog | IDataspaceProtocolCatalogError;\n\t\tcursor?: string;\n\t}> {\n\t\ttry {\n\t\t\tconst trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, \"query\");\n\n\t\t\tlet datasets: IDcatDataset[];\n\t\t\tlet resultCursor: string | undefined;\n\n\t\t\tif (!Is.empty(filter) && !Is.array(filter)) {\n\t\t\t\tthrow new GeneralError(FederatedCatalogueService.CLASS_NAME, \"filterMustBeArray\");\n\t\t\t}\n\n\t\t\tif (!Is.arrayValue(filter)) {\n\t\t\t\tconst result = await this._datasetStorage.query(\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tcursor,\n\t\t\t\t\tlimit\n\t\t\t\t);\n\t\t\t\tdatasets = result.entities.map(entity => datasetEntityToModel(entity));\n\t\t\t\tresultCursor = result.cursor;\n\t\t\t} else if (filter.length > 1) {\n\t\t\t\tthrow new GeneralError(FederatedCatalogueService.CLASS_NAME, \"multipleFiltersNotSupported\");\n\t\t\t} else {\n\t\t\t\tconst singleFilter = filter[0] as { \"@type\"?: string } | undefined;\n\n\t\t\t\tconst filterType = singleFilter?.[\"@type\"];\n\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\t\t\tts: Date.now(),\n\t\t\t\t\tmessage: \"catalogQuery\",\n\t\t\t\t\tdata: { filterType: filterType ?? \"\", cursor: cursor ?? \"\", limit: limit ?? \"\" }\n\t\t\t\t});\n\n\t\t\t\tGuards.stringValue(FederatedCatalogueService.CLASS_NAME, nameof(filterType), filterType);\n\n\t\t\t\tconst selectedFilter = FederatedCatalogueFilterFactory.get(filterType);\n\n\t\t\t\tObjectHelper.propertyDelete(filter, \"@type\");\n\t\t\t\tconst result = await selectedFilter.query(trustInfo, filter, cursor, limit);\n\n\t\t\t\tdatasets = result.datasets;\n\t\t\t\tresultCursor = result.cursor;\n\t\t\t}\n\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: FederatedCatalogueService.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"catalogQueryComplete\",\n\t\t\t\tdata: { resultCount: datasets.length, hasMore: Is.stringValue(resultCursor) }\n\t\t\t});\n\n\t\t\t// Return CatalogError 404 when no datasets exist\n\t\t\tif (!Is.arrayValue(datasets)) {\n\t\t\t\tthrow new NotFoundError(FederatedCatalogueService.CLASS_NAME, \"noDatasetsFound\");\n\t\t\t}\n\n\t\t\tlet requestingParticipantId = trustInfo.identity;\n\n\t\t\t// Group datasets by dcterms:publisher (participantId)\n\t\t\tconst datasetsByParticipant = new Map<string, IDcatDataset[]>();\n\t\t\tfor (const dataset of datasets) {\n\t\t\t\tconst publisher = this.extractPublisher(dataset);\n\t\t\t\tconst participantId = publisher ?? \"unknown\";\n\t\t\t\tconst existing = datasetsByParticipant.get(participantId) ?? [];\n\t\t\t\texisting.push(dataset);\n\t\t\t\tdatasetsByParticipant.set(participantId, existing);\n\t\t\t}\n\n\t\t\tconst participantIds = [...datasetsByParticipant.keys()];\n\n\t\t\t// For anonymous requests (no context), use first publisher as fallback\n\t\t\tif (!Is.stringValue(requestingParticipantId)) {\n\t\t\t\trequestingParticipantId = participantIds[0] ?? \"unknown\";\n\t\t\t}\n\n\t\t\t// Separate own datasets from other participants' datasets\n\t\t\tconst ownDatasets = datasetsByParticipant.get(requestingParticipantId) ?? [];\n\t\t\tconst otherParticipantIds = participantIds.filter(id => id !== requestingParticipantId);\n\n\t\t\tlet catalog: IDataspaceProtocolCatalog;\n\n\t\t\tif (!Is.arrayValue(otherParticipantIds)) {\n\t\t\t\t// Only own datasets (or all datasets belong to requesting participant)\n\t\t\t\tconst catalogId = this.generateCatalogId(ownDatasets, requestingParticipantId);\n\n\t\t\t\tcatalog = {\n\t\t\t\t\t\"@context\": [DataspaceProtocolContexts.Context],\n\t\t\t\t\t\"@id\": catalogId,\n\t\t\t\t\t\"@type\": DataspaceProtocolCatalogTypes.Catalog,\n\t\t\t\t\tparticipantId: requestingParticipantId,\n\t\t\t\t\tdataset: ownDatasets as unknown as IDataspaceProtocolCatalog[\"dataset\"]\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\t// Mixed: own datasets at root level, others in nested catalogs\n\t\t\t\tconst nestedCatalogs: IDataspaceProtocolCatalog[] = [];\n\n\t\t\t\tfor (const participantId of otherParticipantIds) {\n\t\t\t\t\tconst participantDatasets = datasetsByParticipant.get(participantId) ?? [];\n\t\t\t\t\tconst subCatalogId = this.generateCatalogId(participantDatasets, participantId);\n\n\t\t\t\t\tconst subCatalog: IDataspaceProtocolCatalog = {\n\t\t\t\t\t\t\"@context\": [DataspaceProtocolContexts.Context],\n\t\t\t\t\t\t\"@id\": subCatalogId,\n\t\t\t\t\t\t\"@type\": DataspaceProtocolCatalogTypes.Catalog,\n\t\t\t\t\t\tparticipantId,\n\t\t\t\t\t\tdataset: participantDatasets as unknown as IDataspaceProtocolCatalog[\"dataset\"]\n\t\t\t\t\t};\n\n\t\t\t\t\tnestedCatalogs.push(subCatalog);\n\t\t\t\t}\n\n\t\t\t\t// Root catalog contains own datasets and nested catalogs for others\n\t\t\t\tconst rootCatalogId = this.generateCatalogId(datasets, requestingParticipantId);\n\n\t\t\t\tcatalog = {\n\t\t\t\t\t\"@context\": [DataspaceProtocolContexts.Context],\n\t\t\t\t\t\"@id\": rootCatalogId,\n\t\t\t\t\t\"@type\": DataspaceProtocolCatalogTypes.Catalog,\n\t\t\t\t\tparticipantId: requestingParticipantId,\n\t\t\t\t\tdataset: ownDatasets as unknown as IDataspaceProtocolCatalog[\"dataset\"],\n\t\t\t\t\tcatalog: nestedCatalogs\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Normalize to DS Protocol compliant format\n\t\t\t// This ensures the payload matches exactly what the DS Protocol mandates\n\t\t\tconst normalizedCatalog = await DataspaceProtocolHelper.normalize(\n\t\t\t\tJsonLdHelper.toNodeObject(catalog)\n\t\t\t);\n\n\t\t\tconst structuredResult: IDataspaceProtocolCatalog =\n\t\t\t\tJsonLdHelper.toStructuredObject(normalizedCatalog);\n\n\t\t\treturn {\n\t\t\t\tresult: structuredResult,\n\t\t\t\tcursor: resultCursor\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tresult: transformToCatalogError(error)\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Extract publisher from dataset.\n\t * Publisher can be a string or an IFoafAgent object with @id.\n\t * @param dataset The dataset to extract publisher from.\n\t * @returns The publisher string or undefined if not found.\n\t * @internal\n\t */\n\tprivate extractPublisher(dataset: IDcatDataset): string | undefined {\n\t\tconst publisher = dataset[\"dcterms:publisher\"];\n\n\t\t// Handle case where publisher is an object with @id (IFoafAgent)\n\t\tif (Is.object<{ \"@id\"?: string }>(publisher)) {\n\t\t\tconst publisherId = publisher[\"@id\"];\n\t\t\treturn Is.stringValue(publisherId) ? publisherId : undefined;\n\t\t}\n\n\t\treturn Is.stringValue(publisher) ? publisher : undefined;\n\t}\n\n\t/**\n\t * Bake the publishing organization token into each distribution's accessService URL.\n\t * @param entity The dataset entity to modify in place.\n\t * @param organizationId The publishing organization id to bake.\n\t * @internal\n\t */\n\tprivate async bakeOrganizationIntoDistributions(\n\t\tentity: Dataset,\n\t\torganizationId: string\n\t): Promise<void> {\n\t\t// Storage uses the prefixed JSON-LD key \"dcat:accessService\" (not the unprefixed\n\t\t// \"accessService\" that appears in compacted query responses).\n\t\tconst distributions = ArrayHelper.fromObjectOrArray(entity[\"dcat:distribution\"]) ?? [];\n\t\tfor (const dist of distributions) {\n\t\t\tconst accessService = dist?.[\"dcat:accessService\"];\n\t\t\tif (Is.stringValue(accessService)) {\n\t\t\t\tdist[\"dcat:accessService\"] = HttpUrlHelper.addQueryStringParam(\n\t\t\t\t\taccessService,\n\t\t\t\t\tContextIdKeys.Organization,\n\t\t\t\t\torganizationId\n\t\t\t\t);\n\t\t\t} else if (Is.objectValue(accessService)) {\n\t\t\t\t// DCAT object form: a dcat:DataService with an endpointURL.\n\t\t\t\tconst dataService = accessService as { \"dcat:endpointURL\"?: unknown };\n\t\t\t\tif (Is.stringValue(dataService[\"dcat:endpointURL\"])) {\n\t\t\t\t\tdataService[\"dcat:endpointURL\"] = HttpUrlHelper.addQueryStringParam(\n\t\t\t\t\t\tdataService[\"dcat:endpointURL\"],\n\t\t\t\t\t\tContextIdKeys.Organization,\n\t\t\t\t\t\torganizationId\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Generate a deterministic catalog ID using canonical hash.\n\t * @param datasets The datasets to include in the hash.\n\t * @param participantId The participant ID to include in the hash.\n\t * @returns A URN-formatted catalog ID.\n\t * @internal\n\t */\n\tprivate generateCatalogId(datasets: IDcatDataset[], participantId: string): string {\n\t\tconst datasetIds = datasets\n\t\t\t.map(d => d[\"@id\"])\n\t\t\t.filter(id => Is.stringValue(id))\n\t\t\t.sort();\n\n\t\tconst canonicalContent = JsonHelper.canonicalize({\n\t\t\t\"@type\": DataspaceProtocolCatalogTypes.Catalog,\n\t\t\tparticipantId,\n\t\t\tdatasets: datasetIds\n\t\t});\n\n\t\tconst canonicalBytes = Converter.utf8ToBytes(canonicalContent);\n\t\tconst catalogHash = Converter.bytesToHex(Blake2b.sum256(canonicalBytes));\n\t\treturn `urn:x-catalog:${catalogHash}`;\n\t}\n}\n"]}
@@ -0,0 +1,49 @@
1
+ // Copyright 2025 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { HttpErrorHelper } from "@twin.org/api-models";
4
+ import { BaseError, Is } from "@twin.org/core";
5
+ import { DataspaceProtocolCatalogTypes, DataspaceProtocolContexts } from "@twin.org/standards-dataspace-protocol";
6
+ import { HttpStatusCode } from "@twin.org/web";
7
+ /**
8
+ * Transform an error to DS Protocol CatalogError format.
9
+ * Used by both service and route layers to ensure consistent error responses.
10
+ * @param error The error to transform.
11
+ * @returns The CatalogError.
12
+ */
13
+ export function transformToCatalogError(error) {
14
+ const flattened = BaseError.flatten(error);
15
+ // We maintain the reason as the flattened array of errors for more context
16
+ // this also helps preserve the original error messages and types
17
+ // The code property can be any machine-readable string, we use the top-level error name
18
+ // The schema allows for an array of any objects for the reason property
19
+ // and is not limited to just strings or specific error formats
20
+ // https://github.com/eclipse-dataspace-protocol-base/DataspaceProtocol/blob/main/artifacts/src/main/resources/catalog/catalog-error-schema.json
21
+ return {
22
+ "@context": DataspaceProtocolContexts.Context,
23
+ "@type": DataspaceProtocolCatalogTypes.CatalogError,
24
+ code: `${flattened[0].name}:${flattened[0].message}`,
25
+ reason: flattened
26
+ };
27
+ }
28
+ /**
29
+ * Transform the DS Protocol result to an HTTP status code.
30
+ * @param result The result to transform.
31
+ * @returns The transformed status code or undefined if no transformation was found or not an error.
32
+ */
33
+ export function transformErrorToStatusCode(result) {
34
+ // Is this an catalog error?
35
+ if (Is.object(result) &&
36
+ result["@type"] === DataspaceProtocolCatalogTypes.CatalogError) {
37
+ // As per the method above the result.code is the IError name property
38
+ // so we use that to map to status codes
39
+ const codePart = result.code.split(":")[0];
40
+ return HttpErrorHelper.ERROR_TYPE_MAP[codePart] ?? HttpStatusCode.badRequest;
41
+ }
42
+ // Or a regular error
43
+ if (Is.object(result) && !BaseError.isEmpty(result)) {
44
+ // If this is a regular error, we can use the name property to map to status codes
45
+ return HttpErrorHelper.ERROR_TYPE_MAP[result.name] ?? HttpStatusCode.badRequest;
46
+ }
47
+ return undefined;
48
+ }
49
+ //# sourceMappingURL=catalogErrorUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogErrorUtils.js","sourceRoot":"","sources":["../../../src/utils/catalogErrorUtils.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAe,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EACN,6BAA6B,EAC7B,yBAAyB,EAEzB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACrD,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAE3C,2EAA2E;IAC3E,iEAAiE;IACjE,wFAAwF;IACxF,wEAAwE;IACxE,+DAA+D;IAC/D,gJAAgJ;IAChJ,OAAO;QACN,UAAU,EAAE,yBAAyB,CAAC,OAAO;QAC7C,OAAO,EAAE,6BAA6B,CAAC,YAAY;QACnD,IAAI,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE;QACpD,MAAM,EAAE,SAAS;KACjB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAAe;IACzD,4BAA4B;IAC5B,IACC,EAAE,CAAC,MAAM,CAAiC,MAAM,CAAC;QACjD,MAAM,CAAC,OAAO,CAAC,KAAK,6BAA6B,CAAC,YAAY,EAC7D,CAAC;QACF,sEAAsE;QACtE,wCAAwC;QACxC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,OAAO,eAAe,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,cAAc,CAAC,UAAU,CAAC;IAC9E,CAAC;IAED,qBAAqB;IACrB,IAAI,EAAE,CAAC,MAAM,CAAS,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7D,kFAAkF;QAClF,OAAO,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,UAAU,CAAC;IACjF,CAAC;IAED,OAAO,SAAS,CAAC;AAClB,CAAC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { HttpErrorHelper } from \"@twin.org/api-models\";\nimport { BaseError, type IError, Is } from \"@twin.org/core\";\nimport {\n\tDataspaceProtocolCatalogTypes,\n\tDataspaceProtocolContexts,\n\ttype IDataspaceProtocolCatalogError\n} from \"@twin.org/standards-dataspace-protocol\";\nimport { HttpStatusCode } from \"@twin.org/web\";\n\n/**\n * Transform an error to DS Protocol CatalogError format.\n * Used by both service and route layers to ensure consistent error responses.\n * @param error The error to transform.\n * @returns The CatalogError.\n */\nexport function transformToCatalogError(error: unknown): IDataspaceProtocolCatalogError {\n\tconst flattened = BaseError.flatten(error);\n\n\t// We maintain the reason as the flattened array of errors for more context\n\t// this also helps preserve the original error messages and types\n\t// The code property can be any machine-readable string, we use the top-level error name\n\t// The schema allows for an array of any objects for the reason property\n\t// and is not limited to just strings or specific error formats\n\t// https://github.com/eclipse-dataspace-protocol-base/DataspaceProtocol/blob/main/artifacts/src/main/resources/catalog/catalog-error-schema.json\n\treturn {\n\t\t\"@context\": DataspaceProtocolContexts.Context,\n\t\t\"@type\": DataspaceProtocolCatalogTypes.CatalogError,\n\t\tcode: `${flattened[0].name}:${flattened[0].message}`,\n\t\treason: flattened\n\t};\n}\n\n/**\n * Transform the DS Protocol result to an HTTP status code.\n * @param result The result to transform.\n * @returns The transformed status code or undefined if no transformation was found or not an error.\n */\nexport function transformErrorToStatusCode(result: unknown): HttpStatusCode | undefined {\n\t// Is this an catalog error?\n\tif (\n\t\tIs.object<IDataspaceProtocolCatalogError>(result) &&\n\t\tresult[\"@type\"] === DataspaceProtocolCatalogTypes.CatalogError\n\t) {\n\t\t// As per the method above the result.code is the IError name property\n\t\t// so we use that to map to status codes\n\t\tconst codePart = result.code.split(\":\")[0];\n\t\treturn HttpErrorHelper.ERROR_TYPE_MAP[codePart] ?? HttpStatusCode.badRequest;\n\t}\n\n\t// Or a regular error\n\tif (Is.object<IError>(result) && !BaseError.isEmpty(result)) {\n\t\t// If this is a regular error, we can use the name property to map to status codes\n\t\treturn HttpErrorHelper.ERROR_TYPE_MAP[result.name] ?? HttpStatusCode.badRequest;\n\t}\n\n\treturn undefined;\n}\n"]}
@@ -1,20 +1,33 @@
1
1
  /**
2
- * Convert a Dataset entity to an IDataset model.
3
- * Removes internal entity properties that should not be exposed in API responses.
2
+ * Convert a Dataset entity to an IDcatDataset model.
3
+ * Maps entity.id back to model["@id"] and strips the storage-only id field.
4
4
  * @param entity The dataset entity from storage (may be partial from query results).
5
- * @returns The IDataset model for API responses.
5
+ * @returns The IDcatDataset model for API responses.
6
6
  */
7
7
  export function datasetEntityToModel(entity) {
8
- return { ...entity };
8
+ const model = { ...entity };
9
+ const { id } = model;
10
+ // Remove storage-specific fields that should not be exposed in the API model
11
+ delete model.id;
12
+ delete model.ownerId;
13
+ return {
14
+ ...model,
15
+ "@id": id
16
+ };
9
17
  }
10
18
  /**
11
- * Convert an IDataset model to a Dataset entity.
12
- * Prepares the model for storage by converting to entity type.
13
- * Creates a shallow copy to avoid mutating the input model.
14
- * @param model The IDataset model from API requests.
19
+ * Convert an IDcatDataset model to a Dataset entity.
20
+ * Maps model["@id"] to entity.id.
21
+ * @param model The IDcatDataset model from API requests.
22
+ * @param ownerId The owner ID to associate with the dataset entity.
15
23
  * @returns The Dataset entity for storage.
16
24
  */
17
- export function datasetModelToEntity(model) {
18
- return { ...model };
25
+ export function datasetModelToEntity(model, ownerId) {
26
+ const { "@id": atId, ...datasetModel } = model;
27
+ return {
28
+ ...datasetModel,
29
+ id: atId,
30
+ ownerId
31
+ };
19
32
  }
20
33
  //# sourceMappingURL=datasetConverters.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"datasetConverters.js","sourceRoot":"","sources":["../../../src/utils/datasetConverters.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAkC;IACtE,OAAO,EAAE,GAAG,MAAM,EAAc,CAAC;AAClC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAe;IACnD,OAAO,EAAE,GAAG,KAAK,EAAa,CAAC;AAChC,CAAC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IDataset } from \"@twin.org/standards-w3c-dcat\";\nimport type { Dataset } from \"../entities/dataset.js\";\n\n/**\n * Convert a Dataset entity to an IDataset model.\n * Removes internal entity properties that should not be exposed in API responses.\n * @param entity The dataset entity from storage (may be partial from query results).\n * @returns The IDataset model for API responses.\n */\nexport function datasetEntityToModel(entity: Dataset | Partial<Dataset>): IDataset {\n\treturn { ...entity } as IDataset;\n}\n\n/**\n * Convert an IDataset model to a Dataset entity.\n * Prepares the model for storage by converting to entity type.\n * Creates a shallow copy to avoid mutating the input model.\n * @param model The IDataset model from API requests.\n * @returns The Dataset entity for storage.\n */\nexport function datasetModelToEntity(model: IDataset): Dataset {\n\treturn { ...model } as Dataset;\n}\n"]}
1
+ {"version":3,"file":"datasetConverters.js","sourceRoot":"","sources":["../../../src/utils/datasetConverters.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAkC;IACtE,MAAM,KAAK,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC5B,MAAM,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC;IAErB,6EAA6E;IAC7E,OAAO,KAAK,CAAC,EAAE,CAAC;IAChB,OAAO,KAAK,CAAC,OAAO,CAAC;IAErB,OAAO;QACN,GAAG,KAAK;QACR,KAAK,EAAE,EAAE;KACO,CAAC;AACnB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAmB,EAAE,OAAe;IACxE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,YAAY,EAAE,GAAG,KAAK,CAAC;IAE/C,OAAO;QACN,GAAG,YAAY;QACf,EAAE,EAAE,IAAI;QACR,OAAO;KACI,CAAC;AACd,CAAC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IDcatDataset } from \"@twin.org/standards-w3c-dcat\";\nimport type { Dataset } from \"../entities/dataset.js\";\n\n/**\n * Convert a Dataset entity to an IDcatDataset model.\n * Maps entity.id back to model[\"@id\"] and strips the storage-only id field.\n * @param entity The dataset entity from storage (may be partial from query results).\n * @returns The IDcatDataset model for API responses.\n */\nexport function datasetEntityToModel(entity: Dataset | Partial<Dataset>): IDcatDataset {\n\tconst model = { ...entity };\n\tconst { id } = model;\n\n\t// Remove storage-specific fields that should not be exposed in the API model\n\tdelete model.id;\n\tdelete model.ownerId;\n\n\treturn {\n\t\t...model,\n\t\t\"@id\": id\n\t} as IDcatDataset;\n}\n\n/**\n * Convert an IDcatDataset model to a Dataset entity.\n * Maps model[\"@id\"] to entity.id.\n * @param model The IDcatDataset model from API requests.\n * @param ownerId The owner ID to associate with the dataset entity.\n * @returns The Dataset entity for storage.\n */\nexport function datasetModelToEntity(model: IDcatDataset, ownerId: string): Dataset {\n\tconst { \"@id\": atId, ...datasetModel } = model;\n\n\treturn {\n\t\t...datasetModel,\n\t\tid: atId,\n\t\townerId\n\t} as Dataset;\n}\n"]}
@@ -1,128 +1,132 @@
1
- import type { IDataset } from "@twin.org/standards-w3c-dcat";
1
+ import type { IDcatDataset } from "@twin.org/standards-w3c-dcat";
2
2
  /**
3
3
  * Class describing a DCAT dataset for entity storage.
4
4
  * This wrapper enables efficient database indexing and querying while preserving
5
- * the full IDataset JSON-LD structure.
5
+ * the full IDcatDataset JSON-LD structure.
6
6
  */
7
7
  export declare class Dataset {
8
8
  /**
9
- * The unique identifier for the dataset (@id from JSON-LD).
9
+ * The unique identifier for the dataset (mapped from JSON-LD identifier).
10
10
  */
11
- "@id": string;
11
+ id: string;
12
+ /**
13
+ * The owner of the dataset.
14
+ */
15
+ ownerId: string;
12
16
  /**
13
17
  * The JSON-LD context for the dataset.
14
18
  */
15
- "@context": IDataset["@context"];
19
+ "@context": IDcatDataset["@context"];
16
20
  /**
17
21
  * The type of the resource (typically "Dataset").
18
22
  */
19
- "@type": IDataset["@type"];
23
+ "@type": IDcatDataset["@type"];
20
24
  /**
21
25
  * A name given to the resource.
22
26
  */
23
- "dcterms:title"?: IDataset["dcterms:title"];
27
+ "dcterms:title"?: IDcatDataset["dcterms:title"];
24
28
  /**
25
29
  * A free-text account of the resource.
26
30
  */
27
- "dcterms:description"?: IDataset["dcterms:description"];
31
+ "dcterms:description"?: IDcatDataset["dcterms:description"];
28
32
  /**
29
33
  * A unique identifier of the resource.
30
34
  */
31
- "dcterms:identifier"?: IDataset["dcterms:identifier"];
35
+ "dcterms:identifier"?: IDcatDataset["dcterms:identifier"];
32
36
  /**
33
37
  * Date of formal issuance (publication) of the resource.
34
38
  */
35
- "dcterms:issued"?: IDataset["dcterms:issued"];
39
+ "dcterms:issued"?: IDcatDataset["dcterms:issued"];
36
40
  /**
37
41
  * Most recent date on which the resource was changed, updated or modified.
38
42
  */
39
- "dcterms:modified"?: IDataset["dcterms:modified"];
43
+ "dcterms:modified"?: IDcatDataset["dcterms:modified"];
40
44
  /**
41
45
  * A language of the resource.
42
46
  */
43
- "dcterms:language"?: IDataset["dcterms:language"];
47
+ "dcterms:language"?: IDcatDataset["dcterms:language"];
44
48
  /**
45
49
  * An entity responsible for making the resource available.
46
50
  */
47
- "dcterms:publisher"?: IDataset["dcterms:publisher"];
51
+ "dcterms:publisher"?: IDcatDataset["dcterms:publisher"];
48
52
  /**
49
53
  * An entity responsible for producing the resource.
50
54
  */
51
- "dcterms:creator"?: IDataset["dcterms:creator"];
55
+ "dcterms:creator"?: IDcatDataset["dcterms:creator"];
52
56
  /**
53
57
  * Information about who can access the resource or an indication of its security status.
54
58
  */
55
- "dcterms:accessRights"?: IDataset["dcterms:accessRights"];
59
+ "dcterms:accessRights"?: IDcatDataset["dcterms:accessRights"];
56
60
  /**
57
61
  * A legal document under which the resource is made available.
58
62
  */
59
- "dcterms:license"?: IDataset["dcterms:license"];
63
+ "dcterms:license"?: IDcatDataset["dcterms:license"];
60
64
  /**
61
65
  * Information about rights held in and over the resource.
62
66
  */
63
- "dcterms:rights"?: IDataset["dcterms:rights"];
67
+ "dcterms:rights"?: IDcatDataset["dcterms:rights"];
64
68
  /**
65
69
  * An established standard to which the resource conforms.
66
70
  */
67
- "dcterms:conformsTo"?: IDataset["dcterms:conformsTo"];
71
+ "dcterms:conformsTo"?: IDcatDataset["dcterms:conformsTo"];
68
72
  /**
69
73
  * The nature or genre of the resource.
70
74
  */
71
- "dcterms:type"?: IDataset["dcterms:type"];
75
+ "dcterms:type"?: IDcatDataset["dcterms:type"];
72
76
  /**
73
77
  * Relevant contact information for the catalogued resource.
74
78
  */
75
- "dcat:contactPoint"?: IDataset["dcat:contactPoint"];
79
+ "dcat:contactPoint"?: IDcatDataset["dcat:contactPoint"];
76
80
  /**
77
81
  * A keyword or tag describing the resource.
78
82
  */
79
- "dcat:keyword"?: IDataset["dcat:keyword"];
83
+ "dcat:keyword"?: IDcatDataset["dcat:keyword"];
80
84
  /**
81
85
  * A main category of the resource. A resource can have multiple themes.
82
86
  */
83
- "dcat:theme"?: IDataset["dcat:theme"];
87
+ "dcat:theme"?: IDcatDataset["dcat:theme"];
84
88
  /**
85
89
  * A Web page that can be navigated to gain access to the resource.
86
90
  */
87
- "dcat:landingPage"?: IDataset["dcat:landingPage"];
91
+ "dcat:landingPage"?: IDcatDataset["dcat:landingPage"];
88
92
  /**
89
93
  * Link to a description of a relationship with another resource.
90
94
  */
91
- "dcat:qualifiedRelation"?: IDataset["dcat:qualifiedRelation"];
95
+ "dcat:qualifiedRelation"?: IDcatDataset["dcat:qualifiedRelation"];
92
96
  /**
93
97
  * An ODRL conformant policy expressing the rights associated with the resource.
94
98
  */
95
- "odrl:hasPolicy"?: IDataset["odrl:hasPolicy"];
99
+ "odrl:hasPolicy"?: IDcatDataset["odrl:hasPolicy"];
96
100
  /**
97
101
  * An available distribution of the dataset.
98
102
  */
99
- "dcat:distribution"?: IDataset["dcat:distribution"];
103
+ "dcat:distribution"?: IDcatDataset["dcat:distribution"];
100
104
  /**
101
105
  * The frequency at which the dataset is published.
102
106
  */
103
- "dcterms:accrualPeriodicity"?: IDataset["dcterms:accrualPeriodicity"];
107
+ "dcterms:accrualPeriodicity"?: IDcatDataset["dcterms:accrualPeriodicity"];
104
108
  /**
105
109
  * A dataset series of which the dataset is part.
106
110
  */
107
- "dcat:inSeries"?: IDataset["dcat:inSeries"];
111
+ "dcat:inSeries"?: IDcatDataset["dcat:inSeries"];
108
112
  /**
109
113
  * The geographical area covered by the dataset.
110
114
  */
111
- "dcterms:spatial"?: IDataset["dcterms:spatial"];
115
+ "dcterms:spatial"?: IDcatDataset["dcterms:spatial"];
112
116
  /**
113
117
  * Minimum spatial separation resolvable in a dataset, measured in meters.
114
118
  */
115
- "dcat:spatialResolutionInMeters"?: IDataset["dcat:spatialResolutionInMeters"];
119
+ "dcat:spatialResolutionInMeters"?: IDcatDataset["dcat:spatialResolutionInMeters"];
116
120
  /**
117
121
  * The temporal period that the dataset covers.
118
122
  */
119
- "dcterms:temporal"?: IDataset["dcterms:temporal"];
123
+ "dcterms:temporal"?: IDcatDataset["dcterms:temporal"];
120
124
  /**
121
125
  * Minimum time period resolvable in the dataset.
122
126
  */
123
- "dcat:temporalResolution"?: IDataset["dcat:temporalResolution"];
127
+ "dcat:temporalResolution"?: IDcatDataset["dcat:temporalResolution"];
124
128
  /**
125
129
  * An activity that generated, or provides the business context for, the creation of the dataset.
126
130
  */
127
- "prov:wasGeneratedBy"?: IDataset["prov:wasGeneratedBy"];
131
+ "prov:wasGeneratedBy"?: IDcatDataset["prov:wasGeneratedBy"];
128
132
  }
@@ -1,7 +1,9 @@
1
1
  export * from "./entities/dataset.js";
2
2
  export * from "./federatedCatalogueRoutes.js";
3
+ export * from "./models/IFederatedCatalogueServiceConfig.js";
3
4
  export * from "./models/IFederatedCatalogueServiceConstructorOptions.js";
4
5
  export * from "./restEntryPoints.js";
5
6
  export * from "./schema.js";
6
7
  export * from "./services/federatedCatalogueService.js";
8
+ export * from "./utils/catalogErrorUtils.js";
7
9
  export * from "./utils/datasetConverters.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Configuration for the FederatedCatalogueService.
3
+ */
4
+ export interface IFederatedCatalogueServiceConfig {
5
+ }
@@ -1,14 +1,24 @@
1
- import type { ILoggingComponent } from "@twin.org/logging-models";
1
+ import type { IFederatedCatalogueServiceConfig } from "./IFederatedCatalogueServiceConfig.js";
2
2
  /**
3
3
  * Options for the FederatedCatalogueService constructor.
4
4
  */
5
5
  export interface IFederatedCatalogueServiceConstructorOptions {
6
6
  /**
7
- * The type of the entity storage connector for datasets.
7
+ * The entity storage for datasets.
8
+ * @default dataset
8
9
  */
9
- datasetStorageConnectorType?: string;
10
+ datasetEntityStorageType?: string;
10
11
  /**
11
12
  * The logging component for the service.
12
13
  */
13
- loggingComponent?: ILoggingComponent;
14
+ loggingComponentType?: string;
15
+ /**
16
+ * Trust component type for trust verification.
17
+ * @default trust
18
+ */
19
+ trustComponentType?: string;
20
+ /**
21
+ * Configuration for the federated catalogue service.
22
+ */
23
+ config?: IFederatedCatalogueServiceConfig;
14
24
  }
@@ -1,5 +1,6 @@
1
- import type { IBaseFilter, IFederatedCatalogueComponent } from "@twin.org/federated-catalogue-models";
2
- import { type ICatalog, type IDataset } from "@twin.org/standards-w3c-dcat";
1
+ import { type IFederatedCatalogueComponent } from "@twin.org/federated-catalogue-models";
2
+ import { type IDataspaceProtocolCatalog, type IDataspaceProtocolCatalogError } from "@twin.org/standards-dataspace-protocol";
3
+ import { type IDcatDataset } from "@twin.org/standards-w3c-dcat";
3
4
  import type { IFederatedCatalogueServiceConstructorOptions } from "../models/IFederatedCatalogueServiceConstructorOptions.js";
4
5
  /**
5
6
  * Service for managing federated catalogue operations.
@@ -22,31 +23,46 @@ export declare class FederatedCatalogueService implements IFederatedCatalogueCom
22
23
  className(): string;
23
24
  /**
24
25
  * Retrieve a dataset by its unique identifier.
25
- * @param dataSetId The unique identifier of the dataset.
26
- * @returns The dataset if found.
27
- * @throws NotFoundError if the dataset does not exist.
26
+ * @param datasetId The unique identifier of the dataset.
27
+ * @param trustPayload Optional payload for trust evaluation, if applicable.
28
+ * @returns The dataset if found, or a CatalogError if not found or an error occurs.
28
29
  */
29
- get(dataSetId: string): Promise<IDataset>;
30
+ get(datasetId: string, trustPayload: unknown): Promise<IDcatDataset | IDataspaceProtocolCatalogError>;
30
31
  /**
31
32
  * Insert or update a dataset in the catalogue.
32
- * This method is internal and should not be exposed via REST endpoints.
33
- * @param dataSet The dataset to store.
33
+ * @param dataset The dataset to store.
34
+ * @param trustPayload Optional payload for trust evaluation, if applicable.
35
+ * @returns The unique identifier of the stored dataset, or a CatalogError if an error occurs.
34
36
  */
35
- set(dataSet: IDataset): Promise<void>;
36
- /**
37
- * Execute a query against the catalogue using registered filter plugins.
38
- * Returns a complete DCAT Catalog object with proper JSON-LD context, metadata, and datasets.
39
- * The filter payload is evaluated by the appropriate filter plugin based on its structure.
40
- * Pagination properties (cursor, limit) and filter type (@type) are extracted from the filter object.
41
- * @param filter The filter criteria containing @type, optional cursor and limit properties.
42
- * @returns Complete ICatalog object with @context, @id, @type, dcat:dataset, and optional cursor.
43
- * @throws NotFoundError if @type is missing or if the filter type is not registered.
44
- */
45
- query(filter?: IBaseFilter[]): Promise<ICatalog>;
37
+ set(dataset: IDcatDataset, trustPayload: unknown): Promise<string | IDataspaceProtocolCatalogError>;
46
38
  /**
47
39
  * Remove a dataset from the catalogue by its unique identifier.
48
40
  * Indexes are automatically removed as they are stored with the dataset.
49
- * @param dataSetId The unique identifier of the dataset to remove.
41
+ * @param datasetId The unique identifier of the dataset to remove.
42
+ * @param trustPayload Optional payload for trust evaluation, if applicable.
43
+ * @returns Nothing, or a CatalogError if an error occurs.
44
+ */
45
+ remove(datasetId: string, trustPayload: unknown): Promise<IDataspaceProtocolCatalogError | undefined>;
46
+ /**
47
+ * Execute a query against the catalogue using registered filter plugins.
48
+ * Returns a DS Protocol compliant Catalog object with participantId.
49
+ *
50
+ * The root catalog's participantId is the requesting participant (from context).
51
+ * Own datasets (matching requestingParticipantId) go directly in root dataset[].
52
+ * Other participants' datasets are grouped in nested catalog[] entries.
53
+ *
54
+ * For anonymous requests (no context), uses the first publisher found as fallback.
55
+ * Returns CatalogError 404 when no datasets exist, CatalogError 400 for invalid requests.
56
+ *
57
+ * @param filter The filter criteria containing @type, optional cursor and limit properties.
58
+ * @param cursor Optional cursor for pagination.
59
+ * @param limit Optional limit for pagination.
60
+ * @param trustPayload Optional payload for trust evaluation, if applicable.
61
+ * @returns Complete IDataspaceProtocolCatalog with @context, @id, @type, participantId, dataset/catalog,
62
+ * or CatalogError if validation fails or an error occurs.
50
63
  */
51
- remove(dataSetId: string): Promise<void>;
64
+ query(filter: unknown[] | undefined, cursor: string | undefined, limit: number | undefined, trustPayload: unknown): Promise<{
65
+ result: IDataspaceProtocolCatalog | IDataspaceProtocolCatalogError;
66
+ cursor?: string;
67
+ }>;
52
68
  }
@@ -0,0 +1,15 @@
1
+ import { type IDataspaceProtocolCatalogError } from "@twin.org/standards-dataspace-protocol";
2
+ import { HttpStatusCode } from "@twin.org/web";
3
+ /**
4
+ * Transform an error to DS Protocol CatalogError format.
5
+ * Used by both service and route layers to ensure consistent error responses.
6
+ * @param error The error to transform.
7
+ * @returns The CatalogError.
8
+ */
9
+ export declare function transformToCatalogError(error: unknown): IDataspaceProtocolCatalogError;
10
+ /**
11
+ * Transform the DS Protocol result to an HTTP status code.
12
+ * @param result The result to transform.
13
+ * @returns The transformed status code or undefined if no transformation was found or not an error.
14
+ */
15
+ export declare function transformErrorToStatusCode(result: unknown): HttpStatusCode | undefined;
@@ -1,17 +1,17 @@
1
- import type { IDataset } from "@twin.org/standards-w3c-dcat";
1
+ import type { IDcatDataset } from "@twin.org/standards-w3c-dcat";
2
2
  import type { Dataset } from "../entities/dataset.js";
3
3
  /**
4
- * Convert a Dataset entity to an IDataset model.
5
- * Removes internal entity properties that should not be exposed in API responses.
4
+ * Convert a Dataset entity to an IDcatDataset model.
5
+ * Maps entity.id back to model["@id"] and strips the storage-only id field.
6
6
  * @param entity The dataset entity from storage (may be partial from query results).
7
- * @returns The IDataset model for API responses.
7
+ * @returns The IDcatDataset model for API responses.
8
8
  */
9
- export declare function datasetEntityToModel(entity: Dataset | Partial<Dataset>): IDataset;
9
+ export declare function datasetEntityToModel(entity: Dataset | Partial<Dataset>): IDcatDataset;
10
10
  /**
11
- * Convert an IDataset model to a Dataset entity.
12
- * Prepares the model for storage by converting to entity type.
13
- * Creates a shallow copy to avoid mutating the input model.
14
- * @param model The IDataset model from API requests.
11
+ * Convert an IDcatDataset model to a Dataset entity.
12
+ * Maps model["@id"] to entity.id.
13
+ * @param model The IDcatDataset model from API requests.
14
+ * @param ownerId The owner ID to associate with the dataset entity.
15
15
  * @returns The Dataset entity for storage.
16
16
  */
17
- export declare function datasetModelToEntity(model: IDataset): Dataset;
17
+ export declare function datasetModelToEntity(model: IDcatDataset, ownerId: string): Dataset;