feat(server-ts): utils/dates → TypeScript (.mts); utils/ dir now TS (Phase 2)

dates.mts (ESM, typed) joins money.mts — the two most-required server
leaves are now TypeScript. Added .cts to the server tsconfig for the
CJS-module migration path. typecheck:server clean; suite 225 + probe 17/17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-05 13:48:44 -05:00
parent 200c6373f5
commit 3f9a6cf23e
16 changed files with 27 additions and 31 deletions

View File

@ -22,7 +22,7 @@ const {
accountingActiveSql,
applyBankPaymentAsSourceOfTruth,
} = require('../services/paymentAccountingService');
const { localDateString, todayLocal } = require('../utils/dates');
const { localDateString, todayLocal } = require('../utils/dates.mts');
const { roundMoney, sumMoney, toCents, fromCents } = require('../utils/money.mts');
// ── GET /api/bills ────────────────────────────────────────────────────────────

View File

@ -13,7 +13,7 @@ const {
regenerateToken,
revokeToken,
} = require('../services/calendarFeedService');
const { localDateString } = require('../utils/dates');
const { localDateString } = require('../utils/dates.mts');
const { roundMoney, sumMoney, fromCents } = require('../utils/money.mts');
function clampDay(year, month, day) {

View File

@ -9,7 +9,7 @@ const {
const { learnMerchantRuleFromMatch } = require('../services/billMerchantRuleService');
const { markMatched, markUnmatched } = require('../services/transactionMatchState');
const { serializePayment } = require('../services/paymentValidation');
const { todayLocal } = require('../utils/dates');
const { todayLocal } = require('../utils/dates.mts');
function sendMatchError(res, err, fallbackMessage = 'Match operation failed') {
if (err.status) {

View File

@ -10,7 +10,7 @@ const {
markProvisionalManualPaymentsOverridden,
reactivatePaymentsOverriddenBy,
} = require('../services/paymentAccountingService');
const { todayLocal } = require('../utils/dates');
const { todayLocal } = require('../utils/dates.mts');
const { fromCents } = require('../utils/money.mts');
// SQL_NOT_DELETED is a compile-time constant SQL fragment, never user-supplied.

View File

@ -10,7 +10,7 @@ const { checkForUpdates } = require('../services/updateCheckService');
const { getStatus: getBankSyncWorkerStatus } = require('../services/bankSyncWorker');
const { getBankSyncConfig } = require('../services/bankSyncConfigService');
const { accountingActiveSql } = require('../services/paymentAccountingService');
const { localDateString } = require('../utils/dates');
const { localDateString } = require('../utils/dates.mts');
const startTime = Date.now();
let pkg;

View File

@ -22,7 +22,7 @@ const {
findOrCreateCategory,
} = require('../services/merchantStoreMatchService');
const { categorizeTransaction } = require('../services/spendingService');
const { todayLocal } = require('../utils/dates');
const { todayLocal } = require('../utils/dates.mts');
const { roundMoney } = require('../utils/money.mts');
const MATCH_STATUSES = new Set(['unmatched', 'matched', 'ignored']);

View File

@ -3,7 +3,7 @@
const { normalizeMerchant } = require('./subscriptionService');
const { getUserSettings } = require('./userSettings');
const { applyBankPaymentAsSourceOfTruth } = require('./paymentAccountingService');
const { localDateString } = require('../utils/dates');
const { localDateString } = require('../utils/dates.mts');
const { fromCents } = require('../utils/money.mts');
// Word-boundary merchant match — requires the rule to appear as complete word(s)

View File

@ -1,4 +1,4 @@
const { monthKey } = require('../utils/dates');
const { monthKey } = require('../utils/dates.mts');
const { toCents, fromCents } = require('../utils/money.mts');
const VALID_VISIBILITY = ['default', 'all', 'ranges', 'none'];

View File

@ -4,7 +4,7 @@ const { getDb } = require('../db/database');
const { getCycleRange } = require('./statusService');
const { accountingActiveSql } = require('./paymentAccountingService');
const { getUserSettings } = require('./userSettings');
const { localDateString } = require('../utils/dates');
const { localDateString } = require('../utils/dates.mts');
const { roundMoney, fromCents } = require('../utils/money.mts');
const MONTHS_BACK = 3;

View File

@ -7,7 +7,7 @@ const {
markNotificationSuccess,
markNotificationTestSuccess,
} = require('./statusRuntime');
const { localDateString } = require('../utils/dates');
const { localDateString } = require('../utils/dates.mts');
const { fromCents } = require('../utils/money.mts');
// ── Push notification channels ────────────────────────────────────────────────

View File

@ -1,7 +1,7 @@
'use strict';
const { normalizeMerchant } = require('./subscriptionService');
const { localDateString } = require('../utils/dates');
const { localDateString } = require('../utils/dates.mts');
const { toCents, fromCents } = require('../utils/money.mts');
// Spending = unmatched outflows (amount < 0) that haven't been ignored.

View File

@ -1,7 +1,7 @@
'use strict';
const { billingCycleForCycleType, insertBill, validateBillData } = require('./billsService');
const { localDateString, todayLocal } = require('../utils/dates');
const { localDateString, todayLocal } = require('../utils/dates.mts');
const { roundMoney, sumMoney, mulMoney, fromCents } = require('../utils/money.mts');
const SUBSCRIPTION_TYPES = [

View File

@ -7,7 +7,7 @@ const { computeBalanceDelta, applyBalanceDelta } = require('./billsService');
const { computeAmountSuggestionsBatch } = require('./amountSuggestionService');
const { accountingActiveSql } = require('./paymentAccountingService');
const { normalizeMerchant } = require('./subscriptionService');
const { localDateString } = require('../utils/dates');
const { localDateString } = require('../utils/dates.mts');
const { sumMoney, roundMoney, fromCents } = require('../utils/money.mts');
const DEFAULT_PENDING_DAYS = 3;

View File

@ -16,12 +16,12 @@
"types": ["node"]
},
"include": [
"utils/**/*.ts", "utils/**/*.mts",
"services/**/*.ts", "services/**/*.mts",
"routes/**/*.ts", "routes/**/*.mts",
"middleware/**/*.ts", "middleware/**/*.mts",
"db/**/*.ts", "db/**/*.mts",
"workers/**/*.ts", "workers/**/*.mts",
"server.mts"
"utils/**/*.ts", "utils/**/*.mts", "utils/**/*.cts",
"services/**/*.ts", "services/**/*.mts", "services/**/*.cts",
"routes/**/*.ts", "routes/**/*.mts", "routes/**/*.cts",
"middleware/**/*.ts", "middleware/**/*.mts", "middleware/**/*.cts",
"db/**/*.ts", "db/**/*.mts", "db/**/*.cts",
"workers/**/*.ts", "workers/**/*.mts", "workers/**/*.cts",
"server.mts", "server.cts"
]
}

View File

@ -1,17 +1,15 @@
'use strict';
/**
* Server-side local-date helpers.
*
* Bill due dates and payment dates are stored as plain YYYY-MM-DD strings in
* the server's local timezone (the client computes "today" the same way in
* client/pages/TrackerPage.jsx localDateString). Never derive a calendar
* client/pages/TrackerPage.tsx localDateString). Never derive a calendar
* date from Date.toISOString() that yields the UTC date, which disagrees
* with local time around midnight and month boundaries.
*/
/** YYYY-MM-DD in local time. */
function localDateString(date = new Date()) {
export function localDateString(date: Date = new Date()): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
@ -19,25 +17,23 @@ function localDateString(date = new Date()) {
}
/** { year, month } (1-based month) in local time. */
function localYearMonth(date = new Date()) {
export function localYearMonth(date: Date = new Date()): { year: number; month: number } {
return { year: date.getFullYear(), month: date.getMonth() + 1 };
}
/** YYYY-MM-DD in local time, `days` days before `from`. */
function localDateStringDaysAgo(days, from = new Date()) {
export function localDateStringDaysAgo(days: number, from: Date = new Date()): string {
const d = new Date(from);
d.setDate(d.getDate() - days);
return localDateString(d);
}
/** Today as YYYY-MM-DD in local time. Alias of localDateString(). */
function todayLocal() {
export function todayLocal(): string {
return localDateString(new Date());
}
/** YYYY-MM month key in local time (e.g. "2026-06"). */
function monthKey(date = new Date()) {
export function monthKey(date: Date = new Date()): string {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
}
module.exports = { localDateString, localYearMonth, localDateStringDaysAgo, todayLocal, monthKey };

View File

@ -12,7 +12,7 @@ const {
markWorkerStarted,
markWorkerSuccess,
} = require('../services/statusRuntime');
const { localDateString, localDateStringDaysAgo } = require('../utils/dates');
const { localDateString, localDateStringDaysAgo } = require('../utils/dates.mts');
const DAILY_CRON_HOUR = 6;