@keetanetwork/anchor 0.0.60 → 0.0.62
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/lib/chaining.d.ts +29 -6
- package/lib/chaining.d.ts.map +1 -1
- package/lib/chaining.js +61 -24
- package/lib/chaining.js.map +1 -1
- package/lib/queue/drivers/queue_postgres.d.ts.map +1 -1
- package/lib/queue/drivers/queue_postgres.js +47 -43
- package/lib/queue/drivers/queue_postgres.js.map +1 -1
- package/lib/resolver.d.ts +9 -0
- package/lib/resolver.d.ts.map +1 -1
- package/lib/resolver.js +7 -1
- package/lib/resolver.js.map +1 -1
- package/lib/token-metadata.d.ts.map +1 -1
- package/lib/token-metadata.js +1 -1
- package/lib/token-metadata.js.map +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +2 -2
- package/services/fx/client.d.ts +2 -1
- package/services/fx/client.d.ts.map +1 -1
- package/services/fx/client.js +5 -3
- package/services/fx/client.js.map +1 -1
- package/services/storage/clients/contacts.d.ts +5 -11
- package/services/storage/clients/contacts.d.ts.map +1 -1
- package/services/storage/clients/contacts.js +1 -1
- package/services/storage/clients/contacts.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contacts.js","sourceRoot":"","sources":["../../../../src/services/storage/clients/contacts.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssetTransferInstructions, RecipientResolved, KeetaNetAccount, Rail } from '../../asset-movement/common.js';\nimport type { AssetLocationLike, PickChainLocation } from '../../asset-movement/lib/location.js';\nimport type { KeetaStorageAnchorSession, StorageSubClientConfig } from '../client.js';\nimport type { Logger } from '../../../lib/log/index.js';\nimport type { StorageObjectMetadata } from '../common.js';\nimport { lib as KeetaNetLib } from '@keetanetwork/keetanet-client';\nimport { convertAssetLocationToString } from '../../asset-movement/lib/location.js';\nimport { Errors } from '../common.js';\nimport { Buffer } from '../../../lib/utils/buffer.js';\nimport { assertContact } from './contacts.generated.js';\n\n// #region Types\n\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\n/**\n * The structural shape of a transfer instruction, excluding transaction-specific fields.\n */\nexport type TransferInstructionShape = DistributiveOmit<\n\tAssetTransferInstructions,\n\t'value' | 'assetFee' | 'totalReceiveAmount' | 'persistentAddressId'\n>;\n\n/**\n * The type of provider information allowed for a contact address.\n */\nexport type ProviderInformationType = 'username' | 'template';\n\n/**\n * The type of recipient for a persistent address template.\n */\nexport type PersistentAddressTemplateRecipient = Extract<RecipientResolved, { type: 'persistent-address' }>;\n\n/**\n * Base interface for contact addresses with narrowed generic parameters.\n */\nexport interface ContactAddressBase<\n\tRecipientType extends RecipientResolved,\n\tLocation extends AssetLocationLike,\n\tProviderInformationAllowedTypes extends ProviderInformationType\n> {\n\trecipient: RecipientType;\n\tlocation?: Location;\n\tproviderInformation?: { [providerId: string]: ProviderInformationAllowedTypes[] };\n\tpastInstructions?: TransferInstructionShape[];\n}\n\nexport type KeetaAssetLocation = PickChainLocation<'keeta'> | `chain:keeta:${bigint}`;\n\n/**\n * A contact address for a Keeta account.\n */\nexport type KeetaContactAddress = ContactAddressBase<string, KeetaAssetLocation, 'username'>;\n\n/**\n * A contact address for a persistent address template.\n */\nexport type TemplateContactAddress = ContactAddressBase<PersistentAddressTemplateRecipient, AssetLocationLike, 'template'>;\n\n/**\n * A contact address for a non-Keeta, non-persistent-address recipient.\n */\nexport type OtherContactAddress = ContactAddressBase<\n\tExclude<RecipientResolved, KeetaNetAccount | PersistentAddressTemplateRecipient>,\n\tExclude<AssetLocationLike, KeetaAssetLocation>,\n\t'template'\n>;\n\nexport type ContactAddress = KeetaContactAddress | TemplateContactAddress | OtherContactAddress;\n\n/**\n * A stored contact with metadata and an address.\n */\nexport interface Contact {\n\tid: string;\n\tlabel: string;\n\taddress: ContactAddress;\n\trail?: Rail;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\ninterface SharedStorageObjectMetadata extends Pick<StorageObjectMetadata, 'createdAt' | 'updatedAt'> {};\n\nexport interface ContactWithMetadata extends Contact, SharedStorageObjectMetadata {}\n\n// #endregion\n\n// #region Interface\n\n/**\n * Generic contacts client interface\n */\nexport interface ContactsClient {\n\tderiveId(address: ContactAddress): string;\n\n\tcreate(options: {\n\t\tlabel: string;\n\t\taddress: ContactAddress;\n\t\trail?: Rail;\n\t}): Promise<Contact>;\n\n\tget(id: string): Promise<Contact | null>;\n\n\tupdate(id: string, options: {\n\t\tlabel?: string;\n\t\taddress?: ContactAddress;\n\t}): Promise<Contact>;\n\n\tdelete(id: string): Promise<boolean>;\n\n\tlist(options?: {\n\t\tlocation?: AssetLocationLike;\n\t}): Promise<Contact[]>;\n}\n\n// #endregion\n\n// #region Storage Implementation\n\n/**\n * MIME type for contact data.\n */\nconst MIME_TYPE = 'application/json';\n\n/**\n * Canonicalize a contact address for use in ID derivation.\n * Excludes metadata fields (`providerInformation`, `pastInstructions`) that are not part of contact identity.\n *\n * @param address - The contact address to canonicalize.\n *\n * @returns The canonicalized string representation of the contact address identity fields.\n */\nfunction canonicalizeContactAddress(address: ContactAddress): string {\n\tconst { providerInformation: _, pastInstructions: __, ...identity } = address; // eslint-disable-line @typescript-eslint/no-unused-vars\n\treturn(JSON.stringify(identity, function(_key: string, value: unknown): unknown {\n\t\tif (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\t\t\tconst obj = value as { [k: string]: unknown };\n\t\t\treturn(Object.fromEntries(\n\t\t\t\tObject.entries(obj).sort(([a], [b]) => a.localeCompare(b))\n\t\t\t));\n\t\t}\n\n\t\treturn(value);\n\t}));\n}\n\n/**\n * Convert an asset location to a tag for use in storage.\n *\n * @param location - The asset location to convert to a tag.\n *\n * @returns The tag string.\n */\nfunction locationToTag(location: AssetLocationLike): string {\n\tconst str = convertAssetLocationToString(location);\n\n\tconst parts = str.split(':');\n\treturn(parts.join('-'));\n}\n\n/**\n * Convert a contact address to a list of tags for use in storage.\n *\n * @param address - The contact address to convert to tags.\n *\n * @returns The list of tags.\n */\nfunction contactTags(address: ContactAddress): string[] {\n\tif (address.location) {\n\t\treturn([locationToTag(address.location)]);\n\t}\n\n\treturn([]);\n}\n\n/**\n * Storage Anchor-backed implementation of `ContactsClient`.\n * Stores contacts as encrypted JSON objects via a `KeetaStorageAnchorSession`.\n */\nexport class StorageContactsClient implements ContactsClient {\n\treadonly #session: KeetaStorageAnchorSession;\n\treadonly #logger?: Logger | undefined;\n\n\tconstructor(config: StorageSubClientConfig) {\n\t\tthis.#session = config.session;\n\t\tthis.#logger = config.logger;\n\t}\n\n\tderiveId(address: ContactAddress): string {\n\t\tconst data = Buffer.from(canonicalizeContactAddress(address));\n\t\tconst hash = KeetaNetLib.Utils.Hash.Hash(data);\n\n\t\tconst result = Buffer.from(hash).toString('hex');\n\t\treturn(result);\n\t}\n\n\t#serialize(contact: Contact): Buffer {\n\t\treturn(Buffer.from(JSON.stringify(contact)));\n\t}\n\n\t#deserialize(data: Buffer, metadata: SharedStorageObjectMetadata): ContactWithMetadata;\n\t#deserialize(data: Buffer, metadata: null): Contact;\n\t#deserialize(data: Buffer, metadata: SharedStorageObjectMetadata | null): ContactWithMetadata | Contact;\n\t#deserialize(data: Buffer, metadata: SharedStorageObjectMetadata | null): ContactWithMetadata | Contact {\n\t\tconst contact = assertContact(JSON.parse(data.toString()));\n\n\t\tif (metadata) {\n\t\t\treturn({\n\t\t\t\t...contact,\n\t\t\t\t...metadata\n\t\t\t});\n\t\t} else {\n\t\t\treturn(contact);\n\t\t}\n\t}\n\n\tasync create(options: {\n\t\tlabel: string;\n\t\taddress: ContactAddress;\n\t\trail?: Rail | undefined;\n\t}): Promise<Contact> {\n\t\tconst id = this.deriveId(options.address);\n\t\tthis.#logger?.debug('StorageContactsClient::create', `Creating contact ${id}`);\n\n\t\tconst contact: Contact = assertContact({\n\t\t\tid,\n\t\t\tlabel: options.label,\n\t\t\taddress: options.address,\n\t\t\t...(options.rail !== undefined ? { rail: options.rail } : {})\n\t\t});\n\n\t\tawait this.#session.put(id, this.#serialize(contact), {\n\t\t\tmimeType: MIME_TYPE,\n\t\t\ttags: contactTags(options.address)\n\t\t});\n\n\t\tthis.#logger?.debug('StorageContactsClient::create', `Contact created: ${id}`);\n\t\treturn(contact);\n\t}\n\n\tasync get(id: string, includeMetadata: true): Promise<ContactWithMetadata | null>;\n\tasync get(id: string, includeMetadata?: false): Promise<Contact | null>;\n\tasync get(id: string, includeMetadata?: boolean) {\n\t\tthis.#logger?.debug('StorageContactsClient::get', `Getting contact ${id}`);\n\n\t\tconst [ result, metadata ] = await Promise.all([\n\t\t\tthis.#session.get(id),\n\t\t\tincludeMetadata ? this.#session.getMetadata(id) : Promise.resolve(null)\n\t\t]);\n\n\t\tif (!result) {\n\t\t\tthis.#logger?.debug('StorageContactsClient::get', `Contact not found: ${id}`);\n\t\t\treturn(null);\n\t\t}\n\n\t\tthis.#logger?.debug('StorageContactsClient::get', `Contact retrieved: ${id}`);\n\t\treturn(this.#deserialize(result.data, metadata));\n\t}\n\n\tasync update(id: string, options: {\n\t\tlabel?: string;\n\t\taddress?: ContactAddress;\n\t}): Promise<Contact> {\n\t\tthis.#logger?.debug('StorageContactsClient::update', `Updating contact ${id}`);\n\n\t\tconst existing = await this.get(id);\n\t\tif (!existing) {\n\t\t\tthrow(new Errors.DocumentNotFound(`Contact not found: ${id}`));\n\t\t}\n\n\t\tconst newAddress = options.address ?? existing.address;\n\t\tconst newId = this.deriveId(newAddress);\n\n\t\tconst updated: Contact = assertContact({\n\t\t\tid: newId,\n\t\t\tlabel: options.label ?? existing.label,\n\t\t\taddress: newAddress,\n\t\t\t...(existing.rail !== undefined ? { rail: existing.rail } : {})\n\t\t});\n\n\t\tawait this.#session.put(newId, this.#serialize(updated), {\n\t\t\tmimeType: MIME_TYPE,\n\t\t\ttags: contactTags(updated.address)\n\t\t});\n\n\t\tif (newId !== id) {\n\t\t\ttry {\n\t\t\t\tawait this.#session.delete(id);\n\t\t\t} catch (error) {\n\t\t\t\tthis.#logger?.warn('StorageContactsClient::update', `Failed to delete old contact ${id} after re-keying to ${newId}`, error);\n\t\t\t}\n\t\t}\n\n\t\tthis.#logger?.debug('StorageContactsClient::update', `Contact updated: ${newId}`);\n\t\treturn(updated);\n\t}\n\n\tasync delete(id: string): Promise<boolean> {\n\t\tthis.#logger?.debug('StorageContactsClient::delete', `Deleting contact ${id}`);\n\t\tconst result = await this.#session.delete(id);\n\t\tthis.#logger?.debug('StorageContactsClient::delete', `Contact delete ${id}: ${result ? 'removed' : 'not found'}`);\n\t\treturn(result);\n\t}\n\n\tasync list(options?: {\n\t\tlocation?: AssetLocationLike;\n\t}): Promise<ContactWithMetadata[]> {\n\t\tthis.#logger?.debug('StorageContactsClient::list', 'Listing contacts');\n\n\t\tconst criteria: { pathPrefix: string; tags?: string[] } = {\n\t\t\tpathPrefix: this.#session.workingDirectory\n\t\t};\n\n\t\tif (options?.location) {\n\t\t\tcriteria.tags = [locationToTag(options.location)];\n\t\t}\n\n\t\tconst searchResult = await this.#session.search(criteria);\n\t\tconst contacts: ContactWithMetadata[] = [];\n\t\tfor (const metadata of searchResult.results) {\n\t\t\tconst result = await this.#session.get(metadata.path);\n\t\t\tif (result) {\n\t\t\t\ttry {\n\t\t\t\t\tcontacts.push(this.#deserialize(result.data, metadata));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.#logger?.warn('StorageContactsClient::list', `Skipping corrupt contact at ${metadata.path}`, error);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.#logger?.debug('StorageContactsClient::list', `Listed ${contacts.length} contacts`);\n\t\treturn(contacts);\n\t}\n}\n\n// #endregion\n"]}
|
|
1
|
+
{"version":3,"file":"contacts.js","sourceRoot":"","sources":["../../../../src/services/storage/clients/contacts.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssetTransferInstructions, RecipientResolved, KeetaNetAccount, Rail } from '../../asset-movement/common.js';\nimport type { AssetLocationLike, PickChainLocation } from '../../asset-movement/lib/location.js';\nimport type { KeetaStorageAnchorSession, StorageSubClientConfig } from '../client.js';\nimport type { Logger } from '../../../lib/log/index.js';\nimport type { StorageObjectMetadata } from '../common.js';\nimport { lib as KeetaNetLib } from '@keetanetwork/keetanet-client';\nimport { convertAssetLocationToString } from '../../asset-movement/lib/location.js';\nimport { Errors } from '../common.js';\nimport { Buffer } from '../../../lib/utils/buffer.js';\nimport { assertContact } from './contacts.generated.js';\n\n// #region Types\n\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\n/**\n * The structural shape of a transfer instruction, excluding transaction-specific fields.\n */\nexport type TransferInstructionShape = DistributiveOmit<\n\tAssetTransferInstructions,\n\t'value' | 'assetFee' | 'totalReceiveAmount' | 'persistentAddressId'\n>;\n\n/**\n * The type of provider information allowed for a contact address.\n */\nexport type ProviderInformationType = 'username' | 'template';\n\n/**\n * The type of recipient for a persistent address template.\n */\nexport type PersistentAddressTemplateRecipient = Extract<RecipientResolved, { type: 'persistent-address' }>;\n\n/**\n * Base interface for contact addresses with narrowed generic parameters.\n */\nexport interface ContactAddressBase<\n\tRecipientType extends RecipientResolved,\n\tLocation extends AssetLocationLike,\n\tProviderInformationAllowedTypes extends ProviderInformationType\n> {\n\trecipient: RecipientType;\n\tlocation?: Location;\n\tproviderInformation?: { [providerId: string]: ProviderInformationAllowedTypes[] };\n\tpastInstructions?: TransferInstructionShape[];\n}\n\nexport type KeetaAssetLocation = PickChainLocation<'keeta'> | `chain:keeta:${bigint}`;\n\n/**\n * A contact address for a Keeta account.\n */\nexport type KeetaContactAddress = ContactAddressBase<string, KeetaAssetLocation, 'username'>;\n\n/**\n * A contact address for a persistent address template.\n */\nexport type TemplateContactAddress = ContactAddressBase<PersistentAddressTemplateRecipient, AssetLocationLike, 'template'>;\n\n/**\n * A contact address for a non-Keeta, non-persistent-address recipient.\n */\nexport type OtherContactAddress = ContactAddressBase<\n\tExclude<RecipientResolved, KeetaNetAccount | PersistentAddressTemplateRecipient>,\n\tExclude<AssetLocationLike, KeetaAssetLocation>,\n\t'template'\n>;\n\nexport type ContactAddress = KeetaContactAddress | TemplateContactAddress | OtherContactAddress;\n\n/**\n * A stored contact with metadata and an address.\n */\nexport interface Contact {\n\tid: string;\n\tlabel: string;\n\taddress: ContactAddress;\n\trail?: Rail | undefined;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\ninterface SharedStorageObjectMetadata extends Pick<StorageObjectMetadata, 'createdAt' | 'updatedAt'> {};\n\nexport interface ContactWithMetadata extends Contact, SharedStorageObjectMetadata {}\n\ninterface ContactOptions extends Omit<Contact, 'id'>, Partial<Pick<Contact, 'id'>> {}\n\n// #endregion\n\n// #region Interface\n\n/**\n * Generic contacts client interface\n */\nexport interface ContactsClient {\n\tderiveId(address: ContactAddress): string;\n\n\tcreate(options: ContactOptions): Promise<Contact>;\n\n\tget(id: string): Promise<Contact | null>;\n\n\tupdate(id: string, options: {\n\t\tlabel?: string;\n\t\taddress?: ContactAddress;\n\t}): Promise<Contact>;\n\n\tdelete(id: string): Promise<boolean>;\n\n\tlist(options?: {\n\t\tlocation?: AssetLocationLike;\n\t}): Promise<Contact[]>;\n}\n\n// #endregion\n\n// #region Storage Implementation\n\n/**\n * MIME type for contact data.\n */\nconst MIME_TYPE = 'application/json';\n\n/**\n * Canonicalize a contact address for use in ID derivation.\n * Excludes metadata fields (`providerInformation`, `pastInstructions`) that are not part of contact identity.\n *\n * @param address - The contact address to canonicalize.\n *\n * @returns The canonicalized string representation of the contact address identity fields.\n */\nfunction canonicalizeContactAddress(address: ContactAddress): string {\n\tconst { providerInformation: _, pastInstructions: __, ...identity } = address; // eslint-disable-line @typescript-eslint/no-unused-vars\n\treturn(JSON.stringify(identity, function(_key: string, value: unknown): unknown {\n\t\tif (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\t\t\tconst obj = value as { [k: string]: unknown };\n\t\t\treturn(Object.fromEntries(\n\t\t\t\tObject.entries(obj).sort(([a], [b]) => a.localeCompare(b))\n\t\t\t));\n\t\t}\n\n\t\treturn(value);\n\t}));\n}\n\n/**\n * Convert an asset location to a tag for use in storage.\n *\n * @param location - The asset location to convert to a tag.\n *\n * @returns The tag string.\n */\nfunction locationToTag(location: AssetLocationLike): string {\n\tconst str = convertAssetLocationToString(location);\n\n\tconst parts = str.split(':');\n\treturn(parts.join('-'));\n}\n\n/**\n * Convert a contact address to a list of tags for use in storage.\n *\n * @param address - The contact address to convert to tags.\n *\n * @returns The list of tags.\n */\nfunction contactTags(address: ContactAddress): string[] {\n\tif (address.location) {\n\t\treturn([locationToTag(address.location)]);\n\t}\n\n\treturn([]);\n}\n\n/**\n * Storage Anchor-backed implementation of `ContactsClient`.\n * Stores contacts as encrypted JSON objects via a `KeetaStorageAnchorSession`.\n */\nexport class StorageContactsClient implements ContactsClient {\n\treadonly #session: KeetaStorageAnchorSession;\n\treadonly #logger?: Logger | undefined;\n\n\tconstructor(config: StorageSubClientConfig) {\n\t\tthis.#session = config.session;\n\t\tthis.#logger = config.logger;\n\t}\n\n\tderiveId(address: ContactAddress): string {\n\t\tconst data = Buffer.from(canonicalizeContactAddress(address));\n\t\tconst hash = KeetaNetLib.Utils.Hash.Hash(data);\n\n\t\tconst result = Buffer.from(hash).toString('hex');\n\t\treturn(result);\n\t}\n\n\t#serialize(contact: Contact): Buffer {\n\t\treturn(Buffer.from(JSON.stringify(contact)));\n\t}\n\n\t#deserialize(data: Buffer, metadata: SharedStorageObjectMetadata): ContactWithMetadata;\n\t#deserialize(data: Buffer, metadata: null): Contact;\n\t#deserialize(data: Buffer, metadata: SharedStorageObjectMetadata | null): ContactWithMetadata | Contact;\n\t#deserialize(data: Buffer, metadata: SharedStorageObjectMetadata | null): ContactWithMetadata | Contact {\n\t\tconst contact = assertContact(JSON.parse(data.toString()));\n\n\t\tif (metadata) {\n\t\t\treturn({\n\t\t\t\t...contact,\n\t\t\t\t...metadata\n\t\t\t});\n\t\t} else {\n\t\t\treturn(contact);\n\t\t}\n\t}\n\n\tasync create(options: ContactOptions): Promise<Contact> {\n\t\tconst id = options.id ?? this.deriveId(options.address);\n\t\tthis.#logger?.debug('StorageContactsClient::create', `Creating contact ${id}`);\n\n\t\tconst contact: Contact = assertContact({\n\t\t\tid,\n\t\t\tlabel: options.label,\n\t\t\taddress: options.address,\n\t\t\t...(options.rail !== undefined ? { rail: options.rail } : {})\n\t\t});\n\n\t\tawait this.#session.put(id, this.#serialize(contact), {\n\t\t\tmimeType: MIME_TYPE,\n\t\t\ttags: contactTags(options.address)\n\t\t});\n\n\t\tthis.#logger?.debug('StorageContactsClient::create', `Contact created: ${id}`);\n\t\treturn(contact);\n\t}\n\n\tasync get(id: string, includeMetadata: true): Promise<ContactWithMetadata | null>;\n\tasync get(id: string, includeMetadata?: false): Promise<Contact | null>;\n\tasync get(id: string, includeMetadata?: boolean) {\n\t\tthis.#logger?.debug('StorageContactsClient::get', `Getting contact ${id}`);\n\n\t\tconst [ result, metadata ] = await Promise.all([\n\t\t\tthis.#session.get(id),\n\t\t\tincludeMetadata ? this.#session.getMetadata(id) : Promise.resolve(null)\n\t\t]);\n\n\t\tif (!result) {\n\t\t\tthis.#logger?.debug('StorageContactsClient::get', `Contact not found: ${id}`);\n\t\t\treturn(null);\n\t\t}\n\n\t\tthis.#logger?.debug('StorageContactsClient::get', `Contact retrieved: ${id}`);\n\t\treturn(this.#deserialize(result.data, metadata));\n\t}\n\n\tasync update(id: string, options: {\n\t\tlabel?: string;\n\t\taddress?: ContactAddress;\n\t}): Promise<Contact> {\n\t\tthis.#logger?.debug('StorageContactsClient::update', `Updating contact ${id}`);\n\n\t\tconst existing = await this.get(id);\n\t\tif (!existing) {\n\t\t\tthrow(new Errors.DocumentNotFound(`Contact not found: ${id}`));\n\t\t}\n\n\t\tconst newAddress = options.address ?? existing.address;\n\t\tconst newId = this.deriveId(newAddress);\n\n\t\tconst updated: Contact = assertContact({\n\t\t\tid: newId,\n\t\t\tlabel: options.label ?? existing.label,\n\t\t\taddress: newAddress,\n\t\t\t...(existing.rail !== undefined ? { rail: existing.rail } : {})\n\t\t});\n\n\t\tawait this.#session.put(newId, this.#serialize(updated), {\n\t\t\tmimeType: MIME_TYPE,\n\t\t\ttags: contactTags(updated.address)\n\t\t});\n\n\t\tif (newId !== id) {\n\t\t\ttry {\n\t\t\t\tawait this.#session.delete(id);\n\t\t\t} catch (error) {\n\t\t\t\tthis.#logger?.warn('StorageContactsClient::update', `Failed to delete old contact ${id} after re-keying to ${newId}`, error);\n\t\t\t}\n\t\t}\n\n\t\tthis.#logger?.debug('StorageContactsClient::update', `Contact updated: ${newId}`);\n\t\treturn(updated);\n\t}\n\n\tasync delete(id: string): Promise<boolean> {\n\t\tthis.#logger?.debug('StorageContactsClient::delete', `Deleting contact ${id}`);\n\t\tconst result = await this.#session.delete(id);\n\t\tthis.#logger?.debug('StorageContactsClient::delete', `Contact delete ${id}: ${result ? 'removed' : 'not found'}`);\n\t\treturn(result);\n\t}\n\n\tasync list(options?: {\n\t\tlocation?: AssetLocationLike;\n\t}): Promise<ContactWithMetadata[]> {\n\t\tthis.#logger?.debug('StorageContactsClient::list', 'Listing contacts');\n\n\t\tconst criteria: { pathPrefix: string; tags?: string[] } = {\n\t\t\tpathPrefix: this.#session.workingDirectory\n\t\t};\n\n\t\tif (options?.location) {\n\t\t\tcriteria.tags = [locationToTag(options.location)];\n\t\t}\n\n\t\tconst searchResult = await this.#session.search(criteria);\n\t\tconst contacts: ContactWithMetadata[] = [];\n\t\tfor (const metadata of searchResult.results) {\n\t\t\tconst result = await this.#session.get(metadata.path);\n\t\t\tif (result) {\n\t\t\t\ttry {\n\t\t\t\t\tcontacts.push(this.#deserialize(result.data, metadata));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.#logger?.warn('StorageContactsClient::list', `Skipping corrupt contact at ${metadata.path}`, error);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.#logger?.debug('StorageContactsClient::list', `Listed ${contacts.length} contacts`);\n\t\treturn(contacts);\n\t}\n}\n\n// #endregion\n"]}
|