@startinblox/components-ds4go 3.0.3 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.storybook/preview.ts +1 -0
  2. package/biome.json +1 -1
  3. package/dist/custom-getter-ZPFnoSjt-BCNOlbJZ-B4tuxA42.js +338 -0
  4. package/dist/en-BySYJZMr-CWZl5AwU-CWZl5AwU.js +14 -0
  5. package/dist/fr-BZZDTsmw-CNDWt66j-CNDWt66j.js +14 -0
  6. package/dist/index-BSwVRtNS.js +104980 -0
  7. package/dist/index.js +1 -3032
  8. package/dist/quill.snow-C_A_QkE8-D-uedtvC-D-uedtvC.js +13 -0
  9. package/dist/slimselect-NFLzJMfV-DZ7j6Vsj-DZ7j6Vsj.js +5 -0
  10. package/package.json +9 -8
  11. package/src/components/cards/ds4go-card-catalog.ts +132 -0
  12. package/src/components/catalog/ds4go-catalog-filter-holder.ts +459 -0
  13. package/src/components/catalog/ds4go-customer-holder.ts +162 -0
  14. package/src/components/catalog/ds4go-fact-bundle-holder.ts +7 -7
  15. package/src/components/modal/ds4go-customer-modal.ts +134 -0
  16. package/src/components/modal/ds4go-fact-bundle-modal.ts +2 -2
  17. package/src/components/solid-customer-list.ts +195 -0
  18. package/src/components/solid-dsif-explorer-poc.ts +8 -8
  19. package/src/components/solid-dsp-connector.ts +12 -4
  20. package/src/components/solid-fact-bundle-creation.ts +244 -151
  21. package/src/components/solid-fact-bundle.ts +9 -4
  22. package/src/helpers/components/orbitComponent.ts +12 -13
  23. package/src/helpers/i18n/configureLocalization.ts +12 -5
  24. package/src/helpers/index.ts +0 -2
  25. package/src/styles/cards/ds4go-card-catalog.scss +149 -0
  26. package/src/styles/fact-bundle-creation.scss +6 -2
  27. package/src/styles/modal/ds4go-customer-modal.scss +91 -0
  28. package/src/styles/modal/ds4go-fact-bundle-modal.scss +1 -1
  29. package/vite.config.ts +7 -7
  30. package/src/components/solid-boilerplate.ts +0 -76
  31. package/src/helpers/components/ResourceMapper.ts +0 -469
  32. package/src/helpers/components/orbitDspComponent.ts +0 -250
  33. package/src/helpers/mappings/dsp-mapping-config.ts +0 -545
