@classytic/primitives 0.26.4 → 0.27.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 +24 -0
- package/dist/csv.d.mts +36 -0
- package/dist/csv.mjs +50 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
4
4
|
adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## 0.27.0
|
|
7
|
+
|
|
8
|
+
### Added — `./csv` subpath: one CSV cell escaper for the whole fleet
|
|
9
|
+
|
|
10
|
+
`escapeCsvCell(value)` and `neutralizeCsvFormula(value)`. Six CSV producers
|
|
11
|
+
across ledger, the spine and the hosts each hand-rolled RFC 4180 quoting, and
|
|
12
|
+
not one neutralized a leading `= + - @ \t \r` — so a customer-supplied payment
|
|
13
|
+
reference or party name reached the accountant's spreadsheet as a live formula.
|
|
14
|
+
Quoting does not help: a spreadsheet strips the quotes and evaluates what is
|
|
15
|
+
left, which is why the two defences ship as one module.
|
|
16
|
+
|
|
17
|
+
The two functions are separable on purpose. A consumer holding a real CSV
|
|
18
|
+
writer (`csv-stringify`) takes `neutralizeCsvFormula` only, so its quoting is
|
|
19
|
+
not done twice; a hand-rolled `rows.map(...).join(',')` takes `escapeCsvCell`.
|
|
20
|
+
|
|
21
|
+
**Well-formed numbers are exempt.** Prefixing `-1500` would make every negative
|
|
22
|
+
amount text and break `SUM` in every finance export — a fix that quietly
|
|
23
|
+
corrupts the product is worse than the hole. The exemption requires the WHOLE
|
|
24
|
+
cell to be a number, so `-2+3+cmd|'/c calc'!A0` is still neutralized.
|
|
25
|
+
|
|
26
|
+
Not for a machine-to-machine upload contract with a fixed column spec (a
|
|
27
|
+
courier's bulk-import CSV): there the receiver is a parser, not a spreadsheet,
|
|
28
|
+
and an added `'` corrupts the payload.
|
|
29
|
+
|
|
6
30
|
## 0.26.4
|
|
7
31
|
|
|
8
32
|
### Added — `./price-basis` subpath: measured pricing across physical dimensions
|
package/dist/csv.d.mts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/serialization/csv.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* `@classytic/primitives/csv` — make one value safe to put in a CSV cell.
|
|
4
|
+
*
|
|
5
|
+
* Two defences, deliberately separable because a consumer that already has a
|
|
6
|
+
* real CSV writer must NOT get its quoting done twice:
|
|
7
|
+
*
|
|
8
|
+
* - `neutralizeCsvFormula` — stops the cell being executed as a formula when
|
|
9
|
+
* the file is opened in Excel / Sheets / LibreOffice. Use this when a
|
|
10
|
+
* library (`csv-stringify`, `papaparse`, …) is doing the quoting.
|
|
11
|
+
* - `escapeCsvCell` — neutralize + RFC 4180 quoting. Use this in a
|
|
12
|
+
* hand-rolled `rows.map(...).join(',')` serializer.
|
|
13
|
+
*
|
|
14
|
+
* WHY quoting is not enough: a spreadsheet strips the quotes first and
|
|
15
|
+
* evaluates what is left, so `"=cmd|'/c calc'!A1"` still executes. Only
|
|
16
|
+
* changing the LEADING character stops it, which is why these are one module
|
|
17
|
+
* and not a quoting helper someone can use alone.
|
|
18
|
+
*
|
|
19
|
+
* Well-formed numbers are exempt. Prefixing `-1500` would make every negative
|
|
20
|
+
* amount text and break the reader's `SUM` — a fix that quietly corrupts every
|
|
21
|
+
* finance export is worse than the hole. The exemption is the WHOLE cell
|
|
22
|
+
* matching a number, so `-2+3+cmd|'/c calc'!A0` is still neutralized.
|
|
23
|
+
*
|
|
24
|
+
* NOT for a machine-to-machine upload contract with a fixed column spec (a
|
|
25
|
+
* courier's bulk-import CSV, say) — there the receiver is a parser, not a
|
|
26
|
+
* spreadsheet, and an added `'` corrupts the payload.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Prefix `'` when the cell would otherwise be evaluated as a formula.
|
|
30
|
+
* `null`/`undefined` become `''`. Numbers are returned unchanged.
|
|
31
|
+
*/
|
|
32
|
+
declare function neutralizeCsvFormula(value: unknown): string;
|
|
33
|
+
/** `neutralizeCsvFormula` + RFC 4180 quoting. The one-call form. */
|
|
34
|
+
declare function escapeCsvCell(value: unknown): string;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { escapeCsvCell, neutralizeCsvFormula };
|
package/dist/csv.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/serialization/csv.ts
|
|
2
|
+
/**
|
|
3
|
+
* `@classytic/primitives/csv` — make one value safe to put in a CSV cell.
|
|
4
|
+
*
|
|
5
|
+
* Two defences, deliberately separable because a consumer that already has a
|
|
6
|
+
* real CSV writer must NOT get its quoting done twice:
|
|
7
|
+
*
|
|
8
|
+
* - `neutralizeCsvFormula` — stops the cell being executed as a formula when
|
|
9
|
+
* the file is opened in Excel / Sheets / LibreOffice. Use this when a
|
|
10
|
+
* library (`csv-stringify`, `papaparse`, …) is doing the quoting.
|
|
11
|
+
* - `escapeCsvCell` — neutralize + RFC 4180 quoting. Use this in a
|
|
12
|
+
* hand-rolled `rows.map(...).join(',')` serializer.
|
|
13
|
+
*
|
|
14
|
+
* WHY quoting is not enough: a spreadsheet strips the quotes first and
|
|
15
|
+
* evaluates what is left, so `"=cmd|'/c calc'!A1"` still executes. Only
|
|
16
|
+
* changing the LEADING character stops it, which is why these are one module
|
|
17
|
+
* and not a quoting helper someone can use alone.
|
|
18
|
+
*
|
|
19
|
+
* Well-formed numbers are exempt. Prefixing `-1500` would make every negative
|
|
20
|
+
* amount text and break the reader's `SUM` — a fix that quietly corrupts every
|
|
21
|
+
* finance export is worse than the hole. The exemption is the WHOLE cell
|
|
22
|
+
* matching a number, so `-2+3+cmd|'/c calc'!A0` is still neutralized.
|
|
23
|
+
*
|
|
24
|
+
* NOT for a machine-to-machine upload contract with a fixed column spec (a
|
|
25
|
+
* courier's bulk-import CSV, say) — there the receiver is a parser, not a
|
|
26
|
+
* spreadsheet, and an added `'` corrupts the payload.
|
|
27
|
+
*/
|
|
28
|
+
/** Leading characters a spreadsheet may treat as the start of a formula or DDE call. */
|
|
29
|
+
const FORMULA_LEAD = /^[=+\-@\t\r]/;
|
|
30
|
+
/** The WHOLE cell is a plain number — no separators, no currency symbol. */
|
|
31
|
+
const WHOLE_NUMBER = /^[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?$/;
|
|
32
|
+
/** Characters that force RFC 4180 quoting. */
|
|
33
|
+
const NEEDS_QUOTING = /[",\r\n]/;
|
|
34
|
+
/**
|
|
35
|
+
* Prefix `'` when the cell would otherwise be evaluated as a formula.
|
|
36
|
+
* `null`/`undefined` become `''`. Numbers are returned unchanged.
|
|
37
|
+
*/
|
|
38
|
+
function neutralizeCsvFormula(value) {
|
|
39
|
+
const text = value == null ? "" : String(value);
|
|
40
|
+
if (!FORMULA_LEAD.test(text)) return text;
|
|
41
|
+
if (WHOLE_NUMBER.test(text)) return text;
|
|
42
|
+
return `'${text}`;
|
|
43
|
+
}
|
|
44
|
+
/** `neutralizeCsvFormula` + RFC 4180 quoting. The one-call form. */
|
|
45
|
+
function escapeCsvCell(value) {
|
|
46
|
+
const text = neutralizeCsvFormula(value);
|
|
47
|
+
return NEEDS_QUOTING.test(text) ? `"${text.replace(/"/g, "\"\"")}"` : text;
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { escapeCsvCell, neutralizeCsvFormula };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/primitives",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Framework-agnostic TypeScript primitives shared across Classytic packages — Money, events, pagination, tenant config, and other building blocks.",
|
|
5
5
|
"author": "Classytic",
|
|
6
6
|
"license": "MIT",
|
|
@@ -205,6 +205,10 @@
|
|
|
205
205
|
"types": "./dist/canonical.d.mts",
|
|
206
206
|
"default": "./dist/canonical.mjs"
|
|
207
207
|
},
|
|
208
|
+
"./csv": {
|
|
209
|
+
"types": "./dist/csv.d.mts",
|
|
210
|
+
"default": "./dist/csv.mjs"
|
|
211
|
+
},
|
|
208
212
|
"./package.json": "./package.json",
|
|
209
213
|
"./state-diagram": {
|
|
210
214
|
"types": "./dist/state-diagram.d.mts",
|