dotrino-content 0.3.7 → 0.3.9

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/bin/cli.js CHANGED
@@ -100,8 +100,9 @@ if (cmd === 'enroll') {
100
100
  console.log(' (el código NO viaja por la red: lo tipeas tú)')
101
101
  }
102
102
  })
103
- const days = Math.round((res.cert.exp - Date.now()) / 86400000)
104
- console.log(`\nlisto: node enlazado. Certificado válido ${days} días (se renueva solo).`)
103
+ // El papel ya no vence por tiempo: vale mientras el acta lo diga, y se rehace solo
104
+ // cuando cambias permisos. Decir «válido N días» era describir un reloj que ya no existe.
105
+ console.log(`\nlisto: node enlazado. El permiso vale mientras lo diga tu bóveda; si se lo quitas, deja de valer en el acto.`)
105
106
  console.log(`identidad en ${serviceDir()} · enlace en ${linkDir()}`)
106
107
  console.log(`\nahora carga su configuración en la bóveda (namespace «${NS}»):`)
107
108
  console.log(' dotrino-vault secret set content CONTENT_STORAGE=local --public')
package/lib/ref.js CHANGED
@@ -31,8 +31,8 @@ export const isValidCid = (cid) => typeof cid === 'string' && CID_RE.test(cid)
31
31
  * @returns {string} `<owner>/<cid>` o `<owner>/<cid>/<llave>`
32
32
  */
33
33
  export function buildRef ({ owner, cid, key = null }) {
34
- if (!owner) throw new Error('buildRef: falta owner')
35
- if (!isValidCid(cid)) throw new Error(`buildRef: cid inválido: ${cid}`)
34
+ if (!owner) throw new Error('buildRef: owner is required')
35
+ if (!isValidCid(cid)) throw new Error(`buildRef: invalid cid: ${cid}`)
36
36
  return key ? `${owner}/${cid}/${key}` : `${owner}/${cid}`
37
37
  }
38
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotrino-content",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Nodo de contenido del ecosistema Dotrino: guarda los bytes del usuario direccionados por su hash (cid), en su propia maquina y, si el dueno quiere, respaldados en su propio bucket (R2, Backblaze, Hetzner, Storj o S3). Lo publico sale por URL directa; lo privado, cifrado y solo por la red Dotrino.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,9 +23,9 @@
23
23
  "typescript": "5.9.3"
24
24
  },
25
25
  "dependencies": {
26
- "@dotrino/identity": "0.57.0",
27
- "@dotrino/remote-agent": "0.5.1",
28
- "@dotrino/vault": "0.30.0"
26
+ "@dotrino/identity": "0.74.0",
27
+ "@dotrino/remote-agent": "0.6.2",
28
+ "@dotrino/vault": "0.44.0"
29
29
  },
