@aurabx/jmix-js 0.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 (73) hide show
  1. package/.ai/security.md +184 -0
  2. package/.eslintrc.cjs +23 -0
  3. package/.idea/jmix-ts.iml +13 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/.prettierrc +9 -0
  7. package/README.md +335 -0
  8. package/WARP.md +143 -0
  9. package/dist/JmixBuilder.d.ts +82 -0
  10. package/dist/JmixBuilder.d.ts.map +1 -0
  11. package/dist/JmixBuilder.js +353 -0
  12. package/dist/JmixBuilder.js.map +1 -0
  13. package/dist/crypto/PayloadDecryptor.d.ts +12 -0
  14. package/dist/crypto/PayloadDecryptor.d.ts.map +1 -0
  15. package/dist/crypto/PayloadDecryptor.js +39 -0
  16. package/dist/crypto/PayloadDecryptor.js.map +1 -0
  17. package/dist/crypto/PayloadEncryptor.d.ts +21 -0
  18. package/dist/crypto/PayloadEncryptor.d.ts.map +1 -0
  19. package/dist/crypto/PayloadEncryptor.js +79 -0
  20. package/dist/crypto/PayloadEncryptor.js.map +1 -0
  21. package/dist/demo-decrypt-existing.js +27 -0
  22. package/dist/demo-decrypt.js +39 -0
  23. package/dist/demo-no-validation.js +95 -0
  24. package/dist/demo-package-encrypted.js +34 -0
  25. package/dist/demo-package.js +28 -0
  26. package/dist/demo-verify-hash.js +30 -0
  27. package/dist/demo.js +73 -0
  28. package/dist/dicom/DicomProcessor.d.ts +40 -0
  29. package/dist/dicom/DicomProcessor.d.ts.map +1 -0
  30. package/dist/dicom/DicomProcessor.js +231 -0
  31. package/dist/dicom/DicomProcessor.js.map +1 -0
  32. package/dist/index.d.ts +6 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +8 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/types/index.d.ts +165 -0
  37. package/dist/types/index.d.ts.map +1 -0
  38. package/dist/types/index.js +31 -0
  39. package/dist/types/index.js.map +1 -0
  40. package/dist/validation/SchemaValidator.d.ts +25 -0
  41. package/dist/validation/SchemaValidator.d.ts.map +1 -0
  42. package/dist/validation/SchemaValidator.js +125 -0
  43. package/dist/validation/SchemaValidator.js.map +1 -0
  44. package/jest.config.json +20 -0
  45. package/package.json +74 -0
  46. package/samples/sample_config.json +52 -0
  47. package/samples/study_1/series_1/CT.1.1.dcm +0 -0
  48. package/samples/study_1/series_1/CT.1.2.dcm +0 -0
  49. package/samples/study_1/series_1/CT.1.3.dcm +0 -0
  50. package/samples/study_1/series_1/CT.1.4.dcm +0 -0
  51. package/samples/study_1/series_1/CT.1.5.dcm +0 -0
  52. package/samples/study_1/series_2/CT.2.1.dcm +0 -0
  53. package/samples/study_1/series_2/CT.2.2.dcm +0 -0
  54. package/samples/study_1/series_2/CT.2.3.dcm +0 -0
  55. package/samples/study_1/series_2/CT.2.4.dcm +0 -0
  56. package/samples/study_1/series_2/CT.2.5.dcm +0 -0
  57. package/samples/study_1/series_3/CT.3.1.dcm +0 -0
  58. package/samples/study_1/series_3/CT.3.2.dcm +0 -0
  59. package/samples/study_1/series_3/CT.3.3.dcm +0 -0
  60. package/samples/study_1/series_3/CT.3.4.dcm +0 -0
  61. package/samples/study_1/series_3/CT.3.5.dcm +0 -0
  62. package/src/JmixBuilder.ts +572 -0
  63. package/src/crypto/PayloadDecryptor.ts +45 -0
  64. package/src/crypto/PayloadEncryptor.ts +97 -0
  65. package/src/dicom/DicomProcessor.ts +262 -0
  66. package/src/index.ts +30 -0
  67. package/src/types/daikon.d.ts +1 -0
  68. package/src/types/index.ts +201 -0
  69. package/src/validation/SchemaValidator.ts +156 -0
  70. package/test-encrypt.js +10 -0
  71. package/tests/JmixBuilder.test.ts +170 -0
  72. package/tests/SchemaValidator.test.ts +196 -0
  73. package/tsconfig.json +21 -0
