@cogenta/commerce 0.2.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.
Files changed (98) hide show
  1. package/LICENSE +373 -0
  2. package/dist/admin/permissions.d.ts +57 -0
  3. package/dist/admin/permissions.d.ts.map +1 -0
  4. package/dist/admin/permissions.js +87 -0
  5. package/dist/admin/permissions.js.map +1 -0
  6. package/dist/admin/router.d.ts +53 -0
  7. package/dist/admin/router.d.ts.map +1 -0
  8. package/dist/admin/router.js +476 -0
  9. package/dist/admin/router.js.map +1 -0
  10. package/dist/cart/store.d.ts +91 -0
  11. package/dist/cart/store.d.ts.map +1 -0
  12. package/dist/cart/store.js +342 -0
  13. package/dist/cart/store.js.map +1 -0
  14. package/dist/cart/totals.d.ts +73 -0
  15. package/dist/cart/totals.d.ts.map +1 -0
  16. package/dist/cart/totals.js +65 -0
  17. package/dist/cart/totals.js.map +1 -0
  18. package/dist/catalog/store.d.ts +53 -0
  19. package/dist/catalog/store.d.ts.map +1 -0
  20. package/dist/catalog/store.js +358 -0
  21. package/dist/catalog/store.js.map +1 -0
  22. package/dist/catalog/types.d.ts +95 -0
  23. package/dist/catalog/types.d.ts.map +1 -0
  24. package/dist/catalog/types.js +5 -0
  25. package/dist/catalog/types.js.map +1 -0
  26. package/dist/coupon/store.d.ts +103 -0
  27. package/dist/coupon/store.d.ts.map +1 -0
  28. package/dist/coupon/store.js +181 -0
  29. package/dist/coupon/store.js.map +1 -0
  30. package/dist/customer/store.d.ts +41 -0
  31. package/dist/customer/store.d.ts.map +1 -0
  32. package/dist/customer/store.js +103 -0
  33. package/dist/customer/store.js.map +1 -0
  34. package/dist/index.d.ts +42 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +23 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/invoice/pdf.d.ts +67 -0
  39. package/dist/invoice/pdf.d.ts.map +1 -0
  40. package/dist/invoice/pdf.js +449 -0
  41. package/dist/invoice/pdf.js.map +1 -0
  42. package/dist/invoice/store.d.ts +96 -0
  43. package/dist/invoice/store.d.ts.map +1 -0
  44. package/dist/invoice/store.js +221 -0
  45. package/dist/invoice/store.js.map +1 -0
  46. package/dist/money.d.ts +55 -0
  47. package/dist/money.d.ts.map +1 -0
  48. package/dist/money.js +146 -0
  49. package/dist/money.js.map +1 -0
  50. package/dist/order/store.d.ts +75 -0
  51. package/dist/order/store.d.ts.map +1 -0
  52. package/dist/order/store.js +301 -0
  53. package/dist/order/store.js.map +1 -0
  54. package/dist/order/types.d.ts +67 -0
  55. package/dist/order/types.d.ts.map +1 -0
  56. package/dist/order/types.js +71 -0
  57. package/dist/order/types.js.map +1 -0
  58. package/dist/payment/manual.d.ts +27 -0
  59. package/dist/payment/manual.d.ts.map +1 -0
  60. package/dist/payment/manual.js +105 -0
  61. package/dist/payment/manual.js.map +1 -0
  62. package/dist/payment/registry.d.ts +23 -0
  63. package/dist/payment/registry.d.ts.map +1 -0
  64. package/dist/payment/registry.js +30 -0
  65. package/dist/payment/registry.js.map +1 -0
  66. package/dist/payment/store.d.ts +38 -0
  67. package/dist/payment/store.d.ts.map +1 -0
  68. package/dist/payment/store.js +253 -0
  69. package/dist/payment/store.js.map +1 -0
  70. package/dist/payment/stripe.d.ts +10 -0
  71. package/dist/payment/stripe.d.ts.map +1 -0
  72. package/dist/payment/stripe.js +419 -0
  73. package/dist/payment/stripe.js.map +1 -0
  74. package/dist/payment/types.d.ts +124 -0
  75. package/dist/payment/types.d.ts.map +1 -0
  76. package/dist/payment/types.js +18 -0
  77. package/dist/payment/types.js.map +1 -0
  78. package/dist/rows.d.ts +26 -0
  79. package/dist/rows.d.ts.map +1 -0
  80. package/dist/rows.js +87 -0
  81. package/dist/rows.js.map +1 -0
  82. package/dist/shipping/store.d.ts +88 -0
  83. package/dist/shipping/store.d.ts.map +1 -0
  84. package/dist/shipping/store.js +165 -0
  85. package/dist/shipping/store.js.map +1 -0
  86. package/dist/subscription/store.d.ts +106 -0
  87. package/dist/subscription/store.d.ts.map +1 -0
  88. package/dist/subscription/store.js +310 -0
  89. package/dist/subscription/store.js.map +1 -0
  90. package/dist/tables.d.ts +41 -0
  91. package/dist/tables.d.ts.map +1 -0
  92. package/dist/tables.js +383 -0
  93. package/dist/tables.js.map +1 -0
  94. package/dist/tax/store.d.ts +68 -0
  95. package/dist/tax/store.d.ts.map +1 -0
  96. package/dist/tax/store.js +126 -0
  97. package/dist/tax/store.js.map +1 -0
  98. package/package.json +42 -0
