@cooperation/vc-storage 1.0.26 → 1.0.27

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.
@@ -208,7 +208,7 @@ export class GoogleDriveStorage {
208
208
  throw new Error('Folder ID is required to save a file.');
209
209
  }
210
210
  const fileMetadata = {
211
- name: data.fileName || 'resume.json',
211
+ name: data.fileName || data.name + '.json' || data.credentialSubject.person.name.formattedName + '.json' || 'Untitled file.json',
212
212
  parents: [folderId],
213
213
  mimeType: data.mimeType || 'application/json',
214
214
  };
@@ -475,6 +475,61 @@ export class GoogleDriveStorage {
475
475
  await this.updateFileIdsJson(relationsFile.id);
476
476
  return relationsFile;
477
477
  }
478
+ async updateResumeRelation({ authorFolderId, draftFileId, signedFileId, }) {
479
+ try {
480
+ const relationFileName = 'relations.json';
481
+ // Step 1: Check if `relations.json` exists in RESUMES_AUTHOR/
482
+ const existingRelationFiles = await this.searchFiles(`name='${relationFileName}' and '${authorFolderId}' in parents`);
483
+ let relationFileId = null;
484
+ let existingRelations = {};
485
+ if (existingRelationFiles.length > 0) {
486
+ relationFileId = existingRelationFiles[0].id;
487
+ existingRelations = await this.getFileContent(relationFileId);
488
+ }
489
+ else {
490
+ console.log('relations.json does not exist. Will create a new one.');
491
+ }
492
+ // Step 2: Update relations object
493
+ existingRelations[draftFileId] = signedFileId;
494
+ // Step 3: Create or update the file on Drive
495
+ const fileBlob = new Blob([JSON.stringify(existingRelations, null, 2)], {
496
+ type: 'application/json',
497
+ });
498
+ const formData = new FormData();
499
+ const metadata = {
500
+ name: relationFileName,
501
+ parents: [authorFolderId],
502
+ mimeType: 'application/json',
503
+ };
504
+ formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
505
+ formData.append('file', fileBlob);
506
+ let uploadedFile;
507
+ if (relationFileId) {
508
+ // Update existing file
509
+ uploadedFile = await this.fetcher({
510
+ method: 'PATCH',
511
+ headers: {},
512
+ body: formData,
513
+ url: `https://www.googleapis.com/upload/drive/v3/files/${relationFileId}?uploadType=multipart&fields=id,parents`,
514
+ });
515
+ }
516
+ else {
517
+ // Create new file
518
+ uploadedFile = await this.fetcher({
519
+ method: 'POST',
520
+ headers: {},
521
+ body: formData,
522
+ url: `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,parents`,
523
+ });
524
+ }
525
+ console.log(`✅ Resume relation updated. File ID: ${uploadedFile.id}`);
526
+ return uploadedFile;
527
+ }
528
+ catch (error) {
529
+ console.error('❌ Failed to update resume relation:', error);
530
+ throw error;
531
+ }
532
+ }
478
533
  /**
479
534
  * Delete file by id
480
535
  * @param id
@@ -5,21 +5,55 @@ export const resumeFolderTypes = {
5
5
  };
6
6
  export class StorageHandler {
7
7
  storage;
8
+ folderCreationPromises = {};
9
+ createdFolders = {};
8
10
  constructor(storage) {
9
11
  this.storage = storage;
10
12
  }
11
13
  async getOrCreateFolder(folderName, parentId) {
12
- const folders = await this.storage.findFolders(parentId); // Fetch all child folders of the parent
13
- let folder = folders.find((folder) => {
14
- return folder.name === folderName;
15
- });
16
- if (!folder) {
17
- folder = await this.storage.createFolder({
18
- folderName,
19
- parentFolderId: parentId,
20
- });
14
+ const cacheKey = `${parentId}_${folderName}`;
15
+ // Check our local cache first (this is separate from the storage's cache)
16
+ if (this.createdFolders[cacheKey]) {
17
+ console.log(`Using cached folder ${folderName} (${this.createdFolders[cacheKey].id}) under ${parentId}`);
18
+ return this.createdFolders[cacheKey];
21
19
  }
22
- return folder;
20
+ // If there's an existing promise for this folder, wait for it
21
+ if (this.folderCreationPromises[cacheKey]) {
22
+ console.log(`Waiting for existing folder creation: ${folderName} under ${parentId}`);
23
+ return this.folderCreationPromises[cacheKey];
24
+ }
25
+ // Create a new promise for this folder operation
26
+ this.folderCreationPromises[cacheKey] = (async () => {
27
+ try {
28
+ // Double-check if folder exists
29
+ console.log(`Searching for folder ${folderName} under ${parentId}`);
30
+ const folders = await this.storage.findFolders(parentId);
31
+ let folder = folders.find((f) => f.name === folderName);
32
+ if (folder) {
33
+ console.log(`Found existing folder ${folderName} (${folder.id}) under ${parentId}`);
34
+ }
35
+ else {
36
+ console.log(`Creating folder ${folderName} under ${parentId} (no existing folder found)`);
37
+ folder = await this.storage.createFolder({
38
+ folderName,
39
+ parentFolderId: parentId,
40
+ });
41
+ console.log(`Created folder ${folderName} (${folder.id}) under ${parentId}`);
42
+ }
43
+ // Store in our local cache
44
+ this.createdFolders[cacheKey] = folder;
45
+ return folder;
46
+ }
47
+ catch (error) {
48
+ console.error(`Error in getOrCreateFolder(${folderName}, ${parentId}):`, error);
49
+ throw error;
50
+ }
51
+ finally {
52
+ // Clean up the promise after completion
53
+ delete this.folderCreationPromises[cacheKey];
54
+ }
55
+ })();
56
+ return this.folderCreationPromises[cacheKey];
23
57
  }
24
58
  async findFilesInFolder(folderName) {
25
59
  const folders = await this.storage.findFolders();
@@ -36,12 +70,8 @@ export class Resume extends StorageHandler {
36
70
  }
37
71
  async saveResume({ resume, type }) {
38
72
  try {
39
- // Get or create the root folder
40
- const rootFolders = await this.storage.findFolders();
41
- let rootFolder = rootFolders.find((folder) => folder.name === resumeFolderTypes.root);
42
- if (!rootFolder) {
43
- rootFolder = await this.storage.createFolder({ folderName: resumeFolderTypes.root, parentFolderId: 'root' });
44
- }
73
+ let rootFolder = await this.getOrCreateFolder(resumeFolderTypes.root, 'root');
74
+ console.log('🚀 ~ Resume ~ saveResume ~ rootFolder:', rootFolder);
45
75
  // Get or create the subfolder
46
76
  const subFolderName = type === 'sign' ? resumeFolderTypes.signed : resumeFolderTypes.nonSigned;
47
77
  const subFolder = await this.getOrCreateFolder(subFolderName, rootFolder.id);
@@ -72,7 +102,7 @@ export class Resume extends StorageHandler {
72
102
  async getSignedResumes() {
73
103
  try {
74
104
  // Find the root folder first
75
- const rootFolder = await this.findRootFolder();
105
+ const rootFolder = await this.getOrCreateFolder(resumeFolderTypes.root, 'root');
76
106
  // Find or create the signed resumes folder
77
107
  const signedFolder = await this.getOrCreateFolder(resumeFolderTypes.signed, rootFolder.id);
78
108
  // Retrieve all files from the signed folder
@@ -86,7 +116,7 @@ export class Resume extends StorageHandler {
86
116
  async getNonSignedResumes() {
87
117
  try {
88
118
  // Find the root folder first
89
- const rootFolder = await this.findRootFolder();
119
+ const rootFolder = await this.getOrCreateFolder(resumeFolderTypes.root, 'root');
90
120
  // Find or create the non-signed resumes folder
91
121
  const nonSignedFolder = await this.getOrCreateFolder(resumeFolderTypes.nonSigned, rootFolder.id);
92
122
  // Retrieve all files from the non-signed folder
@@ -97,13 +127,28 @@ export class Resume extends StorageHandler {
97
127
  throw new Error('Error while fetching non-signed resumes: ' + error.message);
98
128
  }
99
129
  }
100
- async findRootFolder() {
101
- const rootFolders = await this.storage.findFolders(); // Fetch all root-level folders
102
- const rootFolder = rootFolders.find((folder) => folder.name === resumeFolderTypes.root);
103
- if (!rootFolder) {
104
- throw new Error(`Root folder "${resumeFolderTypes.root}" not found in the root directory.`);
130
+ async saveResumeDraft(data, signedResumeId) {
131
+ try {
132
+ const fileName = `FinalDraft_${signedResumeId}.json`;
133
+ // 1. Find or create root and NON_SIGNED_RESUMES folder
134
+ const rootFolder = await this.getOrCreateFolder(resumeFolderTypes.root, 'root');
135
+ const nonSignedFolder = await this.getOrCreateFolder(resumeFolderTypes.nonSigned, rootFolder.id);
136
+ const dataWithFileName = {
137
+ ...data,
138
+ fileName: `FinalDraft_${signedResumeId}.json`,
139
+ };
140
+ // Save the file
141
+ const savedDraft = await this.storage.saveFile({
142
+ data: dataWithFileName,
143
+ folderId: nonSignedFolder.id,
144
+ });
145
+ console.log(`✅ Draft saved as ${fileName}`);
146
+ return savedDraft;
147
+ }
148
+ catch (error) {
149
+ console.error('❌ Error saving resume draft:', error);
150
+ throw new Error('Failed to save resume draft: ' + error.message);
105
151
  }
106
- return rootFolder;
107
152
  }
108
153
  isResumeFolderExist() { }
109
154
  }
@@ -6,149 +6,150 @@ import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-
6
6
  import { generateDIDSchema } from '../utils/credential.js';
7
7
  import { inlineResumeContext } from '../utils/context.js';
8
8
  export class ResumeVC {
9
- async sign({ formData, issuerDid, keyPair }) {
10
- const unsignedCredential = this.generateUnsignedCredential({ formData, issuerDid });
11
- const suite = new Ed25519Signature2020({
12
- key: new Ed25519VerificationKey2020(keyPair), // Ensure proper initialization
13
- verificationMethod: keyPair.id,
14
- });
15
- try {
16
- const signedVC = await dbVc.issue({
17
- credential: unsignedCredential,
18
- suite,
19
- documentLoader: customDocumentLoader,
20
- });
21
- console.log('🚀 ~ ResumeVC ~ sign ~ signedVC:', JSON.stringify(signedVC));
22
- console.log('Signed VC:', JSON.stringify(signedVC));
23
- } catch (error) {
24
- console.error('Error signing VC:', error.message);
25
- if (error.details) {
26
- console.error('Error details:', JSON.stringify(error.details, null, 2));
27
- }
28
- throw error;
29
- }
30
- return unsignedCredential;
31
- }
32
- generateUnsignedCredential({ formData, issuerDid }) {
33
- const unsignedResumeVC = {
34
- '@context': ['https://www.w3.org/2018/credentials/v1', inlineResumeContext['@context']],
35
- id: `urn:uuid:${uuidv4()}`,
36
- type: ['VerifiableCredential', 'LERRSCredential'],
37
- issuer: issuerDid,
38
- issuanceDate: new Date().toISOString(),
39
- credentialSubject: {
40
- type: 'Resume',
41
- person: {
42
- name: {
43
- formattedName: formData.name || '',
44
- },
45
- primaryLanguage: 'en',
46
- contact: {
47
- fullName: formData.contact.fullName || '',
48
- email: formData.contact.email || '',
49
- phone: formData.contact.phone || '',
50
- location: {
51
- street: formData.contact.location.street || '',
52
- city: formData.contact.location.city || '',
53
- state: formData.contact.location.state || '',
54
- country: formData.contact.location.country || '',
55
- postalCode: formData.contact.location.postalCode || '',
56
- },
57
- socialLinks: {
58
- linkedin: formData.contact.socialLinks.linkedin || '',
59
- github: formData.contact.socialLinks.github || '',
60
- portfolio: formData.contact.socialLinks.portfolio || '',
61
- twitter: formData.contact.socialLinks.twitter || '',
62
- },
63
- },
64
- },
65
- narrative: {
66
- text: formData.summary || '',
67
- },
68
- employmentHistory: formData.experience.items.map((exp) => ({
69
- id: exp.id ? `urn:uuid${exp.id}` : `urn:uuid:${uuidv4()}`, // Ensure each entry has an ID
70
- organization: {
71
- tradeName: exp.company || '',
72
- },
73
- title: exp.title || '',
74
- description: exp.description || '',
75
- duration: exp.duration || '',
76
- startDate: exp.startDate || '',
77
- endDate: exp.endDate || '',
78
- stillEmployed: exp.stillEmployed || false,
79
- verificationStatus: exp.verificationStatus || 'unverified',
80
- credentialLink: exp.credentialLink || null,
81
- verifiedCredentials: exp.verifiedCredentials || [],
82
- })),
83
- educationAndLearning: formData.education.items.map((edu) => ({
84
- id: edu.id ? `urn:uuid${edu.id}` : `urn:uuid:${uuidv4()}`,
85
- institution: edu.institution || '',
86
- degree: edu.degree || '',
87
- fieldOfStudy: edu.fieldOfStudy || '',
88
- duration: edu.duration || '',
89
- startDate: edu.startDate || '',
90
- endDate: edu.endDate || '',
91
- verificationStatus: edu.verificationStatus || 'unverified',
92
- credentialLink: edu.credentialLink || null,
93
- verifiedCredentials: edu.verifiedCredentials || [],
94
- })),
95
- skills: formData.skills.items.map((skill) => ({
96
- id: skill.id ? `urn:uuid${skill.id}` : `urn:uuid:${uuidv4()}`,
97
- name: skill.name || '',
98
- verificationStatus: skill.verificationStatus || 'unverified',
99
- credentialLink: skill.credentialLink || null,
100
- verifiedCredentials: skill.verifiedCredentials || [],
101
- })),
102
- certifications: formData.certifications.items.map((cert) => ({
103
- id: cert.id ? `urn:uuid:${cert.id}` : `urn:uuid:${uuidv4()}`,
104
- name: cert.name || '',
105
- issuer: cert.issuer || '',
106
- date: cert.date || '',
107
- url: cert.url || '',
108
- verificationStatus: cert.verificationStatus || 'unverified',
109
- credentialLink: cert.credentialLink || null,
110
- verifiedCredentials: cert.verifiedCredentials || [],
111
- })),
112
- projects: formData.projects.items.map((proj) => ({
113
- id: proj.id ? `urn:uuid${proj.id}` : `urn:uuid:${uuidv4()}`,
114
- name: proj.name || '',
115
- description: proj.description || '',
116
- url: proj.url || '',
117
- duration: proj.duration || '',
118
- startDate: proj.startDate || '',
119
- endDate: proj.endDate || '',
120
- verificationStatus: proj.verificationStatus || 'unverified',
121
- credentialLink: proj.credentialLink || null,
122
- verifiedCredentials: proj.verifiedCredentials || [],
123
- })),
124
- },
125
- };
126
- console.log('🚀 ~ ResumeVC ~ generateUnsignedCredential ~ unsignedResumeVC:', JSON.stringify(unsignedResumeVC));
127
- return unsignedResumeVC;
128
- }
129
- generateKeyPair = async (address) => {
130
- // Generate the key pair using the library's method
131
- const keyPair = await Ed25519VerificationKey2020.generate();
132
- // Configure key pair attributes
133
- const a = address || keyPair.publicKeyMultibase;
134
- keyPair.controller = `did:key:${a}`;
135
- keyPair.id = `${keyPair.controller}#${a}`;
136
- keyPair.revoked = false;
137
- // The `signer` is already provided by the `Ed25519VerificationKey2020` instance
138
- return keyPair;
139
- };
140
- /**
141
- * Create a new DID with Digital Bazaar's Ed25519VerificationKey2020 key pair.
142
- * @returns {Promise<{didDocument: object, keyPair: object}>} The created DID document and key pair.
143
- * @throws Will throw an error if DID creation fails.
144
- */
145
- async createDID({ keyPair }) {
146
- try {
147
- const didDocument = await generateDIDSchema(keyPair);
148
- return didDocument;
149
- } catch (error) {
150
- console.error('Error creating DID:', error);
151
- throw error;
152
- }
153
- }
9
+ async sign({ formData, issuerDid, keyPair }) {
10
+ const unsignedCredential = this.generateUnsignedCredential({ formData, issuerDid });
11
+ const suite = new Ed25519Signature2020({
12
+ key: new Ed25519VerificationKey2020(keyPair), // Ensure proper initialization
13
+ verificationMethod: keyPair.id,
14
+ });
15
+ try {
16
+ const signedVC = await dbVc.issue({
17
+ credential: unsignedCredential,
18
+ suite,
19
+ documentLoader: customDocumentLoader,
20
+ });
21
+ console.log('Signed VC:', signedVC);
22
+ }
23
+ catch (error) {
24
+ console.error('Error signing VC:', error.message);
25
+ if (error.details) {
26
+ console.error('Error details:', JSON.stringify(error.details, null, 2));
27
+ }
28
+ throw error;
29
+ }
30
+ return unsignedCredential;
31
+ }
32
+ generateUnsignedCredential({ formData, issuerDid }) {
33
+ const unsignedResumeVC = {
34
+ '@context': ['https://www.w3.org/2018/credentials/v1', inlineResumeContext['@context']],
35
+ id: `urn:uuid:${uuidv4()}`,
36
+ type: ['VerifiableCredential', 'LERRSCredential'],
37
+ issuer: issuerDid,
38
+ issuanceDate: new Date().toISOString(),
39
+ credentialSubject: {
40
+ type: 'Resume',
41
+ person: {
42
+ name: {
43
+ formattedName: formData.name || '',
44
+ },
45
+ primaryLanguage: 'en',
46
+ contact: {
47
+ fullName: formData.contact.fullName || '',
48
+ email: formData.contact.email || '',
49
+ phone: formData.contact.phone || '',
50
+ location: {
51
+ street: formData.contact.location.street || '',
52
+ city: formData.contact.location.city || '',
53
+ state: formData.contact.location.state || '',
54
+ country: formData.contact.location.country || '',
55
+ postalCode: formData.contact.location.postalCode || '',
56
+ },
57
+ socialLinks: {
58
+ linkedin: formData.contact.socialLinks.linkedin || '',
59
+ github: formData.contact.socialLinks.github || '',
60
+ portfolio: formData.contact.socialLinks.portfolio || '',
61
+ twitter: formData.contact.socialLinks.twitter || '',
62
+ },
63
+ },
64
+ },
65
+ narrative: {
66
+ text: formData.summary || '',
67
+ },
68
+ employmentHistory: formData.experience.items.map((exp) => ({
69
+ id: exp.id ? `urn:uuid${exp.id}` : `urn:uuid:${uuidv4()}`, // Ensure each entry has an ID
70
+ organization: {
71
+ tradeName: exp.company || '',
72
+ },
73
+ title: exp.title || '',
74
+ description: exp.description || '',
75
+ duration: exp.duration || '',
76
+ startDate: exp.startDate || '',
77
+ endDate: exp.endDate || '',
78
+ stillEmployed: exp.stillEmployed || false,
79
+ verificationStatus: exp.verificationStatus || 'unverified',
80
+ credentialLink: exp.credentialLink || null,
81
+ verifiedCredentials: exp.verifiedCredentials || [],
82
+ })),
83
+ educationAndLearning: formData.education.items.map((edu) => ({
84
+ id: edu.id ? `urn:uuid${edu.id}` : `urn:uuid:${uuidv4()}`,
85
+ institution: edu.institution || '',
86
+ degree: edu.degree || '',
87
+ fieldOfStudy: edu.fieldOfStudy || '',
88
+ duration: edu.duration || '',
89
+ startDate: edu.startDate || '',
90
+ endDate: edu.endDate || '',
91
+ verificationStatus: edu.verificationStatus || 'unverified',
92
+ credentialLink: edu.credentialLink || null,
93
+ verifiedCredentials: edu.verifiedCredentials || [],
94
+ })),
95
+ skills: formData.skills.items.map((skill) => ({
96
+ id: skill.id ? `urn:uuid${skill.id}` : `urn:uuid:${uuidv4()}`,
97
+ name: skill.name || '',
98
+ verificationStatus: skill.verificationStatus || 'unverified',
99
+ credentialLink: skill.credentialLink || null,
100
+ verifiedCredentials: skill.verifiedCredentials || [],
101
+ })),
102
+ certifications: formData.certifications.items.map((cert) => ({
103
+ id: cert.id ? `urn:uuid:${cert.id}` : `urn:uuid:${uuidv4()}`,
104
+ name: cert.name || '',
105
+ issuer: cert.issuer || '',
106
+ date: cert.date || '',
107
+ url: cert.url || '',
108
+ verificationStatus: cert.verificationStatus || 'unverified',
109
+ credentialLink: cert.credentialLink || null,
110
+ verifiedCredentials: cert.verifiedCredentials || [],
111
+ })),
112
+ projects: formData.projects.items.map((proj) => ({
113
+ id: proj.id ? `urn:uuid${proj.id}` : `urn:uuid:${uuidv4()}`,
114
+ name: proj.name || '',
115
+ description: proj.description || '',
116
+ url: proj.url || '',
117
+ duration: proj.duration || '',
118
+ startDate: proj.startDate || '',
119
+ endDate: proj.endDate || '',
120
+ verificationStatus: proj.verificationStatus || 'unverified',
121
+ credentialLink: proj.credentialLink || null,
122
+ verifiedCredentials: proj.verifiedCredentials || [],
123
+ })),
124
+ },
125
+ };
126
+ console.log('🚀 ~ ResumeVC ~ generateUnsignedCredential ~ unsignedResumeVC:', JSON.stringify(unsignedResumeVC));
127
+ return unsignedResumeVC;
128
+ }
129
+ generateKeyPair = async (address) => {
130
+ // Generate the key pair using the library's method
131
+ const keyPair = await Ed25519VerificationKey2020.generate();
132
+ // Configure key pair attributes
133
+ const a = address || keyPair.publicKeyMultibase;
134
+ keyPair.controller = `did:key:${a}`;
135
+ keyPair.id = `${keyPair.controller}#${a}`;
136
+ keyPair.revoked = false;
137
+ // The `signer` is already provided by the `Ed25519VerificationKey2020` instance
138
+ return keyPair;
139
+ };
140
+ /**
141
+ * Create a new DID with Digital Bazaar's Ed25519VerificationKey2020 key pair.
142
+ * @returns {Promise<{didDocument: object, keyPair: object}>} The created DID document and key pair.
143
+ * @throws Will throw an error if DID creation fails.
144
+ */
145
+ async createDID({ keyPair }) {
146
+ try {
147
+ const didDocument = await generateDIDSchema(keyPair);
148
+ return didDocument;
149
+ }
150
+ catch (error) {
151
+ console.error('Error creating DID:', error);
152
+ throw error;
153
+ }
154
+ }
154
155
  }