@@ -0,0 +1,21 @@
1
+ export interface EncryptResult {
2
+ ephemeral_public_key: string;
3
+ iv: string;
4
+ auth_tag: string;
5
+ }
6
+ export declare class PayloadEncryptor {
7
+ /**
8
+ * Create a tar archive (in-memory) from a directory.
9
+ * Paths inside the tar are relative to baseDir.
10
+ */
11
+ static tarDirectory(baseDir: string): Promise<Buffer>;
12
+ /**
13
+ * Encrypt payload tar with AES-256-GCM using ECDH (X25519) derived key via HKDF-SHA256.
14
+ * recipientPublicKeyBase64 is a 32-byte Curve25519 public key, base64 encoded.
15
+ */
16
+ static encryptTar(tarData: Buffer, recipientPublicKeyBase64: string): {
17
+ ciphertext: Buffer;
18
+ result: EncryptResult;
19
+ };
20
+ }
21
+ //# sourceMappingURL=PayloadEncryptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PayloadEncryptor.d.ts","sourceRoot":"","sources":["../../src/crypto/PayloadEncryptor.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,aAAa;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,gBAAgB;IAC3B;;;OAGG;WACU,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmC3D;;;OAGG;IACH,MAAM,CAAC,UAAU,CACf,OAAO,EAAE,MAAM,EACf,wBAAwB,EAAE,MAAM,GAC/B;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,aAAa,CAAA;KAAE;CAoCjD"}
@@ -0,0 +1,79 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { randomBytes, createCipheriv, hkdfSync } from 'crypto';
4
+ import nacl from 'tweetnacl';
5
+ import tar from 'tar-stream';
6
+ import { buffer as consumeBuffer } from 'node:stream/consumers';
7
+ export class PayloadEncryptor {
8
+ /**
9
+ * Create a tar archive (in-memory) from a directory.
10
+ * Paths inside the tar are relative to baseDir.
11
+ */
12
+ static async tarDirectory(baseDir) {
13
+ const pack = tar.pack();
14
+ async function addDir(dir, relPrefix = '') {
15
+ const entries = await fs.readdir(dir, { withFileTypes: true });
16
+ for (const entry of entries) {
17
+ const abs = path.join(dir, entry.name);
18
+ const rel = path.posix.join(relPrefix, entry.name);
19
+ if (entry.isDirectory()) {
20
+ await addDir(abs, rel);
21
+ }
22
+ else if (entry.isFile()) {
23
+ const stat = await fs.stat(abs);
24
+ const content = await fs.readFile(abs);
25
+ await new Promise((resolve, reject) => {
26
+ const header = {
27
+ name: rel,
28
+ size: stat.size,
29
+ mode: stat.mode,
30
+ mtime: stat.mtime,
31
+ type: 'file',
32
+ };
33
+ pack.entry(header, content, (err) => (err ? reject(err) : resolve()));
34
+ });
35
+ }
36
+ }
37
+ }
38
+ await addDir(baseDir);
39
+ pack.finalize();
40
+ // Collect to Buffer using Node's consumers
41
+ const outBuf = await consumeBuffer(pack);
42
+ return Buffer.from(outBuf);
43
+ }
44
+ /**
45
+ * Encrypt payload tar with AES-256-GCM using ECDH (X25519) derived key via HKDF-SHA256.
46
+ * recipientPublicKeyBase64 is a 32-byte Curve25519 public key, base64 encoded.
47
+ */
48
+ static encryptTar(tarData, recipientPublicKeyBase64) {
49
+ const recipientPub = Buffer.from(recipientPublicKeyBase64, 'base64');
50
+ if (recipientPub.length !== 32) {
51
+ throw new Error('Invalid recipient public key length: expected 32 bytes base64');
52
+ }
53
+ // Ephemeral keypair
54
+ const eph = nacl.box.keyPair();
55
+ // X25519 shared secret
56
+ const shared = nacl.scalarMult(eph.secretKey, new Uint8Array(recipientPub));
57
+ // Derive 32-byte AES key with HKDF-SHA256
58
+ const salt = Buffer.alloc(0); // empty salt
59
+ const info = Buffer.from('JMIX-Payload-Encryption');
60
+ const hkdfOut = hkdfSync('sha256', Buffer.from(shared), salt, info, 32);
61
+ const key = Buffer.from(hkdfOut);
62
+ // Encrypt with AES-256-GCM
63
+ const iv = randomBytes(12);
64
+ const cipher = createCipheriv('aes-256-gcm', key, iv);
65
+ const enc1 = cipher.update(tarData);
66
+ const enc2 = cipher.final();
67
+ const auth = cipher.getAuthTag();
68
+ const ciphertext = Buffer.concat([enc1, enc2]);
69
+ return {
70
+ ciphertext,
71
+ result: {
72
+ ephemeral_public_key: Buffer.from(eph.publicKey).toString('base64'),
73
+ iv: iv.toString('base64'),
74
+ auth_tag: auth.toString('base64'),
75
+ },
76
+ };
77
+ }
78
+ }
79
+ //# sourceMappingURL=PayloadEncryptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PayloadEncryptor.js","sourceRoot":"","sources":["../../src/crypto/PayloadEncryptor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC/D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,GAAG,MAAM,YAAY,CAAC;AAC7B,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAQhE,MAAM,OAAO,gBAAgB;IAC3B;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,OAAe;QACvC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QAExB,KAAK,UAAU,MAAM,CAAC,GAAW,EAAE,SAAS,GAAG,EAAE;YAC/C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBACvC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBACnD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;oBACxB,MAAM,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBACzB,CAAC;qBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;oBAC1B,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;oBAChC,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;oBACvC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;wBAC1C,MAAM,MAAM,GAAG;4BACb,IAAI,EAAE,GAAG;4BACT,IAAI,EAAE,IAAI,CAAC,IAAI;4BACf,IAAI,EAAE,IAAI,CAAC,IAAI;4BACf,KAAK,EAAE,IAAI,CAAC,KAAK;4BACjB,IAAI,EAAE,MAAe;yBACtB,CAAC;wBACF,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,GAAQ,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;oBAC7E,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QACtB,IAAI,CAAC,QAAQ,EAAE,CAAC;QAEhB,2CAA2C;QAC3C,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAW,CAAC,CAAC;QAChD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,UAAU,CACf,OAAe,EACf,wBAAgC;QAEhC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE,QAAQ,CAAC,CAAC;QACrE,IAAI,YAAY,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACnF,CAAC;QAED,oBAAoB;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAE/B,uBAAuB;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC;QAE5E,0CAA0C;QAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa;QAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QACxE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEjC,2BAA2B;QAC3B,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACtD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;QAEjC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAE/C,OAAO;YACL,UAAU;YACV,MAAM,EAAE;gBACN,oBAAoB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBACnE,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;aAClC;SACF,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Decrypt an existing ENCRYPTED JMIX envelope directory.
4
+ // Usage:
5
+ // node demo-decrypt-existing.js <envelopeDir> <recipientPrivateKeyBase64>
6
+
7
+ import { JmixBuilder } from './index.js';
8
+
9
+ async function main() {
10
+ const envelopeDir = process.argv[2];
11
+ const privKeyB64 = process.argv[3];
12
+
13
+ if (!envelopeDir || !privKeyB64) {
14
+ console.error('Usage: node demo-decrypt-existing.js <envelopeDir> <recipientPrivateKeyBase64>');
15
+ process.exit(2);
16
+ }
17
+
18
+ const builder = new JmixBuilder();
19
+ console.log('šŸ”“ Decrypting envelope:', envelopeDir);
20
+ const payloadPath = await builder.decryptEnvelope(envelopeDir, privKeyB64);
21
+ console.log('āœ… Decrypted payload at:', payloadPath);
22
+ }
23
+
24
+ main().catch(err => {
25
+ console.error('āŒ Decryption failed:', err?.message || String(err));
26
+ process.exit(1);
27
+ });
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Demo: Encrypt and then decrypt a JMIX envelope using a fresh X25519 keypair.
4
+ // Usage (self-contained):
5
+ // node demo-decrypt.js [dicomPath] [outputRoot]
6
+
7
+ import { JmixBuilder } from './index.js';
8
+ import nacl from 'tweetnacl';
9
+ import * as fs from 'fs/promises';
10
+
11
+ async function main() {
12
+ const dicomPath = process.argv[2] || './samples/study_1';
13
+ const outputRoot = process.argv[3] || './tmp';
14
+
15
+ const builder = new JmixBuilder();
16
+ const config = await JmixBuilder.loadConfig('./samples/sample_config.json');
17
+
18
+ // Generate a recipient keypair
19
+ const kp = nacl.box.keyPair();
20
+ const pubB64 = Buffer.from(kp.publicKey).toString('base64');
21
+ const privB64 = Buffer.from(kp.secretKey).toString('base64');
22
+
23
+ console.log('šŸ”’ Building encrypted envelope...');
24
+ const encryptedDir = await builder.packageEncryptedToDirectory(dicomPath, config, outputRoot, pubB64);
25
+ console.log('Encrypted package:', encryptedDir);
26
+
27
+ console.log('šŸ”“ Decrypting envelope...');
28
+ const payloadPath = await builder.decryptEnvelope(encryptedDir, privB64);
29
+ console.log('Decrypted payload path:', payloadPath);
30
+
31
+ // Show a couple of files
32
+ const files = await fs.readdir(payloadPath);
33
+ console.log('Payload entries:', files);
34
+ }
35
+
36
+ main().catch(err => {
37
+ console.error('āŒ Demo failed:', err?.message || String(err));
38
+ process.exit(1);
39
+ });
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Demo script to test the JMIX TypeScript library without schema validation
4
+ import { JmixBuilder } from './index.js';
5
+ import * as fs from 'fs/promises';
6
+ import * as path from 'path';
7
+
8
+ async function demo() {
9
+ console.log('šŸš€ JMIX TypeScript Library Demo (No Validation)');
10
+ console.log('=================================================\n');
11
+
12
+ try {
13
+ // Create a JmixBuilder instance with schema validation disabled
14
+ const builder = new JmixBuilder({
15
+ outputPath: './tmp/demo',
16
+ schemaValidatorOptions: {
17
+ schemaPath: './non-existent-schemas', // Force schemas to not be found
18
+ strictMode: false
19
+ }
20
+ });
21
+
22
+ console.log('āœ… JmixBuilder created (validation disabled)');
23
+
24
+ // Create a sample configuration
25
+ const config = await JmixBuilder.loadConfig('./samples/sample_config.json');
26
+ console.log('āœ… Sample configuration created');
27
+ console.log(' Patient:', config.patient.name);
28
+ console.log(' Sender:', config.sender.name);
29
+ console.log(' Security:', config.security.classification);
30
+
31
+ // Create a test DICOM directory (empty for demo)
32
+ const testDicomPath = './samples/study_1';
33
+ await fs.mkdir(testDicomPath, { recursive: true });
34
+ console.log('āœ… Using sample DICOM directory');
35
+
36
+ // Build a JMIX envelope
37
+ console.log('\nšŸ“¦ Building JMIX envelope...');
38
+ const envelope = await builder.buildFromDicom(testDicomPath, config);
39
+
40
+ console.log('āœ… JMIX envelope built successfully!');
41
+ console.log(' Envelope ID:', envelope.manifest.envelope_id);
42
+ console.log(' Audit events:', Array.isArray(envelope.audit?.audit) ? envelope.audit.audit.length : 0);
43
+ console.log(' Patient:', envelope.metadata.patient.name);
44
+ console.log(' DICOM instances:', envelope.metadata.dicom.instance_count);
45
+
46
+ // Save to files
47
+ console.log('\nšŸ’¾ Saving envelope to files...');
48
+ await builder.saveToFiles(envelope);
49
+
50
+ console.log('āœ… Files saved to ./tmp/demo/');
51
+
52
+ // List the saved files
53
+ const savedFiles = await fs.readdir('./tmp/demo');
54
+ console.log(' Files created:');
55
+ for (const file of savedFiles.filter(f => f.endsWith('.json'))) {
56
+ const filePath = path.join('./tmp/demo', file);
57
+ const stats = await fs.stat(filePath);
58
+ console.log(` - ${file} (${Math.round(stats.size / 1024 * 10) / 10} KB)`);
59
+ }
60
+
61
+ // Show a snippet of the manifest
62
+ console.log('\nšŸ“„ Sample manifest content:');
63
+ const manifestPath = path.join('./tmp/demo', 'manifest.json');
64
+ const manifestContent = await fs.readFile(manifestPath, 'utf-8');
65
+ const manifest = JSON.parse(manifestContent);
66
+ console.log(` JMIX Version: ${manifest.jmix_version}`);
67
+ console.log(` Created: ${manifest.created_at}`);
68
+ console.log(` Sender: ${manifest.sender.name}`);
69
+ console.log(` Patient: ${manifest.patient.name} (ID: ${manifest.patient.id})`);
70
+
71
+ console.log('\nšŸŽ‰ Demo completed successfully!');
72
+ console.log('\nšŸ“ This demonstrates:');
73
+ console.log(' āœ“ TypeScript JMIX library setup');
74
+ console.log(' āœ“ ESM module imports');
75
+ console.log(' āœ“ DICOM directory processing (empty folder fallback)');
76
+ console.log(' āœ“ JMIX envelope generation');
77
+ console.log(' āœ“ File output to ./tmp directory (as per user rules)');
78
+ console.log(' āœ“ Schema validation gracefully skipped when schemas not found');
79
+
80
+ console.log('\nšŸ” To explore the generated files:');
81
+ console.log(' cat ./tmp/demo/manifest.json | jq .');
82
+ console.log(' cat ./tmp/demo/metadata.json | jq .');
83
+ console.log(' cat ./tmp/demo/audit.json | jq .');
84
+
85
+ } catch (error) {
86
+ console.error('āŒ Demo failed:', error.message);
87
+ if (error.cause) {
88
+ console.error(' Cause:', error.cause.message);
89
+ }
90
+ process.exit(1);
91
+ }
92
+ }
93
+
94
+ // Run the demo
95
+ demo();
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Demo that builds an ENCRYPTED JMIX package.
4
+ // Usage:
5
+ // node demo-package-encrypted.js <recipientPublicKeyBase64> [dicomPath] [outputRoot]
6
+ //
7
+ // recipientPublicKeyBase64 is a Curve25519 (X25519) public key, 32 bytes base64.
8
+
9
+ import { JmixBuilder } from './index.js';
10
+
11
+ async function main() {
12
+ try {
13
+ const recipientKey = process.argv[2];
14
+ if (!recipientKey) {
15
+ console.error('Usage: node demo-package-encrypted.js <recipientPublicKeyBase64> [dicomPath] [outputRoot]');
16
+ process.exit(2);
17
+ }
18
+ const dicomPath = process.argv[3] || './samples/study_1';
19
+ const outputRoot = process.argv[4] || './tmp';
20
+
21
+ const config = await JmixBuilder.loadConfig('./samples/sample_config.json');
22
+
23
+ const builder = new JmixBuilder({ outputPath: './tmp' });
24
+ console.log('šŸ”’ Packaging ENCRYPTED JMIX folder...');
25
+ const out = await builder.packageEncryptedToDirectory(dicomPath, config, outputRoot, recipientKey);
26
+ console.log('āœ… Encrypted package at:', out);
27
+ console.log('Contains: manifest.json, audit.json, payload.encrypted');
28
+ } catch (err) {
29
+ console.error('āŒ Encrypted packaging failed:', err instanceof Error ? err.message : String(err));
30
+ process.exit(1);
31
+ }
32
+ }
33
+
34
+ main();
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Demo that packages a JMIX folder including original DICOM files
4
+ import { JmixBuilder } from './index.js';
5
+
6
+ async function main() {
7
+ try {
8
+ const dicomPath = process.argv[2] || './samples/study_1';
9
+ const outputRoot = process.argv[3] || './tmp';
10
+
11
+ const config = await JmixBuilder.loadConfig('./samples/sample_config.json');
12
+
13
+ const builder = new JmixBuilder({ outputPath: './tmp' });
14
+ console.log('šŸ“¦ Packaging JMIX folder...');
15
+ const out = await builder.packageToDirectory(dicomPath, config, outputRoot);
16
+ console.log('āœ… Package created at:', out);
17
+ console.log('Structure:');
18
+ console.log(' - manifest.json');
19
+ console.log(' - audit.json');
20
+ console.log(' - payload/metadata.json');
21
+ console.log(' - payload/dicom/... (original files)');
22
+ } catch (err) {
23
+ console.error('āŒ Packaging failed:', err instanceof Error ? err.message : String(err));
24
+ process.exit(1);
25
+ }
26
+ }
27
+
28
+ main();
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Verify payload hash for a JMIX envelope.
4
+ // Usage:
5
+ // node demo-verify-hash.js <envelopeDir> [recipientPrivateKeyBase64]
6
+ // - If payload/ exists, no key is required.
7
+ // - If only payload.encrypted exists, pass the Curve25519 private key.
8
+
9
+ import { JmixBuilder } from './index.js';
10
+
11
+ async function main() {
12
+ const envelopeDir = process.argv[2];
13
+ const privKey = process.argv[3];
14
+
15
+ if (!envelopeDir) {
16
+ console.error('Usage: node demo-verify-hash.js <envelopeDir> [recipientPrivateKeyBase64]');
17
+ process.exit(2);
18
+ }
19
+
20
+ const b = new JmixBuilder();
21
+ const result = await b.verifyPayloadHash(envelopeDir, { recipientPrivateKeyBase64: privKey });
22
+ console.log('Mode:', result.mode);
23
+ console.log('Expected:', result.expected);
24
+ console.log('Computed:', result.computed);
25
+ console.log('OK:', result.ok);
26
+
27
+ process.exit(result.ok ? 0 : 1);
28
+ }
29
+
30
+ main().catch(err => { console.error('āŒ Verification failed:', err?.message || String(err)); process.exit(1); });
package/dist/demo.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Demo script to test the JMIX TypeScript library
4
+ import { JmixBuilder } from './index.js';
5
+ import * as fs from 'fs/promises';
6
+ import * as path from 'path';
7
+
8
+ async function demo() {
9
+ console.log('šŸš€ JMIX TypeScript Library Demo');
10
+ console.log('================================\n');
11
+
12
+ try {
13
+ // Create a JmixBuilder instance
14
+ const builder = new JmixBuilder({
15
+ outputPath: './tmp/demo'
16
+ });
17
+
18
+ console.log('āœ… JmixBuilder created');
19
+
20
+ // Create a sample configuration
21
+ const config = await JmixBuilder.loadConfig('./samples/sample_config.json');
22
+ console.log('āœ… Sample configuration created');
23
+ console.log(' Patient:', config.patient.name);
24
+ console.log(' Sender:', config.sender.name);
25
+ console.log(' Security:', config.security.classification);
26
+
27
+ // Use sample DICOM directory
28
+ const testDicomPath = './samples/study_1';
29
+ await fs.mkdir(testDicomPath, { recursive: true });
30
+ console.log('āœ… Using sample DICOM directory');
31
+
32
+ // Build a JMIX envelope
33
+ console.log('\nšŸ“¦ Building JMIX envelope...');
34
+ const envelope = await builder.buildFromDicom(testDicomPath, config);
35
+
36
+ console.log('āœ… JMIX envelope built successfully!');
37
+ console.log(' Envelope ID:', envelope.manifest.envelope_id);
38
+ console.log(' Audit events:', Array.isArray(envelope.audit?.audit) ? envelope.audit.audit.length : 0);
39
+ console.log(' Patient:', envelope.metadata.patient.name);
40
+ console.log(' DICOM instances:', envelope.metadata.dicom.instance_count);
41
+
42
+ // Save to files
43
+ console.log('\nšŸ’¾ Saving envelope to files...');
44
+ await builder.saveToFiles(envelope);
45
+
46
+ console.log('āœ… Files saved to ./tmp/demo/');
47
+
48
+ // List the saved files
49
+ const savedFiles = await fs.readdir('./tmp/demo');
50
+ console.log(' Files created:');
51
+ for (const file of savedFiles.filter(f => f.endsWith('.json'))) {
52
+ const filePath = path.join('./tmp/demo', file);
53
+ const stats = await fs.stat(filePath);
54
+ console.log(` - ${file} (${Math.round(stats.size / 1024 * 10) / 10} KB)`);
55
+ }
56
+
57
+ console.log('\nšŸŽ‰ Demo completed successfully!');
58
+ console.log('\nTo explore the generated files:');
59
+ console.log(' cat ./tmp/demo/manifest.json | jq .');
60
+ console.log(' cat ./tmp/demo/metadata.json | jq .');
61
+ console.log(' cat ./tmp/demo/audit.json | jq .');
62
+
63
+ } catch (error) {
64
+ console.error('āŒ Demo failed:', error.message);
65
+ if (error.cause) {
66
+ console.error(' Cause:', error.cause.message);
67
+ }
68
+ process.exit(1);
69
+ }
70
+ }
71
+
72
+ // Run the demo
73
+ demo();
@@ -0,0 +1,40 @@
1
+ import { DicomMetadata, Config } from '../types/index.js';
2
+ export declare class DicomProcessor {
3
+ /**
4
+ * Process a DICOM directory and extract metadata
5
+ */
6
+ processDicomFolder(dicomPath: string, config?: Config): Promise<DicomMetadata>;
7
+ /**
8
+ * Recursively find DICOM files in a directory
9
+ */
10
+ private findDicomFiles;
11
+ /**
12
+ * Check if a file is a DICOM file by checking the magic signature
13
+ */
14
+ private isDicomFile;
15
+ /**
16
+ * Create metadata from config when DICOM parsing fails or no files found
17
+ */
18
+ private createMetadataFromConfig;
19
+ /**
20
+ * Generate a study UID (placeholder implementation)
21
+ */
22
+ private generateStudyUID;
23
+ /**
24
+ * Estimate series count based on file naming patterns or directory structure
25
+ */
26
+ private estimateSeriesCount;
27
+ /**
28
+ * Attempt to read a DICOM tag value using Daikon's API (best-effort)
29
+ */
30
+ private readTag;
31
+ /**
32
+ * Format DICOM date to ISO format
33
+ */
34
+ static formatDicomDate(dicomDate: string): string;
35
+ /**
36
+ * Format DICOM person name to readable format
37
+ */
38
+ static formatDicomPersonName(dicomName: string): string;
39
+ }
40
+ //# sourceMappingURL=DicomProcessor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DicomProcessor.d.ts","sourceRoot":"","sources":["../../src/dicom/DicomProcessor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,aAAa,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAatE,qBAAa,cAAc;IACzB;;OAEG;IACG,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC;IA6FzB;;OAEG;YACW,cAAc;IA6B5B;;OAEG;YACW,WAAW;IAqBzB;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAgBhC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAW3B;;OAEG;IAEH,OAAO,CAAC,OAAO;IAcf;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAYjD;;OAEG;IACH,MAAM,CAAC,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAWxD"}