@@ -1,469 +0,0 @@
1
- /**
2
- * Resource Mapper Service
3
- *
4
- * Provides unified, configurable mapping from source data formats (FC self-descriptions,
5
- * DSP datasets, etc.) to TEMS-compatible resource objects with LDP containers.
6
- *
7
- * Architecture:
8
- * - Stores (sib-core): Fetch raw data
9
- * - ResourceMapper (solid-tems): Transform raw data to TEMS format
10
- * - Components (solid-tems): Configure mapper and render UI
11
- *
12
- * Usage:
13
- * ```typescript
14
- * const mapper = new ResourceMapper(fcMappingConfig);
15
- * const temsResource = mapper.map(sourceData, context);
16
- * ```
17
- */
18
-
19
- import type { Resource, UnknownResource } from "@src/component";
20
-
21
- // import type { Resource } from '@startinblox/core';
22
-
23
- // ============================================================================
24
- // Types and Interfaces
25
- // ============================================================================
26
-
27
- export interface ResourceMapperConfig {
28
- /**
29
- * Base field mappings (simple property extractions)
30
- */
31
- baseFields: Record<string, FieldMapping>;
32
-
33
- /**
34
- * LDP Container field mappings (arrays wrapped in ldp:Container)
35
- */
36
- containerFields?: Record<string, ContainerFieldMapping>;
37
-
38
- /**
39
- * Nested object mappings
40
- */
41
- nestedObjects?: Record<string, NestedObjectMapping>;
42
-
43
- /**
44
- * Contract negotiation field mappings
45
- */
46
- contractFields?: Record<string, FieldMapping>;
47
-
48
- /**
49
- * Optional custom post-processing function
50
- */
51
- postProcess?: (
52
- resource: Resource,
53
- source: any,
54
- context: MappingContext,
55
- ) => Resource;
56
- }
57
-
58
- export interface FieldMapping {
59
- /**
60
- * Path to source value (array of keys to traverse)
61
- */
62
- source: string[];
63
-
64
- /**
65
- * Optional transform function
66
- */
67
- transform?: (value: any, source: any, context: MappingContext) => any;
68
-
69
- /**
70
- * Optional fallback if source is not found
71
- */
72
- fallback?: string | ((source: any, context: MappingContext) => any);
73
-
74
- /**
75
- * Optional default value if source and fallback are not found
76
- */
77
- defaultValue?: any;
78
- }
79
-
80
- export interface ContainerFieldMapping {
81
- /**
82
- * Path to source array
83
- */
84
- source: string[];
85
-
86
- /**
87
- * LDP container type (e.g., 'ldp:Container')
88
- */
89
- containerType: string;
90
-
91
- /**
92
- * Item type for contained objects (e.g., 'tems:Category')
93
- */
94
- itemType: string;
95
-
96
- /**
97
- * Field mappings for each item
98
- */
99
- itemFields: Record<
100
- string,
101
- (item: any, index: number, context: MappingContext) => any
102
- >;
103
-
104
- /**
105
- * Optional filter function to include/exclude items
106
- */
107
- filter?: (item: any, index: number, context: MappingContext) => boolean;
108
-
109
- /**
110
- * Optional transform for the entire array before item mapping
111
- */
112
- transform?: (value: any, source: any, context: MappingContext) => any[];
113
- }
114
-
115
- export interface NestedObjectMapping {
116
- /**
117
- * Path to source object
118
- */
119
- source: string[];
120
-
121
- /**
122
- * Field mappings for the nested object
123
- */
124
- fields: Record<string, string[] | FieldMapping>;
125
-
126
- /**
127
- * Optional type for the nested object
128
- */
129
- type?: string;
130
- }
131
-
132
- export interface MappingContext {
133
- /**
134
- * Base URL for generating @id values
135
- */
136
- temsServiceBase?: string;
137
- temsCategoryBase?: string;
138
- temsImageBase?: string;
139
- temsProviderBase?: string;
140
-
141
- /**
142
- * Additional context data (provider info, etc.)
143
- */
144
- [key: string]: any;
145
- }
146
-
147
- // ============================================================================
148
- // Transform Functions
149
- // ============================================================================
150
-
151
- /**
152
- * Strips URN prefixes from IDs
153
- */
154
- export function stripUrnPrefix(value: string | undefined): string {
155
- if (!value) return "";
156
- return value.replace(/^urn:uuid:/i, "").replace(/^urn:tems:/i, "");
157
- }
158
-
159
- /**
160
- * Processes ODRL policy: strips URN prefixes and adds target field
161
- */
162
- export function processPolicyTransform(
163
- policy: any,
164
- source: any,
165
- _context: MappingContext,
166
- ): any {
167
- if (!policy) return null;
168
-
169
- const processedPolicy = JSON.parse(JSON.stringify(policy)); // Deep clone
170
-
171
- // Strip URN prefix from policy @id
172
- if (processedPolicy["@id"]) {
173
- processedPolicy["@id"] = stripUrnPrefix(processedPolicy["@id"]);
174
- }
175
-
176
- // Add target field (asset ID)
177
- const assetId = getNestedValue(source, ["@id"]) || "";
178
- processedPolicy.target = stripUrnPrefix(assetId);
179
-
180
- return processedPolicy;
181
- }
182
-
183
- /**
184
- * Extracts first value from array or returns the value itself
185
- */
186
- export function firstOrSelf(value: any): any {
187
- return Array.isArray(value) && value.length > 0 ? value[0] : value;
188
- }
189
-
190
- /**
191
- * Generates unique ID based on name
192
- */
193
- export function generateIdFromName(name: string, base: string): string {
194
- const slug = name
195
- .toLowerCase()
196
- .replace(/[^a-z0-9]+/g, "-")
197
- .replace(/^-+|-+$/g, "");
198
- return `${base}${slug}`;
199
- }
200
-
201
- // ============================================================================
202
- // Helper Functions
203
- // ============================================================================
204
-
205
- /**
206
- * Gets nested value from object using path array
207
- */
208
- export function getNestedValue(obj: any, path: string[]): any {
209
- // If path is empty, return undefined (don't return the entire object)
210
- // Empty paths are used when the field should be computed via transform or use defaultValue
211
- if (path.length === 0) {
212
- return undefined;
213
- }
214
-
215
- let current = obj;
216
- for (const key of path) {
217
- if (current === null || current === undefined) {
218
- return undefined;
219
- }
220
- current = current[key];
221
- }
222
- return current;
223
- }
224
-
225
- /**
226
- * Sets nested value in object using path array
227
- */
228
- export function setNestedValue(obj: any, path: string[], value: any): void {
229
- if (path.length === 0) return;
230
-
231
- let current = obj;
232
- for (let i = 0; i < path.length - 1; i++) {
233
- const key = path[i];
234
- if (!(key in current)) {
235
- current[key] = {};
236
- }
237
- current = current[key];
238
- }
239
-
240
- current[path[path.length - 1]] = value;
241
- }
242
-
243
- // ============================================================================
244
- // ResourceMapper Class
245
- // ============================================================================
246
-
247
- export class ResourceMapper {
248
- private config: ResourceMapperConfig;
249
-
250
- constructor(config: ResourceMapperConfig) {
251
- this.config = config;
252
- }
253
-
254
- /**
255
- * Maps source data to TEMS-compatible resource
256
- */
257
- map(source: any, context: MappingContext = {}): Resource {
258
- const resource: UnknownResource = {};
259
-
260
- // Map base fields
261
- for (const [destKey, mapping] of Object.entries(this.config.baseFields)) {
262
- const value = this.mapField(source, mapping, context);
263
- if (value !== undefined) {
264
- resource[destKey] = value;
265
- }
266
- }
267
-
268
- // Map container fields
269
- if (this.config.containerFields) {
270
- for (const [destKey, mapping] of Object.entries(
271
- this.config.containerFields,
272
- )) {
273
- const container = this.mapContainerField(source, mapping, context);
274
- if (container) {
275
- resource[destKey] = container;
276
- }
277
- }
278
- }
279
-
280
- // Map nested objects
281
- if (this.config.nestedObjects) {
282
- for (const [destKey, mapping] of Object.entries(
283
- this.config.nestedObjects,
284
- )) {
285
- const nestedObj = this.mapNestedObject(source, mapping, context);
286
- if (nestedObj) {
287
- resource[destKey] = nestedObj;
288
- }
289
- }
290
- }
291
-
292
- // Map contract fields
293
- if (this.config.contractFields) {
294
- for (const [destKey, mapping] of Object.entries(
295
- this.config.contractFields,
296
- )) {
297
- const value = this.mapField(source, mapping, context);
298
- if (value !== undefined) {
299
- resource[destKey] = value;
300
- }
301
- }
302
- }
303
-
304
- // Apply post-processing
305
- if (this.config.postProcess) {
306
- return this.config.postProcess((resource as Resource), source, context);
307
- }
308
-
309
- return (resource as Resource);
310
- }
311
-
312
- /**
313
- * Maps a single field
314
- */
315
- private mapField(
316
- source: any,
317
- mapping: FieldMapping,
318
- context: MappingContext,
319
- ): any {
320
- // Get source value
321
- let value = getNestedValue(source, mapping.source);
322
-
323
- // Apply transform
324
- if (mapping.transform) {
325
- value = mapping.transform(value, source, context);
326
- }
327
-
328
- // Apply fallback
329
- if (value === undefined || value === null || value === "") {
330
- if (mapping.fallback) {
331
- if (typeof mapping.fallback === "function") {
332
- value = mapping.fallback(source, context);
333
- } else {
334
- // Fallback is a path to another field
335
- value = getNestedValue(source, [mapping.fallback]);
336
- }
337
- }
338
- }
339
-
340
- // Apply default value
341
- if (value === undefined || value === null || value === "") {
342
- value = mapping.defaultValue;
343
- }
344
-
345
- return value;
346
- }
347
-
348
- /**
349
- * Maps a container field (array wrapped in LDP container)
350
- */
351
- private mapContainerField(
352
- source: any,
353
- mapping: ContainerFieldMapping,
354
- context: MappingContext,
355
- ): any {
356
- // Get source array
357
- let sourceArray = getNestedValue(source, mapping.source);
358
-
359
- if (!sourceArray) {
360
- return null;
361
- }
362
-
363
- // Apply array-level transform
364
- if (mapping.transform) {
365
- sourceArray = mapping.transform(sourceArray, source, context);
366
- }
367
-
368
- // Ensure it's an array
369
- if (!Array.isArray(sourceArray)) {
370
- sourceArray = [sourceArray];
371
- }
372
-
373
- // Filter items
374
- if (mapping.filter) {
375
- sourceArray = sourceArray.filter((item: any, index: number) =>
376
- mapping.filter!(item, index, context),
377
- );
378
- }
379
-
380
- // Map items
381
- const items = sourceArray.map((item: any, index: number) => {
382
- const mappedItem: any = {
383
- "@type": mapping.itemType,
384
- };
385
-
386
- for (const [fieldKey, fieldFn] of Object.entries(mapping.itemFields)) {
387
- const fieldValue = fieldFn(item, index, context);
388
- if (fieldValue !== undefined) {
389
- mappedItem[fieldKey] = fieldValue;
390
- }
391
- }
392
-
393
- return mappedItem;
394
- });
395
-
396
- // Return LDP container
397
- return {
398
- "@type": mapping.containerType,
399
- "ldp:contains": items,
400
- };
401
- }
402
-
403
- /**
404
- * Maps a nested object
405
- */
406
- private mapNestedObject(
407
- source: any,
408
- mapping: NestedObjectMapping,
409
- context: MappingContext,
410
- ): any {
411
- // Get source object
412
- const sourceObj = getNestedValue(source, mapping.source);
413
-
414
- if (!sourceObj) {
415
- return null;
416
- }
417
-
418
- const nestedObj: any = {};
419
-
420
- // Add type if specified
421
- if (mapping.type) {
422
- nestedObj["@type"] = mapping.type;
423
- }
424
-
425
- // Map fields
426
- for (const [destKey, fieldMapping] of Object.entries(mapping.fields)) {
427
- let value: any;
428
-
429
- if (Array.isArray(fieldMapping)) {
430
- // Simple path mapping
431
- value = getNestedValue(sourceObj, fieldMapping);
432
- } else {
433
- // Full FieldMapping
434
- value = this.mapField(sourceObj, fieldMapping, context);
435
- }
436
-
437
- if (value !== undefined) {
438
- nestedObj[destKey] = value;
439
- }
440
- }
441
-
442
- return Object.keys(nestedObj).length > (mapping.type ? 1 : 0)
443
- ? nestedObj
444
- : null;
445
- }
446
-
447
- /**
448
- * Unwraps LDP containers (converts ldp:contains arrays to plain arrays)
449
- *
450
- * This is typically called by components after mapping to simplify data structure
451
- * for rendering.
452
- */
453
- static unwrapLdpContainers(resource: Resource): Resource {
454
- const unwrapped: Resource = { ...resource };
455
-
456
- for (const [key, value] of Object.entries(unwrapped)) {
457
- if (
458
- value &&
459
- typeof value === "object" &&
460
- value["@type"] === "ldp:Container" &&
461
- value["ldp:contains"]
462
- ) {
463
- unwrapped[key] = value["ldp:contains"];
464
- }
465
- }
466
-
467
- return unwrapped;
468
- }
469
- }
@@ -1,250 +0,0 @@
1
- import {
2
- StoreService,
3
- StoreType,
4
- } from "https://cdn.jsdelivr.net/npm/@startinblox/core@beta/+esm";
5
- import { OrbitComponent, setupComponentSubscriptions } from "@helpers";
6
- import { ResourceMapper } from "@helpers/components/ResourceMapper";
7
- import { dspMappingConfig } from "@helpers/mappings/dsp-mapping-config";
8
- import type { DSPComponentParameters, Resource } from "@src/component.d.ts";
9
- import type { PropertyValues } from "lit";
10
- import { property, state } from "lit/decorators.js";
11
-
12
- export interface DSPProviderConfig {
13
- name: string;
14
- address: string;
15
- color?: string;
16
- participantId?: string;
17
- }
18
-
19
- export default class extends OrbitComponent {
20
- @property({ attribute: "participant-connector-uri", reflect: true })
21
- participantConnectorUri?: string;
22
-
23
- @property({ attribute: "participant-id", reflect: true })
24
- participantId?: string;
25
-
26
- @property({ attribute: "participant-api-key", reflect: true })
27
- participantApiKey?: string;
28
-
29
- @property({ attribute: "api-gateway-config", reflect: true })
30
- apiGatewayConfig?: string;
31
-
32
- @state()
33
- storeService?: StoreService;
34
-
35
- @state()
36
- dspStoreService?: StoreService;
37
-
38
- @state()
39
- _apiGatewayConfigParsed?: any;
40
-
41
- @property({ type: Array })
42
- providers: DSPProviderConfig[] = [];
43
-
44
- willUpdate(_changedProperties: PropertyValues<this>) {
45
- if (
46
- (!this.providers || this.providers.length === 0) &&
47
- this.component?.parameters?.providers
48
- ) {
49
- this.providers = this.component.parameters.providers || [];
50
- }
51
- }
52
-
53
- protected async _attach(
54
- defaultRoute: boolean | string,
55
- setupSubscriptions: boolean,
56
- ignoreRouter: boolean,
57
- ) {
58
- if (!this.orbit) {
59
- if (window.orbit) {
60
- this.orbit = window.orbit;
61
- if (setupSubscriptions) {
62
- setupComponentSubscriptions({
63
- component: this,
64
- defaultRoute: defaultRoute,
65
- ignoreRouter: ignoreRouter,
66
- });
67
-
68
- if (this.route) {
69
- this.component = this.orbit.getComponentFromRoute(this.route);
70
- if (this.component) {
71
- for (const c of this.orbit.components) {
72
- if (c.uniq === this.component.uniq) {
73
- c.instance = this;
74
- }
75
- }
76
- }
77
- }
78
-
79
- // Get connector configuration from attributes or component params
80
- if (!this.participantConnectorUri) {
81
- const params = this.component?.parameters as DSPComponentParameters;
82
- if (params?.["participant-connector-uri"]) {
83
- this.participantConnectorUri =
84
- params["participant-connector-uri"];
85
- }
86
- if (params?.["participant-api-key"]) {
87
- this.participantApiKey = params["participant-api-key"];
88
- }
89
- if (params?.providers) {
90
- this.providers = params.providers;
91
- }
92
- if (params?.["participant-id"]) {
93
- this.participantId = params["participant-id"];
94
- }
95
- }
96
-
97
- // Parse API Gateway configuration (OPTIONAL)
98
- const params = this.component?.parameters as DSPComponentParameters;
99
-
100
- if (params?.["api-gateway-config"]) {
101
- this.apiGatewayConfig = params["api-gateway-config"];
102
- try {
103
- this._apiGatewayConfigParsed =
104
- typeof this.apiGatewayConfig === "string"
105
- ? JSON.parse(this.apiGatewayConfig)
106
- : this.apiGatewayConfig;
107
- } catch (_error) {
108
- this._apiGatewayConfigParsed = null;
109
- }
110
- }
111
-
112
- // Initialize DataspaceConnectorStore
113
- if (!this.storeService && this.participantConnectorUri) {
114
- try {
115
- // Use unique store name based on component ID to avoid singleton conflicts
116
- const storeName = `dsp-connector-${this.component.uniq}`;
117
-
118
- const config = {
119
- type: StoreType.DataspaceConnector,
120
- endpoint: this.participantConnectorUri,
121
- catalogEndpoint: `${this.participantConnectorUri}/management/v3/catalog/request`,
122
- contractNegotiationEndpoint: `${this.participantConnectorUri}/management/v3/contractnegotiations`,
123
- transferProcessEndpoint: `${this.participantConnectorUri}/management/v3/transferprocesses`,
124
- edrsEndpoint: `${this.participantConnectorUri}/management/v3/edrs`,
125
- authMethod: "dsp-api-key",
126
- dspApiKey: this.participantApiKey || "password",
127
- participantId: this.participantId || "stbx-consumer",
128
- retryAttempts: 10,
129
- timeout: 30000,
130
- // Include API Gateway configuration ONLY if available
131
- ...(this._apiGatewayConfigParsed && {
132
- apiGatewayConfig: this._apiGatewayConfigParsed,
133
- }),
134
- };
135
-
136
- this.storeService = StoreService.addStore(storeName, config);
137
- // Also set dspStoreService for contract negotiation in tems-modal
138
- this.dspStoreService = this.storeService;
139
- // Expose globally for FederatedIndexManager auto-negotiation
140
- if (!window.dspStore) {
141
- window.dspStore = this.storeService;
142
- }
143
- } catch (error) {
144
- console.error("DSP Store initialization error:", error);
145
- }
146
- }
147
- await this._afterAttach();
148
-
149
- this.ready = true;
150
- this.dispatchEvent(
151
- new CustomEvent("component-ready", {
152
- detail: {
153
- component: this.component,
154
- },
155
- }),
156
- );
157
-
158
- return Promise.resolve(true);
159
- }
160
- }
161
- }
162
- return Promise.resolve(false);
163
- }
164
-
165
- /**
166
- * Fetch catalog from multiple DSP providers
167
- */
168
- async fetchFederatedCatalog(): Promise<Resource[]> {
169
- if (!this.storeService || !this.providers || this.providers.length === 0) {
170
- console.warn("DSP store or providers not configured");
171
- return [];
172
- }
173
-
174
- try {
175
- // FIXME: Avoid magic strings
176
- // Use default base URLs for TEMS resources
177
- const baseURL = "https://api.tems.example.com/services/";
178
-
179
- // Initialize ResourceMapper with DSP configuration
180
- const mapper = new ResourceMapper(dspMappingConfig);
181
-
182
- const catalogPromises = this.providers.map(async (provider) => {
183
- try {
184
- const catalog = await this.storeService.getCatalog(provider.address);
185
-
186
- if (catalog?.["dcat:dataset"]) {
187
- // Normalize datasets to array - catalog may return single object if only one dataset
188
- const rawDatasets = catalog["dcat:dataset"];
189
- const datasets = Array.isArray(rawDatasets)
190
- ? rawDatasets
191
- : [rawDatasets];
192
- // IMPORTANT: Prefer config's participantId over catalog response
193
- // This ensures assets from different providers are distinguishable
194
- // even if EDC catalogs return the same participantId (misconfiguration)
195
- const catalogResponseParticipantId =
196
- catalog.participantId ||
197
- catalog["edc:participantId"] ||
198
- catalog["https://w3id.org/edc/v0.0.1/ns/participantId"];
199
-
200
- // Use config's participantId as authoritative (user configured it intentionally)
201
- // Only fall back to catalog response if config doesn't have participantId
202
- const effectiveParticipantId =
203
- provider.participantId || catalogResponseParticipantId;
204
-
205
- // Map each dataset using ResourceMapper
206
- return datasets.map((dataset: Resource) => {
207
- const context = {
208
- temsServiceBase: baseURL,
209
- temsCategoryBase: baseURL.replace(
210
- "/services/",
211
- "/providers/categories/",
212
- ),
213
- temsImageBase: baseURL.replace(
214
- "/services/",
215
- "/objects/images/",
216
- ),
217
- temsProviderBase: baseURL.replace("/services/", "/providers/"),
218
- // Provider context for mapping - use config's participantId as authoritative
219
- providerName: provider.name,
220
- providerAddress: provider.address,
221
- providerColor: provider.color,
222
- providerParticipantId: effectiveParticipantId,
223
- };
224
-
225
- return mapper.map(dataset, context);
226
- });
227
- }
228
-
229
- return [];
230
- } catch (_error) {
231
- return [];
232
- }
233
- });
234
-
235
- const results = await Promise.all(catalogPromises);
236
- const allDatasets = results.flat();
237
-
238
- return allDatasets;
239
- } catch (_error) {
240
- return [];
241
- }
242
- }
243
-
244
- /**
245
- * Get current DataspaceConnectorStore instance
246
- */
247
- getStoreService() {
248
- return this.storeService;
249
- }
250
- }