apispain 1.0.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.
- package/README.md +111 -0
- package/index.js +98 -0
- package/package.json +17 -0
- package/test.js +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# apispain
|
|
2
|
+
|
|
3
|
+
SDK oficial para la [API de datos públicos de España](https://apispain.es).
|
|
4
|
+
|
|
5
|
+
Accede a licitaciones (PLACE), subvenciones (BDNS), sociedades (BORME) y normativa (BOE) con una sola librería.
|
|
6
|
+
|
|
7
|
+
## Instalación
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install apispain
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Inicio rápido
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import Apispain from 'apispain'
|
|
17
|
+
|
|
18
|
+
const client = new Apispain({ apiKey: 'tu-api-key' })
|
|
19
|
+
|
|
20
|
+
// Licitaciones abiertas
|
|
21
|
+
const { data } = await client.place.getLicitaciones({ abiertas: true, limit: 10 })
|
|
22
|
+
console.log(data[0].objeto)
|
|
23
|
+
|
|
24
|
+
// Subvenciones por CCAA
|
|
25
|
+
const subs = await client.bdns.getSubvenciones({ ccaa: 'Cataluña', limit: 5 })
|
|
26
|
+
|
|
27
|
+
// Sociedad por ID
|
|
28
|
+
const soc = await client.borme.getSociedadById('ES123456789')
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Constructor
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
new Apispain({
|
|
35
|
+
apiKey: string, // requerido
|
|
36
|
+
baseUrl: string, // default: 'https://api.apispain.es/v1'
|
|
37
|
+
timeout: number, // ms, default: 30000
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Namespaces
|
|
42
|
+
|
|
43
|
+
### `client.place` — Licitaciones (PLACE)
|
|
44
|
+
|
|
45
|
+
| Método | Parámetros |
|
|
46
|
+
|--------|-----------|
|
|
47
|
+
| `getLicitaciones(params)` | `q`, `cpv`, `organo`, `abiertas`, `importeMin`, `importeMax`, `limit`, `cursor` |
|
|
48
|
+
| `getLicitacionById(id)` | — |
|
|
49
|
+
|
|
50
|
+
### `client.bdns` — Subvenciones (BDNS)
|
|
51
|
+
|
|
52
|
+
| Método | Parámetros |
|
|
53
|
+
|--------|-----------|
|
|
54
|
+
| `getSubvenciones(params)` | `q`, `ccaa`, `sector`, `abiertas`, `importeMin`, `importeMax`, `limit`, `cursor` |
|
|
55
|
+
| `getSubvencionById(id)` | — |
|
|
56
|
+
|
|
57
|
+
### `client.borme` — Registro Mercantil
|
|
58
|
+
|
|
59
|
+
| Método | Parámetros |
|
|
60
|
+
|--------|-----------|
|
|
61
|
+
| `getSociedades(params)` | `q`, `provincia`, `limit`, `cursor` |
|
|
62
|
+
| `getSociedadById(id)` | — |
|
|
63
|
+
| `getActos(params)` | `tipo`, `fechaDesde`, `limit`, `cursor` |
|
|
64
|
+
|
|
65
|
+
### `client.boe` — Boletín Oficial del Estado
|
|
66
|
+
|
|
67
|
+
| Método | Parámetros |
|
|
68
|
+
|--------|-----------|
|
|
69
|
+
| `getDocumentos(params)` | `q`, `seccion`, `fecha`, `limit`, `cursor` |
|
|
70
|
+
| `getDocumentoById(id)` | — |
|
|
71
|
+
|
|
72
|
+
### `client.webhooks`
|
|
73
|
+
|
|
74
|
+
| Método | Descripción |
|
|
75
|
+
|--------|-------------|
|
|
76
|
+
| `list()` | Lista tus webhooks |
|
|
77
|
+
| `create({ url, eventos })` | Crea un webhook |
|
|
78
|
+
| `delete(id)` | Elimina un webhook |
|
|
79
|
+
|
|
80
|
+
## Manejo de errores
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
import { ApispainError } from 'apispain'
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const data = await client.place.getLicitaciones({ limit: 20 })
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err instanceof ApispainError) {
|
|
89
|
+
console.error(err.status) // 401, 429, 500...
|
|
90
|
+
console.error(err.message) // mensaje legible
|
|
91
|
+
console.error(err.body) // body JSON completo
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Paginación
|
|
97
|
+
|
|
98
|
+
Todas las listas devuelven `{ data, pagination: { nextCursor } }`. Pasa `cursor` en la siguiente llamada:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
let cursor
|
|
102
|
+
do {
|
|
103
|
+
const res = await client.bdns.getSubvenciones({ limit: 100, cursor })
|
|
104
|
+
processar(res.data)
|
|
105
|
+
cursor = res.pagination.nextCursor
|
|
106
|
+
} while (cursor)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Licencia
|
|
110
|
+
|
|
111
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apispain — SDK oficial para la API de datos públicos de España
|
|
3
|
+
* https://apispain.es
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class ApispainError extends Error {
|
|
7
|
+
constructor(message, status, body) {
|
|
8
|
+
super(message)
|
|
9
|
+
this.name = 'ApispainError'
|
|
10
|
+
this.status = status
|
|
11
|
+
this.body = body
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class Apispain {
|
|
16
|
+
#apiKey
|
|
17
|
+
#baseUrl
|
|
18
|
+
#timeout
|
|
19
|
+
|
|
20
|
+
constructor({ apiKey, baseUrl = 'https://api.apispain.es/v1', timeout = 30000 } = {}) {
|
|
21
|
+
if (!apiKey) throw new Error('apiKey is required')
|
|
22
|
+
this.#apiKey = apiKey
|
|
23
|
+
this.#baseUrl = baseUrl.replace(/\/$/, '')
|
|
24
|
+
this.#timeout = timeout
|
|
25
|
+
|
|
26
|
+
this.borme = {
|
|
27
|
+
getSociedades: (params) => this.#request('/borme/sociedades', params),
|
|
28
|
+
getSociedadById: (id) => this.#request(`/borme/sociedades/${id}`),
|
|
29
|
+
getActos: (params) => this.#request('/borme/actos', params),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.boe = {
|
|
33
|
+
getDocumentos: (params) => this.#request('/boe/documentos', params),
|
|
34
|
+
getDocumentoById: (id) => this.#request(`/boe/documentos/${id}`),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.bdns = {
|
|
38
|
+
getSubvenciones: (params) => this.#request('/bdns/subvenciones', params),
|
|
39
|
+
getSubvencionById: (id) => this.#request(`/bdns/subvenciones/${id}`),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.place = {
|
|
43
|
+
getLicitaciones: (params) => this.#request('/place/licitaciones', params),
|
|
44
|
+
getLicitacionById: (id) => this.#request(`/place/licitaciones/${id}`),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.webhooks = {
|
|
48
|
+
list: () => this.#request('/webhooks'),
|
|
49
|
+
create: (body) => this.#request('/webhooks', null, 'POST', body),
|
|
50
|
+
delete: (id) => this.#request(`/webhooks/${id}`, null, 'DELETE'),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async #request(path, params = null, method = 'GET', body = null) {
|
|
55
|
+
const controller = new AbortController()
|
|
56
|
+
const timer = setTimeout(() => controller.abort(), this.#timeout)
|
|
57
|
+
|
|
58
|
+
let url = `${this.#baseUrl}${path}`
|
|
59
|
+
if (params) {
|
|
60
|
+
const qs = new URLSearchParams(
|
|
61
|
+
Object.fromEntries(Object.entries(params).filter(([, v]) => v !== undefined && v !== null))
|
|
62
|
+
)
|
|
63
|
+
if (qs.toString()) url += `?${qs}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(url, {
|
|
68
|
+
method,
|
|
69
|
+
headers: {
|
|
70
|
+
'Authorization': `Bearer ${this.#apiKey}`,
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
'User-Agent': 'apispain-sdk/1.0.0',
|
|
73
|
+
},
|
|
74
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
75
|
+
signal: controller.signal,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const data = await res.json().catch(() => ({}))
|
|
79
|
+
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
throw new ApispainError(
|
|
82
|
+
data?.message ?? `HTTP ${res.status}`,
|
|
83
|
+
res.status,
|
|
84
|
+
data
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return data
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (err.name === 'AbortError') throw new ApispainError('Request timeout', 408, null)
|
|
91
|
+
throw err
|
|
92
|
+
} finally {
|
|
93
|
+
clearTimeout(timer)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export default Apispain
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apispain",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SDK oficial para la API de datos públicos de España (BORME, BOE, BDNS, PLACE)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["spain", "api", "borme", "boe", "bdns", "place", "licitaciones", "subvenciones"],
|
|
11
|
+
"author": "apispain",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/apispain/sdk"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/test.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Apispain, ApispainError } from './index.js'
|
|
2
|
+
|
|
3
|
+
let passed = 0
|
|
4
|
+
let failed = 0
|
|
5
|
+
|
|
6
|
+
function ok(label) { console.log(` ✓ ${label}`); passed++ }
|
|
7
|
+
function fail(label, err) { console.log(` ✗ ${label}: ${err}`); failed++ }
|
|
8
|
+
|
|
9
|
+
// Test 1: constructor throws without apiKey
|
|
10
|
+
try {
|
|
11
|
+
new Apispain()
|
|
12
|
+
fail('throws without apiKey', 'no error thrown')
|
|
13
|
+
} catch (e) {
|
|
14
|
+
if (e.message === 'apiKey is required') ok('throws without apiKey')
|
|
15
|
+
else fail('throws without apiKey', e.message)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Test 2: ApispainError has .status
|
|
19
|
+
try {
|
|
20
|
+
const err = new ApispainError('test error', 401, { message: 'Unauthorized' })
|
|
21
|
+
if (err.status === 401 && err.name === 'ApispainError') ok('ApispainError has .status and .name')
|
|
22
|
+
else fail('ApispainError shape', `status=${err.status} name=${err.name}`)
|
|
23
|
+
} catch (e) {
|
|
24
|
+
fail('ApispainError', e.message)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Test 3: client instantiates correctly
|
|
28
|
+
try {
|
|
29
|
+
const client = new Apispain({ apiKey: 'test-key' })
|
|
30
|
+
const hasAll = ['borme', 'boe', 'bdns', 'place', 'webhooks'].every(ns => typeof client[ns] === 'object')
|
|
31
|
+
if (hasAll) ok('client has all namespaces')
|
|
32
|
+
else fail('client namespaces', 'missing namespace')
|
|
33
|
+
} catch (e) {
|
|
34
|
+
fail('client instantiation', e.message)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Test 4: real API call with invalid key → 401 ApispainError
|
|
38
|
+
console.log('\n [network] testing real API with invalid key...')
|
|
39
|
+
try {
|
|
40
|
+
const client = new Apispain({ apiKey: 'invalid-key-test', baseUrl: 'https://api.apispain.es/v1' })
|
|
41
|
+
await client.bdns.getSubvenciones({ limit: 1 })
|
|
42
|
+
fail('should throw on invalid key', 'no error thrown')
|
|
43
|
+
} catch (e) {
|
|
44
|
+
if (e instanceof ApispainError && e.status === 401) ok(`real API returns 401 ApispainError (message: "${e.message}")`)
|
|
45
|
+
else fail('real API 401', `got ${e.constructor.name}: ${e.message}`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(`\n ${passed} passed, ${failed} failed\n`)
|
|
49
|
+
if (failed > 0) process.exit(1)
|