tallyd 0.0.0-stage → 0.1.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 +1493 -2
- package/dist/accounts/chart.d.ts +96 -0
- package/dist/accounts/chart.js +244 -0
- package/dist/accounts/index.d.ts +3 -0
- package/dist/accounts/index.js +3 -0
- package/dist/accounts/standard.d.ts +12 -0
- package/dist/accounts/standard.js +95 -0
- package/dist/accounts/types.d.ts +20 -0
- package/dist/accounts/types.js +40 -0
- package/dist/cli/args.d.ts +45 -0
- package/dist/cli/args.js +147 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/main.d.ts +6 -0
- package/dist/cli/main.js +25 -0
- package/dist/cli/run.d.ts +34 -0
- package/dist/cli/run.js +1660 -0
- package/dist/dashboard/charts.d.ts +38 -0
- package/dist/dashboard/charts.js +173 -0
- package/dist/dashboard/index.d.ts +3 -0
- package/dist/dashboard/index.js +3 -0
- package/dist/dashboard/model.d.ts +137 -0
- package/dist/dashboard/model.js +190 -0
- package/dist/dashboard/render.d.ts +23 -0
- package/dist/dashboard/render.js +231 -0
- package/dist/dashboard/script.d.ts +28 -0
- package/dist/dashboard/script.js +704 -0
- package/dist/dashboard/styles.d.ts +14 -0
- package/dist/dashboard/styles.js +726 -0
- package/dist/demo/disposal.d.ts +80 -0
- package/dist/demo/disposal.js +215 -0
- package/dist/demo/evidence.d.ts +153 -0
- package/dist/demo/evidence.js +348 -0
- package/dist/demo/foreign.d.ts +29 -0
- package/dist/demo/foreign.js +113 -0
- package/dist/demo/foreignReport.d.ts +10 -0
- package/dist/demo/foreignReport.js +89 -0
- package/dist/demo/generator.d.ts +108 -0
- package/dist/demo/generator.js +445 -0
- package/dist/demo/group.d.ts +70 -0
- package/dist/demo/group.js +195 -0
- package/dist/demo/groupPeriods.d.ts +27 -0
- package/dist/demo/groupPeriods.js +125 -0
- package/dist/demo/groupReport.d.ts +13 -0
- package/dist/demo/groupReport.js +87 -0
- package/dist/demo/ingest.d.ts +6 -0
- package/dist/demo/ingest.js +61 -0
- package/dist/demo/main.d.ts +6 -0
- package/dist/demo/main.js +7 -0
- package/dist/demo/mainDisposal.d.ts +1 -0
- package/dist/demo/mainDisposal.js +2 -0
- package/dist/demo/mainEvidence.d.ts +8 -0
- package/dist/demo/mainEvidence.js +9 -0
- package/dist/demo/mainForeign.d.ts +6 -0
- package/dist/demo/mainForeign.js +7 -0
- package/dist/demo/mainGroup.d.ts +6 -0
- package/dist/demo/mainGroup.js +7 -0
- package/dist/demo/mainGroupPeriods.d.ts +6 -0
- package/dist/demo/mainGroupPeriods.js +7 -0
- package/dist/demo/mainIngest.d.ts +6 -0
- package/dist/demo/mainIngest.js +7 -0
- package/dist/demo/mainMidYear.d.ts +6 -0
- package/dist/demo/mainMidYear.js +7 -0
- package/dist/demo/mainReconcile.d.ts +6 -0
- package/dist/demo/mainReconcile.js +7 -0
- package/dist/demo/mainStatements.d.ts +6 -0
- package/dist/demo/mainStatements.js +7 -0
- package/dist/demo/midYear.d.ts +51 -0
- package/dist/demo/midYear.js +162 -0
- package/dist/demo/month.d.ts +13 -0
- package/dist/demo/month.js +142 -0
- package/dist/demo/random.d.ts +48 -0
- package/dist/demo/random.js +94 -0
- package/dist/demo/receivables.d.ts +36 -0
- package/dist/demo/receivables.js +104 -0
- package/dist/demo/reconcile.d.ts +20 -0
- package/dist/demo/reconcile.js +104 -0
- package/dist/demo/report.d.ts +6 -0
- package/dist/demo/report.js +26 -0
- package/dist/demo/statement.d.ts +13 -0
- package/dist/demo/statement.js +51 -0
- package/dist/demo/statements.d.ts +6 -0
- package/dist/demo/statements.js +29 -0
- package/dist/demo/supplierRun.d.ts +46 -0
- package/dist/demo/supplierRun.js +94 -0
- package/dist/demo/unrealisedEvidence.d.ts +51 -0
- package/dist/demo/unrealisedEvidence.js +183 -0
- package/dist/fx/average.d.ts +60 -0
- package/dist/fx/average.js +139 -0
- package/dist/fx/document.d.ts +60 -0
- package/dist/fx/document.js +205 -0
- package/dist/fx/exposure.d.ts +108 -0
- package/dist/fx/exposure.js +262 -0
- package/dist/fx/index.d.ts +8 -0
- package/dist/fx/index.js +8 -0
- package/dist/fx/rate.d.ts +77 -0
- package/dist/fx/rate.js +200 -0
- package/dist/fx/revaluation.d.ts +92 -0
- package/dist/fx/revaluation.js +197 -0
- package/dist/fx/settlement.d.ts +84 -0
- package/dist/fx/settlement.js +162 -0
- package/dist/fx/table.d.ts +120 -0
- package/dist/fx/table.js +271 -0
- package/dist/fx/translate.d.ts +126 -0
- package/dist/fx/translate.js +258 -0
- package/dist/group/accounts.d.ts +74 -0
- package/dist/group/accounts.js +179 -0
- package/dist/group/acquisition.d.ts +324 -0
- package/dist/group/acquisition.js +416 -0
- package/dist/group/aggregate.d.ts +143 -0
- package/dist/group/aggregate.js +288 -0
- package/dist/group/comparative.d.ts +102 -0
- package/dist/group/comparative.js +166 -0
- package/dist/group/consolidate.d.ts +196 -0
- package/dist/group/consolidate.js +635 -0
- package/dist/group/disposal.d.ts +181 -0
- package/dist/group/disposal.js +233 -0
- package/dist/group/document.d.ts +41 -0
- package/dist/group/document.js +326 -0
- package/dist/group/index.d.ts +12 -0
- package/dist/group/index.js +12 -0
- package/dist/group/intercompany.d.ts +136 -0
- package/dist/group/intercompany.js +238 -0
- package/dist/group/interest.d.ts +109 -0
- package/dist/group/interest.js +224 -0
- package/dist/group/movement.d.ts +206 -0
- package/dist/group/movement.js +432 -0
- package/dist/group/structure.d.ts +151 -0
- package/dist/group/structure.js +327 -0
- package/dist/group/timeline.d.ts +109 -0
- package/dist/group/timeline.js +169 -0
- package/dist/group/unrealised.d.ts +165 -0
- package/dist/group/unrealised.js +314 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/ledger/close.d.ts +75 -0
- package/dist/ledger/close.js +142 -0
- package/dist/ledger/date.d.ts +48 -0
- package/dist/ledger/date.js +141 -0
- package/dist/ledger/entry.d.ts +150 -0
- package/dist/ledger/entry.js +264 -0
- package/dist/ledger/index.d.ts +6 -0
- package/dist/ledger/index.js +6 -0
- package/dist/ledger/ledger.d.ts +101 -0
- package/dist/ledger/ledger.js +308 -0
- package/dist/ledger/serialise.d.ts +68 -0
- package/dist/ledger/serialise.js +204 -0
- package/dist/ledger/trialBalance.d.ts +49 -0
- package/dist/ledger/trialBalance.js +103 -0
- package/dist/money/currency.d.ts +37 -0
- package/dist/money/currency.js +67 -0
- package/dist/money/index.d.ts +3 -0
- package/dist/money/index.js +3 -0
- package/dist/money/money.d.ts +106 -0
- package/dist/money/money.js +300 -0
- package/dist/money/rounding.d.ts +41 -0
- package/dist/money/rounding.js +117 -0
- package/dist/reconcile/accuracy.d.ts +65 -0
- package/dist/reconcile/accuracy.js +162 -0
- package/dist/reconcile/assignment.d.ts +83 -0
- package/dist/reconcile/assignment.js +326 -0
- package/dist/reconcile/bankView.d.ts +65 -0
- package/dist/reconcile/bankView.js +86 -0
- package/dist/reconcile/bridge.d.ts +76 -0
- package/dist/reconcile/bridge.js +129 -0
- package/dist/reconcile/candidates.d.ts +84 -0
- package/dist/reconcile/candidates.js +174 -0
- package/dist/reconcile/decisions.d.ts +116 -0
- package/dist/reconcile/decisions.js +168 -0
- package/dist/reconcile/index.d.ts +12 -0
- package/dist/reconcile/index.js +12 -0
- package/dist/reconcile/matcher.d.ts +89 -0
- package/dist/reconcile/matcher.js +306 -0
- package/dist/reconcile/memory.d.ts +120 -0
- package/dist/reconcile/memory.js +281 -0
- package/dist/reconcile/posting.d.ts +185 -0
- package/dist/reconcile/posting.js +481 -0
- package/dist/reconcile/scoring.d.ts +98 -0
- package/dist/reconcile/scoring.js +319 -0
- package/dist/reconcile/similarity.d.ts +88 -0
- package/dist/reconcile/similarity.js +272 -0
- package/dist/reconcile/subsetSum.d.ts +61 -0
- package/dist/reconcile/subsetSum.js +136 -0
- package/dist/reports/ageing.d.ts +75 -0
- package/dist/reports/ageing.js +149 -0
- package/dist/reports/balanceSheet.d.ts +57 -0
- package/dist/reports/balanceSheet.js +107 -0
- package/dist/reports/incomeStatement.d.ts +59 -0
- package/dist/reports/incomeStatement.js +132 -0
- package/dist/reports/index.d.ts +4 -0
- package/dist/reports/index.js +4 -0
- package/dist/reports/period.d.ts +37 -0
- package/dist/reports/period.js +78 -0
- package/dist/statement/amount.d.ts +55 -0
- package/dist/statement/amount.js +211 -0
- package/dist/statement/columns.d.ts +39 -0
- package/dist/statement/columns.js +221 -0
- package/dist/statement/csv.d.ts +50 -0
- package/dist/statement/csv.js +185 -0
- package/dist/statement/dates.d.ts +52 -0
- package/dist/statement/dates.js +204 -0
- package/dist/statement/duplicates.d.ts +51 -0
- package/dist/statement/duplicates.js +101 -0
- package/dist/statement/import.d.ts +52 -0
- package/dist/statement/import.js +172 -0
- package/dist/statement/index.d.ts +20 -0
- package/dist/statement/index.js +32 -0
- package/dist/statement/line.d.ts +60 -0
- package/dist/statement/line.js +73 -0
- package/dist/statement/ofx.d.ts +47 -0
- package/dist/statement/ofx.js +199 -0
- package/package.json +72 -4
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argument parsing, written rather than installed.
|
|
3
|
+
*
|
|
4
|
+
* A dependency would do this, and for a tool this size the dependency is the
|
|
5
|
+
* larger risk: everything a ledger CLI needs — long flags, `--flag=value` and
|
|
6
|
+
* `--flag value`, short aliases, repeated values, `--` to stop parsing — is a
|
|
7
|
+
* page of code that can be tested against the awkward cases instead of
|
|
8
|
+
* trusted.
|
|
9
|
+
*
|
|
10
|
+
* The awkward cases are the point. `--account -1100` should be a value, not a
|
|
11
|
+
* missing flag followed by a stray negative number. `--as-at=2026-08-31` and
|
|
12
|
+
* `--as-at 2026-08-31` must mean the same thing. An unknown flag must be an
|
|
13
|
+
* error rather than silently ignored, because a typo in `--as-at` that reports
|
|
14
|
+
* the wrong period without complaining is exactly the failure a report tool
|
|
15
|
+
* cannot afford.
|
|
16
|
+
*/
|
|
17
|
+
export class ArgumentError extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "ArgumentError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function specFor(specs, name) {
|
|
24
|
+
const direct = specs[name];
|
|
25
|
+
if (direct !== undefined)
|
|
26
|
+
return { key: name, spec: direct };
|
|
27
|
+
if (name.length === 1) {
|
|
28
|
+
for (const [key, spec] of Object.entries(specs)) {
|
|
29
|
+
if (spec.short === name)
|
|
30
|
+
return { key, spec };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw new ArgumentError(`Unknown option: ${name.length === 1 ? "-" : "--"}${name}`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse `argv` against a flag table.
|
|
37
|
+
*
|
|
38
|
+
* Everything after a bare `--` is positional, however it looks. Anything not
|
|
39
|
+
* recognised is an error rather than a shrug.
|
|
40
|
+
*/
|
|
41
|
+
export function parseArgs(argv, specs) {
|
|
42
|
+
const positional = [];
|
|
43
|
+
const flags = {};
|
|
44
|
+
let index = 0;
|
|
45
|
+
let literal = false;
|
|
46
|
+
const setValue = (key, spec, value) => {
|
|
47
|
+
if (spec.kind === "list") {
|
|
48
|
+
const existing = flags[key];
|
|
49
|
+
if (Array.isArray(existing))
|
|
50
|
+
existing.push(value);
|
|
51
|
+
else
|
|
52
|
+
flags[key] = [value];
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
flags[key] = value;
|
|
56
|
+
};
|
|
57
|
+
while (index < argv.length) {
|
|
58
|
+
const token = argv[index];
|
|
59
|
+
index++;
|
|
60
|
+
if (literal) {
|
|
61
|
+
positional.push(token);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (token === "--") {
|
|
65
|
+
literal = true;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const isLong = token.startsWith("--") && token.length > 2;
|
|
69
|
+
const isShort = !isLong && token.startsWith("-") && token.length > 1 && token !== "-";
|
|
70
|
+
if (!isLong && !isShort) {
|
|
71
|
+
positional.push(token);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const body = isLong ? token.slice(2) : token.slice(1);
|
|
75
|
+
const equals = body.indexOf("=");
|
|
76
|
+
const name = equals === -1 ? body : body.slice(0, equals);
|
|
77
|
+
const inline = equals === -1 ? null : body.slice(equals + 1);
|
|
78
|
+
const { key, spec } = specFor(specs, name);
|
|
79
|
+
if (spec.kind === "boolean") {
|
|
80
|
+
if (inline !== null) {
|
|
81
|
+
if (inline !== "true" && inline !== "false") {
|
|
82
|
+
throw new ArgumentError(`--${key} is a switch and takes no value`);
|
|
83
|
+
}
|
|
84
|
+
flags[key] = inline === "true";
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
flags[key] = true;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (inline !== null) {
|
|
91
|
+
setValue(key, spec, inline);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// A value is whatever comes next, even if it starts with a dash: an
|
|
95
|
+
// account code or a negative amount is a legitimate value.
|
|
96
|
+
const next = argv[index];
|
|
97
|
+
if (next === undefined) {
|
|
98
|
+
throw new ArgumentError(`--${key} needs a value`);
|
|
99
|
+
}
|
|
100
|
+
index++;
|
|
101
|
+
setValue(key, spec, next);
|
|
102
|
+
}
|
|
103
|
+
return Object.freeze({ positional: Object.freeze(positional), flags: Object.freeze(flags) });
|
|
104
|
+
}
|
|
105
|
+
export function stringFlag(parsed, name) {
|
|
106
|
+
const value = parsed.flags[name];
|
|
107
|
+
if (value === undefined)
|
|
108
|
+
return undefined;
|
|
109
|
+
if (typeof value !== "string") {
|
|
110
|
+
throw new ArgumentError(`--${name} takes a single value`);
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
export function requiredFlag(parsed, name) {
|
|
115
|
+
const value = stringFlag(parsed, name);
|
|
116
|
+
if (value === undefined)
|
|
117
|
+
throw new ArgumentError(`--${name} is required`);
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
export function booleanFlag(parsed, name) {
|
|
121
|
+
return parsed.flags[name] === true;
|
|
122
|
+
}
|
|
123
|
+
export function listFlag(parsed, name) {
|
|
124
|
+
const value = parsed.flags[name];
|
|
125
|
+
if (value === undefined)
|
|
126
|
+
return [];
|
|
127
|
+
if (Array.isArray(value))
|
|
128
|
+
return value;
|
|
129
|
+
if (typeof value === "string")
|
|
130
|
+
return [value];
|
|
131
|
+
throw new ArgumentError(`--${name} takes values`);
|
|
132
|
+
}
|
|
133
|
+
/** Render a flag table as aligned help text. */
|
|
134
|
+
export function renderFlags(specs, indent = " ") {
|
|
135
|
+
const entries = Object.entries(specs);
|
|
136
|
+
if (entries.length === 0)
|
|
137
|
+
return "";
|
|
138
|
+
const left = entries.map(([name, spec]) => {
|
|
139
|
+
const short = spec.short === undefined ? " " : `-${spec.short}, `;
|
|
140
|
+
const placeholder = spec.kind === "boolean" ? "" : ` <${spec.placeholder ?? (spec.kind === "list" ? "value..." : "value")}>`;
|
|
141
|
+
return `${short}--${name}${placeholder}`;
|
|
142
|
+
});
|
|
143
|
+
const width = Math.max(...left.map((text) => text.length));
|
|
144
|
+
return entries
|
|
145
|
+
.map(([, spec], i) => `${indent}${left[i].padEnd(width + 2)}${spec.describe}`)
|
|
146
|
+
.join("\n");
|
|
147
|
+
}
|
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The executable. Everything interesting is in `run`, which is a pure
|
|
4
|
+
* function; this file exists to connect it to the process.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { run } from "./run.js";
|
|
8
|
+
import { date } from "../ledger/date.js";
|
|
9
|
+
function today() {
|
|
10
|
+
// The only place in the codebase that reads the clock, kept to one line so
|
|
11
|
+
// it is obvious where non-determinism enters.
|
|
12
|
+
const now = new Date();
|
|
13
|
+
const iso = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")}`;
|
|
14
|
+
return date(iso);
|
|
15
|
+
}
|
|
16
|
+
const result = run(process.argv.slice(2), {
|
|
17
|
+
readFile: (path) => readFileSync(path, "utf8"),
|
|
18
|
+
writeFile: (path, contents) => writeFileSync(path, contents, "utf8"),
|
|
19
|
+
today,
|
|
20
|
+
});
|
|
21
|
+
if (result.stdout !== "")
|
|
22
|
+
process.stdout.write(`${result.stdout}\n`);
|
|
23
|
+
if (result.stderr !== "")
|
|
24
|
+
process.stderr.write(result.stderr);
|
|
25
|
+
process.exit(result.code);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The commands.
|
|
3
|
+
*
|
|
4
|
+
* `run` is a pure function of its arguments and an environment: it takes the
|
|
5
|
+
* argv, a filesystem reader and a clock, and returns text plus an exit code.
|
|
6
|
+
* Nothing here touches `process` or `console`, which is what makes the whole
|
|
7
|
+
* CLI testable end to end without spawning anything.
|
|
8
|
+
*
|
|
9
|
+
* Exit codes carry meaning. Zero is success; 1 is a usage or input problem;
|
|
10
|
+
* and 2 is reserved for work that ran to completion and came out wrong — a
|
|
11
|
+
* reconciliation that does not balance, a trial balance that does not agree.
|
|
12
|
+
* A CI job that treats an unbalanced reconciliation as a pass is worse than no
|
|
13
|
+
* CI job at all.
|
|
14
|
+
*/
|
|
15
|
+
import type { CalendarDate } from "../ledger/date.js";
|
|
16
|
+
export interface CliEnvironment {
|
|
17
|
+
/** Read a file as UTF-8, or throw. */
|
|
18
|
+
readonly readFile: (path: string) => string;
|
|
19
|
+
/** Today, for commands that default their as-at date. */
|
|
20
|
+
readonly today: () => CalendarDate;
|
|
21
|
+
/**
|
|
22
|
+
* Write a file, for the commands that produce an artefact. Absent in
|
|
23
|
+
* read-only environments, in which case those commands say so rather than
|
|
24
|
+
* failing halfway through.
|
|
25
|
+
*/
|
|
26
|
+
readonly writeFile?: (path: string, contents: string) => void;
|
|
27
|
+
}
|
|
28
|
+
export interface CliResult {
|
|
29
|
+
readonly stdout: string;
|
|
30
|
+
readonly stderr: string;
|
|
31
|
+
readonly code: number;
|
|
32
|
+
}
|
|
33
|
+
/** Run one invocation. Never throws; every failure becomes an exit code. */
|
|
34
|
+
export declare function run(argv: readonly string[], environment: CliEnvironment): CliResult;
|