@@ -0,0 +1,449 @@
1
+ /**
2
+ * An invoice rendered as a PDF, written byte by byte, with no dependency.
3
+ *
4
+ * Rule R9 asks for zero dependencies before a small one and rule R10 forbids
5
+ * native code, which rules out `pdfkit` (a large tree) and `puppeteer` (a
6
+ * browser download plus a native binary). A PDF that only has to place text on
7
+ * A4 in a base-14 font is a few hundred lines of byte assembly, so that is what
8
+ * this is.
9
+ *
10
+ * Three decisions shape the file it produces:
11
+ *
12
+ * - **Base-14 fonts only.** `/Helvetica` and `/Helvetica-Bold` are guaranteed
13
+ * present in every conforming reader, so nothing is embedded and an invoice
14
+ * stays a few kilobytes. The price is `WinAnsiEncoding`: one byte per glyph,
15
+ * and a character outside that repertoire becomes `?` rather than a broken
16
+ * file.
17
+ * - **No compression.** A `/FlateDecode` stream would need zlib and would make
18
+ * the output unreadable to anything but a PDF parser. Uncompressed streams
19
+ * keep an invoice greppable and the whole file auditable by eye.
20
+ * - **Deterministic to the byte.** Nothing here reads the clock or a random
21
+ * source. The same document renders to the same bytes forever, so a hash
22
+ * stored next to an invoice still means something a year later. `/CreationDate`
23
+ * is derived from the document's own `issuedAt` or omitted entirely.
24
+ */
25
+ // --- Page geometry -------------------------------------------------------
26
+ // A4 in points, the unit PDF measures everything in (72 to the inch).
27
+ const PAGE_WIDTH = 595;
28
+ const PAGE_HEIGHT = 842;
29
+ const MARGIN = 50;
30
+ const CONTENT_WIDTH = PAGE_WIDTH - MARGIN * 2;
31
+ const RIGHT_EDGE = MARGIN + CONTENT_WIDTH;
32
+ /** Baseline of the first line on a page. PDF's origin is the bottom left. */
33
+ const TOP_Y = PAGE_HEIGHT - MARGIN;
34
+ /** Nothing is drawn below this; the footer lives underneath it. */
35
+ const BOTTOM_Y = MARGIN + 24;
36
+ const COLUMN_GUTTER = 6;
37
+ /**
38
+ * Helvetica is proportional, so this is a deliberate approximation: a fixed
39
+ * measure of half an em per character. It over-estimates `l` and `i` and
40
+ * under-estimates `W`, which for an invoice — digits, short labels, addresses —
41
+ * lands close enough to wrap sensibly. The alternative is embedding the
42
+ * Helvetica width table, 315 numbers of accidental complexity for a table of
43
+ * amounts.
44
+ */
45
+ const CHAR_WIDTH_RATIO = 0.5;
46
+ const SIZE_TITLE = 20;
47
+ const SIZE_META = 10;
48
+ const SIZE_ROW = 9;
49
+ const SIZE_FOOTER = 8;
50
+ const LEAD_META = 13;
51
+ /** Extra space between two wrapped lines inside one table cell. */
52
+ const ROW_LEADING = 3;
53
+ // --- Object numbering ----------------------------------------------------
54
+ // Fixed for the first five; pages then take two objects each (the page
55
+ // dictionary and its content stream), which is what makes the page object
56
+ // number computable without a second pass.
57
+ const OBJ_CATALOG = 1;
58
+ const OBJ_PAGE_TREE = 2;
59
+ const OBJ_FONT_REGULAR = 3;
60
+ const OBJ_FONT_BOLD = 4;
61
+ const OBJ_INFO = 5;
62
+ const FIRST_PAGE_OBJECT = 6;
63
+ /**
64
+ * The one entry point: a document in, a complete PDF file out.
65
+ *
66
+ * Total by design — it never throws. Every input that could be called invalid
67
+ * has a defined, lossless rendering instead: a row shorter than the header is
68
+ * padded, a row longer than the header widens the table, an unrepresentable
69
+ * character becomes `?`, and text too wide for its column wraps rather than
70
+ * being cut. An invoice is a legal document; losing a line to a validation
71
+ * decision taken inside a renderer would be worse than printing it oddly.
72
+ */
73
+ export function renderInvoicePdf(document) {
74
+ return serialise(layoutDocument(document), document);
75
+ }
76
+ // --- Layout --------------------------------------------------------------
77
+ function layoutDocument(document) {
78
+ const pages = [];
79
+ const startPage = () => {
80
+ const page = { texts: [], rules: [] };
81
+ pages.push(page);
82
+ return page;
83
+ };
84
+ let page = startPage();
85
+ let y = TOP_Y;
86
+ const write = (x, size, bold, text) => {
87
+ if (text !== '')
88
+ page.texts.push({ x, y, size, bold, text });
89
+ };
90
+ const writeRight = (right, size, bold, text) => {
91
+ if (text !== '')
92
+ page.texts.push({ x: right - measure(text, size), y, size, bold, text });
93
+ };
94
+ const rule = () => {
95
+ page.rules.push({ y, from: MARGIN, to: RIGHT_EDGE });
96
+ };
97
+ const breakPage = () => {
98
+ page = startPage();
99
+ y = TOP_Y;
100
+ };
101
+ // Heading. The number and the date are printed verbatim, without invented
102
+ // labels: the caller knows which language its customers read, this file does
103
+ // not, and a hard-coded "No." on a French invoice is worse than no label.
104
+ y -= SIZE_TITLE;
105
+ write(MARGIN, SIZE_TITLE, true, document.title);
106
+ y -= 20;
107
+ write(MARGIN, SIZE_META, false, document.number);
108
+ writeRight(RIGHT_EDGE, SIZE_META, false, document.issuedAt);
109
+ y -= 8;
110
+ rule();
111
+ y -= 22;
112
+ // Seller on the left, buyer on the right, both starting on the same line.
113
+ const addressTop = y;
114
+ const halfWidth = Math.floor(CONTENT_WIDTH / 2);
115
+ const writeAddress = (block, x) => {
116
+ y = addressTop;
117
+ block.forEach((line, index) => {
118
+ write(x, SIZE_META, index === 0, line);
119
+ y -= LEAD_META;
120
+ });
121
+ };
122
+ writeAddress(document.seller, MARGIN);
123
+ writeAddress(document.buyer, MARGIN + halfWidth);
124
+ const addressLines = Math.max(document.seller.length, document.buyer.length);
125
+ y = addressTop - addressLines * LEAD_META - 16;
126
+ // Line-item table.
127
+ const columnCount = Math.max(document.columns.length, ...document.lines.map((line) => line.cells.length), 0);
128
+ const widths = columnWidths(columnCount);
129
+ const offsets = columnOffsets(widths);
130
+ const rowHeight = (cells, size) => Math.max(1, ...cells.map((cell, index) => wrapCell(cell, widths, index, size).length)) *
131
+ (size + ROW_LEADING);
132
+ const paintRow = (cells, size, bold) => {
133
+ const top = y;
134
+ let height = size + ROW_LEADING;
135
+ cells.forEach((cell, index) => {
136
+ const width = widths[index] ?? 0;
137
+ const x = offsets[index] ?? MARGIN;
138
+ const lines = wrapCell(cell, widths, index, size);
139
+ // The last column carries amounts, and a column of amounts is only
140
+ // readable right-aligned.
141
+ const isLast = index === cells.length - 1 && cells.length === widths.length;
142
+ y = top;
143
+ for (const line of lines) {
144
+ if (isLast)
145
+ writeRight(x + width - COLUMN_GUTTER, size, bold, line);
146
+ else
147
+ write(x, size, bold, line);
148
+ y -= size + ROW_LEADING;
149
+ }
150
+ height = Math.max(height, lines.length * (size + ROW_LEADING));
151
+ });
152
+ y = top - height;
153
+ };
154
+ const paintHeader = () => {
155
+ if (columnCount === 0)
156
+ return;
157
+ paintRow(padCells(document.columns, columnCount), SIZE_ROW, true);
158
+ y += 2;
159
+ rule();
160
+ y -= 10;
161
+ };
162
+ if (columnCount > 0) {
163
+ if (y - rowHeight(padCells(document.columns, columnCount), SIZE_ROW) - 12 < BOTTOM_Y) {
164
+ breakPage();
165
+ }
166
+ paintHeader();
167
+ }
168
+ for (const line of document.lines) {
169
+ const cells = padCells(line.cells, columnCount);
170
+ if (y - rowHeight(cells, SIZE_ROW) < BOTTOM_Y) {
171
+ breakPage();
172
+ paintHeader();
173
+ }
174
+ paintRow(cells, SIZE_ROW, false);
175
+ }
176
+ // Totals, right-aligned in two columns of their own.
177
+ if (document.totals.length > 0) {
178
+ y -= 6;
179
+ if (y < BOTTOM_Y)
180
+ breakPage();
181
+ rule();
182
+ y -= 16;
183
+ document.totals.forEach(([label, amount], index) => {
184
+ if (y < BOTTOM_Y)
185
+ breakPage();
186
+ const bold = index === document.totals.length - 1;
187
+ writeRight(RIGHT_EDGE - 130, SIZE_META, bold, label);
188
+ writeRight(RIGHT_EDGE, SIZE_META, bold, amount);
189
+ y -= LEAD_META + 3;
190
+ });
191
+ }
192
+ // Footers last: the page count is only known once every page exists.
193
+ const footer = document.footer;
194
+ pages.forEach((target, index) => {
195
+ const baseline = MARGIN - 12;
196
+ if (footer !== undefined && footer !== '') {
197
+ target.texts.push({ x: MARGIN, y: baseline, size: SIZE_FOOTER, bold: false, text: footer });
198
+ }
199
+ const counter = `${index + 1} / ${pages.length}`;
200
+ target.texts.push({
201
+ x: RIGHT_EDGE - measure(counter, SIZE_FOOTER),
202
+ y: baseline,
203
+ size: SIZE_FOOTER,
204
+ bold: false,
205
+ text: counter,
206
+ });
207
+ });
208
+ return pages;
209
+ }
210
+ /**
211
+ * The first column gets 40 % of the width and the rest share what is left.
212
+ *
213
+ * An invoice's first column is a description and the others are quantities,
214
+ * unit prices and amounts — short. This is not a layout engine, and a fixed
215
+ * split is honest about that.
216
+ */
217
+ function columnWidths(count) {
218
+ if (count <= 0)
219
+ return [];
220
+ if (count === 1)
221
+ return [CONTENT_WIDTH];
222
+ const first = Math.round(CONTENT_WIDTH * 0.4);
223
+ const other = Math.floor((CONTENT_WIDTH - first) / (count - 1));
224
+ return [first, ...Array.from({ length: count - 1 }, () => other)];
225
+ }
226
+ function columnOffsets(widths) {
227
+ const offsets = [];
228
+ let x = MARGIN;
229
+ for (const width of widths) {
230
+ offsets.push(x);
231
+ x += width;
232
+ }
233
+ return offsets;
234
+ }
235
+ function padCells(cells, count) {
236
+ if (cells.length >= count)
237
+ return cells;
238
+ return [...cells, ...Array.from({ length: count - cells.length }, () => '')];
239
+ }
240
+ function wrapCell(cell, widths, index, size) {
241
+ const width = widths[index] ?? CONTENT_WIDTH;
242
+ return wrapText(cell, width - COLUMN_GUTTER, size);
243
+ }
244
+ /**
245
+ * Wraps rather than truncates. A cut description on an invoice is a missing
246
+ * piece of a legal document; an ugly wrap is only ugly.
247
+ */
248
+ function wrapText(text, maxWidth, size) {
249
+ const maxChars = Math.max(1, Math.floor(maxWidth / (size * CHAR_WIDTH_RATIO)));
250
+ const out = [];
251
+ for (const paragraph of text.split(/\r?\n/u)) {
252
+ let current = '';
253
+ for (const word of paragraph.split(' ')) {
254
+ for (const piece of hardSplit(word, maxChars)) {
255
+ if (current === '')
256
+ current = piece;
257
+ else if (countCharacters(current) + 1 + countCharacters(piece) <= maxChars) {
258
+ current = `${current} ${piece}`;
259
+ }
260
+ else {
261
+ out.push(current);
262
+ current = piece;
263
+ }
264
+ }
265
+ }
266
+ out.push(current);
267
+ }
268
+ return out;
269
+ }
270
+ /** A word wider than its column (a URL, a long SKU) is broken, never dropped. */
271
+ function hardSplit(word, maxChars) {
272
+ const characters = [...word];
273
+ if (characters.length <= maxChars)
274
+ return [word];
275
+ const pieces = [];
276
+ for (let index = 0; index < characters.length; index += maxChars) {
277
+ pieces.push(characters.slice(index, index + maxChars).join(''));
278
+ }
279
+ return pieces;
280
+ }
281
+ function countCharacters(text) {
282
+ return [...text].length;
283
+ }
284
+ function measure(text, size) {
285
+ return countCharacters(text) * size * CHAR_WIDTH_RATIO;
286
+ }
287
+ // --- Text encoding -------------------------------------------------------
288
+ /**
289
+ * The twenty-seven WinAnsi code points that are not Latin-1.
290
+ *
291
+ * WinAnsi (Windows-1252) agrees with Latin-1 everywhere except 0x80–0x9F,
292
+ * where Latin-1 has unused control codes and WinAnsi has typography — the euro
293
+ * sign, curly quotes, the dashes a real invoice actually contains. Without this
294
+ * table a euro sign silently becomes a control byte.
295
+ */
296
+ const WIN_ANSI_EXTRAS = new Map([
297
+ [0x20ac, 0x80], // €
298
+ [0x201a, 0x82],
299
+ [0x0192, 0x83],
300
+ [0x201e, 0x84],
301
+ [0x2026, 0x85], // …
302
+ [0x2020, 0x86],
303
+ [0x2021, 0x87],
304
+ [0x02c6, 0x88],
305
+ [0x2030, 0x89],
306
+ [0x0160, 0x8a],
307
+ [0x2039, 0x8b],
308
+ [0x0152, 0x8c], // Œ
309
+ [0x017d, 0x8e],
310
+ [0x2018, 0x91],
311
+ [0x2019, 0x92], // ’
312
+ [0x201c, 0x93],
313
+ [0x201d, 0x94],
314
+ [0x2022, 0x95],
315
+ [0x2013, 0x96], // –
316
+ [0x2014, 0x97], // —
317
+ [0x02dc, 0x98],
318
+ [0x2122, 0x99],
319
+ [0x0161, 0x9a],
320
+ [0x203a, 0x9b],
321
+ [0x0153, 0x9c], // œ
322
+ [0x017e, 0x9e],
323
+ [0x0178, 0x9f],
324
+ ]);
325
+ const QUESTION_MARK = 0x3f;
326
+ /** One Unicode code point to the one WinAnsi byte that can carry it, or `?`. */
327
+ function winAnsiByte(codePoint) {
328
+ if (codePoint === 0x09)
329
+ return 0x20; // A tab has no width in a PDF string.
330
+ if (codePoint < 0x20)
331
+ return QUESTION_MARK;
332
+ if (codePoint <= 0x7e)
333
+ return codePoint;
334
+ const extra = WIN_ANSI_EXTRAS.get(codePoint);
335
+ if (extra !== undefined)
336
+ return extra;
337
+ // 0x7F and 0x80–0x9F are undefined in WinAnsi; 0xA0–0xFF is Latin-1 as is.
338
+ if (codePoint >= 0xa0 && codePoint <= 0xff)
339
+ return codePoint;
340
+ return QUESTION_MARK;
341
+ }
342
+ /**
343
+ * A PDF string literal: WinAnsi bytes between parentheses, with the three
344
+ * characters that would end or nest the literal escaped.
345
+ *
346
+ * This is the single most load-bearing function in the file. A customer named
347
+ * `Smith (Ltd)` whose parentheses are not escaped closes the literal early and
348
+ * every byte after it becomes an operator — the whole document, not just that
349
+ * one name, stops being a PDF.
350
+ */
351
+ function pdfString(text) {
352
+ let out = '(';
353
+ for (const character of text) {
354
+ const byte = winAnsiByte(character.codePointAt(0) ?? QUESTION_MARK);
355
+ if (byte === 0x28 || byte === 0x29 || byte === 0x5c)
356
+ out += '\\';
357
+ out += String.fromCharCode(byte);
358
+ }
359
+ return `${out})`;
360
+ }
361
+ // --- Serialisation -------------------------------------------------------
362
+ /**
363
+ * The file is assembled as a string in which **every character is one byte**
364
+ * (`pdfString` guarantees it, and every other fragment is ASCII). That single
365
+ * invariant is what makes the cross-reference table correct for free: a byte
366
+ * offset is a string index, so no offset is ever estimated.
367
+ */
368
+ function serialise(pages, document) {
369
+ const kids = pages.map((_, index) => `${FIRST_PAGE_OBJECT + index * 2} 0 R`).join(' ');
370
+ const objects = [
371
+ `<< /Type /Catalog /Pages ${OBJ_PAGE_TREE} 0 R >>`,
372
+ `<< /Type /Pages /Kids [${kids}] /Count ${pages.length} >>`,
373
+ '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>',
374
+ '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>',
375
+ infoDictionary(document),
376
+ ];
377
+ pages.forEach((page, index) => {
378
+ const contents = FIRST_PAGE_OBJECT + index * 2 + 1;
379
+ objects.push(`<< /Type /Page /Parent ${OBJ_PAGE_TREE} 0 R /MediaBox [0 0 ${PAGE_WIDTH} ${PAGE_HEIGHT}]` +
380
+ ` /Resources << /Font << /F1 ${OBJ_FONT_REGULAR} 0 R /F2 ${OBJ_FONT_BOLD} 0 R >> >>` +
381
+ ` /Contents ${contents} 0 R >>`);
382
+ objects.push(contentStreamObject(page));
383
+ });
384
+ // The binary comment on the second line tells every tool that has ever
385
+ // shipped a heuristic that this file is binary and must not be newline
386
+ // translated on transfer.
387
+ let out = '%PDF-1.4\n%âãÏÓ\n';
388
+ const offsets = [];
389
+ objects.forEach((body, index) => {
390
+ offsets.push(out.length);
391
+ out += `${index + 1} 0 obj\n${body}\nendobj\n`;
392
+ });
393
+ const xrefOffset = out.length;
394
+ // Entry zero is the head of the free list, and every entry is exactly twenty
395
+ // bytes: ten digits, a space, five digits, a space, the type, then a two-byte
396
+ // end of line. Readers that seek into this table by multiplication break on a
397
+ // nineteen-byte entry.
398
+ out += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`;
399
+ for (const offset of offsets)
400
+ out += `${String(offset).padStart(10, '0')} 00000 n \n`;
401
+ out += `trailer\n<< /Size ${objects.length + 1} /Root ${OBJ_CATALOG} 0 R`;
402
+ out += ` /Info ${OBJ_INFO} 0 R >>\nstartxref\n${xrefOffset}\n%%EOF\n`;
403
+ return toBytes(out);
404
+ }
405
+ function contentStreamObject(page) {
406
+ let stream = '0.6 w\n';
407
+ for (const line of page.rules) {
408
+ stream += `${number(line.from)} ${number(line.y)} m ${number(line.to)} ${number(line.y)} l S\n`;
409
+ }
410
+ for (const op of page.texts) {
411
+ const font = op.bold ? 'F2' : 'F1';
412
+ stream += `BT /${font} ${number(op.size)} Tf ${number(op.x)} ${number(op.y)} Td `;
413
+ stream += `${pdfString(op.text)} Tj ET\n`;
414
+ }
415
+ // `/Length` counts the bytes between `stream\n` and `\nendstream`. Because
416
+ // one character is one byte here, that is exactly the string length.
417
+ return `<< /Length ${stream.length} >>\nstream\n${stream}\nendstream`;
418
+ }
419
+ /**
420
+ * `/CreationDate` is only written when `issuedAt` starts with an ISO date.
421
+ *
422
+ * Guessing at a human-formatted date with `Date.parse` would make the bytes
423
+ * depend on the engine's parser, and the clock is out of the question: the
424
+ * whole point is that regenerating an invoice yields the same file. No date at
425
+ * all is better than a date that moves.
426
+ */
427
+ function infoDictionary(document) {
428
+ let info = `<< /Title ${pdfString(`${document.title} ${document.number}`)} /Producer (Cogenta)`;
429
+ const iso = /^(\d{4})-(\d{2})-(\d{2})/u.exec(document.issuedAt);
430
+ if (iso !== null)
431
+ info += ` /CreationDate (D:${iso[1]}${iso[2]}${iso[3]}000000Z)`;
432
+ return `${info} >>`;
433
+ }
434
+ /** Two decimals at most, and never an exponent, so the bytes never drift. */
435
+ function number(value) {
436
+ return String(Math.round(value * 100) / 100);
437
+ }
438
+ function toBytes(text) {
439
+ const bytes = new Uint8Array(text.length);
440
+ for (let index = 0; index < text.length; index += 1) {
441
+ const code = text.charCodeAt(index);
442
+ // Defence in depth for the one-character-one-byte invariant: substituting
443
+ // keeps the length — and therefore every recorded offset — correct, where
444
+ // encoding to UTF-8 would silently shift every offset after it.
445
+ bytes[index] = code > 0xff ? QUESTION_MARK : code;
446
+ }
447
+ return bytes;
448
+ }
449
+ //# sourceMappingURL=pdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf.js","sourceRoot":"","sources":["../../src/invoice/pdf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAmCH,4EAA4E;AAC5E,sEAAsE;AAEtE,MAAM,UAAU,GAAG,GAAG,CAAA;AACtB,MAAM,WAAW,GAAG,GAAG,CAAA;AACvB,MAAM,MAAM,GAAG,EAAE,CAAA;AACjB,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,GAAG,CAAC,CAAA;AAC7C,MAAM,UAAU,GAAG,MAAM,GAAG,aAAa,CAAA;AACzC,6EAA6E;AAC7E,MAAM,KAAK,GAAG,WAAW,GAAG,MAAM,CAAA;AAClC,mEAAmE;AACnE,MAAM,QAAQ,GAAG,MAAM,GAAG,EAAE,CAAA;AAC5B,MAAM,aAAa,GAAG,CAAC,CAAA;AAEvB;;;;;;;GAOG;AACH,MAAM,gBAAgB,GAAG,GAAG,CAAA;AAE5B,MAAM,UAAU,GAAG,EAAE,CAAA;AACrB,MAAM,SAAS,GAAG,EAAE,CAAA;AACpB,MAAM,QAAQ,GAAG,CAAC,CAAA;AAClB,MAAM,WAAW,GAAG,CAAC,CAAA;AACrB,MAAM,SAAS,GAAG,EAAE,CAAA;AACpB,mEAAmE;AACnE,MAAM,WAAW,GAAG,CAAC,CAAA;AAErB,4EAA4E;AAC5E,uEAAuE;AACvE,0EAA0E;AAC1E,2CAA2C;AAE3C,MAAM,WAAW,GAAG,CAAC,CAAA;AACrB,MAAM,aAAa,GAAG,CAAC,CAAA;AACvB,MAAM,gBAAgB,GAAG,CAAC,CAAA;AAC1B,MAAM,aAAa,GAAG,CAAC,CAAA;AACvB,MAAM,QAAQ,GAAG,CAAC,CAAA;AAClB,MAAM,iBAAiB,GAAG,CAAC,CAAA;AAqB3B;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAA4B;IAC3D,OAAO,SAAS,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAA;AACtD,CAAC;AAED,4EAA4E;AAE5E,SAAS,cAAc,CAAC,QAA4B;IAClD,MAAM,KAAK,GAAc,EAAE,CAAA;IAC3B,MAAM,SAAS,GAAG,GAAY,EAAE;QAC9B,MAAM,IAAI,GAAY,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;QAC9C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChB,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,IAAI,IAAI,GAAG,SAAS,EAAE,CAAA;IACtB,IAAI,CAAC,GAAG,KAAK,CAAA;IAEb,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,IAAY,EAAE,IAAa,EAAE,IAAY,EAAQ,EAAE;QAC3E,IAAI,IAAI,KAAK,EAAE;YAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAC9D,CAAC,CAAA;IACD,MAAM,UAAU,GAAG,CAAC,KAAa,EAAE,IAAY,EAAE,IAAa,EAAE,IAAY,EAAQ,EAAE;QACpF,IAAI,IAAI,KAAK,EAAE;YAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3F,CAAC,CAAA;IACD,MAAM,IAAI,GAAG,GAAS,EAAE;QACtB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,CAAA;IACtD,CAAC,CAAA;IACD,MAAM,SAAS,GAAG,GAAS,EAAE;QAC3B,IAAI,GAAG,SAAS,EAAE,CAAA;QAClB,CAAC,GAAG,KAAK,CAAA;IACX,CAAC,CAAA;IAED,0EAA0E;IAC1E,6EAA6E;IAC7E,0EAA0E;IAC1E,CAAC,IAAI,UAAU,CAAA;IACf,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC/C,CAAC,IAAI,EAAE,CAAA;IACP,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;IAChD,UAAU,CAAC,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAC3D,CAAC,IAAI,CAAC,CAAA;IACN,IAAI,EAAE,CAAA;IACN,CAAC,IAAI,EAAE,CAAA;IAEP,0EAA0E;IAC1E,MAAM,UAAU,GAAG,CAAC,CAAA;IACpB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC,CAAA;IAC/C,MAAM,YAAY,GAAG,CAAC,KAAwB,EAAE,CAAS,EAAQ,EAAE;QACjE,CAAC,GAAG,UAAU,CAAA;QACd,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YAC5B,KAAK,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,KAAK,CAAC,EAAE,IAAI,CAAC,CAAA;YACtC,CAAC,IAAI,SAAS,CAAA;QAChB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAA;IACD,YAAY,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,YAAY,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAChD,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAC5E,CAAC,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,GAAG,EAAE,CAAA;IAE9C,mBAAmB;IACnB,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAC1B,QAAQ,CAAC,OAAO,CAAC,MAAM,EACvB,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAClD,CAAC,CACF,CAAA;IACD,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,CAAA;IACxC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IAErC,MAAM,SAAS,GAAG,CAAC,KAAwB,EAAE,IAAY,EAAU,EAAE,CACnE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;QACtF,CAAC,IAAI,GAAG,WAAW,CAAC,CAAA;IAEtB,MAAM,QAAQ,GAAG,CAAC,KAAwB,EAAE,IAAY,EAAE,IAAa,EAAQ,EAAE;QAC/E,MAAM,GAAG,GAAG,CAAC,CAAA;QACb,IAAI,MAAM,GAAG,IAAI,GAAG,WAAW,CAAA;QAC/B,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAChC,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,MAAM,CAAA;YAClC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;YACjD,mEAAmE;YACnE,0BAA0B;YAC1B,MAAM,MAAM,GAAG,KAAK,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,CAAA;YAC3E,CAAC,GAAG,GAAG,CAAA;YACP,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,MAAM;oBAAE,UAAU,CAAC,CAAC,GAAG,KAAK,GAAG,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;;oBAC9D,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;gBAC/B,CAAC,IAAI,IAAI,GAAG,WAAW,CAAA;YACzB,CAAC;YACD,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,WAAW,CAAC,CAAC,CAAA;QAChE,CAAC,CAAC,CAAA;QACF,CAAC,GAAG,GAAG,GAAG,MAAM,CAAA;IAClB,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,GAAS,EAAE;QAC7B,IAAI,WAAW,KAAK,CAAC;YAAE,OAAM;QAC7B,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAA;QACjE,CAAC,IAAI,CAAC,CAAA;QACN,IAAI,EAAE,CAAA;QACN,CAAC,IAAI,EAAE,CAAA;IACT,CAAC,CAAA;IAED,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;QACpB,IAAI,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YACrF,SAAS,EAAE,CAAA;QACb,CAAC;QACD,WAAW,EAAE,CAAA;IACf,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;QAC/C,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,EAAE,CAAC;YAC9C,SAAS,EAAE,CAAA;YACX,WAAW,EAAE,CAAA;QACf,CAAC;QACD,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAA;IAClC,CAAC;IAED,qDAAqD;IACrD,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,CAAC,IAAI,CAAC,CAAA;QACN,IAAI,CAAC,GAAG,QAAQ;YAAE,SAAS,EAAE,CAAA;QAC7B,IAAI,EAAE,CAAA;QACN,CAAC,IAAI,EAAE,CAAA;QACP,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE;YACjD,IAAI,CAAC,GAAG,QAAQ;gBAAE,SAAS,EAAE,CAAA;YAC7B,MAAM,IAAI,GAAG,KAAK,KAAK,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAA;YACjD,UAAU,CAAC,UAAU,GAAG,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;YACpD,UAAU,CAAC,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;YAC/C,CAAC,IAAI,SAAS,GAAG,CAAC,CAAA;QACpB,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,qEAAqE;IACrE,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAA;IAC9B,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;QAC9B,MAAM,QAAQ,GAAG,MAAM,GAAG,EAAE,CAAA;QAC5B,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;YAC1C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;QAC7F,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,KAAK,GAAG,CAAC,MAAM,KAAK,CAAC,MAAM,EAAE,CAAA;QAChD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;YAChB,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC;YAC7C,CAAC,EAAE,QAAQ;YACX,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,OAAO;SACd,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,KAAa;IACjC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,EAAE,CAAA;IACzB,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,CAAC,aAAa,CAAC,CAAA;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,GAAG,GAAG,CAAC,CAAA;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAA;IAC/D,OAAO,CAAC,KAAK,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;AACnE,CAAC;AAED,SAAS,aAAa,CAAC,MAAyB;IAC9C,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,IAAI,CAAC,GAAG,MAAM,CAAA;IACd,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACf,CAAC,IAAI,KAAK,CAAA;IACZ,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAS,QAAQ,CAAC,KAAwB,EAAE,KAAa;IACvD,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,KAAK,CAAA;IACvC,OAAO,CAAC,GAAG,KAAK,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;AAC9E,CAAC;AAED,SAAS,QAAQ,CACf,IAAY,EACZ,MAAyB,EACzB,KAAa,EACb,IAAY;IAEZ,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,aAAa,CAAA;IAC5C,OAAO,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,aAAa,EAAE,IAAI,CAAC,CAAA;AACpD,CAAC;AAED;;;GAGG;AACH,SAAS,QAAQ,CAAC,IAAY,EAAE,QAAgB,EAAE,IAAY;IAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,IAAI,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAA;IAC9E,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7C,IAAI,OAAO,GAAG,EAAE,CAAA;QAChB,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACxC,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;gBAC9C,IAAI,OAAO,KAAK,EAAE;oBAAE,OAAO,GAAG,KAAK,CAAA;qBAC9B,IAAI,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;oBAC3E,OAAO,GAAG,GAAG,OAAO,IAAI,KAAK,EAAE,CAAA;gBACjC,CAAC;qBAAM,CAAC;oBACN,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;oBACjB,OAAO,GAAG,KAAK,CAAA;gBACjB,CAAC;YACH,CAAC;QACH,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACnB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,iFAAiF;AACjF,SAAS,SAAS,CAAC,IAAY,EAAE,QAAgB;IAC/C,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;IAC5B,IAAI,UAAU,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,MAAM,EAAE,KAAK,IAAI,QAAQ,EAAE,CAAC;QACjE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IACjE,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;IACnC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAA;AACzB,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,IAAY;IACzC,OAAO,eAAe,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,gBAAgB,CAAA;AACxD,CAAC;AAED,4EAA4E;AAE5E;;;;;;;GAOG;AACH,MAAM,eAAe,GAAgC,IAAI,GAAG,CAAC;IAC3D,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI;IACpB,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI;IACpB,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI;IACpB,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI;IACpB,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI;IACpB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI;IACpB,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI;IACpB,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;CACf,CAAC,CAAA;AAEF,MAAM,aAAa,GAAG,IAAI,CAAA;AAE1B,gFAAgF;AAChF,SAAS,WAAW,CAAC,SAAiB;IACpC,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA,CAAC,sCAAsC;IAC1E,IAAI,SAAS,GAAG,IAAI;QAAE,OAAO,aAAa,CAAA;IAC1C,IAAI,SAAS,IAAI,IAAI;QAAE,OAAO,SAAS,CAAA;IACvC,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAC5C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IACrC,2EAA2E;IAC3E,IAAI,SAAS,IAAI,IAAI,IAAI,SAAS,IAAI,IAAI;QAAE,OAAO,SAAS,CAAA;IAC5D,OAAO,aAAa,CAAA;AACtB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,GAAG,GAAG,GAAG,CAAA;IACb,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,CAAA;QACnE,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,GAAG,IAAI,IAAI,CAAA;QAChE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;IAClC,CAAC;IACD,OAAO,GAAG,GAAG,GAAG,CAAA;AAClB,CAAC;AAED,4EAA4E;AAE5E;;;;;GAKG;AACH,SAAS,SAAS,CAAC,KAAyB,EAAE,QAA4B;IACxE,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,iBAAiB,GAAG,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACtF,MAAM,OAAO,GAAa;QACxB,4BAA4B,aAAa,SAAS;QAClD,0BAA0B,IAAI,YAAY,KAAK,CAAC,MAAM,KAAK;QAC3D,mFAAmF;QACnF,wFAAwF;QACxF,cAAc,CAAC,QAAQ,CAAC;KACzB,CAAA;IAED,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC5B,MAAM,QAAQ,GAAG,iBAAiB,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC,CAAA;QAClD,OAAO,CAAC,IAAI,CACV,0BAA0B,aAAa,uBAAuB,UAAU,IAAI,WAAW,GAAG;YACxF,+BAA+B,gBAAgB,YAAY,aAAa,YAAY;YACpF,cAAc,QAAQ,SAAS,CAClC,CAAA;QACD,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAA;IACzC,CAAC,CAAC,CAAA;IAEF,uEAAuE;IACvE,uEAAuE;IACvE,0BAA0B;IAC1B,IAAI,GAAG,GAAG,mBAAmB,CAAA;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC9B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACxB,GAAG,IAAI,GAAG,KAAK,GAAG,CAAC,WAAW,IAAI,YAAY,CAAA;IAChD,CAAC,CAAC,CAAA;IAEF,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAA;IAC7B,6EAA6E;IAC7E,8EAA8E;IAC9E,8EAA8E;IAC9E,uBAAuB;IACvB,GAAG,IAAI,WAAW,OAAO,CAAC,MAAM,GAAG,CAAC,yBAAyB,CAAA;IAC7D,KAAK,MAAM,MAAM,IAAI,OAAO;QAAE,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,aAAa,CAAA;IACrF,GAAG,IAAI,qBAAqB,OAAO,CAAC,MAAM,GAAG,CAAC,UAAU,WAAW,MAAM,CAAA;IACzE,GAAG,IAAI,UAAU,QAAQ,uBAAuB,UAAU,WAAW,CAAA;IAErE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAA;AACrB,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAa;IACxC,IAAI,MAAM,GAAG,SAAS,CAAA;IACtB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAA;IACjG,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;QAClC,MAAM,IAAI,OAAO,IAAI,IAAI,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAA;QACjF,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAA;IAC3C,CAAC;IACD,2EAA2E;IAC3E,qEAAqE;IACrE,OAAO,cAAc,MAAM,CAAC,MAAM,gBAAgB,MAAM,aAAa,CAAA;AACvE,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,cAAc,CAAC,QAA4B;IAClD,IAAI,IAAI,GAAG,aAAa,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC,sBAAsB,CAAA;IAC/F,MAAM,GAAG,GAAG,2BAA2B,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAC/D,IAAI,GAAG,KAAK,IAAI;QAAE,IAAI,IAAI,qBAAqB,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,UAAU,CAAA;IACjF,OAAO,GAAG,IAAI,KAAK,CAAA;AACrB,CAAC;AAED,6EAA6E;AAC7E,SAAS,MAAM,CAAC,KAAa;IAC3B,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAA;AAC9C,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACzC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;QACnC,0EAA0E;QAC1E,0EAA0E;QAC1E,gEAAgE;QAChE,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAA;IACnD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,96 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ import type { OrderStore } from '../order/store.js';
3
+ import type { PdfInvoiceDocument } from './pdf.js';
4
+ /**
5
+ * An invoice, and the number that makes it one.
6
+ *
7
+ * The number is the whole point of this file. A sequential, gapless,
8
+ * never-reused, never-editable number is an accounting obligation in most of
9
+ * the jurisdictions this CMS will be deployed in — not a nice-to-have, and not
10
+ * something to approximate with a count or a timestamp.
11
+ */
12
+ export interface Invoice {
13
+ readonly id: string;
14
+ readonly orderId: string;
15
+ readonly series: string;
16
+ /** The ordinal within the series. 1, 2, 3… with no gaps. */
17
+ readonly seq: number;
18
+ /** The number a human sees and an auditor checks: `2026-000042`. */
19
+ readonly number: string;
20
+ readonly issuedAt: string;
21
+ readonly currency: string;
22
+ readonly totalMinor: number;
23
+ /** The frozen document. What the invoice said the day it was issued. */
24
+ readonly document: InvoiceDocument;
25
+ }
26
+ /**
27
+ * The snapshot stored with the invoice.
28
+ *
29
+ * Copied, never joined. An order can be refunded, a product renamed, a
30
+ * customer's address corrected; an invoice issued last March must still say
31
+ * what it said last March, because that is the copy the tax office has.
32
+ */
33
+ export interface InvoiceDocument {
34
+ readonly number: string;
35
+ readonly issuedAt: string;
36
+ readonly orderReference: string;
37
+ readonly seller: readonly string[];
38
+ readonly buyer: readonly string[];
39
+ readonly currency: string;
40
+ readonly lines: readonly {
41
+ readonly sku: string;
42
+ readonly title: string;
43
+ readonly quantity: number;
44
+ readonly unitPriceMinor: number;
45
+ readonly taxRateBp: number;
46
+ readonly totalMinor: number;
47
+ }[];
48
+ readonly subtotalMinor: number;
49
+ readonly discountMinor: number;
50
+ readonly shippingMinor: number;
51
+ readonly taxMinor: number;
52
+ readonly totalMinor: number;
53
+ readonly footer: string | null;
54
+ }
55
+ export interface SellerDetails {
56
+ /** Address block, one line per string. Shown verbatim on the invoice. */
57
+ readonly address: readonly string[];
58
+ /** Legal footer: VAT number, company registration, payment terms. */
59
+ readonly footer?: string;
60
+ }
61
+ export interface IssueInvoiceInput {
62
+ readonly orderId: string;
63
+ /** Usually the year. One sequence per series, each dense in itself. */
64
+ readonly series?: string;
65
+ readonly buyerAddress?: readonly string[];
66
+ readonly actorId?: string | null;
67
+ }
68
+ export interface InvoiceStoreDependencies {
69
+ readonly orders: OrderStore;
70
+ readonly seller: SellerDetails;
71
+ }
72
+ export interface InvoiceStore {
73
+ /** Issues the invoice for an order. Exactly once, ever. */
74
+ issue(input: IssueInvoiceInput): Promise<Invoice>;
75
+ read(id: string): Promise<Invoice | null>;
76
+ readByOrder(orderId: string): Promise<Invoice | null>;
77
+ readByNumber(number: string): Promise<Invoice | null>;
78
+ list(options?: {
79
+ readonly series?: string;
80
+ readonly limit?: number;
81
+ }): Promise<readonly Invoice[]>;
82
+ /** The PDF for an invoice. Regenerated from the snapshot, byte-identical. */
83
+ pdf(id: string): Promise<Uint8Array>;
84
+ }
85
+ /** `2026-000042`. Zero-padded so the numbers sort as strings too. */
86
+ export declare function formatInvoiceNumber(series: string, seq: number): string;
87
+ export declare function createInvoiceStore(db: DatabaseHandle, dependencies: InvoiceStoreDependencies, now?: () => number): InvoiceStore;
88
+ /**
89
+ * The stored snapshot, laid out for the PDF writer.
90
+ *
91
+ * A separate step on purpose: the snapshot is the record and the PDF is a
92
+ * rendering of it. Regenerating a PDF five years later from the same snapshot
93
+ * produces the same bytes, because nothing here reads a clock or a live row.
94
+ */
95
+ export declare function pdfDocumentFor(document: InvoiceDocument): PdfInvoiceDocument;
96
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/invoice/store.ts"],"names":[],"mappings":"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"}