treasury-fiscaldata 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moshe Malka
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # treasury-fiscaldata
2
+
3
+ Typed, zero-dependency client for the **US Treasury [FiscalData API](https://fiscaldata.treasury.gov/)** — Debt to the Penny, average interest rates on Treasury securities, exchange rates, and every other endpoint, with typed pagination, sorting, and filtering. **No API key required.**
4
+
5
+ ```
6
+ npm install treasury-fiscaldata
7
+ ```
8
+
9
+ ## Why
10
+
11
+ FiscalData is the Treasury's clean, documented API for federal financial data — the national debt to the penny, what the government pays on its securities, official exchange rates, daily Treasury statements, and much more. It's free and keyless, but its filter/pagination syntax (`filter=record_date:gte:2020-01-01`, `page[size]=…`) is fiddly to hand-build. This wraps it with a typed query, convenience methods for the popular endpoints, and an async paginator.
12
+
13
+ This is the sibling of [`treasurydirect`](https://github.com/moshejs/treasurydirect) — a *different* Treasury API. TreasuryDirect is auctions and securities; FiscalData is accounting and debt.
14
+
15
+ ```ts
16
+ import { latestDebt, debtToPenny, avgInterestRates, query } from "treasury-fiscaldata";
17
+
18
+ const debt = await latestDebt();
19
+ Number(debt.tot_pub_debt_out_amt); // 39_414_179_016_130.09
20
+
21
+ // A date range, with the two-condition filter handled for you:
22
+ const history = await debtToPenny({ startDate: "2026-07-01", endDate: "2026-07-08" });
23
+
24
+ // Any endpoint, fully typed pagination/sort/filter:
25
+ const rates = await query("v2/accounting/od/avg_interest_rates", {
26
+ filter: { security_type_desc: "Marketable", record_date: ">=2026-01-01" },
27
+ sort: ["-record_date"],
28
+ pageSize: 100,
29
+ });
30
+ ```
31
+
32
+ ## API
33
+
34
+ Every function takes an optional final `options`: `{ fetch?, baseUrl? }` (inject `fetch` for tests/proxies).
35
+
36
+ - **`query(endpoint, params?)`** — any endpoint (path after `/services/api/fiscal_service/`). `params`: `fields`, `filter`, `sort`, `pageNumber`, `pageSize`. Returns `{ data, meta, links }`.
37
+ - **`queryAll(endpoint, params?)`** — async generator that follows pagination and yields every row. Narrow with `filter` first — some endpoints have millions of rows.
38
+ - **`latestDebt()`** / **`debtToPenny({ startDate, endDate })`** — Debt to the Penny.
39
+ - **`avgInterestRates()`** — average interest rates on Treasury securities.
40
+ - **`exchangeRates(params?)`** — Treasury reporting rates of exchange.
41
+
42
+ **Filters** come in two forms: a record `{ field: ">=value" }` (leading operators `>= <= > < =` are translated; bare value means equality — one condition per field), or a raw string array `["record_date:gte:2020-01-01", "record_date:lte:2020-12-31"]` when you need two conditions on one field. Failures throw `FiscalDataError` with `status` and `url`.
43
+
44
+ The mocked test suite runs offline; `npm run smoke` exercises the live API.
45
+
46
+ ## Related
47
+
48
+ Sibling to [`treasurydirect`](https://github.com/moshejs/treasurydirect). Part of a fixed-income toolkit: [`newyorkfed`](https://github.com/moshejs/newyorkfed) · [`tbill`](https://github.com/moshejs/tbill) · [`tips-index-ratio`](https://github.com/moshejs/tips-index-ratio) · [`compounded-sofr`](https://github.com/moshejs/compounded-sofr) · [`day-count`](https://github.com/moshejs/day-count) · [`accrued-interest`](https://github.com/moshejs/accrued-interest) · [`32nds`](https://github.com/moshejs/32nds) · [`sifma-holidays`](https://github.com/moshejs/sifma-holidays) · [`instrument-identifiers`](https://github.com/moshejs/instrument-identifiers).
49
+
50
+ ## Author
51
+
52
+ Built by **[Moshe Malka](https://moshemalka.com)** — engineering leader in New York City. Studio work at [Quentin.Code](https://www.quentin.software/).
53
+
54
+ MIT © Moshe Malka
package/dist/index.cjs ADDED
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FiscalDataError: () => FiscalDataError,
24
+ avgInterestRates: () => avgInterestRates,
25
+ debtToPenny: () => debtToPenny,
26
+ exchangeRates: () => exchangeRates,
27
+ latestDebt: () => latestDebt,
28
+ query: () => query,
29
+ queryAll: () => queryAll
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+ var FiscalDataError = class extends Error {
33
+ constructor(status, url) {
34
+ super(`FiscalData request failed with HTTP ${status}: ${url}`);
35
+ this.name = "FiscalDataError";
36
+ this.status = status;
37
+ this.url = url;
38
+ }
39
+ };
40
+ var DEFAULT_BASE = "https://api.fiscaldata.treasury.gov";
41
+ var API_PREFIX = "/services/api/fiscal_service/";
42
+ function buildQuery(params) {
43
+ const sp = new URLSearchParams();
44
+ if (params.fields && params.fields.length) sp.set("fields", params.fields.join(","));
45
+ if (params.filter) {
46
+ let parts;
47
+ if (Array.isArray(params.filter)) {
48
+ parts = params.filter;
49
+ } else {
50
+ parts = Object.entries(params.filter).map(([field, expr]) => {
51
+ const m = /^(>=|<=|>|<|=)(.*)$/.exec(expr);
52
+ const opMap = {
53
+ ">=": "gte",
54
+ "<=": "lte",
55
+ ">": "gt",
56
+ "<": "lt",
57
+ "=": "eq"
58
+ };
59
+ return m ? `${field}:${opMap[m[1]]}:${m[2]}` : `${field}:eq:${expr}`;
60
+ });
61
+ }
62
+ sp.set("filter", parts.join(","));
63
+ }
64
+ if (params.sort && params.sort.length) sp.set("sort", params.sort.join(","));
65
+ if (params.pageSize !== void 0) {
66
+ if (!Number.isInteger(params.pageSize) || params.pageSize <= 0 || params.pageSize > 1e4) {
67
+ throw new RangeError(`pageSize must be an integer in 1\u201310000, got ${params.pageSize}`);
68
+ }
69
+ sp.set("page[size]", String(params.pageSize));
70
+ }
71
+ if (params.pageNumber !== void 0) {
72
+ if (!Number.isInteger(params.pageNumber) || params.pageNumber <= 0) {
73
+ throw new RangeError(`pageNumber must be a positive integer, got ${params.pageNumber}`);
74
+ }
75
+ sp.set("page[number]", String(params.pageNumber));
76
+ }
77
+ const s = sp.toString();
78
+ return s ? `?${s}` : "";
79
+ }
80
+ async function request(endpoint, params, opts) {
81
+ const doFetch = opts.fetch ?? globalThis.fetch;
82
+ const base = opts.baseUrl ?? DEFAULT_BASE;
83
+ const path = endpoint.startsWith("/") ? endpoint : API_PREFIX + endpoint;
84
+ const url = base + path + buildQuery(params);
85
+ const res = await doFetch(url, { headers: { accept: "application/json" } });
86
+ if (!res.ok) throw new FiscalDataError(res.status, url);
87
+ return await res.json();
88
+ }
89
+ function query(endpoint, params = {}, opts = {}) {
90
+ return request(endpoint, params, opts);
91
+ }
92
+ async function* queryAll(endpoint, params = {}, opts = {}) {
93
+ let page = params.pageNumber ?? 1;
94
+ const size = params.pageSize ?? 100;
95
+ for (; ; ) {
96
+ const res = await request(endpoint, { ...params, pageNumber: page, pageSize: size }, opts);
97
+ for (const row of res.data) yield row;
98
+ if (page >= res.meta["total-pages"] || res.data.length === 0) return;
99
+ page++;
100
+ }
101
+ }
102
+ async function latestDebt(opts = {}) {
103
+ const res = await request(
104
+ "v2/accounting/od/debt_to_penny",
105
+ { sort: ["-record_date"], pageSize: 1 },
106
+ opts
107
+ );
108
+ if (!res.data.length) throw new FiscalDataError(204, "debt_to_penny (empty)");
109
+ return res.data[0];
110
+ }
111
+ async function debtToPenny(range, opts = {}) {
112
+ checkDate(range.startDate, "startDate");
113
+ checkDate(range.endDate, "endDate");
114
+ return request(
115
+ "v2/accounting/od/debt_to_penny",
116
+ {
117
+ filter: [`record_date:gte:${range.startDate}`, `record_date:lte:${range.endDate}`],
118
+ sort: ["record_date"],
119
+ pageSize: 1e4
120
+ },
121
+ opts
122
+ );
123
+ }
124
+ function avgInterestRates(opts = {}) {
125
+ return request(
126
+ "v2/accounting/od/avg_interest_rates",
127
+ { sort: ["-record_date"], pageSize: 100 },
128
+ opts
129
+ );
130
+ }
131
+ function exchangeRates(params = {}, opts = {}) {
132
+ return request(
133
+ "v1/accounting/od/rates_of_exchange",
134
+ { sort: ["-record_date"], pageSize: 100, ...params },
135
+ opts
136
+ );
137
+ }
138
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
139
+ function checkDate(d, label) {
140
+ if (!ISO_DATE_RE.test(d)) throw new RangeError(`Invalid ${label}: "${d}" (expected YYYY-MM-DD)`);
141
+ }
142
+ // Annotate the CommonJS export names for ESM import in node:
143
+ 0 && (module.exports = {
144
+ FiscalDataError,
145
+ avgInterestRates,
146
+ debtToPenny,
147
+ exchangeRates,
148
+ latestDebt,
149
+ query,
150
+ queryAll
151
+ });
@@ -0,0 +1,116 @@
1
+ /**
2
+ * treasury-fiscaldata — typed, zero-dependency client for the US Treasury's
3
+ * FiscalData API (api.fiscaldata.treasury.gov).
4
+ *
5
+ * Debt to the Penny, average interest rates on Treasury securities, exchange
6
+ * rates, and any other FiscalData endpoint via a generic query with typed
7
+ * pagination, sorting, and filtering. No API key required.
8
+ *
9
+ * Sibling to the `treasurydirect` package — a different Treasury API with
10
+ * different data.
11
+ */
12
+ interface FiscalDataOptions {
13
+ /** Custom fetch implementation (testing, proxies). Defaults to global fetch. */
14
+ fetch?: typeof globalThis.fetch;
15
+ /** Base URL. Defaults to "https://api.fiscaldata.treasury.gov". */
16
+ baseUrl?: string;
17
+ }
18
+ /** FiscalData pagination metadata (returned as `meta` on every response). */
19
+ interface FiscalDataMeta {
20
+ count: number;
21
+ labels: Record<string, string>;
22
+ dataTypes: Record<string, string>;
23
+ dataFormats: Record<string, string>;
24
+ "total-count": number;
25
+ "total-pages": number;
26
+ }
27
+ interface FiscalDataResponse<T> {
28
+ data: T[];
29
+ meta: FiscalDataMeta;
30
+ links: {
31
+ self: string;
32
+ first: string;
33
+ prev: string | null;
34
+ next: string | null;
35
+ last: string;
36
+ };
37
+ }
38
+ interface QueryParams {
39
+ /** Comma-free array of fields to return (maps to the `fields` param). */
40
+ fields?: string[];
41
+ /**
42
+ * Filters. Two forms:
43
+ * - a record `{ security_type_desc: "Marketable", record_date: ">=2020-01-01" }`
44
+ * — bare value means equality; a leading operator (`>=`, `<=`, `>`, `<`, `=`)
45
+ * is translated. One condition per field.
46
+ * - a string array of raw FiscalData conditions
47
+ * `["record_date:gte:2020-01-01", "record_date:lte:2020-12-31"]` — use this
48
+ * when you need two conditions on the same field (e.g. a date range).
49
+ */
50
+ filter?: Record<string, string> | string[];
51
+ /** Sort fields; prefix with "-" for descending, e.g. ["-record_date"]. */
52
+ sort?: string[];
53
+ /** 1-based page number. */
54
+ pageNumber?: number;
55
+ /** Page size (FiscalData max 10000). */
56
+ pageSize?: number;
57
+ }
58
+ declare class FiscalDataError extends Error {
59
+ readonly status: number;
60
+ readonly url: string;
61
+ constructor(status: number, url: string);
62
+ }
63
+ /**
64
+ * Query any FiscalData endpoint (path after
65
+ * `/services/api/fiscal_service/`, e.g. `"v2/accounting/od/debt_to_penny"`).
66
+ *
67
+ * ```ts
68
+ * const res = await query("v2/accounting/od/debt_to_penny", {
69
+ * sort: ["-record_date"],
70
+ * pageSize: 1,
71
+ * });
72
+ * res.data[0].tot_pub_debt_out_amt;
73
+ * ```
74
+ */
75
+ declare function query<T = Record<string, string>>(endpoint: string, params?: QueryParams, opts?: FiscalDataOptions): Promise<FiscalDataResponse<T>>;
76
+ /**
77
+ * Iterate every page of an endpoint, yielding rows. Follows `links.next`
78
+ * until exhausted. Beware unbounded endpoints — narrow with `filter` first.
79
+ */
80
+ declare function queryAll<T = Record<string, string>>(endpoint: string, params?: QueryParams, opts?: FiscalDataOptions): AsyncGenerator<T, void, unknown>;
81
+ interface DebtToPennyRow {
82
+ record_date: string;
83
+ debt_held_public_amt: string;
84
+ intragov_hold_amt: string;
85
+ tot_pub_debt_out_amt: string;
86
+ [field: string]: string;
87
+ }
88
+ /** The latest Debt to the Penny record. */
89
+ declare function latestDebt(opts?: FiscalDataOptions): Promise<DebtToPennyRow>;
90
+ /** Debt to the Penny over a date range (inclusive), oldest first. */
91
+ declare function debtToPenny(range: {
92
+ startDate: string;
93
+ endDate: string;
94
+ }, opts?: FiscalDataOptions): Promise<FiscalDataResponse<DebtToPennyRow>>;
95
+ interface AvgInterestRateRow {
96
+ record_date: string;
97
+ security_type_desc: string;
98
+ security_desc: string;
99
+ avg_interest_rate_amt: string;
100
+ [field: string]: string;
101
+ }
102
+ /** Average interest rates on Treasury securities for the latest record date. */
103
+ declare function avgInterestRates(opts?: FiscalDataOptions): Promise<FiscalDataResponse<AvgInterestRateRow>>;
104
+ interface ExchangeRateRow {
105
+ record_date: string;
106
+ country: string;
107
+ currency: string;
108
+ country_currency_desc: string;
109
+ exchange_rate: string;
110
+ effective_date: string;
111
+ [field: string]: string;
112
+ }
113
+ /** Treasury reporting rates of exchange, most recent first. */
114
+ declare function exchangeRates(params?: QueryParams, opts?: FiscalDataOptions): Promise<FiscalDataResponse<ExchangeRateRow>>;
115
+
116
+ export { type AvgInterestRateRow, type DebtToPennyRow, type ExchangeRateRow, FiscalDataError, type FiscalDataMeta, type FiscalDataOptions, type FiscalDataResponse, type QueryParams, avgInterestRates, debtToPenny, exchangeRates, latestDebt, query, queryAll };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * treasury-fiscaldata — typed, zero-dependency client for the US Treasury's
3
+ * FiscalData API (api.fiscaldata.treasury.gov).
4
+ *
5
+ * Debt to the Penny, average interest rates on Treasury securities, exchange
6
+ * rates, and any other FiscalData endpoint via a generic query with typed
7
+ * pagination, sorting, and filtering. No API key required.
8
+ *
9
+ * Sibling to the `treasurydirect` package — a different Treasury API with
10
+ * different data.
11
+ */
12
+ interface FiscalDataOptions {
13
+ /** Custom fetch implementation (testing, proxies). Defaults to global fetch. */
14
+ fetch?: typeof globalThis.fetch;
15
+ /** Base URL. Defaults to "https://api.fiscaldata.treasury.gov". */
16
+ baseUrl?: string;
17
+ }
18
+ /** FiscalData pagination metadata (returned as `meta` on every response). */
19
+ interface FiscalDataMeta {
20
+ count: number;
21
+ labels: Record<string, string>;
22
+ dataTypes: Record<string, string>;
23
+ dataFormats: Record<string, string>;
24
+ "total-count": number;
25
+ "total-pages": number;
26
+ }
27
+ interface FiscalDataResponse<T> {
28
+ data: T[];
29
+ meta: FiscalDataMeta;
30
+ links: {
31
+ self: string;
32
+ first: string;
33
+ prev: string | null;
34
+ next: string | null;
35
+ last: string;
36
+ };
37
+ }
38
+ interface QueryParams {
39
+ /** Comma-free array of fields to return (maps to the `fields` param). */
40
+ fields?: string[];
41
+ /**
42
+ * Filters. Two forms:
43
+ * - a record `{ security_type_desc: "Marketable", record_date: ">=2020-01-01" }`
44
+ * — bare value means equality; a leading operator (`>=`, `<=`, `>`, `<`, `=`)
45
+ * is translated. One condition per field.
46
+ * - a string array of raw FiscalData conditions
47
+ * `["record_date:gte:2020-01-01", "record_date:lte:2020-12-31"]` — use this
48
+ * when you need two conditions on the same field (e.g. a date range).
49
+ */
50
+ filter?: Record<string, string> | string[];
51
+ /** Sort fields; prefix with "-" for descending, e.g. ["-record_date"]. */
52
+ sort?: string[];
53
+ /** 1-based page number. */
54
+ pageNumber?: number;
55
+ /** Page size (FiscalData max 10000). */
56
+ pageSize?: number;
57
+ }
58
+ declare class FiscalDataError extends Error {
59
+ readonly status: number;
60
+ readonly url: string;
61
+ constructor(status: number, url: string);
62
+ }
63
+ /**
64
+ * Query any FiscalData endpoint (path after
65
+ * `/services/api/fiscal_service/`, e.g. `"v2/accounting/od/debt_to_penny"`).
66
+ *
67
+ * ```ts
68
+ * const res = await query("v2/accounting/od/debt_to_penny", {
69
+ * sort: ["-record_date"],
70
+ * pageSize: 1,
71
+ * });
72
+ * res.data[0].tot_pub_debt_out_amt;
73
+ * ```
74
+ */
75
+ declare function query<T = Record<string, string>>(endpoint: string, params?: QueryParams, opts?: FiscalDataOptions): Promise<FiscalDataResponse<T>>;
76
+ /**
77
+ * Iterate every page of an endpoint, yielding rows. Follows `links.next`
78
+ * until exhausted. Beware unbounded endpoints — narrow with `filter` first.
79
+ */
80
+ declare function queryAll<T = Record<string, string>>(endpoint: string, params?: QueryParams, opts?: FiscalDataOptions): AsyncGenerator<T, void, unknown>;
81
+ interface DebtToPennyRow {
82
+ record_date: string;
83
+ debt_held_public_amt: string;
84
+ intragov_hold_amt: string;
85
+ tot_pub_debt_out_amt: string;
86
+ [field: string]: string;
87
+ }
88
+ /** The latest Debt to the Penny record. */
89
+ declare function latestDebt(opts?: FiscalDataOptions): Promise<DebtToPennyRow>;
90
+ /** Debt to the Penny over a date range (inclusive), oldest first. */
91
+ declare function debtToPenny(range: {
92
+ startDate: string;
93
+ endDate: string;
94
+ }, opts?: FiscalDataOptions): Promise<FiscalDataResponse<DebtToPennyRow>>;
95
+ interface AvgInterestRateRow {
96
+ record_date: string;
97
+ security_type_desc: string;
98
+ security_desc: string;
99
+ avg_interest_rate_amt: string;
100
+ [field: string]: string;
101
+ }
102
+ /** Average interest rates on Treasury securities for the latest record date. */
103
+ declare function avgInterestRates(opts?: FiscalDataOptions): Promise<FiscalDataResponse<AvgInterestRateRow>>;
104
+ interface ExchangeRateRow {
105
+ record_date: string;
106
+ country: string;
107
+ currency: string;
108
+ country_currency_desc: string;
109
+ exchange_rate: string;
110
+ effective_date: string;
111
+ [field: string]: string;
112
+ }
113
+ /** Treasury reporting rates of exchange, most recent first. */
114
+ declare function exchangeRates(params?: QueryParams, opts?: FiscalDataOptions): Promise<FiscalDataResponse<ExchangeRateRow>>;
115
+
116
+ export { type AvgInterestRateRow, type DebtToPennyRow, type ExchangeRateRow, FiscalDataError, type FiscalDataMeta, type FiscalDataOptions, type FiscalDataResponse, type QueryParams, avgInterestRates, debtToPenny, exchangeRates, latestDebt, query, queryAll };
package/dist/index.js ADDED
@@ -0,0 +1,120 @@
1
+ // src/index.ts
2
+ var FiscalDataError = class extends Error {
3
+ constructor(status, url) {
4
+ super(`FiscalData request failed with HTTP ${status}: ${url}`);
5
+ this.name = "FiscalDataError";
6
+ this.status = status;
7
+ this.url = url;
8
+ }
9
+ };
10
+ var DEFAULT_BASE = "https://api.fiscaldata.treasury.gov";
11
+ var API_PREFIX = "/services/api/fiscal_service/";
12
+ function buildQuery(params) {
13
+ const sp = new URLSearchParams();
14
+ if (params.fields && params.fields.length) sp.set("fields", params.fields.join(","));
15
+ if (params.filter) {
16
+ let parts;
17
+ if (Array.isArray(params.filter)) {
18
+ parts = params.filter;
19
+ } else {
20
+ parts = Object.entries(params.filter).map(([field, expr]) => {
21
+ const m = /^(>=|<=|>|<|=)(.*)$/.exec(expr);
22
+ const opMap = {
23
+ ">=": "gte",
24
+ "<=": "lte",
25
+ ">": "gt",
26
+ "<": "lt",
27
+ "=": "eq"
28
+ };
29
+ return m ? `${field}:${opMap[m[1]]}:${m[2]}` : `${field}:eq:${expr}`;
30
+ });
31
+ }
32
+ sp.set("filter", parts.join(","));
33
+ }
34
+ if (params.sort && params.sort.length) sp.set("sort", params.sort.join(","));
35
+ if (params.pageSize !== void 0) {
36
+ if (!Number.isInteger(params.pageSize) || params.pageSize <= 0 || params.pageSize > 1e4) {
37
+ throw new RangeError(`pageSize must be an integer in 1\u201310000, got ${params.pageSize}`);
38
+ }
39
+ sp.set("page[size]", String(params.pageSize));
40
+ }
41
+ if (params.pageNumber !== void 0) {
42
+ if (!Number.isInteger(params.pageNumber) || params.pageNumber <= 0) {
43
+ throw new RangeError(`pageNumber must be a positive integer, got ${params.pageNumber}`);
44
+ }
45
+ sp.set("page[number]", String(params.pageNumber));
46
+ }
47
+ const s = sp.toString();
48
+ return s ? `?${s}` : "";
49
+ }
50
+ async function request(endpoint, params, opts) {
51
+ const doFetch = opts.fetch ?? globalThis.fetch;
52
+ const base = opts.baseUrl ?? DEFAULT_BASE;
53
+ const path = endpoint.startsWith("/") ? endpoint : API_PREFIX + endpoint;
54
+ const url = base + path + buildQuery(params);
55
+ const res = await doFetch(url, { headers: { accept: "application/json" } });
56
+ if (!res.ok) throw new FiscalDataError(res.status, url);
57
+ return await res.json();
58
+ }
59
+ function query(endpoint, params = {}, opts = {}) {
60
+ return request(endpoint, params, opts);
61
+ }
62
+ async function* queryAll(endpoint, params = {}, opts = {}) {
63
+ let page = params.pageNumber ?? 1;
64
+ const size = params.pageSize ?? 100;
65
+ for (; ; ) {
66
+ const res = await request(endpoint, { ...params, pageNumber: page, pageSize: size }, opts);
67
+ for (const row of res.data) yield row;
68
+ if (page >= res.meta["total-pages"] || res.data.length === 0) return;
69
+ page++;
70
+ }
71
+ }
72
+ async function latestDebt(opts = {}) {
73
+ const res = await request(
74
+ "v2/accounting/od/debt_to_penny",
75
+ { sort: ["-record_date"], pageSize: 1 },
76
+ opts
77
+ );
78
+ if (!res.data.length) throw new FiscalDataError(204, "debt_to_penny (empty)");
79
+ return res.data[0];
80
+ }
81
+ async function debtToPenny(range, opts = {}) {
82
+ checkDate(range.startDate, "startDate");
83
+ checkDate(range.endDate, "endDate");
84
+ return request(
85
+ "v2/accounting/od/debt_to_penny",
86
+ {
87
+ filter: [`record_date:gte:${range.startDate}`, `record_date:lte:${range.endDate}`],
88
+ sort: ["record_date"],
89
+ pageSize: 1e4
90
+ },
91
+ opts
92
+ );
93
+ }
94
+ function avgInterestRates(opts = {}) {
95
+ return request(
96
+ "v2/accounting/od/avg_interest_rates",
97
+ { sort: ["-record_date"], pageSize: 100 },
98
+ opts
99
+ );
100
+ }
101
+ function exchangeRates(params = {}, opts = {}) {
102
+ return request(
103
+ "v1/accounting/od/rates_of_exchange",
104
+ { sort: ["-record_date"], pageSize: 100, ...params },
105
+ opts
106
+ );
107
+ }
108
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
109
+ function checkDate(d, label) {
110
+ if (!ISO_DATE_RE.test(d)) throw new RangeError(`Invalid ${label}: "${d}" (expected YYYY-MM-DD)`);
111
+ }
112
+ export {
113
+ FiscalDataError,
114
+ avgInterestRates,
115
+ debtToPenny,
116
+ exchangeRates,
117
+ latestDebt,
118
+ query,
119
+ queryAll
120
+ };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "treasury-fiscaldata",
3
+ "version": "1.0.0",
4
+ "description": "Typed, zero-dependency client for the US Treasury FiscalData API — Debt to the Penny, average interest rates, exchange rates, and any endpoint with typed pagination, sorting, and filtering. No API key required.",
5
+ "keywords": [
6
+ "treasury",
7
+ "fiscaldata",
8
+ "fiscal-data",
9
+ "debt-to-the-penny",
10
+ "national-debt",
11
+ "government-data",
12
+ "interest-rates",
13
+ "exchange-rates",
14
+ "finance",
15
+ "api-client",
16
+ "fixed-income"
17
+ ],
18
+ "homepage": "https://moshemalka.com",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/moshejs/treasury-fiscaldata.git"
22
+ },
23
+ "bugs": "https://github.com/moshejs/treasury-fiscaldata/issues",
24
+ "author": "Moshe Malka <hello@moshemalka.com> (https://moshemalka.com)",
25
+ "license": "MIT",
26
+ "type": "module",
27
+ "main": "./dist/index.cjs",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js",
34
+ "require": "./dist/index.cjs"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "sideEffects": false,
41
+ "scripts": {
42
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
43
+ "test": "vitest run",
44
+ "typecheck": "tsc --noEmit",
45
+ "smoke": "node scripts/smoke.mjs",
46
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build"
47
+ },
48
+ "devDependencies": {
49
+ "tsup": "^8.5.0",
50
+ "typescript": "^5.8.0",
51
+ "vitest": "^3.2.0"
52
+ },
53
+ "engines": {
54
+ "node": ">=18"
55
+ }
56
+ }