30
30
  "keywords": [
31
31
  "dotrino",
package/src/agent.js CHANGED
@@ -53,10 +53,10 @@ export const isLinked = (dir = linkDir()) => {
53
53
  export async function startContentAgent ({
54
54
  node, dir = linkDir(), proxyUrl, version = null, client, quiet = false, onRevoked, announce = true
55
55
  } = {}) {
56
- if (!node) throw new Error('startContentAgent: falta node')
56
+ if (!node) throw new Error('startContentAgent: node is required')
57
57
  const link = loadLink(dir)
58
58
  if (!link?.iss) {
59
- throw new Error('este node no está enlazado a un vault. Ejecuta primero: dotrino-content enroll <código>')
59
+ throw new Error('this node is not linked to a vault. Run first: dotrino-content enroll <code>')
60
60
  }
61
61
 
62
62
  // El node representa a la maestra que lo certificó: su huella es el `ownerId`.
package/src/announce.js CHANGED
@@ -51,8 +51,8 @@ export const REPUBLISH_MS = 4 * 60 * 1000
51
51
  * @returns {{ channels: () => string[], close: () => void }}
52
52
  */
53
53
  export function startAnnounce ({ client, owner, quiet = false, intervalMs = REPUBLISH_MS }) {
54
- if (!client) throw new Error('startAnnounce: falta client')
55
- if (!owner) throw new Error('startAnnounce: falta owner')
54
+ if (!client) throw new Error('startAnnounce: client is required')
55
+ if (!owner) throw new Error('startAnnounce: owner is required')
56
56
 
57
57
  let stopped = false
58
58
  let current = []
@@ -73,7 +73,7 @@ export function startAnnounce ({ client, owner, quiet = false, intervalMs = REPU
73
73
  await client.publish(name, { app: 'content', owner })
74
74
  done.push(name)
75
75
  } catch (e) {
76
- if (!quiet) console.error(`[content] no me pude anunciar en ${name}: ${e.message}`)
76
+ if (!quiet) console.error(`[content] could not announce on ${name}: ${e.message}`)
77
77
  }
78
78
  }
79
79
  // Se avisa cuando CAMBIA en cuántos proxios está anunciado, no solo la primera vez.
@@ -82,7 +82,7 @@ export function startAnnounce ({ client, owner, quiet = false, intervalMs = REPU
82
82
  // estuviera en los dos. Leyendo ese log parecía una federación rota que no lo estaba.
83
83
  const changed = done.length !== current.length
84
84
  current = done
85
- if (changed && !quiet) console.log(`[content] anunciado como node de ${owner.slice(0, 16)} en ${done.length} proxio(s) · token ${client.token}`)
85
+ if (changed && !quiet) console.log(`[content] announced as node for ${owner.slice(0, 16)} on ${done.length} proxio(s) · token ${client.token}`)
86
86
  }
87
87
 
88
88
  publishAll()
@@ -120,7 +120,7 @@ export function startAnnounce ({ client, owner, quiet = false, intervalMs = REPU
120
120
  * @returns {Promise<string[]>} tokens (direcciones en el proxy)
121
121
  */
122
122
  export async function findNodes ({ client, owner }) {
123
- if (!client || !owner) throw new Error('findNodes: faltan client u owner')
123
+ if (!client || !owner) throw new Error('findNodes: client and owner are required')
124
124
  const known = Array.isArray(client.knownNodes) && client.knownNodes.length
125
125
  ? client.knownNodes
126
126
  : (client.node ? [client.node] : [])
@@ -33,7 +33,7 @@ const IMMUTABLE = 'public, max-age=31536000, immutable'
33
33
 
34
34
  /** Del `cid` a la clave del objeto. Sin prefijos: el bucket ya separa público de privado. */
35
35
  const keyOf = (cid) => {
36
- if (!isValidCid(cid)) throw new Error(`cid inválido: ${cid}`)
36
+ if (!isValidCid(cid)) throw new Error(`invalid cid: ${cid}`)
37
37
  return cid
38
38
  }
39
39
 
@@ -46,7 +46,7 @@ export class S3BlobStore {
46
46
  * @param {(m:string)=>void} [o.log]
47
47
  */
48
48
  constructor ({ root, priv, pub = null, log = () => {} }) {
49
- if (!priv) throw new Error('S3BlobStore: falta el bucket privado')
49
+ if (!priv) throw new Error('S3BlobStore: the private bucket is missing')
50
50
  this.cache = new BlobStore(root)
51
51
  this.priv = priv
52
52
  this.pub = pub
@@ -63,7 +63,7 @@ export class S3BlobStore {
63
63
  /** El bucket que le toca a un blob según su ACL. */
64
64
  bucketFor (isPublic) {
65
65
  if (!isPublic) return this.priv
66
- if (!this.pub) throw new Error('este node no tiene bucket público (CONTENT_S3_BUCKET_PUBLIC)')
66
+ if (!this.pub) throw new Error('this node has no public bucket (CONTENT_S3_BUCKET_PUBLIC)')
67
67
  return this.pub
68
68
  }
69
69
 
package/src/blobstore.js CHANGED
@@ -44,7 +44,7 @@ export class BlobStore {
44
44
 
45
45
  /** Path en disco de un cid (no comprueba existencia). */
46
46
  pathFor (cid) {
47
- if (!isValidCid(cid)) throw new Error(`cid inválido: ${cid}`)
47
+ if (!isValidCid(cid)) throw new Error(`invalid cid: ${cid}`)
48
48
  const hex = cid.slice('sha256-'.length)
49
49
  return path.join(this.blobsDir, hex.slice(0, 2), hex.slice(2, 4), cid)
50
50
  }
@@ -73,7 +73,7 @@ export class BlobStore {
73
73
  transform (chunk, _enc, cb) {
74
74
  size += chunk.length
75
75
  if (opts.maxBytes && size > opts.maxBytes) {
76
- cb(Object.assign(new Error('blob demasiado grande'), { code: 'ETOOBIG' }))
76
+ cb(Object.assign(new Error('blob too large'), { code: 'ETOOBIG' }))
77
77
  return
78
78
  }
79
79
  hash.update(chunk)
package/src/node.js CHANGED
@@ -22,7 +22,7 @@ export class ContentNode {
22
22
  * store: almacén ya montado (`storage.js`); por defecto, disco
23
23
  */
24
24
  constructor (opts) {
25
- if (!opts?.dir) throw new Error('falta opts.dir')
25
+ if (!opts?.dir) throw new Error('opts.dir is required')
26
26
  this.dir = opts.dir
27
27
  this.maxBytes = opts.maxBytes || 0
28
28
  this.maxBlobBytes = opts.maxBlobBytes || 0
@@ -61,7 +61,7 @@ export class ContentNode {
61
61
  const over = (this.index.cachedBytes() + size) - this.maxBytes
62
62
  if (over > 0 && this.gc({ needBytes: over }).freed < over) {
63
63
  await this.store.remove(cid)
64
- throw Object.assign(new Error('cuota de disco excedida'), { code: 'ENOSPC' })
64
+ throw Object.assign(new Error('disk quota exceeded'), { code: 'ENOSPC' })
65
65
  }
66
66
  }
67
67
  this.index.upsert({ cid, size, mime, owner: this.owner, enc, acl, ttl, meta })
@@ -120,7 +120,7 @@ export class ContentNode {
120
120
  const p = (prev ? prev.catch(() => {}) : Promise.resolve())
121
121
  .then(() => this.store.upload(cid, meta))
122
122
  .then(() => { this.index.setRemote(cid, true) })
123
- .catch((e) => { this.log(`[almacén] no se pudo subir ${cid.slice(0, 14)}…: ${e.message}`) })
123
+ .catch((e) => { this.log(`[storage] could not upload ${cid.slice(0, 14)}…: ${e.message}`) })
124
124
  .finally(() => this._uploading.delete(cid))
125
125
  this._uploading.set(cid, p)
126
126
  return p
package/src/ops.js CHANGED
@@ -47,11 +47,24 @@ const fail = (rid, code, error) => ({ rid, ok: false, code, error })
47
47
  * llegue: ni campos de más ni textos sin fin.
48
48
  * @returns {{name?:string,title?:string,description?:string}|null}
49
49
  */
50
+ /** Cuántos enlaces lleva una tarjeta, y cuánto puede medir cada uno. */
51
+ const MAX_LINKS = 4
52
+ const MAX_LINK_LEN = 300
53
+
50
54
  function cleanMeta (src) {
51
55
  if (!src || typeof src !== 'object') return null
52
56
  const out = Object.fromEntries(['name', 'title', 'description']
53
57
  .map((k) => [k, typeof src[k] === 'string' ? src[k].trim().slice(0, 300) : null])
54
58
  .filter(([, v]) => v))
59
+ // Los ENLACES del contenido (en un eco, la fuente de la que habla). Solo http(s)
60
+ // y contados: `meta` lo escribe quien publica y la tarjeta lo pinta como `href`,
61
+ // así que aquí se filtra en vez de creerse.
62
+ const links = (Array.isArray(src.links) ? src.links : [])
63
+ .filter((u) => typeof u === 'string')
64
+ .map((u) => u.trim())
65
+ .filter((u) => /^https?:\/\//i.test(u) && u.length <= MAX_LINK_LEN)
66
+ .slice(0, MAX_LINKS)
67
+ if (links.length) out.links = links
55
68
  return Object.keys(out).length ? out : null
56
69
  }
57
70
 
package/src/s3.js CHANGED
@@ -142,7 +142,7 @@ export class S3Bucket {
142
142
  */
143
143
  constructor ({ endpoint, bucket, accessKeyId, secretAccessKey, region = 'auto', fetch: f = fetch }) {
144
144
  if (!endpoint || !bucket || !accessKeyId || !secretAccessKey) {
145
- throw new Error('S3Bucket: faltan endpoint, bucket o credenciales')
145
+ throw new Error('S3Bucket: missing endpoint, bucket or credentials')
146
146
  }
147
147
  this.base = `${String(endpoint).replace(/\/+$/, '')}/${bucket}`
148
148
  this.bucket = bucket
@@ -198,7 +198,7 @@ export class S3Bucket {
198
198
  * @param {{ sha256?: string, size?: number, contentType?: string, cacheControl?: string }} [o]
199
199
  */
200
200
  async put (key, body, { sha256 = '', size = 0, contentType = '', cacheControl = '' } = {}) {
201
- if (!sha256) throw new Error('S3Bucket.put: hace falta el sha256 del contenido')
201
+ if (!sha256) throw new Error('S3Bucket.put: the sha256 of the content is required')
202
202
  /** @type {Record<string,string>} */
203
203
  const headers = {}
204
204
  if (contentType) headers['content-type'] = contentType
package/src/server.js CHANGED
@@ -49,8 +49,8 @@ export function createServer (node) {
49
49
  await route(node, req, res)
50
50
  } catch (err) {
51
51
  if (res.headersSent) { res.destroy(); return }
52
- if (err.code === 'ETOOBIG') return json(res, 413, { error: 'blob demasiado grande' })
53
- if (err.code === 'ENOSPC') return json(res, 507, { error: 'cuota de disco excedida' })
52
+ if (err.code === 'ETOOBIG') return json(res, 413, { error: 'blob too large' })
53
+ if (err.code === 'ENOSPC') return json(res, 507, { error: 'disk quota exceeded' })
54
54
  json(res, 500, { error: err.message })
55
55
  }
56
56
  })
@@ -71,16 +71,16 @@ async function route (node, req, res) {
71
71
  }
72
72
 
73
73
  if (top === 'c' && cid) {
74
- if (!isValidCid(cid)) return json(res, 400, { error: 'cid inválido' })
74
+ if (!isValidCid(cid)) return json(res, 400, { error: 'invalid cid' })
75
75
  const meta = node.stat(cid)
76
- if (!meta) return json(res, 404, { error: 'no existe' })
76
+ if (!meta) return json(res, 404, { error: 'not found' })
77
77
 
78
78
  if (req.method === 'DELETE') {
79
79
  await node.remove(cid)
80
80
  return json(res, 200, { ok: true })
81
81
  }
82
82
  if (req.method !== 'GET' && req.method !== 'HEAD') {
83
- return json(res, 405, { error: 'método no permitido' })
83
+ return json(res, 405, { error: 'method not allowed' })
84
84
  }
85
85
 
86
86
  const headers = {
@@ -115,9 +115,9 @@ async function route (node, req, res) {
115
115
  if (req.method === 'GET' && top === 'stats') return json(res, 200, node.stats())
116
116
 
117
117
  if (req.method === 'POST' && (top === 'pin' || top === 'unpin') && cid) {
118
- if (!isValidCid(cid)) return json(res, 400, { error: 'cid inválido' })
118
+ if (!isValidCid(cid)) return json(res, 400, { error: 'invalid cid' })
119
119
  const ok = node.pin(cid, top === 'pin')
120
- return ok ? json(res, 200, { ok: true }) : json(res, 404, { error: 'no existe' })
120
+ return ok ? json(res, 200, { ok: true }) : json(res, 404, { error: 'not found' })
121
121
  }
122
122
 
123
123
  // PUBLICAR / despublicar. Existía solo por el plano de control, y eso dejaba sin poder
@@ -127,16 +127,16 @@ async function route (node, req, res) {
127
127
  // Con bucket detrás, marcar público MUEVE los bytes al bucket público, que es otro
128
128
  // (§15.1): por eso pasa por `node.setAcl` y no por el índice a secas.
129
129
  if (req.method === 'POST' && (top === 'public' || top === 'private') && cid) {
130
- if (!isValidCid(cid)) return json(res, 400, { error: 'cid inválido' })
130
+ if (!isValidCid(cid)) return json(res, 400, { error: 'invalid cid' })
131
131
  const meta = node.stat(cid)
132
- if (!meta) return json(res, 404, { error: 'no existe' })
132
+ if (!meta) return json(res, 404, { error: 'not found' })
133
133
  // Un blob CIFRADO no puede ser público, y se rechaza aquí además de en el índice:
134
134
  // es la misma frontera del §7.2, y una frontera que solo se comprueba en un sitio
135
135
  // acaba teniendo un camino que no pasa por ese sitio.
136
- if (top === 'public' && meta.enc) return json(res, 400, { error: 'un blob cifrado no puede ser público' })
136
+ if (top === 'public' && meta.enc) return json(res, 400, { error: 'an encrypted blob cannot be public' })
137
137
  const ok = node.setAcl(cid, top === 'public' ? 'public' : null)
138
- return ok ? json(res, 200, { ok: true, acl: top === 'public' ? 'public' : null }) : json(res, 404, { error: 'no existe' })
138
+ return ok ? json(res, 200, { ok: true, acl: top === 'public' ? 'public' : null }) : json(res, 404, { error: 'not found' })
139
139
  }
140
140
 
141
- json(res, 404, { error: 'ruta desconocida' })
141
+ json(res, 404, { error: 'unknown route' })
142
142
  }
package/src/storage.js CHANGED
@@ -83,7 +83,7 @@ export async function checkBuckets (cfg, { priv, pub }, f = fetch) {
83
83
 
84
84
  // 1. El mismo bucket para las dos cosas: lo privado acabaría en el que tiene dominio.
85
85
  if (cfg.pub && cfg.pub === cfg.priv) {
86
- fatal.push('el bucket privado y el público son el mismo: lo cifrado acabaría en el que tiene dominio')
86
+ fatal.push('the private and public buckets are the same: encrypted blobs would land in the one with a domain')
87
87
  }
88
88
 
89
89
  // 2. El privado NO puede responder sin credenciales. Se pide un objeto que no existe:
@@ -98,7 +98,7 @@ export async function checkBuckets (cfg, { priv, pub }, f = fetch) {
98
98
  try {
99
99
  const r = await f(priv.urlFor('sha256-' + '0'.repeat(64)), { method: 'GET' })
100
100
  if (r.status === 404 || r.ok) {
101
- fatal.push(`el bucket privado «${cfg.priv}» responde sin credenciales (HTTP ${r.status}): está abierto al mundo`)
101
+ fatal.push(`the private bucket "${cfg.priv}" answers without credentials (HTTP ${r.status}): it is open to the world`)
102
102
  }
103
103
  } catch (e) {
104
104
  warn.push(`no se pudo comprobar si el bucket privado está cerrado: ${e.message}`)
@@ -143,12 +143,12 @@ export async function openStore ({ dir, env = process.env, log = () => {}, check
143
143
  if (cfg.kind === 'local') return { store: await new BlobStore(dir).init(), cfg }
144
144
 
145
145
  if (!cfg.provider) {
146
- log(`[almacén] CONTENT_STORAGE=«${cfg.kind}» no es un proveedor conocido (${Object.keys(PROVIDERS).join(', ')}); sigo en local`)
146
+ log(`[storage] CONTENT_STORAGE="${cfg.kind}" is not a known provider (${Object.keys(PROVIDERS).join(', ')}); staying on LOCAL`)
147
147
  return { store: await new BlobStore(dir).init(), cfg }
148
148
  }
149
149
  if (cfg.missing.length) {
150
- log(`[almacén] ${cfg.kind} pedido pero faltan variables: ${cfg.missing.join(', ')}`)
151
- log('[almacén] sigo en LOCAL: un almacén a medio configurar es peor que ninguno')
150
+ log(`[storage] ${cfg.kind} requested but these variables are missing: ${cfg.missing.join(', ')}`)
151
+ log('[storage] staying on LOCAL: a half-configured store is worse than none')
152
152
  return { store: await new BlobStore(dir).init(), cfg }
153
153
  }
154
154
 
@@ -160,15 +160,15 @@ export async function openStore ({ dir, env = process.env, log = () => {}, check
160
160
 
161
161
  if (check) {
162
162
  const { ok, fatal, warn } = await checkBuckets(cfg, { priv, pub }, f)
163
- for (const w of warn) log(`[almacén] ⚠ ${w}`)
163
+ for (const w of warn) log(`[storage] ⚠ ${w}`)
164
164
  if (!ok) {
165
- for (const e of fatal) log(`[almacén] ✖ ${e}`)
166
- log('[almacén] sigo en LOCAL hasta que eso se arregle')
165
+ for (const e of fatal) log(`[storage] ✖ ${e}`)
166
+ log('[storage] staying on LOCAL until that is fixed')
167
167
  return { store: await new BlobStore(dir).init(), cfg }
168
168
  }
169
169
  }
170
170
 
171
- log(`[almacén] ${cfg.kind}: privado «${cfg.priv}»${cfg.pub ? `, público «${cfg.pub}» en ${cfg.baseUrl}` : ' (sin bucket público: lo público viaja por la red)'}`)
171
+ log(`[storage] ${cfg.kind}: private "${cfg.priv}"${cfg.pub ? `, public "${cfg.pub}" at ${cfg.baseUrl}` : ' (no public bucket: public reads go over the network)'}`)
172
172
  return { store: await new S3BlobStore({ root: dir, priv, pub, log }).init(), cfg }
173
173
  }
174
174
 
package/src/vaultEnv.js CHANGED
@@ -95,7 +95,7 @@ export function startVaultConfig ({ dir = serviceDir(), onSecrets, log = console
95
95
  let expired = false
96
96
  const deadline = setTimeout(() => {
97
97
  expired = true
98
- log(`[vault] la bóveda no contestó en ${Math.round(firstWaitMs / 1000)}s: arranco con lo local y me reinicio cuando llegue`)
98
+ log(`[vault] no answer in ${Math.round(firstWaitMs / 1000)}s: starting with the local config and restarting when it arrives`)
99
99
  arrive(null)
100
100
  }, firstWaitMs)
101
101
  deadline.unref?.()
@@ -106,13 +106,13 @@ export function startVaultConfig ({ dir = serviceDir(), onSecrets, log = console
106
106
  const secrets = await waitForSecrets({
107
107
  dir,
108
108
  ns: NS,
109
- onRetry: (e, delay) => log(`[vault] sin configuración todavía (${e.message}); reintento en ${Math.round(delay / 1000)}s`)
109
+ onRetry: (e, delay) => log(`[vault] no config yet (${e.message}); retrying in ${Math.round(delay / 1000)}s`)
110
110
  })
111
111
  if (stopped) return
112
112
 
113
113
  const { injected, overridden } = applyEnv(secrets)
114
- log(`[vault] ${injected.length} valor(es) del vault aplicados al entorno`)
115
- if (overridden.length) log(`[vault] pisaron el entorno de esta máquina: ${overridden.join(', ')}`)
114
+ log(`[vault] applied ${injected.length} value(s) from the vault to the environment`)
115
+ if (overridden.length) log(`[vault] these overrode the machine environment: ${overridden.join(', ')}`)
116
116
  clearTimeout(deadline)
117
117
  arrive(secrets)
118
118
  onSecrets?.(secrets)
@@ -124,7 +124,7 @@ export function startVaultConfig ({ dir = serviceDir(), onSecrets, log = console
124
124
  // configuración correcta puesta en el entorno y sin usarla. Se sale, y el
125
125
  // supervisor lo levanta ya con todo.
126
126
  if (expired) {
127
- log('[vault] llegó la configuración después de arrancar: me reinicio para tomarla')
127
+ log('[vault] config arrived after startup: restarting to pick it up')
128
128
  setTimeout(() => process.exit(0), 300)
129
129
  return
130
130
  }
@@ -139,13 +139,13 @@ export function startVaultConfig ({ dir = serviceDir(), onSecrets, log = console
139
139
  ...(onChange
140
140
  ? {
141
141
  onUpdate: ({ reason }) => {
142
- log(`[vault] ${reason === 'revoked' ? 'este aparato fue revocado' : 'llegó configuración nueva'}`)
142
+ log(`[vault] ${reason === 'revoked' ? 'this device was revoked' : 'new config arrived'}`)
143
143
  onChange()
144
144
  }
145
145
  }
146
146
  : {})
147
147
  })
148
- })().catch((e) => { log(`[vault] no se pudo leer la configuración: ${e.message}`); clearTimeout(deadline); arrive(null) })
148
+ })().catch((e) => { log(`[vault] could not read the config: ${e.message}`); clearTimeout(deadline); arrive(null) })
149
149
 
150
150
  return {
151
151
  enabled: true,