dataconv-client-sdk-ts 0.3.2 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,42 @@
1
1
  # DataConv Client SDK for TypeScript
2
2
 
3
- TypeScript SDK for consuming the `adapter-ingestion-py` pre-conversion API.
4
-
5
- It includes:
6
-
7
- - tenant/software configuration creation and polling
3
+ DataConv TypeScript SDK for consuming the `adapter-ingestion-py`
4
+ pre-conversion API. It is distinct from the GDC, VetChain and GW SDKs and does
5
+ not depend on React or React Native.
6
+
7
+ ## Table of contents
8
+
9
+ - [DataConv Client SDK for TypeScript](#dataconv-client-sdk-for-typescript)
10
+ - [Table of contents](#table-of-contents)
11
+ - [Features](#features)
12
+ - [Installation](#installation)
13
+ - [Configuration](#configuration)
14
+ - [Discover form fields](#discover-form-fields)
15
+ - [Field selection tracking (UI dedup)](#field-selection-tracking-ui-dedup)
16
+ - [React example: multi-dropdown dedup](#react-example-multi-dropdown-dedup)
17
+ - [End-to-end flow](#end-to-end-flow)
18
+ - [Multipart / local file upload](#multipart--local-file-upload)
19
+ - [Backend initialization](#backend-initialization)
20
+ - [CLI](#cli)
21
+ - [Recommended local setup (no IP/DID flags in commands)](#recommended-local-setup-no-ipdid-flags-in-commands)
22
+ - [Command helper and endpoint-resolution conventions](#command-helper-and-endpoint-resolution-conventions)
23
+ - [One-shot evidence script](#one-shot-evidence-script)
24
+ - [Notes](#notes)
25
+
26
+ ---
27
+
28
+ ## Features
29
+
30
+ - Discover frontend field descriptors from `/.well-known/api-config.json`
31
+ - Track which fields have already been selected across UI dropdowns
32
+ - Tenant/software configuration creation and polling
8
33
  - Excel/XLSX upload via DIDComm attachment or `multipart/form-data`
9
34
  - `_upload-response` polling
10
- - promotion through `Composition/_patch` and `Patient/_batch`
11
- - tenant-scoped search under `/host/.../org.hl7.fhir.api/{resourceType}/_search`
12
- - helpers to read the converted `Bundle` and keep the last received response
35
+ - Promotion through `Composition/_patch` and `Patient/_batch`
36
+ - Tenant-scoped dataset search under `/publisher/.../dataset/{resourceType}/_search`
37
+ - Helpers to read the converted `Bundle` and keep the last received response
38
+
39
+ ---
13
40
 
14
41
  ## Installation
15
42
 
@@ -23,11 +50,161 @@ npm install dataconv-client-sdk-ts
23
50
  DATACONV_BASE_URL=http://localhost:8080
24
51
  ```
25
52
 
26
- ## Basic usage
53
+ ---
54
+
55
+ ## Discover form fields
56
+
57
+ The client reads the API discovery document published by the server and returns frontend-ready field descriptors.
58
+
59
+ ```ts
60
+ import { DataConvClient } from 'dataconv-client-sdk-ts';
61
+
62
+ const client = new DataConvClient({
63
+ issuerDid: 'did:web:clinic.example:employee:loader',
64
+ tenantId: 'VATES-B00000000',
65
+ jurisdiction: 'ES',
66
+ crypto: globalThis.crypto
67
+ });
68
+
69
+ const apiConfig = await client.getWellKnownApiConfig();
70
+
71
+ console.log(apiConfig.language); // "es"
72
+ console.log(apiConfig.fields);
73
+ // [
74
+ // { code: 'section', display: 'Departamento o sección: ...' },
75
+ // { code: 'coverage_insurer', display: 'Identificador o nombre de la aseguradora' }
76
+ // ]
77
+ ```
78
+
79
+ The returned object includes:
80
+
81
+ | Property | Type | Description |
82
+ |---|---|---|
83
+ | `language` | `string` | Language code of the API config (`"es"`, ...) |
84
+ | `fields` | `{ code, display }[]` | Ready-to-use options for dropdowns |
85
+ | `supportedFields` | `Record<string, string>` | Raw `code → display` map |
86
+ | `endpoints` | `Record<string, string>` | Endpoint paths for `create`, `upload`, etc. |
87
+
88
+ The recommended frontend flow is:
89
+
90
+ 1. Read `fields` from `getWellKnownApiConfig()`.
91
+ 2. Let the user map spreadsheet columns to those field codes.
92
+ 3. Submit `mappingConfig.fieldMap` using those same codes.
93
+
94
+ ---
95
+
96
+ ### Field selection tracking (UI dedup)
97
+
98
+ The SDK keeps a per-session set of already-selected field codes so the UI can prevent a user from assigning the same field to two different dropdowns.
99
+
100
+ | Method | Returns | Description |
101
+ |---|---|---|
102
+ | `selectField(code, mappedTo?)` | `boolean` | `true` if added, `false` if already selected |
103
+ | `unselectField(code)` | `boolean` | `true` if removed, `false` if not present |
104
+ | `isFieldSelected(code)` | `boolean` | Whether the code is currently selected |
105
+ | `getSelectedFieldCodes()` | `string[]` | All currently selected codes |
106
+ | `getSelectedFieldMappings()` | `Record<string, string>` | Map of selected field code → mapped source column |
107
+ | `getSelectedMappingForField(code)` | `string \| undefined` | Source column currently associated to the selected code |
108
+ | `clearSelectedFields()` | `void` | Reset selection state |
109
+
110
+ ```ts
111
+ client.selectField('section'); // true
112
+ client.selectField('section'); // false → already selected
113
+ client.selectField('concept', 'CONCEPTO');
114
+ client.getSelectedMappingForField('concept'); // 'CONCEPTO'
115
+ client.isFieldSelected('section'); // true
116
+ client.unselectField('section'); // true
117
+ client.getSelectedFieldCodes(); // []
118
+ ```
119
+
120
+ ---
121
+
122
+ ### React example: multi-dropdown dedup
123
+
124
+ ```tsx
125
+ import { useEffect, useMemo, useState } from 'react';
126
+ import { DataConvClient } from 'dataconv-client-sdk-ts';
127
+
128
+ const client = new DataConvClient({
129
+ issuerDid: 'did:web:clinic.example:employee:loader',
130
+ tenantId: 'VATES-B00000000',
131
+ jurisdiction: 'ES',
132
+ crypto: globalThis.crypto
133
+ });
134
+
135
+ type FieldOption = { code: string; display: string };
136
+
137
+ export function FieldMappingForm() {
138
+ const [options, setOptions] = useState<FieldOption[]>([]);
139
+ const [mapping, setMapping] = useState<Record<string, string>>({
140
+ colA: '',
141
+ colB: '',
142
+ colC: ''
143
+ });
144
+
145
+ useEffect(() => {
146
+ let mounted = true;
147
+ client.getSupportedFields().then((fields) => {
148
+ if (mounted) setOptions(fields);
149
+ });
150
+ return () => {
151
+ mounted = false;
152
+ client.clearSelectedFields();
153
+ };
154
+ }, []);
155
+
156
+ const selectedSet = useMemo(() => new Set(client.getSelectedFieldCodes()), [mapping]);
157
+
158
+ const onChangeField = (columnKey: string, newCode: string) => {
159
+ const previousCode = mapping[columnKey];
160
+ if (previousCode) {
161
+ client.unselectField(previousCode);
162
+ }
163
+
164
+ if (newCode && !client.selectField(newCode, columnKey)) {
165
+ const mappedTo = client.getSelectedMappingForField(newCode);
166
+ if (previousCode) {
167
+ client.selectField(previousCode);
168
+ }
169
+ alert(`El campo ${newCode} ya ha sido seleccionado${mappedTo ? ` en ${mappedTo}` : ''}.`);
170
+ return;
171
+ }
172
+
173
+ setMapping((current) => ({ ...current, [columnKey]: newCode }));
174
+ };
175
+
176
+ return (
177
+ <>
178
+ {Object.keys(mapping).map((columnKey) => (
179
+ <select
180
+ key={columnKey}
181
+ value={mapping[columnKey]}
182
+ onChange={(event) => onChangeField(columnKey, event.target.value)}
183
+ >
184
+ <option value="">Selecciona un campo</option>
185
+ {options.map((field) => {
186
+ const selectedInAnotherDropdown =
187
+ selectedSet.has(field.code) && mapping[columnKey] !== field.code;
188
+ return (
189
+ <option key={field.code} value={field.code} disabled={selectedInAnotherDropdown}>
190
+ {field.display}
191
+ </option>
192
+ );
193
+ })}
194
+ </select>
195
+ ))}
196
+ </>
197
+ );
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## End-to-end flow
27
204
 
28
- Minimum end-to-end flow:
205
+ Minimum steps to go from field discovery to promoted resources:
29
206
 
30
- 1. Initialize the client and tokens.
207
+ 1. Discover fields and initialize the client.
31
208
  2. Create the tenant/software configuration.
32
209
  3. Wait for `_create-response`.
33
210
  4. Upload the Excel file.
@@ -52,8 +229,6 @@ client.setVpToken('<vp_token>');
52
229
 
53
230
  // 1. Discover frontend field descriptors from the API.
54
231
  const apiConfig = await client.getWellKnownApiConfig();
55
-
56
- // Example UI options:
57
232
  const fieldOptions = apiConfig.fields;
58
233
  // [
59
234
  // { code: 'section', display: 'Departamento o sección: ...' },
@@ -135,6 +310,13 @@ const searchResponse = await client.searchResources({
135
310
  });
136
311
  ```
137
312
 
313
+ For DIDComm polling responses, the SDK also exposes:
314
+
315
+ - `getMainDiagnosticInfoByResponse(response)` — reads `OperationOutcome.issue[0].diagnostics` from a response.
316
+ - `getMainDiagnosticInfo()` — reads it from the last stored config/conversion response.
317
+
318
+ ---
319
+
138
320
  ## Multipart / local file upload
139
321
 
140
322
  ```ts
@@ -145,231 +327,200 @@ await client.uploadSpreadsheetMultipart({
145
327
  });
146
328
  ```
147
329
 
148
- ## CLI evidence for real upload + polling
330
+ ---
331
+
332
+ ## Backend initialization
149
333
 
150
- The package also exposes a CLI that performs a real upload call, waits for `_upload-response`, saves the full DIDComm response to JSON, and prints the main diagnostic summary.
334
+ If the backend instantiates the SDK after `Organization/_activate`, it can inject `axios` or `fetch`:
151
335
 
152
- ```bash
153
- dataconv-client \
154
- --service-did did:web:dataconv-api.example.org \
155
- --resolved-base-url http://127.0.0.1:8080 \
156
- --tenant-id demo-tenant \
157
- --software-id api-config \
158
- --resource-type Composition \
159
- --file ./examples/example-api-config.xlsx \
160
- --issuer-did did:web:organization.example.org:employee:loader \
161
- --authorization-bearer <token> \
162
- --output-json ./artifacts/appmypets-upload-response.json
336
+ ```ts
337
+ import axios from 'axios';
338
+ import { DataConvClient } from 'dataconv-client-sdk-ts';
339
+
340
+ const client = new DataConvClient({
341
+ issuerDid: activatedOrganizationDid,
342
+ tenantId: tenantAlternateName,
343
+ jurisdiction: 'ES',
344
+ httpClient: axios.create({ baseURL: process.env.DATACONV_BASE_URL }),
345
+ crypto: globalThis.crypto
346
+ });
347
+
348
+ client.setVpToken(vpTokenFromActivation);
349
+ ```
350
+
351
+ Also works with `fetch`:
352
+
353
+ ```ts
354
+ const client = new DataConvClient({
355
+ issuerDid: activatedOrganizationDid,
356
+ tenantId: tenantAlternateName,
357
+ jurisdiction: 'ES',
358
+ fetch,
359
+ crypto: globalThis.crypto
360
+ });
163
361
  ```
164
362
 
165
- The CLI prints:
363
+ ---
166
364
 
167
- - the exact public upload URL used
168
- - the Excel path and size in KB
169
- - the `Location` header returned by `_upload`
170
- - the `thid`
171
- - the exact polling URL used
172
- - the output JSON file path
173
- - the main diagnostic text from the final DIDComm response
365
+ ## CLI
174
366
 
175
- This is useful when you need a reproducible console trace for a justification dossier without pasting the whole JSON response into the report.
367
+ The package exposes a CLI that can first create mapping config, then upload a file, wait for `_upload-response`, save the full DIDComm response to JSON, and print the main outcome summary.
176
368
 
177
- When `--service-did` is used, the CLI can resolve it locally by either:
369
+ ### Local setup
178
370
 
179
- - passing `--resolved-base-url <url>`
180
- - or defining `DATACONV_SERVICE_DID_MAP` as a JSON object, for example:
371
+ Copy `.env.example` to `.env.local`, fill in your values, and source it before running commands:
181
372
 
182
373
  ```bash
183
- export DATACONV_SERVICE_DID_MAP='{"did:web:dataconv-api.example.org":"http://127.0.0.1:8080"}'
374
+ cp .env.example .env.local
375
+ # edit .env.local: tenantId, issuerDid, base URL, DATACONV_ID_TOKEN
376
+ source .env.local
184
377
  ```
185
378
 
186
- This keeps public examples neutral while still allowing local or temporary endpoint resolution outside the repository.
187
-
188
- ## Discover form fields from `/.well-known/api-config.json`
379
+ The helper script `scripts/evidencia-publicacion.sh` loads `.env.local` automatically (falls back to `.env.example`).
189
380
 
190
- The client can read the API discovery document published by the server and return frontend-ready field descriptors.
381
+ For advanced DID document-based endpoint resolution and full command reference, see [docs/cli-reference.md](docs/cli-reference.md).
191
382
 
192
- ```ts
193
- const apiConfig = await client.getWellKnownApiConfig();
383
+ ```bash
384
+ # 1) login against your IdP (store OIDC id_token locally)
385
+ dataconv login --id-token "$DATACONV_ID_TOKEN"
194
386
 
195
- console.log(apiConfig.language); // "es"
196
- console.log(apiConfig.fields);
197
- // [
198
- // { code: 'section', display: 'Departamento o sección: ...' },
199
- // { code: 'coverage_insurer', display: 'Identificador o nombre de la aseguradora' }
200
- // ]
387
+ # 2) exchange Bearer access token
388
+ dataconv exchange --scope "excel/_upload Subject/_search ChargeItem/_search DocumentReference/_search"
201
389
 
202
- const supportedFields = await client.getSupportedFields();
390
+ # 3) optional tenant-admin step: create a constrained API key
391
+ dataconv api-key-create --email ops@example.com --target "publisher/cds-es/v1/animal-care/vates-a00000001/dataset/*/*/_upload"
203
392
 
204
- // Optional: track selection state for UI dropdown dedup checks.
205
- if (!client.selectField('section')) {
206
- alert('Campo "section" ya seleccionado en otro dropdown');
207
- }
208
- if (!client.selectField('section')) {
209
- alert('No puedes seleccionar el mismo campo dos veces');
210
- }
393
+ # 4) upload + automatic polling
394
+ dataconv upload ./examples/example-api-config.xlsx \
395
+ --output-json ./artifacts/upload-response.json
211
396
 
212
- const uploadResponse = await client.uploadSpreadsheetAndWait(
213
- 'https://example.com/AppMyPets-api-config.xlsx?dl=1',
214
- {
215
- softwareId: 'api-config',
216
- resourceType: 'Composition',
217
- fileName: 'AppMyPets-api-config.xlsx'
218
- }
219
- );
397
+ # 5) optional: patch/batch after review, then search with Bearer token
398
+ dataconv search --resource-type DocumentReference --params '{"_count": 5}'
220
399
 
221
- const summaryText = client.getMainDiagnosticInfoByResponse(uploadResponse);
222
- console.log(summaryText);
400
+ # optional alternative to patch
401
+ dataconv batch --thid "<thid-obtenido-de-upload>"
223
402
  ```
224
403
 
225
- The returned object includes:
404
+ Output includes:
226
405
 
227
- - `language`
228
- - `supportedFields` as raw `code -> display`
229
- - `fields` as `{ code, display }[]`
230
- - `endpoints` for `create`, `createResponse`, `upload`, and `uploadResponse`
406
+ - exact public upload URL used
407
+ - Excel path and size in KB
408
+ - `Location` header from `_upload`
409
+ - `thid`
410
+ - exact polling URL used
411
+ - output JSON file path
412
+ - main `OperationOutcome.issue[0].description` when available (fallback: `diagnostics`)
231
413
 
232
- For DIDComm polling responses, the SDK also exposes:
414
+ When `--mapping-json` is provided to `upload`, CLI creates tenant mapping config first and polls `config/_create-response` before submitting the spreadsheet.
233
415
 
234
- - `getMainDiagnosticInfoByResponse(response)` to read the main `OperationOutcome.issue[0].diagnostics`
235
- - `getMainDiagnosticInfo()` to read it from the last stored config/conversion response
416
+ The CLI prints evidence-style process logs, for example:
236
417
 
237
- The recommended frontend flow is:
418
+ - authentication/exchange against configured dataspace name
419
+ - upload accepted with `thid`
420
+ - automatic `_upload-response` polling
421
+ - final summary and JSON artifact path
238
422
 
239
- 1. Read `fields` from `/.well-known/api-config.json`.
240
- 2. Let the user map spreadsheet columns to those field codes.
241
- 3. Submit `mappingConfig.fieldMap` using those same codes.
423
+ ### Command helper and endpoint-resolution conventions
242
424
 
243
- ### React example: avoid duplicate field selection across dropdowns
425
+ Use per-command help to see expected conventions:
244
426
 
245
- ```tsx
246
- import { useEffect, useMemo, useState } from 'react';
247
- import { DataConvClient } from 'dataconv-client-sdk-ts';
427
+ ```bash
428
+ dataconv help exchange
429
+ dataconv help upload
430
+ dataconv search --help
431
+ ```
248
432
 
249
- const client = new DataConvClient({
250
- issuerDid: 'did:web:clinic.example:employee:loader',
251
- tenantId: 'VATES-B00000000',
252
- jurisdiction: 'ES',
253
- crypto: globalThis.crypto
254
- });
433
+ Service IDs used as CLI resolution metadata:
255
434
 
256
- type FieldOption = { code: string; display: string };
435
+ - `exchange`: `#identity:openid:token:_exchange`
436
+ - `upload` (update): `#dataset:{softwareId}:{resourceType}:_upload`
437
+ - `patch` (publish): `#dataset:{softwareId}:{resourceType}:_patch`
438
+ - `batch` (publish): `#dataset:{softwareId}:{resourceType}:_batch`
439
+ - `search`: `#dataset:api:{resourceType}:_search`
257
440
 
258
- export function FieldMappingForm() {
259
- const [options, setOptions] = useState<FieldOption[]>([]);
260
- const [mapping, setMapping] = useState<Record<string, string>>({
261
- colA: '',
262
- colB: '',
263
- colC: ''
264
- });
441
+ Fallback env vars for localhost testing:
265
442
 
266
- useEffect(() => {
267
- let mounted = true;
268
- client.getSupportedFields().then((fields) => {
269
- if (mounted) setOptions(fields);
270
- });
271
- return () => {
272
- mounted = false;
273
- client.clearSelectedFields();
274
- };
275
- }, []);
443
+ - `PUBLISHER_OPENID_EXCHANGE`
444
+ - `PUBLISHER_DATASET_UPDATE`
445
+ - `PUBLISHER_DATASET_PATCH`
446
+ - `PUBLISHER_DATASET_BATCH`
447
+ - `PUBLISHER_DATASET_SEARCH`
276
448
 
277
- const selectedSet = useMemo(() => new Set(client.getSelectedFieldCodes()), [mapping]);
449
+ Important contract note:
278
450
 
279
- const onChangeField = (columnKey: string, newCode: string) => {
280
- const previousCode = mapping[columnKey];
281
- if (previousCode) {
282
- client.unselectField(previousCode);
283
- }
451
+ - `--organization-did` and `--service-id` are stored as CLI-side endpoint-resolution context.
452
+ - The `/exchange` request body remains OpenAPI-compatible (no extra payload fields derived from service-id/fallback metadata).
284
453
 
285
- if (newCode && !client.selectField(newCode)) {
286
- if (previousCode) {
287
- client.selectField(previousCode);
288
- }
289
- alert(`El campo ${newCode} ya ha sido seleccionado anteriormente.`);
290
- return;
291
- }
454
+ ### One-shot evidence script
292
455
 
293
- setMapping((current) => ({ ...current, [columnKey]: newCode }));
294
- };
456
+ Use the included helper to run login/exchange/upload/search in one shot:
295
457
 
296
- return (
297
- <>
298
- {Object.keys(mapping).map((columnKey) => (
299
- <select
300
- key={columnKey}
301
- value={mapping[columnKey]}
302
- onChange={(event) => onChangeField(columnKey, event.target.value)}
303
- >
304
- <option value="">Selecciona un campo</option>
305
- {options.map((field) => {
306
- const selectedInAnotherDropdown =
307
- selectedSet.has(field.code) && mapping[columnKey] !== field.code;
308
- return (
309
- <option key={field.code} value={field.code} disabled={selectedInAnotherDropdown}>
310
- {field.display}
311
- </option>
312
- );
313
- })}
314
- </select>
315
- ))}
316
- </>
317
- );
318
- }
458
+ ```bash
459
+ chmod +x ./scripts/publish-dataset.sh
460
+ ./scripts/evidencia-publicacion.sh
461
+
462
+ # opcional: forzar mapping JSON externo + promoción por batch
463
+ DATACONV_PUBLICACION_MAPPING_JSON=./examples/mappings/qvet-v1.json \
464
+ DATACONV_PUBLICACION_HEADER_ROW_INDEX=1 \
465
+ DATACONV_PROMOTION_MODE=batch \
466
+ ./scripts/evidencia-publicacion.sh
319
467
  ```
320
468
 
321
- In this pattern:
322
-
323
- - `selectField(code)` returns `false` when the code was already used.
324
- - `unselectField(code)` frees a code when a dropdown changes value.
325
- - `getSelectedFieldCodes()` helps disable options already selected elsewhere.
326
-
327
- ## Backend initialization
469
+ Generated files:
328
470
 
329
- If the backend instantiates the SDK after `Organization/_activate`, it can inject `axios` or `fetch` just like `ica-client-sdk-ts`.
471
+ - `./artifacts/datasets/upload-response.json`
472
+ - `./artifacts/datasets/search-subject.json`
473
+ - `./artifacts/datasets/search-documentreference.json`
474
+ - `./artifacts/datasets/dcat-files.json`
330
475
 
331
- ```ts
332
- import axios from 'axios';
333
- import { DataConvClient } from 'dataconv-client-sdk-ts';
476
+ Notes on scope model and flow:
334
477
 
335
- const httpClient = axios.create({
336
- baseURL: process.env.DATACONV_BASE_URL
337
- });
478
+ - You can request endpoint-action scopes (recommended for evaluator logs), e.g. `excel/_upload` or `DocumentReference/_search`.
479
+ - Backend accepts these action scopes as equivalent to coarse scopes (`dataconv.upload` / `dataconv.read`).
480
+ - `tenantId`, `jurisdiction`, `sector`, and `softwareId` are taken from env/profile defaults (recommended tenant format: `VATES-<NIF>`).
481
+ - API keys do **not** mint `id_token`s. The `id_token` always comes from the external IdP/login step.
482
+ - API keys are used at `/exchange` time to constrain or delegate scopes. The resulting Bearer `access_token` is what you use for config, upload, patch/batch, and search.
483
+ - For explicit exceptional non-confidential mode, send `api_key_profile=api-key-exception.v1` in `/exchange` payload (server must allow it).
484
+ - Recommended API key authorization model is atomic:
485
+ - one rule entry (`data[].resource`) = one consent-like authorization rule = one ODRL policy object.
486
+ - include `scope` (mandatory), and preferably `target` + `instrument` (ODRL).
338
487
 
339
- const client = new DataConvClient({
340
- issuerDid: activatedOrganizationDid,
341
- tenantId: tenantAlternateName,
342
- jurisdiction: 'ES',
343
- httpClient,
344
- crypto: globalThis.crypto
345
- });
488
+ When `--service-did` is used, resolve it locally with either `--resolved-base-url` or the `DATACONV_SERVICE_DID_MAP` env variable:
346
489
 
347
- client.setVpToken(vpTokenFromActivation);
490
+ ```bash
491
+ export DATACONV_SERVICE_DID_MAP='{"did:web:dataconv-api.example.org":"http://127.0.0.1:8080"}'
348
492
  ```
349
493
 
350
- It also works with `fetch`:
351
-
352
- ```ts
353
- const client = new DataConvClient({
354
- issuerDid: activatedOrganizationDid,
355
- tenantId: tenantAlternateName,
356
- jurisdiction: 'ES',
357
- fetch,
358
- crypto: globalThis.crypto
359
- });
360
- ```
494
+ > **DEMO_MODE** If the backend runs with `DEMO_MODE=true`, generate a test `id_token` without a real IdP:
495
+ >
496
+ > ```bash
497
+ > # genera un id_token demo (alg:none, solo requiere campo email en el payload)
498
+ > export DATACONV_ID_TOKEN=$(node -e '
499
+ > const h = Buffer.from(JSON.stringify({alg:"none",typ:"JWT"})).toString("base64url");
500
+ > const p = Buffer.from(JSON.stringify({email:"admin@example.com"})).toString("base64url");
501
+ > console.log(`${h}.${p}.`);
502
+ > ')
503
+ > # eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIn0.
504
+ > ```
505
+ >
506
+ > Use the token with `--authorization-bearer "$DATACONV_ID_TOKEN"`. No signature verification is performed in demo mode; only the `email` claim is required.
507
+
508
+ ---
361
509
 
362
510
  ## Notes
363
511
 
364
- - For configuration and conversion calls, the operational tenant identifier should be `tenantId`, typically the organization's VAT/taxId.
365
- - `sector` is variable and is part of the public route for config, digital twin, and search.
512
+ - `tenantId` is the operational tenant identifier, typically the organization's VAT/taxId.
513
+ - `sector` is part of the public route for publisher config, dataset publication, and dataset search.
366
514
  - `patchConversion()` defaults to `Composition/_patch`.
367
515
  - `batchPromotion()` defaults to `Patient/_batch`.
368
- - `searchResources()` resolves to `/host/cds-{jurisdiction}/v1/{sector}/{tenantId}/org.hl7.fhir.api/{resourceType}/_search`.
369
- - Parameter names sent by `searchResources()` are lowercase. Example: `userselected`, `date`.
370
- - Even if the caller passes `userSelected`, the SDK normalizes it to `userselected` before sending the body.
371
- - In `_search`, the current comparators are prefixed in the value: `ge2026-01-01`, `gt...`, `le...`, `lt...`.
372
- - The pre-conversion service can already require `vp_token` and/or `id_token` depending on `PRECONV_AUTH_MODE`, although it does not yet validate signatures or the full session exchange in `adapter-ingestion-py`.
373
- - The current backend still rejects `source_format=csv`; the SDK models it because the route exists, but real support today is Excel/XLSX.
374
- - `gdc-common-utils-ts` is consumed from npm; this SDK adds the concrete pre-conversion types on top of those DIDComm helpers.
375
- - ICA VCs and `controller.publicKeyJwk` belong to the backend onboarding/`_activate` flow. `DataConvClient` is instantiated afterwards, once the tenant is already activated and only pre-conversion calls are needed.
516
+ - `searchResources()` resolves to `/publisher/cds-{jurisdiction}/v1/{sector}/{tenantId}/dataset/{resourceType}/_search`.
517
+ - Search parameter names are normalized to lowercase (`userSelected` `userselected`).
518
+ - Search comparators are prefixed in the value: `ge2026-01-01`, `gt...`, `le...`, `lt...`.
519
+ - The backend may require `vp_token` and/or `id_token` depending on `PRECONV_AUTH_MODE`.
520
+ - CSV upload is modeled in the SDK but the current backend only accepts Excel/XLSX.
521
+ - `gdc-common-utils-ts` is an npm dependency; this SDK adds concrete pre-conversion types on top of those DIDComm helpers.
522
+ - ICA VCs and `controller.publicKeyJwk` belong to the `_activate` onboarding flow. `DataConvClient` is used afterwards, once the tenant is already activated.
523
+
524
+ ## Roadmap and Briefing
525
+ - `BRIEFING_DATASPACE_EN.md`
526
+ - `TODO_ROADMAP.md`