@ekwo-ai/coda 0.4.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.md +206 -0
- package/dist/checks.d.ts +12 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +39 -0
- package/dist/checks.js.map +1 -0
- package/dist/decimal.d.ts +16 -0
- package/dist/decimal.d.ts.map +1 -0
- package/dist/decimal.js +25 -0
- package/dist/decimal.js.map +1 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +12 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +546 -0
- package/dist/index.js.map +1 -0
- package/dist/records.d.ts +17 -0
- package/dist/records.d.ts.map +1 -0
- package/dist/records.js +91 -0
- package/dist/records.js.map +1 -0
- package/dist/types.d.ts +254 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
package/dist/records.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { StatementFileError } from './errors.js';
|
|
2
|
+
const BYTE_ORDER_MARK = 0xfeff;
|
|
3
|
+
const END_OF_FILE_MARK = 0x1a;
|
|
4
|
+
const REPLACEMENT = 0xfffd;
|
|
5
|
+
/** A control character other than the two that end a line, or the replacement character. */
|
|
6
|
+
function unreadable(code) {
|
|
7
|
+
if (code === 0x0a || code === 0x0d)
|
|
8
|
+
return false;
|
|
9
|
+
return code < 0x20 || (code >= 0x7f && code <= 0x9f) || code === REPLACEMENT;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Bytes to text, fatally: a byte that is not the encoding announced is an
|
|
13
|
+
* error and not a replacement character in somebody's name. ISO-8859-1 maps
|
|
14
|
+
* every byte, so it is never guessed — the caller says so, or the bytes are
|
|
15
|
+
* UTF-8 (of which ASCII, which is what the standard writes, is a subset).
|
|
16
|
+
*/
|
|
17
|
+
export function decode(input, encoding, maxBytes) {
|
|
18
|
+
let text;
|
|
19
|
+
if (typeof input === 'string') {
|
|
20
|
+
if (input.length > maxBytes) {
|
|
21
|
+
throw new StatementFileError('too_large', `the file is longer than ${maxBytes} bytes`);
|
|
22
|
+
}
|
|
23
|
+
text = input;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
if (input.byteLength > maxBytes) {
|
|
27
|
+
throw new StatementFileError('too_large', `the file is ${input.byteLength} bytes, over the limit of ${maxBytes}`);
|
|
28
|
+
}
|
|
29
|
+
if (encoding === 'iso-8859-1') {
|
|
30
|
+
const parts = [];
|
|
31
|
+
for (let i = 0; i < input.length; i += 8192) {
|
|
32
|
+
parts.push(String.fromCharCode(...input.subarray(i, i + 8192)));
|
|
33
|
+
}
|
|
34
|
+
text = parts.join('');
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
try {
|
|
38
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(input);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new StatementFileError('unsupported_encoding', "the bytes are not UTF-8; if the bank writes ISO-8859-1, say so with { encoding: 'iso-8859-1' } — it is not guessed, because every sequence of bytes is valid ISO-8859-1");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (text.charCodeAt(0) === BYTE_ORDER_MARK)
|
|
46
|
+
text = text.slice(1);
|
|
47
|
+
// The DOS end-of-file mark some transfers leave behind.
|
|
48
|
+
let end = text.length;
|
|
49
|
+
while (end > 0 && text.charCodeAt(end - 1) === END_OF_FILE_MARK)
|
|
50
|
+
end -= 1;
|
|
51
|
+
text = text.slice(0, end);
|
|
52
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
53
|
+
const code = text.charCodeAt(i);
|
|
54
|
+
if (unreadable(code)) {
|
|
55
|
+
throw new StatementFileError('unsupported_encoding', `the file holds the character U+${code.toString(16).toUpperCase().padStart(4, '0')} at offset ${i}: a control or replacement character, which is a file decoded with the wrong encoding, or not a text file`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return text;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The records of the file: its lines, or — when the file has no line break at
|
|
62
|
+
* all, as some transfers deliver it — consecutive slices of the record length.
|
|
63
|
+
* Every record is exactly `length` characters, or the file is refused: nothing
|
|
64
|
+
* is padded and nothing is trimmed, because the last position of a record
|
|
65
|
+
* means something.
|
|
66
|
+
*/
|
|
67
|
+
export function splitRecords(text, length) {
|
|
68
|
+
let records;
|
|
69
|
+
if (!/[\r\n]/.test(text) && text.length > length) {
|
|
70
|
+
if (text.length % length !== 0) {
|
|
71
|
+
throw new StatementFileError('invalid_record_length', `the file has no line break and its ${text.length} characters are not a whole number of ${length}-character records`);
|
|
72
|
+
}
|
|
73
|
+
records = [];
|
|
74
|
+
for (let i = 0; i < text.length; i += length)
|
|
75
|
+
records.push(text.slice(i, i + length));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
records = text.split(/\r\n|\n|\r/);
|
|
79
|
+
while (records.length > 0 && records[records.length - 1] === '')
|
|
80
|
+
records.pop();
|
|
81
|
+
}
|
|
82
|
+
if (records.length === 0)
|
|
83
|
+
throw new StatementFileError('empty_file', 'the file holds no record');
|
|
84
|
+
records.forEach((record, index) => {
|
|
85
|
+
if (record.length !== length) {
|
|
86
|
+
throw new StatementFileError('invalid_record_length', `${record.length} characters where the format has ${length}`, index + 1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return records;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a statement is once it has been read: an account, two balances and
|
|
3
|
+
* lines. The shape is declared here and imported from nowhere — it names no
|
|
4
|
+
* table and no ledger. It is, field for field, the shape `@ekwo-ai/camt053`
|
|
5
|
+
* declares for itself, so whatever takes one takes the other; what only CODA
|
|
6
|
+
* says comes after, under names of its own.
|
|
7
|
+
*
|
|
8
|
+
* Every amount is a **decimal string**, signed from the account holder's side:
|
|
9
|
+
* money in is positive, money out is negative, an overdrawn balance is
|
|
10
|
+
* negative. No figure of a statement passes through a `number`.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* How an account is identified, which the file says itself (record 1,
|
|
14
|
+
* position 2): an IBAN, or a domestic number that is not one.
|
|
15
|
+
*/
|
|
16
|
+
export type AccountIdentifier = {
|
|
17
|
+
kind: 'iban';
|
|
18
|
+
/** As written, without spaces, upper case. */
|
|
19
|
+
value: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'other';
|
|
22
|
+
/** As written, without spaces. */
|
|
23
|
+
value: string;
|
|
24
|
+
/** `BBAN` when the file says the number is a domestic one; null for a counterparty's, which says nothing. */
|
|
25
|
+
scheme: string | null;
|
|
26
|
+
issuer: string | null;
|
|
27
|
+
};
|
|
28
|
+
export interface StatementAccount {
|
|
29
|
+
identifier: AccountIdentifier;
|
|
30
|
+
/** The ISO currency code of the account zone. Null when the zone leaves it blank. */
|
|
31
|
+
currency: string | null;
|
|
32
|
+
/** Record 1, "account description". */
|
|
33
|
+
name: string | null;
|
|
34
|
+
/** Record 1, "name of the account holder". */
|
|
35
|
+
ownerName: string | null;
|
|
36
|
+
/** Record 0, "BIC of the bank holding the account". */
|
|
37
|
+
servicerBic: string | null;
|
|
38
|
+
}
|
|
39
|
+
export interface StatementBalance {
|
|
40
|
+
/** `OPBD` for the old balance (record 1), `CLBD` for the new one (record 8). */
|
|
41
|
+
type: string;
|
|
42
|
+
/** Signed: a debit balance is negative. */
|
|
43
|
+
amount: string;
|
|
44
|
+
/** The currency of the account zone; null when the file leaves it blank. */
|
|
45
|
+
currency: string | null;
|
|
46
|
+
/** `YYYY-MM-DD`. */
|
|
47
|
+
date: string | null;
|
|
48
|
+
}
|
|
49
|
+
export interface StructuredReference {
|
|
50
|
+
/** Twelve digits for a Belgian structured communication, `RF…` for an ISO 11649 one — bare, as a camt.053 carries it. */
|
|
51
|
+
reference: string;
|
|
52
|
+
/** `SCOR`. */
|
|
53
|
+
type: string | null;
|
|
54
|
+
/** `BBA` for communication types 101 and 102, `ISO` for type 100. */
|
|
55
|
+
issuer: string | null;
|
|
56
|
+
}
|
|
57
|
+
export interface Remittance {
|
|
58
|
+
/** The free communication, the pieces of records 2.1, 2.2 and 2.3 put back together. */
|
|
59
|
+
unstructured: string[];
|
|
60
|
+
structured: StructuredReference[];
|
|
61
|
+
}
|
|
62
|
+
export interface Counterparty {
|
|
63
|
+
name: string | null;
|
|
64
|
+
account: AccountIdentifier | null;
|
|
65
|
+
/** Record 2.2, "BIC of the counterparty's bank". */
|
|
66
|
+
agentBic: string | null;
|
|
67
|
+
/** Information record of type 008 (money out) or 009 (money in). */
|
|
68
|
+
ultimateName: string | null;
|
|
69
|
+
}
|
|
70
|
+
export interface BankTransactionCode {
|
|
71
|
+
/** Always null: CODA has its own code list, not the ISO 20022 one. */
|
|
72
|
+
domain: string | null;
|
|
73
|
+
family: string | null;
|
|
74
|
+
subFamily: string | null;
|
|
75
|
+
/** The eight digits of the transaction code, as written. */
|
|
76
|
+
proprietary: string | null;
|
|
77
|
+
/** `CODA`. */
|
|
78
|
+
proprietaryIssuer: string | null;
|
|
79
|
+
}
|
|
80
|
+
/** The eight positions of a CODA transaction code, apart. */
|
|
81
|
+
export interface CodaTransactionCode {
|
|
82
|
+
/** 0 simple, 1 and 2 totals, 3 simple with detail, 5 to 9 details. */
|
|
83
|
+
type: string;
|
|
84
|
+
family: string;
|
|
85
|
+
transaction: string;
|
|
86
|
+
category: string;
|
|
87
|
+
}
|
|
88
|
+
export interface StatementLine {
|
|
89
|
+
/** Position among the statement's lines, from 1. */
|
|
90
|
+
index: number;
|
|
91
|
+
/** Which movement of the statement this line comes from, from 1: one per continuous sequence number. */
|
|
92
|
+
entry: number;
|
|
93
|
+
/**
|
|
94
|
+
* When the movement was a total split into its details: which one this is,
|
|
95
|
+
* from 1, and out of how many. Both null when the line is the whole movement.
|
|
96
|
+
*/
|
|
97
|
+
detail: number | null;
|
|
98
|
+
detailCount: number | null;
|
|
99
|
+
/** `BOOK`: a coded statement carries booked movements and nothing else. */
|
|
100
|
+
status: string;
|
|
101
|
+
booked: boolean;
|
|
102
|
+
/** Transaction 49 or 99 of its family: a correction or cancellation entry. */
|
|
103
|
+
reversal: boolean;
|
|
104
|
+
/** Record 2.1, "entry date". */
|
|
105
|
+
bookingDate: string | null;
|
|
106
|
+
/** Record 2.1, "value date"; null when the bank writes 000000. */
|
|
107
|
+
valueDate: string | null;
|
|
108
|
+
/** Signed: positive is money in. Null when the file's figure is unreadable. */
|
|
109
|
+
amount: string | null;
|
|
110
|
+
currency: string | null;
|
|
111
|
+
/** Signed amount of the whole movement when this line is one part of it. */
|
|
112
|
+
entryAmount: string | null;
|
|
113
|
+
/**
|
|
114
|
+
* Record 2.1, "reference number of the bank" — which the standard calls
|
|
115
|
+
* purely informative, and which a bank may change without notice. Null when
|
|
116
|
+
* blank or all zeros.
|
|
117
|
+
*/
|
|
118
|
+
bankReference: string | null;
|
|
119
|
+
/** Always null: CODA numbers a movement by its position, which is `sequenceNumber`. */
|
|
120
|
+
entryReference: string | null;
|
|
121
|
+
/** Record 2.2, "customer reference", on a simple movement or a detail. */
|
|
122
|
+
endToEndId: string | null;
|
|
123
|
+
transactionId: string | null;
|
|
124
|
+
instructionId: string | null;
|
|
125
|
+
/** Record 2.2, "customer reference", on a total (types 1 and 2). */
|
|
126
|
+
paymentInformationId: string | null;
|
|
127
|
+
/** The mandate reference of a structured communication of type 127. */
|
|
128
|
+
mandateId: string | null;
|
|
129
|
+
counterparty: Counterparty | null;
|
|
130
|
+
remittance: Remittance;
|
|
131
|
+
bankTransactionCode: BankTransactionCode | null;
|
|
132
|
+
/** Record 2.2, "ISO reason return code", else the reason of a type 127 communication. */
|
|
133
|
+
returnReason: string | null;
|
|
134
|
+
/** Always null: a CODA reports in the currency of the account. */
|
|
135
|
+
instructedAmount: {
|
|
136
|
+
amount: string;
|
|
137
|
+
currency: string;
|
|
138
|
+
} | null;
|
|
139
|
+
exchangeRate: string | null;
|
|
140
|
+
/** The free text of the information records (3) of the movement, joined. */
|
|
141
|
+
additionalInformation: string | null;
|
|
142
|
+
/** "Continuous sequence number", as written. */
|
|
143
|
+
sequenceNumber: string;
|
|
144
|
+
/** "Detail number", as written: `0000` for the movement itself. */
|
|
145
|
+
detailNumber: string;
|
|
146
|
+
transactionCode: CodaTransactionCode | null;
|
|
147
|
+
/** Position 125 of record 2.1, as written; `0` when the record opens or closes no globalisation. */
|
|
148
|
+
globalisationCode: string;
|
|
149
|
+
/** A structured communication, as written, whether or not this reader decodes its type. */
|
|
150
|
+
structuredCommunication: {
|
|
151
|
+
type: string;
|
|
152
|
+
text: string;
|
|
153
|
+
} | null;
|
|
154
|
+
/** Record 2.2: 1 reject, 2 return, 3 refund, 4 reversal, 5 cancellation. */
|
|
155
|
+
rTransactionType: string | null;
|
|
156
|
+
categoryPurpose: string | null;
|
|
157
|
+
purpose: string | null;
|
|
158
|
+
/** Every information record (3) of the movement: its communication type, or null when free, and its text. */
|
|
159
|
+
information: {
|
|
160
|
+
type: string | null;
|
|
161
|
+
text: string;
|
|
162
|
+
}[];
|
|
163
|
+
}
|
|
164
|
+
export interface Statement {
|
|
165
|
+
/**
|
|
166
|
+
* CODA gives a statement no identifier. This one is made of what it does
|
|
167
|
+
* give: the year of the creation date and the sequence number of the coded
|
|
168
|
+
* statement, `2026-042`. The same file always makes the same one.
|
|
169
|
+
*/
|
|
170
|
+
id: string;
|
|
171
|
+
/** Record 1, "sequence number of the coded statement of account": restarts at 001 each year. */
|
|
172
|
+
electronicSequenceNumber: string | null;
|
|
173
|
+
/**
|
|
174
|
+
* Always null. The number of the paper statement is in `paperSequenceNumber`:
|
|
175
|
+
* the standard lets a bank write a Julian date or zeros there, so it is not
|
|
176
|
+
* offered as a number whose holes mean a missing statement.
|
|
177
|
+
*/
|
|
178
|
+
legalSequenceNumber: string | null;
|
|
179
|
+
/** Record 0, "creation date", by day. */
|
|
180
|
+
createdAt: string | null;
|
|
181
|
+
/** Always null: the dates of the two balances are what CODA gives. */
|
|
182
|
+
period: {
|
|
183
|
+
from: string | null;
|
|
184
|
+
to: string | null;
|
|
185
|
+
} | null;
|
|
186
|
+
page: {
|
|
187
|
+
number: string;
|
|
188
|
+
last: boolean;
|
|
189
|
+
} | null;
|
|
190
|
+
account: StatementAccount;
|
|
191
|
+
openingBalance: StatementBalance | null;
|
|
192
|
+
closingBalance: StatementBalance | null;
|
|
193
|
+
balances: StatementBalance[];
|
|
194
|
+
lines: StatementLine[];
|
|
195
|
+
/**
|
|
196
|
+
* Old balance plus the movements equals new balance: true, false, or null
|
|
197
|
+
* when it could not be checked. False is reported as `balance_mismatch` and
|
|
198
|
+
* **never corrected**.
|
|
199
|
+
*/
|
|
200
|
+
balanced: boolean | null;
|
|
201
|
+
paperSequenceNumber: string | null;
|
|
202
|
+
/** Record 0, position 17: the bank says this file is a duplicate of one it already sent. */
|
|
203
|
+
duplicate: boolean;
|
|
204
|
+
/** Record 0, "file reference as determined by the bank". */
|
|
205
|
+
fileReference: string | null;
|
|
206
|
+
/** Record 0, "name addressee". */
|
|
207
|
+
addressee: string | null;
|
|
208
|
+
/** Record 0, "identification number of the account holder", as written. */
|
|
209
|
+
holderIdentification: string | null;
|
|
210
|
+
/** Record 0, code "separate application": `00000` is every movement of the account. */
|
|
211
|
+
separateApplication: string;
|
|
212
|
+
/** The free communications (record 4), one text per communication. */
|
|
213
|
+
freeCommunications: string[];
|
|
214
|
+
}
|
|
215
|
+
export interface Violation {
|
|
216
|
+
/** A stable token, for a caller that branches on it. */
|
|
217
|
+
code: ViolationCode;
|
|
218
|
+
/** What is wrong, in a sentence. */
|
|
219
|
+
message: string;
|
|
220
|
+
/** Which statement of the file, from 1. */
|
|
221
|
+
statement: number;
|
|
222
|
+
/** Which line of that statement (`StatementLine.index`), where there is one. */
|
|
223
|
+
line?: number;
|
|
224
|
+
}
|
|
225
|
+
export type ViolationCode = 'balance_mismatch' | 'closing_balance_missing' | 'currency_missing' | 'record_count_mismatch' | 'debit_total_mismatch' | 'credit_total_mismatch' | 'separate_application' | 'invalid_amount' | 'invalid_direction' | 'invalid_date' | 'booking_date_missing' | 'invalid_iban' | 'invalid_structured_reference' | 'batch_not_split';
|
|
226
|
+
export interface StatementFile {
|
|
227
|
+
format: 'coda';
|
|
228
|
+
/** The version code of the header record: `2`. */
|
|
229
|
+
version: string;
|
|
230
|
+
/**
|
|
231
|
+
* What was read, under one name: `coda.2`. The word is XML's and is kept
|
|
232
|
+
* because it is the key under which `@ekwo-ai/camt053` says the same thing.
|
|
233
|
+
*/
|
|
234
|
+
namespace: string;
|
|
235
|
+
statements: Statement[];
|
|
236
|
+
violations: Violation[];
|
|
237
|
+
}
|
|
238
|
+
export interface ReadOptions {
|
|
239
|
+
/** Refuse a larger file unread. Default 32 MiB. */
|
|
240
|
+
maxBytes?: number;
|
|
241
|
+
/**
|
|
242
|
+
* How bytes are decoded. Default `utf-8`, fatally — which reads ASCII, the
|
|
243
|
+
* character set of the standard. `iso-8859-1` is said, never guessed.
|
|
244
|
+
* Ignored when the input is already a string.
|
|
245
|
+
*/
|
|
246
|
+
encoding?: 'utf-8' | 'iso-8859-1';
|
|
247
|
+
/**
|
|
248
|
+
* CODA writes a year on two digits. `YY` at or above this is 19YY, under it
|
|
249
|
+
* 20YY. Default 80: version 2 of the standard has no file from before 1980
|
|
250
|
+
* and this reader will need another look in 2080.
|
|
251
|
+
*/
|
|
252
|
+
pivotYear?: number;
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a statement is once it has been read: an account, two balances and
|
|
3
|
+
* lines. The shape is declared here and imported from nowhere — it names no
|
|
4
|
+
* table and no ledger. It is, field for field, the shape `@ekwo-ai/camt053`
|
|
5
|
+
* declares for itself, so whatever takes one takes the other; what only CODA
|
|
6
|
+
* says comes after, under names of its own.
|
|
7
|
+
*
|
|
8
|
+
* Every amount is a **decimal string**, signed from the account holder's side:
|
|
9
|
+
* money in is positive, money out is negative, an overdrawn balance is
|
|
10
|
+
* negative. No figure of a statement passes through a `number`.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ekwo-ai/coda",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Reads a CODA coded statement of account (Febelfin, version 2) into plain objects in TypeScript: account, balances and lines, amounts as decimal strings, and what does not add up. Zero dependencies.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"coda",
|
|
7
|
+
"febelfin",
|
|
8
|
+
"bank-statement",
|
|
9
|
+
"coded-statement-of-account",
|
|
10
|
+
"reconciliation",
|
|
11
|
+
"structured-communication",
|
|
12
|
+
"fixed-width",
|
|
13
|
+
"parser"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Ekwo AI",
|
|
17
|
+
"homepage": "https://github.com/Ekwo-ai/ekwo-os/tree/main/packages/formats/coda#readme",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Ekwo-ai/ekwo-os.git",
|
|
21
|
+
"directory": "packages/formats/coda"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/Ekwo-ai/ekwo-os/issues"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"main": "./dist/index.js",
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"import": "./dist/index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc -p tsconfig.build.json",
|
|
46
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
47
|
+
"test": "vitest run"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^24.0.0",
|
|
51
|
+
"typescript": "^5.9.0",
|
|
52
|
+
"vitest": "^3.2.0"
|
|
53
|
+
}
|
|
54
|
+
}
|