@@ -76,6 +76,11 @@ export declare class GoogleDriveStorage {
76
76
  createRelationsFile({ vcFolderId }: {
77
77
  vcFolderId: string;
78
78
  }): Promise<any>;
79
+ updateResumeRelation({ authorFolderId, draftFileId, signedFileId, }: {
80
+ authorFolderId: string;
81
+ draftFileId: string;
82
+ signedFileId: string;
83
+ }): Promise<any>;
79
84
  /**
80
85
  * Delete file by id
81
86
  * @param id
@@ -6,6 +6,8 @@ export declare const resumeFolderTypes: {
6
6
  };
7
7
  export declare class StorageHandler {
8
8
  protected storage: GoogleDriveStorage;
9
+ private folderCreationPromises;
10
+ private createdFolders;
9
11
  constructor(storage: GoogleDriveStorage);
10
12
  getOrCreateFolder(folderName: string, parentId: string): Promise<any>;
11
13
  protected findFilesInFolder(folderName: string): Promise<any[]>;
@@ -22,7 +24,7 @@ export declare class Resume extends StorageHandler {
22
24
  }>;
23
25
  getSignedResumes(): Promise<any[]>;
24
26
  getNonSignedResumes(): Promise<any[]>;
25
- private findRootFolder;
27
+ saveResumeDraft(data: any, signedResumeId: string): Promise<any>;
26
28
  private isResumeFolderExist;
27
29
  }
28
30
  export default Resume;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cooperation/vc-storage",
3
3
  "type": "module",
4
- "version": "1.0.26",
4
+ "version": "1.0.27",
5
5
  "description": "Sign and store your verifiable credentials.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/types/index.d.ts",