@ekwo-ai/document-reading 0.0.0-stage → 0.19.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/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +155 -2
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +100 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +434 -0
- package/dist/model.js.map +1 -0
- package/dist/purchase.d.ts +123 -0
- package/dist/purchase.d.ts.map +1 -0
- package/dist/purchase.js +184 -0
- package/dist/purchase.js.map +1 -0
- package/dist/structured.d.ts +29 -0
- package/dist/structured.d.ts.map +1 -0
- package/dist/structured.js +51 -0
- package/dist/structured.js.map +1 -0
- package/package.json +62 -4
package/dist/purchase.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The purchase a received invoice becomes, before anybody's books are read.
|
|
3
|
+
*
|
|
4
|
+
* Whichever way the invoice was read — a Factur-X file, a UBL message, a
|
|
5
|
+
* photograph read by a model — it is one `ReceivedInvoice`, and this file
|
|
6
|
+
* turns it into the draft of a purchase document in the columns of Ekwo OS
|
|
7
|
+
* (`documents`, `document_lines`): the fields a caller passes to
|
|
8
|
+
* `create_document` once it has chosen, in its own books, the supplier, the
|
|
9
|
+
* account and the tax. Those three choices need the books and the country
|
|
10
|
+
* pack, so they are not made here; the draft carries what they are made
|
|
11
|
+
* from — the supplier as the invoice names it, and each line's VAT category
|
|
12
|
+
* and rate.
|
|
13
|
+
*
|
|
14
|
+
* What the core has a column for goes in that column: the unit code of a
|
|
15
|
+
* line, the supplier's invoice number, the references, the due date, the
|
|
16
|
+
* tax point. What it has none for and EN 16931 carries on a line — the period
|
|
17
|
+
* a line bills (BG-26) — goes in the line's `attributes`, which hold "what an
|
|
18
|
+
* electronic invoice format asks of this line that no other column says".
|
|
19
|
+
* The period of the whole invoice (BG-14) has no column on a document: each
|
|
20
|
+
* line that states no period of its own takes it, which is what EN 16931
|
|
21
|
+
* means by it, and the draft returns it as well for whoever keeps it beside
|
|
22
|
+
* the document.
|
|
23
|
+
*
|
|
24
|
+
* Nothing is defaulted: no currency, no date, no name. Where the invoice is
|
|
25
|
+
* silent the draft is silent, and the caller asks, or fills in its own.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* The names under which a line's `attributes` carry what no column of
|
|
29
|
+
* `document_lines` holds. The period is BT-134 and BT-135; the three
|
|
30
|
+
* `invoiced_*` keep the invoice's own quantity, unit and price where the
|
|
31
|
+
* line had to be written as one unit at its amount (see `PurchaseDraftLine`).
|
|
32
|
+
*/
|
|
33
|
+
export const LINE_ATTRIBUTES = {
|
|
34
|
+
periodStart: 'period_start',
|
|
35
|
+
periodEnd: 'period_end',
|
|
36
|
+
invoicedQuantity: 'invoiced_quantity',
|
|
37
|
+
invoicedUnitCode: 'invoiced_unit_code',
|
|
38
|
+
invoicedNetPrice: 'invoiced_net_price',
|
|
39
|
+
};
|
|
40
|
+
const nonEmpty = (value) => value === null || value === undefined || value.trim().length === 0 ? null : value;
|
|
41
|
+
/** The decimals a currency is written with, as the platform's `Intl` knows it; null for a code it does not know. */
|
|
42
|
+
export function currencyDigits(currency) {
|
|
43
|
+
try {
|
|
44
|
+
return new Intl.NumberFormat('en', { style: 'currency', currency }).resolvedOptions().maximumFractionDigits ?? null;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** A decimal as an integer and a scale: `-12.50` is `{ units: -1250n, scale: 2 }`. */
|
|
51
|
+
function decimal(text) {
|
|
52
|
+
const match = /^(-?)(\d+)(?:\.(\d+))?$/.exec(text.trim());
|
|
53
|
+
if (match === null)
|
|
54
|
+
return null;
|
|
55
|
+
const fraction = match[3] ?? '';
|
|
56
|
+
const units = BigInt(`${match[2]}${fraction}`);
|
|
57
|
+
return { units: match[1] === '-' ? -units : units, scale: fraction.length };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Whether `quantity × price`, rounded half up on its absolute value at
|
|
61
|
+
* `digits` decimals — the rounding of `document_lines.amount_untaxed` — is
|
|
62
|
+
* `amount`.
|
|
63
|
+
*/
|
|
64
|
+
export function makesAmount(quantity, price, amount, digits) {
|
|
65
|
+
const q = decimal(quantity);
|
|
66
|
+
const p = decimal(price);
|
|
67
|
+
const a = decimal(amount);
|
|
68
|
+
if (q === null || p === null || a === null)
|
|
69
|
+
return false;
|
|
70
|
+
const product = q.units * p.units;
|
|
71
|
+
const scale = q.scale + p.scale;
|
|
72
|
+
const negative = product < 0n;
|
|
73
|
+
const magnitude = negative ? -product : product;
|
|
74
|
+
let rounded;
|
|
75
|
+
if (scale <= digits) {
|
|
76
|
+
rounded = magnitude * 10n ** BigInt(digits - scale);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const divisor = 10n ** BigInt(scale - digits);
|
|
80
|
+
rounded = magnitude / divisor + ((magnitude % divisor) * 2n >= divisor ? 1n : 0n);
|
|
81
|
+
}
|
|
82
|
+
const signed = negative ? -rounded : rounded;
|
|
83
|
+
// The amount may be written with more decimals than the currency has, as long as they are zeros.
|
|
84
|
+
return a.scale <= digits ? signed === a.units * 10n ** BigInt(digits - a.scale) : signed * 10n ** BigInt(a.scale - digits) === a.units;
|
|
85
|
+
}
|
|
86
|
+
function periodAttributes(period) {
|
|
87
|
+
const out = {};
|
|
88
|
+
if (period?.start)
|
|
89
|
+
out[LINE_ATTRIBUTES.periodStart] = period.start;
|
|
90
|
+
if (period?.end)
|
|
91
|
+
out[LINE_ATTRIBUTES.periodEnd] = period.end;
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
function lineOf(line, digits, invoicing) {
|
|
95
|
+
const name = nonEmpty(line.name) ?? nonEmpty(line.description) ?? nonEmpty(line.note);
|
|
96
|
+
const perUnit = line.priceBaseQuantity === null || /^0*1(\.0*)?$/.test(line.priceBaseQuantity);
|
|
97
|
+
const exact = line.quantity !== null &&
|
|
98
|
+
line.netPrice !== null &&
|
|
99
|
+
perUnit &&
|
|
100
|
+
line.netAmount.length > 0 &&
|
|
101
|
+
makesAmount(line.quantity, line.netPrice, line.netAmount, digits);
|
|
102
|
+
const attributes = periodAttributes(line.period ?? invoicing);
|
|
103
|
+
if (!exact) {
|
|
104
|
+
if (line.quantity !== null)
|
|
105
|
+
attributes[LINE_ATTRIBUTES.invoicedQuantity] = line.quantity;
|
|
106
|
+
if (line.unitCode !== null)
|
|
107
|
+
attributes[LINE_ATTRIBUTES.invoicedUnitCode] = line.unitCode;
|
|
108
|
+
if (line.netPrice !== null)
|
|
109
|
+
attributes[LINE_ATTRIBUTES.invoicedNetPrice] = line.netPrice;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
name,
|
|
113
|
+
description: name === nonEmpty(line.name) ? nonEmpty(line.description) : null,
|
|
114
|
+
quantity: exact ? line.quantity : '1',
|
|
115
|
+
unit_code: exact ? (line.unitCode ?? 'C62') : 'C62',
|
|
116
|
+
unit_price: exact ? line.netPrice : line.netAmount,
|
|
117
|
+
vat_category: line.vatCategory,
|
|
118
|
+
vat_rate: line.vatRate,
|
|
119
|
+
attributes,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function adjustmentOf(one, invoicing) {
|
|
123
|
+
return {
|
|
124
|
+
name: nonEmpty(one.reason) ?? nonEmpty(one.reasonCode),
|
|
125
|
+
description: null,
|
|
126
|
+
quantity: '1',
|
|
127
|
+
unit_code: 'C62',
|
|
128
|
+
unit_price: one.charge ? one.amount : one.amount.startsWith('-') ? one.amount.slice(1) : `-${one.amount}`,
|
|
129
|
+
vat_category: one.vatCategory,
|
|
130
|
+
vat_rate: one.vatRate,
|
|
131
|
+
attributes: periodAttributes(invoicing),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The draft of the purchase a received invoice becomes.
|
|
136
|
+
*
|
|
137
|
+
* One line per line of the invoice, its amount net of the line's own
|
|
138
|
+
* allowances; then one line per allowance (negative) or charge of the whole
|
|
139
|
+
* document. A credit note is a purchase credit note, its figures positive as
|
|
140
|
+
* the invoice writes them. Where the currency was not read, amounts are
|
|
141
|
+
* compared at the decimals the invoice wrote them with.
|
|
142
|
+
*/
|
|
143
|
+
export function purchaseDraftOf(invoice) {
|
|
144
|
+
const currency = nonEmpty(invoice.currency);
|
|
145
|
+
const invoicing = invoice.invoicingPeriod === null || (invoice.invoicingPeriod.start === null && invoice.invoicingPeriod.end === null)
|
|
146
|
+
? null
|
|
147
|
+
: invoice.invoicingPeriod;
|
|
148
|
+
const lines = invoice.lines.map((line) => {
|
|
149
|
+
const written = decimal(line.netAmount)?.scale ?? 2;
|
|
150
|
+
const digits = currency === null ? written : (currencyDigits(currency) ?? written);
|
|
151
|
+
return lineOf(line, digits, invoicing);
|
|
152
|
+
});
|
|
153
|
+
lines.push(...invoice.allowances.map((one) => adjustmentOf(one, invoicing)));
|
|
154
|
+
const means = invoice.paymentMeans.find((one) => one.reference !== null || one.account !== null);
|
|
155
|
+
const iban = invoice.paymentMeans.map((one) => one.account).find((account) => account?.kind === 'iban');
|
|
156
|
+
return {
|
|
157
|
+
doc_type: invoice.kind === 'credit_note' ? 'purchase_credit_note' : 'purchase_invoice',
|
|
158
|
+
document_date: nonEmpty(invoice.issueDate),
|
|
159
|
+
due_date: invoice.dueDate,
|
|
160
|
+
tax_point_date: invoice.taxPointDate,
|
|
161
|
+
supplier_reference: nonEmpty(invoice.number),
|
|
162
|
+
currency_code: currency,
|
|
163
|
+
buyer_reference: invoice.buyerReference,
|
|
164
|
+
order_reference: invoice.orderReference,
|
|
165
|
+
contract_reference: invoice.contractReference,
|
|
166
|
+
project_reference: invoice.projectReference,
|
|
167
|
+
delivery_date: invoice.deliveryDate,
|
|
168
|
+
payment_terms: invoice.paymentTerms,
|
|
169
|
+
payment_reference: means?.reference ?? null,
|
|
170
|
+
payee_iban: iban?.value ?? null,
|
|
171
|
+
invoicing_period: invoicing,
|
|
172
|
+
supplier: {
|
|
173
|
+
name: invoice.seller.name,
|
|
174
|
+
trade_name: invoice.seller.tradeName,
|
|
175
|
+
vat_id: invoice.seller.vatId,
|
|
176
|
+
legal_id: invoice.seller.legalId?.value ?? null,
|
|
177
|
+
country: invoice.seller.address?.country ?? null,
|
|
178
|
+
},
|
|
179
|
+
total: nonEmpty(invoice.totals.taxInclusive),
|
|
180
|
+
payable: nonEmpty(invoice.totals.payable),
|
|
181
|
+
lines,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=purchase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"purchase.js","sourceRoot":"","sources":["../src/purchase.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The exact path: a file that carries its invoice as data.
|
|
3
|
+
*
|
|
4
|
+
* Three files do — a CII D16B `CrossIndustryInvoice`, a UBL 2.1 invoice or
|
|
5
|
+
* credit note, and a PDF with its Factur-X or ZUGFeRD XML attached — and the
|
|
6
|
+
* e-invoicing bricks of this repository read each of them into one shape,
|
|
7
|
+
* EN 16931's `ReceivedInvoice`: `readCii()` and `readFacturX()` of
|
|
8
|
+
* `@ekwo-ai/factur-x`, `readUbl()` of `@ekwo-ai/peppol-ubl`. This file only
|
|
9
|
+
* says which of them a file is for. No model is called: what the file says is
|
|
10
|
+
* what is returned.
|
|
11
|
+
*
|
|
12
|
+
* The readers are imported when a file needs them, so a page that never reads
|
|
13
|
+
* a PDF never downloads the PDF library.
|
|
14
|
+
*/
|
|
15
|
+
import type { ReadOptions, ReceivedInvoiceFile } from '@ekwo-ai/factur-x';
|
|
16
|
+
/** What a file is, as far as reading it goes: by its first bytes, never by its name. */
|
|
17
|
+
export type FileKind = 'pdf' | 'xml' | 'other';
|
|
18
|
+
/** The kind of a file, from its first kilobyte: `%PDF-` makes a PDF, a `<` after the byte-order mark and blanks makes XML. */
|
|
19
|
+
export declare function fileKind(bytes: Uint8Array): FileKind;
|
|
20
|
+
/**
|
|
21
|
+
* The e-invoice a file holds, read exactly; `undefined` where it holds none —
|
|
22
|
+
* a photograph, a PDF with no XML inside, an XML that is not an invoice, a
|
|
23
|
+
* file over the readers' limits — so that the caller reads it another way,
|
|
24
|
+
* with a model (`readingSchema()`, `readingOf()`).
|
|
25
|
+
*
|
|
26
|
+
* CII and UBL are told apart by the root the first four kilobytes name.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readStructured(file: Uint8Array | ArrayBuffer, options?: ReadOptions): Promise<ReceivedInvoiceFile | undefined>;
|
|
29
|
+
//# sourceMappingURL=structured.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structured.d.ts","sourceRoot":"","sources":["../src/structured.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE1E,wFAAwF;AACxF,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;AAE/C,8HAA8H;AAC9H,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ,CAIpD;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,UAAU,GAAG,WAAW,EAC9B,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAkB1C"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The exact path: a file that carries its invoice as data.
|
|
3
|
+
*
|
|
4
|
+
* Three files do — a CII D16B `CrossIndustryInvoice`, a UBL 2.1 invoice or
|
|
5
|
+
* credit note, and a PDF with its Factur-X or ZUGFeRD XML attached — and the
|
|
6
|
+
* e-invoicing bricks of this repository read each of them into one shape,
|
|
7
|
+
* EN 16931's `ReceivedInvoice`: `readCii()` and `readFacturX()` of
|
|
8
|
+
* `@ekwo-ai/factur-x`, `readUbl()` of `@ekwo-ai/peppol-ubl`. This file only
|
|
9
|
+
* says which of them a file is for. No model is called: what the file says is
|
|
10
|
+
* what is returned.
|
|
11
|
+
*
|
|
12
|
+
* The readers are imported when a file needs them, so a page that never reads
|
|
13
|
+
* a PDF never downloads the PDF library.
|
|
14
|
+
*/
|
|
15
|
+
/** The kind of a file, from its first kilobyte: `%PDF-` makes a PDF, a `<` after the byte-order mark and blanks makes XML. */
|
|
16
|
+
export function fileKind(bytes) {
|
|
17
|
+
const head = new TextDecoder('latin1').decode(bytes.subarray(0, 1024));
|
|
18
|
+
if (head.includes('%PDF-'))
|
|
19
|
+
return 'pdf';
|
|
20
|
+
return /^()?\s*</.test(head) ? 'xml' : 'other';
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The e-invoice a file holds, read exactly; `undefined` where it holds none —
|
|
24
|
+
* a photograph, a PDF with no XML inside, an XML that is not an invoice, a
|
|
25
|
+
* file over the readers' limits — so that the caller reads it another way,
|
|
26
|
+
* with a model (`readingSchema()`, `readingOf()`).
|
|
27
|
+
*
|
|
28
|
+
* CII and UBL are told apart by the root the first four kilobytes name.
|
|
29
|
+
*/
|
|
30
|
+
export async function readStructured(file, options = {}) {
|
|
31
|
+
const bytes = file instanceof Uint8Array ? file : new Uint8Array(file);
|
|
32
|
+
try {
|
|
33
|
+
switch (fileKind(bytes)) {
|
|
34
|
+
case 'pdf':
|
|
35
|
+
return await (await import('@ekwo-ai/factur-x/pdf')).readFacturX(bytes, options);
|
|
36
|
+
case 'xml': {
|
|
37
|
+
const head = new TextDecoder().decode(bytes.subarray(0, 4096));
|
|
38
|
+
if (/CrossIndustryInvoice/.test(head))
|
|
39
|
+
return (await import('@ekwo-ai/factur-x')).readCii(bytes, options);
|
|
40
|
+
return (await import('@ekwo-ai/peppol-ubl')).readUbl(bytes, options);
|
|
41
|
+
}
|
|
42
|
+
default:
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Not an e-invoice the readers take: it is read as a picture of one.
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=structured.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structured.js","sourceRoot":"","sources":["../src/structured.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,8HAA8H;AAC9H,MAAM,UAAU,QAAQ,CAAC,KAAiB;IACxC,MAAM,IAAI,GAAG,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IACvE,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IACzC,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAA8B,EAC9B,UAAuB,EAAE;IAEzB,MAAM,KAAK,GAAG,IAAI,YAAY,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACvE,IAAI,CAAC;QACH,QAAQ,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,KAAK,KAAK;gBACR,OAAO,MAAM,CAAC,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACnF,KAAK,KAAK,CAAC,CAAC,CAAC;gBACX,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;gBAC/D,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC;oBAAE,OAAO,CAAC,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBAC1G,OAAO,CAAC,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACvE,CAAC;YACD;gBACE,OAAO,SAAS,CAAC;QACrB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekwo-ai/document-reading",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.19.0",
|
|
4
|
+
"description": "One reader for received invoices: CII, Factur-X and UBL read exactly, and a photograph or a plain PDF read by a vision model through a published schema and prompt — both into the EN 16931 ReceivedInvoice.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"invoice",
|
|
7
|
+
"receipt",
|
|
8
|
+
"ocr",
|
|
9
|
+
"vision",
|
|
10
|
+
"en16931",
|
|
11
|
+
"factur-x",
|
|
12
|
+
"zugferd",
|
|
13
|
+
"ubl",
|
|
14
|
+
"cii",
|
|
15
|
+
"peppol",
|
|
16
|
+
"json-schema"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Ekwo AI",
|
|
20
|
+
"homepage": "https://github.com/Ekwo-ai/ekwo-os/tree/main/packages/document-reading#readme",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/Ekwo-ai/ekwo-os.git",
|
|
24
|
+
"directory": "packages/document-reading"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/Ekwo-ai/ekwo-os/issues"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"CHANGELOG.md"
|
|
39
|
+
],
|
|
40
|
+
"main": "./dist/index.js",
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"exports": {
|
|
43
|
+
".": {
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"import": "./dist/index.js"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc -p tsconfig.build.json",
|
|
50
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@ekwo-ai/factur-x": "^0.19.0",
|
|
56
|
+
"@ekwo-ai/peppol-ubl": "^0.19.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^24.0.0",
|
|
60
|
+
"pdf-lib": "^1.17.1",
|
|
61
|
+
"typescript": "^5.9.0",
|
|
62
|
+
"vitest": "^5.0.3"
|
|
63
|
+
}
|
|
64
|
+
}
|