@deuriib/better-dgii-ecf 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.es.md +159 -0
- package/README.md +196 -2
- package/dist/client.cjs +11 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +31 -0
- package/dist/client.d.ts +31 -0
- package/dist/client.js +9 -0
- package/dist/client.js.map +1 -0
- package/dist/index.cjs +1337 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +1307 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin-BVYTmUJU.d.cts +1058 -0
- package/dist/plugin-BVYTmUJU.d.ts +1058 -0
- package/package.json +89 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Deuri Vasquez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.es.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# better-dgii-ecf
|
|
2
|
+
|
|
3
|
+
[English](README.md) · [Español](README.es.md)
|
|
4
|
+
|
|
5
|
+
> Plugin de [Better Auth](https://better-auth.com) para los servicios de facturación
|
|
6
|
+
> electrónica (**e-CF**) de la **DGII** de República Dominicana, construido sobre
|
|
7
|
+
> [`@deuriib/dgii-ecf`](https://www.npmjs.com/package/@deuriib/dgii-ecf).
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@deuriib/better-dgii-ecf)
|
|
10
|
+
[](https://github.com/deuriib/better-dgii-ecf/actions/workflows/ci.yml)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
|
|
13
|
+
`better-dgii-ecf` convierte el SDK de la DGII en una API autenticada. Decide **qué
|
|
14
|
+
contribuyente** actúa en cada petición, resuelve su certificado **en el servidor** y
|
|
15
|
+
registra cada transmisión para que un reintento no pueda emitir dos veces el mismo
|
|
16
|
+
comprobante fiscal.
|
|
17
|
+
|
|
18
|
+
Es un plugin, no un generador de documentos: tu aplicación modela la factura, el plugin
|
|
19
|
+
se encarga de la identidad, las credenciales, la idempotencia y la auditoría.
|
|
20
|
+
|
|
21
|
+
## ✨ Por qué existe
|
|
22
|
+
|
|
23
|
+
Una integración con la DGII tiene tres problemas que el framework de autenticación no
|
|
24
|
+
resuelve:
|
|
25
|
+
|
|
26
|
+
| Problema | Qué pasa sin el plugin | Qué hace el plugin |
|
|
27
|
+
| ------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
|
28
|
+
| **¿Quién factura?** | El RNC viene del navegador, así que cualquiera factura como cualquier contribuyente. | Resuelve el contribuyente desde la organización, la petición o un mapeo del servidor, y luego lo autoriza. |
|
|
29
|
+
| **¿Dónde vive el certificado?** | El `.p12` termina en el cliente o en cada cuerpo de petición. | Resuelve la llave por un callback exclusivo del servidor, firma en el servidor y nunca devuelve el token de la DGII. |
|
|
30
|
+
| **¿Llegó el comprobante?** | Un timeout más un reintento emite dos veces el mismo e-NCF. | Escribe una fila `pending` antes de transmitir y reproduce el envío conocido en vez de repetirlo. |
|
|
31
|
+
|
|
32
|
+
## 📦 Instalación
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bun add @deuriib/better-dgii-ecf @deuriib/dgii-ecf better-auth
|
|
36
|
+
# o: npm install @deuriib/better-dgii-ecf @deuriib/dgii-ecf better-auth
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Dependencias peer: `better-auth@^1.7`, `@deuriib/dgii-ecf@^0.6`.
|
|
40
|
+
|
|
41
|
+
## 🚀 Inicio rápido — modo organización
|
|
42
|
+
|
|
43
|
+
El modo recomendado. El `taxPayerId` vive en la organización, así que el RNC con el que
|
|
44
|
+
un usuario puede facturar es el de la organización a la que pertenece.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// auth.ts
|
|
48
|
+
import { betterAuth } from 'better-auth';
|
|
49
|
+
import { organization } from 'better-auth/plugins';
|
|
50
|
+
import { P12Reader, Environment } from '@deuriib/dgii-ecf';
|
|
51
|
+
import { dgiiEcf, dgiiEcfOrganizationSchema } from '@deuriib/better-dgii-ecf';
|
|
52
|
+
|
|
53
|
+
export const auth = betterAuth({
|
|
54
|
+
plugins: [
|
|
55
|
+
organization({
|
|
56
|
+
// Hace escribible el campo `taxPayerId` de la organización.
|
|
57
|
+
schema: { organization: dgiiEcfOrganizationSchema() },
|
|
58
|
+
}),
|
|
59
|
+
dgiiEcf({
|
|
60
|
+
environment: Environment.Cert,
|
|
61
|
+
// El único lugar donde se lee un certificado. Se ejecuta en el servidor,
|
|
62
|
+
// por contribuyente, nunca desde el cuerpo de una petición.
|
|
63
|
+
credentials: async ({ taxPayerId }) => {
|
|
64
|
+
const p12 = await certificates.for(taxPayerId); // tu almacenamiento
|
|
65
|
+
return new P12Reader(p12.password)
|
|
66
|
+
.getKeyFromStringBase64(p12.base64)
|
|
67
|
+
.unwrapOrThrow();
|
|
68
|
+
},
|
|
69
|
+
status: { apiKey: process.env.DGII_STATUS_API_KEY! },
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// auth-client.ts
|
|
77
|
+
import { createAuthClient } from 'better-auth/client';
|
|
78
|
+
import { organizationClient } from 'better-auth/client/plugins';
|
|
79
|
+
import { dgiiEcfClient } from '@deuriib/better-dgii-ecf/client';
|
|
80
|
+
|
|
81
|
+
export const authClient = createAuthClient({
|
|
82
|
+
plugins: [organizationClient(), dgiiEcfClient()],
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const { data, error } = await authClient.dgiiEcf.documents({
|
|
88
|
+
xml: invoiceXml, // construido por tu aplicación
|
|
89
|
+
sign: true, // firmado aquí, con la llave de la organización
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 🚀 Inicio rápido — modo por petición
|
|
94
|
+
|
|
95
|
+
Sin el plugin de organización: cada llamada indica su contribuyente. Es la forma de un
|
|
96
|
+
despliegue de un solo emisor, y funciona porque no hay un modelo de inquilino contra el
|
|
97
|
+
cual comparar el identificador.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
dgiiEcf({
|
|
101
|
+
environment: Environment.Dev,
|
|
102
|
+
credentials: async () => loadSingleTenantCertificate(),
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
await auth.api.dgiiEcfSendDocument({
|
|
108
|
+
body: { xml, sign: true, taxPayerId: '131123456' },
|
|
109
|
+
headers,
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Ambos modos son de primera clase; el
|
|
114
|
+
[orden de resolución](docs/usage.md#which-taxpayer-does-a-call-act-for) es fijo y
|
|
115
|
+
`GET /dgii-ecf/taxpayer` lo reporta, para que la interfaz nunca tenga que adivinar.
|
|
116
|
+
|
|
117
|
+
## 🔐 Postura de seguridad
|
|
118
|
+
|
|
119
|
+
- **Ningún certificado en el navegador.** La llave se resuelve por un callback del
|
|
120
|
+
servidor; `sign: true` firma dentro del proceso que la tiene.
|
|
121
|
+
- **Ningún token de la DGII en la red.** `authenticate` devuelve `issuedAt` /
|
|
122
|
+
`expiresAt`; el token no sale del servidor, ni siquiera en un cuerpo de error.
|
|
123
|
+
- **El emisor debe coincidir.** Un documento cuyo `RNCEmisor` difiere del contribuyente
|
|
124
|
+
autorizado se rechaza.
|
|
125
|
+
- **Idempotente por construcción.** La llave de repetición es el SHA-256 del documento,
|
|
126
|
+
y la fila `pending` se escribe antes de transmitir.
|
|
127
|
+
- **Nada fiscal en la tabla de auditoría.** Solo un hash, el e-NCF, el track id y la
|
|
128
|
+
operación.
|
|
129
|
+
|
|
130
|
+
Lee [docs/security.md](docs/security.md) para el modelo de amenazas, incluido lo que el
|
|
131
|
+
plugin **no** protege a propósito.
|
|
132
|
+
|
|
133
|
+
## 🧪 Desarrollo
|
|
134
|
+
|
|
135
|
+
Todo corre por [mise](https://mise.jdx.dev) — local y en CI, con la misma cadena de
|
|
136
|
+
herramientas fijada.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
mise install # Node, Bun, pre-commit y gitleaks
|
|
140
|
+
mise run bootstrap # dependencias + git hooks
|
|
141
|
+
mise run ci # formato + tipos + cobertura + build + chequeos del paquete
|
|
142
|
+
mise tasks # lista todas las tareas
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 📚 Documentación
|
|
146
|
+
|
|
147
|
+
- [Guía de uso](docs/usage.md) — modos, endpoints, repetición, errores
|
|
148
|
+
- [Arquitectura](docs/architecture.md) — ciclo de vida y modelo de datos
|
|
149
|
+
- [Seguridad](docs/security.md) — modelo de amenazas y garantías
|
|
150
|
+
- [Decisiones](docs/decisions.md) — por qué los valores por defecto son estos
|
|
151
|
+
- [Contribuir](CONTRIBUTING.md) · [Código de Conducta](CODE_OF_CONDUCT.md)
|
|
152
|
+
|
|
153
|
+
## ⚖️ Licencia
|
|
154
|
+
|
|
155
|
+
[MIT](LICENSE) © 2026 Deuri Vasquez.
|
|
156
|
+
|
|
157
|
+
> No está afiliado a la DGII. Es una implementación independiente de la especificación
|
|
158
|
+
> técnica pública del e-CF (Ley 32-23, Decreto 587-24). Valida siempre contra la
|
|
159
|
+
> [documentación oficial](https://dgii.gov.do/cicloContribuyente/facturacion/comprobantesFiscalesElectronicosE-CF/Paginas/default.aspx).
|
package/README.md
CHANGED
|
@@ -1,3 +1,197 @@
|
|
|
1
|
-
#
|
|
1
|
+
# better-dgii-ecf
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](README.md) · [Español](README.es.md)
|
|
4
|
+
|
|
5
|
+
> A [Better Auth](https://better-auth.com) plugin for the Dominican Republic's **DGII**
|
|
6
|
+
> electronic invoicing (e-CF) services, built on
|
|
7
|
+
> [`@deuriib/dgii-ecf`](https://www.npmjs.com/package/@deuriib/dgii-ecf).
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@deuriib/better-dgii-ecf)
|
|
10
|
+
[](https://github.com/deuriib/better-dgii-ecf/actions/workflows/ci.yml)
|
|
11
|
+
[](https://github.com/deuriib/better-dgii-ecf/actions/workflows/codeql.yml)
|
|
12
|
+
[](https://docs.npmjs.com/trusted-publishers)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
[](https://www.conventionalcommits.org/)
|
|
15
|
+
|
|
16
|
+
`better-dgii-ecf` turns the DGII SDK into an authenticated API surface. It decides
|
|
17
|
+
**which taxpayer** a request acts for, resolves that taxpayer's certificate
|
|
18
|
+
**server-side**, and records every transmission so a retry can never issue the same
|
|
19
|
+
fiscal document twice.
|
|
20
|
+
|
|
21
|
+
It is a plugin, not a document builder: your application owns the invoice model, the
|
|
22
|
+
plugin owns identity, credentials, replay safety and the audit trail.
|
|
23
|
+
|
|
24
|
+
## ✨ Why this plugin exists
|
|
25
|
+
|
|
26
|
+
A DGII integration has three problems that authentication frameworks do not solve:
|
|
27
|
+
|
|
28
|
+
| Problem | What goes wrong without it | What the plugin does |
|
|
29
|
+
| ------------------------------------ | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| **Who is invoicing?** | The RNC comes from the browser, so any user can invoice as any taxpayer. | Resolves the taxpayer from the organization, the request, or a server-side mapping — then authorizes it. |
|
|
31
|
+
| **Where does the certificate live?** | The `.p12` ends up in the client, or in every request body. | Resolves key material through a server-only callback; signs server-side; never returns a DGII token. |
|
|
32
|
+
| **Did the invoice land?** | A timeout plus a retry issues the same e-NCF twice. | Persists a pending row before transmitting and replays a known submission instead of re-sending it. |
|
|
33
|
+
|
|
34
|
+
## 📦 Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bun add @deuriib/better-dgii-ecf @deuriib/dgii-ecf better-auth
|
|
38
|
+
# or: npm install @deuriib/better-dgii-ecf @deuriib/dgii-ecf better-auth
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Peer dependencies: `better-auth@^1.7`, `@deuriib/dgii-ecf@^0.6`.
|
|
42
|
+
|
|
43
|
+
## 🚀 Quick start — organization mode
|
|
44
|
+
|
|
45
|
+
The recommended setup. `taxPayerId` lives on the organization, so the RNC a user can
|
|
46
|
+
invoice under is the organization they belong to.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// auth.ts
|
|
50
|
+
import { betterAuth } from 'better-auth';
|
|
51
|
+
import { organization } from 'better-auth/plugins';
|
|
52
|
+
import { P12Reader, Environment } from '@deuriib/dgii-ecf';
|
|
53
|
+
import { dgiiEcf, dgiiEcfOrganizationSchema } from '@deuriib/better-dgii-ecf';
|
|
54
|
+
|
|
55
|
+
export const auth = betterAuth({
|
|
56
|
+
plugins: [
|
|
57
|
+
organization({
|
|
58
|
+
// Makes `taxPayerId` writable on the organization (create + update).
|
|
59
|
+
schema: { organization: dgiiEcfOrganizationSchema() },
|
|
60
|
+
}),
|
|
61
|
+
dgiiEcf({
|
|
62
|
+
environment: Environment.Cert,
|
|
63
|
+
// The only place a certificate is ever read. Called server-side, once per
|
|
64
|
+
// taxpayer, never from a request body.
|
|
65
|
+
credentials: async ({ taxPayerId }) => {
|
|
66
|
+
const p12 = await certificates.for(taxPayerId); // your storage
|
|
67
|
+
return new P12Reader(p12.password)
|
|
68
|
+
.getKeyFromStringBase64(p12.base64)
|
|
69
|
+
.unwrapOrThrow();
|
|
70
|
+
},
|
|
71
|
+
status: { apiKey: process.env.DGII_STATUS_API_KEY! },
|
|
72
|
+
}),
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// auth-client.ts
|
|
79
|
+
import { createAuthClient } from 'better-auth/client';
|
|
80
|
+
import { organizationClient } from 'better-auth/client/plugins';
|
|
81
|
+
import { dgiiEcfClient } from '@deuriib/better-dgii-ecf/client';
|
|
82
|
+
|
|
83
|
+
export const authClient = createAuthClient({
|
|
84
|
+
plugins: [organizationClient(), dgiiEcfClient()],
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const { data, error } = await authClient.dgiiEcf.documents({
|
|
90
|
+
xml: invoiceXml, // built by your application
|
|
91
|
+
sign: true, // signed here, with the organization's key
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 🚀 Quick start — per-call mode
|
|
96
|
+
|
|
97
|
+
No organization plugin: every call names its taxpayer. This is the single-tenant shape,
|
|
98
|
+
and it works because there is no tenant model to check the id against.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
dgiiEcf({
|
|
102
|
+
environment: Environment.Dev,
|
|
103
|
+
credentials: async () => loadSingleTenantCertificate(),
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
await auth.api.dgiiEcfSendDocument({
|
|
109
|
+
body: { xml, sign: true, taxPayerId: '131123456' },
|
|
110
|
+
headers,
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Both modes are first class;
|
|
115
|
+
[the resolution order](docs/usage.md#which-taxpayer-does-a-call-act-for) is fixed and
|
|
116
|
+
reported back by `GET /dgii-ecf/taxpayer`, so a UI never has to guess.
|
|
117
|
+
|
|
118
|
+
## 🔌 What you get
|
|
119
|
+
|
|
120
|
+
| Method | Path | Purpose |
|
|
121
|
+
| -------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------- |
|
|
122
|
+
| `dgiiEcf.taxpayer` | `GET /dgii-ecf/taxpayer` | Resolve which taxpayer the next call uses, and why |
|
|
123
|
+
| `dgiiEcf.authenticate` | `POST /dgii-ecf/authenticate` | Warm the DGII token; returns the validity window, never the token |
|
|
124
|
+
| `dgiiEcf.documents` | `POST /dgii-ecf/documents` | Submit an e-CF, routing on the document's own content |
|
|
125
|
+
| `dgiiEcf.documents.electronic` | `POST /dgii-ecf/documents/electronic` | Submit to the general e-CF reception only |
|
|
126
|
+
| `dgiiEcf.documents.summary` | `POST /dgii-ecf/documents/summary` | Submit an RFCE summary invoice |
|
|
127
|
+
| `dgiiEcf.documents.commercialApproval` | `POST /dgii-ecf/documents/commercial-approval` | Submit an ACECF |
|
|
128
|
+
| `dgiiEcf.documents.voidRange` | `POST /dgii-ecf/documents/void-range` | Void an e-NCF range (ANECF) |
|
|
129
|
+
| `dgiiEcf.documents.reconcile` | `POST /dgii-ecf/documents/reconcile` | Resolve a submission whose outcome is unknown |
|
|
130
|
+
| `dgiiEcf.tracks` | `GET /dgii-ecf/tracks` | Read a track result |
|
|
131
|
+
| `dgiiEcf.inquiries.status` | `POST /dgii-ecf/inquiries/status` | Existence check by e-NCF |
|
|
132
|
+
| `dgiiEcf.inquiries.tracks` | `POST /dgii-ecf/inquiries/tracks` | Every track DGII holds for an e-NCF |
|
|
133
|
+
| `dgiiEcf.inquiries.summary` | `POST /dgii-ecf/inquiries/summary` | RFCE inquiry (production only) |
|
|
134
|
+
| `dgiiEcf.directories.customers` | `GET /dgii-ecf/directories/customers` | Receiver service directory |
|
|
135
|
+
| `dgiiEcf.directories.all` | `GET /dgii-ecf/directories/all` | Every receiver that publishes URLs |
|
|
136
|
+
| `dgiiEcf.serviceStatus` | `GET /dgii-ecf/service-status` | DGII service status |
|
|
137
|
+
| `dgiiEcf.maintenanceWindows` | `GET /dgii-ecf/maintenance-windows` | DGII maintenance windows |
|
|
138
|
+
| `dgiiEcf.serviceAvailability` | `GET /dgii-ecf/service-availability` | Is one environment inside a window |
|
|
139
|
+
| `dgiiEcf.submissions` | `GET /dgii-ecf/submissions` | The submission audit trail |
|
|
140
|
+
|
|
141
|
+
Every response echoes `taxPayerId`, `organizationId` and `environment`, so a caller can
|
|
142
|
+
always tell which identity acted.
|
|
143
|
+
|
|
144
|
+
## 🔐 Security posture
|
|
145
|
+
|
|
146
|
+
- **No certificate in a browser.** Key material is resolved through a server-only
|
|
147
|
+
callback; `sign: true` signs inside the process that holds it.
|
|
148
|
+
- **No DGII token on the wire.** `authenticate` returns `issuedAt` / `expiresAt`; the
|
|
149
|
+
bearer token never leaves the server, not even in an error body.
|
|
150
|
+
- **The issuer must match.** A document whose `RNCEmisor` differs from the authorized
|
|
151
|
+
taxpayer is refused, so a member of organization A cannot submit a document for RNC B.
|
|
152
|
+
- **Replay safe by construction.** The replay key defaults to the SHA-256 of the
|
|
153
|
+
document, and the pending row is written before transmission.
|
|
154
|
+
- **No fiscal content in the audit table.** Only a hash, the e-NCF, the track id and the
|
|
155
|
+
operation.
|
|
156
|
+
|
|
157
|
+
Read [docs/security.md](docs/security.md) for the threat model, including what the
|
|
158
|
+
plugin deliberately does **not** protect against.
|
|
159
|
+
|
|
160
|
+
## 🧪 Development
|
|
161
|
+
|
|
162
|
+
Everything runs through [mise](https://mise.jdx.dev) — locally and in CI, with the same
|
|
163
|
+
pinned toolchain.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
mise install # provision Node, Bun, pre-commit, gitleaks
|
|
167
|
+
mise run bootstrap # dependencies + git hooks
|
|
168
|
+
mise run ci # format check + typecheck + coverage + build + pack checks
|
|
169
|
+
mise tasks # list every task
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
| Task | What it does |
|
|
173
|
+
| --------------------- | ------------------------------------------------------- |
|
|
174
|
+
| `mise run lint` | Prettier check + `tsc --noEmit` |
|
|
175
|
+
| `mise run test` | Vitest |
|
|
176
|
+
| `mise run coverage` | Vitest with thresholds enforced |
|
|
177
|
+
| `mise run build` | ESM + CJS + declarations |
|
|
178
|
+
| `mise run pack-check` | `publint` + `are-the-types-wrong` on the packed tarball |
|
|
179
|
+
| `mise run publish` | OIDC trusted publish (no token on disk) |
|
|
180
|
+
|
|
181
|
+
## 📚 Documentation
|
|
182
|
+
|
|
183
|
+
- [Usage guide](docs/usage.md) — modes, endpoints, replay, errors
|
|
184
|
+
- [Architecture](docs/architecture.md) — request lifecycle and data model
|
|
185
|
+
- [Security](docs/security.md) — threat model and guarantees
|
|
186
|
+
- [Decision record](docs/decisions.md) — why the defaults are what they are
|
|
187
|
+
- [Releasing](docs/releasing.md) — trusted publishing, provenance, hotfixes
|
|
188
|
+
- [Contributing](CONTRIBUTING.md) · [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
189
|
+
|
|
190
|
+
## ⚖️ License
|
|
191
|
+
|
|
192
|
+
[MIT](LICENSE) © 2026 Deuri Vasquez.
|
|
193
|
+
|
|
194
|
+
> Not affiliated with the DGII. This is an independent implementation of the publicly
|
|
195
|
+
> documented e-CF specification (Ley 32-23, Decreto 587-24). Always validate against the
|
|
196
|
+
> official
|
|
197
|
+
> [DGII documentation](https://dgii.gov.do/cicloContribuyente/facturacion/comprobantesFiscalesElectronicosE-CF/Paginas/default.aspx).
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":";;;AAqBO,IAAM,gBAAgB,OAC1B;AAAA,EACC,EAAA,EAAI,UAAA;AAAA,EACJ,oBAAoB;AACtB,CAAA","file":"client.cjs","sourcesContent":["import type { BetterAuthClientPlugin } from 'better-auth/client';\nimport type { DgiiEcfPlugin } from './plugin.js';\n\n/**\n * Client plugin for {@link import('./plugin.js').dgiiEcf}.\n *\n * It adds no runtime code: Better Auth infers every method from the server\n * plugin's endpoints, so the client cannot drift from the server — a renamed\n * path breaks the build instead of the invoice. Methods are namespaced under\n * `dgiiEcf`:\n *\n * ```ts\n * await authClient.dgiiEcf.documents({ xml, sign: true });\n * await authClient.dgiiEcf.inquiries.status({ ncf, taxPayerId });\n * await authClient.dgiiEcf.serviceStatus();\n * ```\n *\n * The `id` must match the server plugin's `id` so Better Auth can pair them.\n * Importing this module pulls only types, which is what makes it safe to use\n * from a browser bundle.\n */\nexport const dgiiEcfClient = () =>\n ({\n id: 'dgii-ecf',\n $InferServerPlugin: {} as DgiiEcfPlugin,\n }) satisfies BetterAuthClientPlugin;\n\nexport type DgiiEcfClientPlugin = ReturnType<typeof dgiiEcfClient>;\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { D as DgiiEcfPlugin } from './plugin-BVYTmUJU.cjs';
|
|
2
|
+
import '@deuriib/dgii-ecf';
|
|
3
|
+
import 'better-auth';
|
|
4
|
+
import 'zod/v4/core';
|
|
5
|
+
import '@better-auth/core/db';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Client plugin for {@link import('./plugin.js').dgiiEcf}.
|
|
9
|
+
*
|
|
10
|
+
* It adds no runtime code: Better Auth infers every method from the server
|
|
11
|
+
* plugin's endpoints, so the client cannot drift from the server — a renamed
|
|
12
|
+
* path breaks the build instead of the invoice. Methods are namespaced under
|
|
13
|
+
* `dgiiEcf`:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* await authClient.dgiiEcf.documents({ xml, sign: true });
|
|
17
|
+
* await authClient.dgiiEcf.inquiries.status({ ncf, taxPayerId });
|
|
18
|
+
* await authClient.dgiiEcf.serviceStatus();
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* The `id` must match the server plugin's `id` so Better Auth can pair them.
|
|
22
|
+
* Importing this module pulls only types, which is what makes it safe to use
|
|
23
|
+
* from a browser bundle.
|
|
24
|
+
*/
|
|
25
|
+
declare const dgiiEcfClient: () => {
|
|
26
|
+
id: "dgii-ecf";
|
|
27
|
+
$InferServerPlugin: DgiiEcfPlugin;
|
|
28
|
+
};
|
|
29
|
+
type DgiiEcfClientPlugin = ReturnType<typeof dgiiEcfClient>;
|
|
30
|
+
|
|
31
|
+
export { type DgiiEcfClientPlugin, dgiiEcfClient };
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { D as DgiiEcfPlugin } from './plugin-BVYTmUJU.js';
|
|
2
|
+
import '@deuriib/dgii-ecf';
|
|
3
|
+
import 'better-auth';
|
|
4
|
+
import 'zod/v4/core';
|
|
5
|
+
import '@better-auth/core/db';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Client plugin for {@link import('./plugin.js').dgiiEcf}.
|
|
9
|
+
*
|
|
10
|
+
* It adds no runtime code: Better Auth infers every method from the server
|
|
11
|
+
* plugin's endpoints, so the client cannot drift from the server — a renamed
|
|
12
|
+
* path breaks the build instead of the invoice. Methods are namespaced under
|
|
13
|
+
* `dgiiEcf`:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* await authClient.dgiiEcf.documents({ xml, sign: true });
|
|
17
|
+
* await authClient.dgiiEcf.inquiries.status({ ncf, taxPayerId });
|
|
18
|
+
* await authClient.dgiiEcf.serviceStatus();
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* The `id` must match the server plugin's `id` so Better Auth can pair them.
|
|
22
|
+
* Importing this module pulls only types, which is what makes it safe to use
|
|
23
|
+
* from a browser bundle.
|
|
24
|
+
*/
|
|
25
|
+
declare const dgiiEcfClient: () => {
|
|
26
|
+
id: "dgii-ecf";
|
|
27
|
+
$InferServerPlugin: DgiiEcfPlugin;
|
|
28
|
+
};
|
|
29
|
+
type DgiiEcfClientPlugin = ReturnType<typeof dgiiEcfClient>;
|
|
30
|
+
|
|
31
|
+
export { type DgiiEcfClientPlugin, dgiiEcfClient };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":";AAqBO,IAAM,gBAAgB,OAC1B;AAAA,EACC,EAAA,EAAI,UAAA;AAAA,EACJ,oBAAoB;AACtB,CAAA","file":"client.js","sourcesContent":["import type { BetterAuthClientPlugin } from 'better-auth/client';\nimport type { DgiiEcfPlugin } from './plugin.js';\n\n/**\n * Client plugin for {@link import('./plugin.js').dgiiEcf}.\n *\n * It adds no runtime code: Better Auth infers every method from the server\n * plugin's endpoints, so the client cannot drift from the server — a renamed\n * path breaks the build instead of the invoice. Methods are namespaced under\n * `dgiiEcf`:\n *\n * ```ts\n * await authClient.dgiiEcf.documents({ xml, sign: true });\n * await authClient.dgiiEcf.inquiries.status({ ncf, taxPayerId });\n * await authClient.dgiiEcf.serviceStatus();\n * ```\n *\n * The `id` must match the server plugin's `id` so Better Auth can pair them.\n * Importing this module pulls only types, which is what makes it safe to use\n * from a browser bundle.\n */\nexport const dgiiEcfClient = () =>\n ({\n id: 'dgii-ecf',\n $InferServerPlugin: {} as DgiiEcfPlugin,\n }) satisfies BetterAuthClientPlugin;\n\nexport type DgiiEcfClientPlugin = ReturnType<typeof dgiiEcfClient>;\n"]}
|