Skip to content
This repository was archived by the owner on Jul 21, 2026. It is now read-only.

Commit 592107b

Browse files
committed
Add transaction link schema and related functionality; update examples and tests
1 parent 03153cf commit 592107b

17 files changed

Lines changed: 256 additions & 6 deletions

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,24 @@ const transactionSplit = {
210210
};
211211
```
212212

213+
### TransactionLink
214+
215+
Validates links between related transactions, such as transfers recorded in separate accounts.
216+
217+
```typescript
218+
const transactionLink = {
219+
id: string;
220+
sourceTransactionId: string;
221+
destinationTransactionId: string;
222+
createdAt: string;
223+
updatedAt: string | null;
224+
deletedAt?: string | null;
225+
version?: number;
226+
};
227+
```
228+
229+
Cross-transaction rules like matching dates, matching amounts, or opposite signs are not enforced by this schema and should be handled in application logic.
230+
213231
### LucaSchema
214232

215233
Validates the full ledger export.
@@ -223,6 +241,7 @@ const lucaSchema = {
223241
recurringTransactions: RecurringTransaction[];
224242
recurringTransactionEvents: RecurringTransactionEvent[];
225243
transactions: Transaction[];
244+
transactionLinks?: TransactionLink[];
226245
transactionSplits: TransactionSplit[];
227246
};
228247
```
@@ -251,7 +270,7 @@ import {
251270
- `validate(schemaKey, data)``{ valid: boolean, errors: AjvError[] }`
252271
- `validateCollection(schemaKey, array)``{ valid: boolean, errors: [{ index, entity, errors }] }`
253272
- `getDateFieldPaths(schemaKey)``string[]` of `format: date` fields for a schema key
254-
- `getDateFieldPathsByCollection()``{ accounts, categories, statements, recurringTransactions, recurringTransactionEvents, transactions, transactionSplits }`
273+
- `getDateFieldPathsByCollection()``{ accounts, categories, statements, recurringTransactions, recurringTransactionEvents, transactions, transactionLinks, transactionSplits }`
255274
- `getValidFields(schemaKey)``Set<string>` of all fields (includes common fields when applicable)
256275
- `getRequiredFields(schemaKey)``Set<string>` of required fields (includes common required fields)
257276
- `stripInvalidFields(schemaKey, data)` → new object with only schema-defined keys

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This file is a complete, valid Luca Schema document. It is intended to:
1919
- **Recurring transactions**: Multiple frequencies and states.
2020
- **Recurring transaction events**: Modified and deleted occurrences.
2121
- **Transactions**: A mix of states and categories, some linked to statements.
22+
- **Transaction links**: Related transaction pairs used to model transfers across accounts.
2223
- **Transaction splits**: One split transaction showing sums that match the parent.
2324

2425
### Data characteristics

examples/luca-schema-example.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,24 @@
660660
"createdAt": "2024-02-01T00:00:00Z",
661661
"updatedAt": null
662662
},
663+
{
664+
"id": "75b5faeb-acde-4c4c-8401-0bf2c4f1e9d1",
665+
"accountId": "cf9df9b9-9fc0-424b-a137-47e9ba144c73",
666+
"date": "2024-02-01",
667+
"authorizedAt": null,
668+
"postedAt": "2024-02-01T00:00:00Z",
669+
"currency": "USD",
670+
"amount": -100000,
671+
"description": "Transfer to High Yield Savings",
672+
"memo": "Monthly savings transfer",
673+
"categoryId": null,
674+
"statementId": null,
675+
"aggregationServiceId": "plaid_txn_016b",
676+
"transactionState": "COMPLETED",
677+
"deletedAt": null,
678+
"createdAt": "2024-02-01T00:00:00Z",
679+
"updatedAt": null
680+
},
663681
{
664682
"id": "e2984e2a-baa9-4240-ae84-9794cfe96620",
665683
"accountId": "c5742bfc-835e-4baa-8e1c-71afba58297f",
@@ -733,6 +751,15 @@
733751
"updatedAt": "2024-03-12T10:00:00Z"
734752
}
735753
],
754+
"transactionLinks": [
755+
{
756+
"id": "d6ae6cc3-5949-4bb2-8187-d1be36df94a1",
757+
"sourceTransactionId": "75b5faeb-acde-4c4c-8401-0bf2c4f1e9d1",
758+
"destinationTransactionId": "08bfed71-ab12-4898-ac9c-f2980eefa945",
759+
"createdAt": "2024-02-01T00:05:00Z",
760+
"updatedAt": null
761+
}
762+
],
736763
"transactionSplits": [
737764
{
738765
"id": "fbc07cc4-1ade-442c-962f-b893b599a16d",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@luca-financial/luca-schema",
3-
"version": "3.1.0",
3+
"version": "3.2.0",
44
"description": "Schemas for the Luca Ledger application",
55
"author": "Johnathan Aspinwall",
66
"main": "dist/esm/index.js",

src/examples/lucaSchema.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,39 @@
9999
"transactionState": "SCHEDULED",
100100
"createdAt": "2024-01-10T00:00:00Z",
101101
"updatedAt": "2024-01-15T00:00:00Z"
102+
},
103+
{
104+
"id": "10000000-0000-0000-0000-000000000004",
105+
"accountId": "00000000-0000-0000-0000-000000000001",
106+
"categoryId": null,
107+
"statementId": null,
108+
"amount": -5000,
109+
"date": "2024-01-12",
110+
"description": "ATM cash withdrawal",
111+
"transactionState": "COMPLETED",
112+
"createdAt": "2024-01-12T09:00:00Z",
113+
"updatedAt": "2024-01-12T09:00:00Z"
114+
},
115+
{
116+
"id": "10000000-0000-0000-0000-000000000005",
117+
"accountId": "00000000-0000-0000-0000-000000000005",
118+
"categoryId": null,
119+
"statementId": null,
120+
"amount": 5000,
121+
"date": "2024-01-12",
122+
"description": "Cash from Main Street Bank",
123+
"transactionState": "COMPLETED",
124+
"createdAt": "2024-01-12T09:05:00Z",
125+
"updatedAt": "2024-01-12T09:05:00Z"
126+
}
127+
],
128+
"transactionLinks": [
129+
{
130+
"id": "12000000-0000-0000-0000-000000000001",
131+
"sourceTransactionId": "10000000-0000-0000-0000-000000000004",
132+
"destinationTransactionId": "10000000-0000-0000-0000-000000000005",
133+
"createdAt": "2024-01-12T09:05:00Z",
134+
"updatedAt": null
102135
}
103136
],
104137
"transactionSplits": []

src/examples/transactionLinks.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[
2+
{
3+
"id": "12000000-0000-0000-0000-000000000001",
4+
"sourceTransactionId": "10000000-0000-0000-0000-000000000011",
5+
"destinationTransactionId": "10000000-0000-0000-0000-000000000012",
6+
"createdAt": "2024-03-05T10:05:00Z",
7+
"updatedAt": null
8+
}
9+
]

src/examples/transactions.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,29 @@
118118
"transactionState": "COMPLETED",
119119
"createdAt": "2024-02-20T00:00:00Z",
120120
"updatedAt": "2024-03-01T00:00:00Z"
121+
},
122+
{
123+
"id": "10000000-0000-0000-0000-000000000011",
124+
"accountId": "00000000-0000-0000-0000-000000000001",
125+
"categoryId": null,
126+
"statementId": null,
127+
"amount": -25000,
128+
"date": "2024-03-05",
129+
"description": "Transfer to High-Yield Savings",
130+
"transactionState": "COMPLETED",
131+
"createdAt": "2024-03-05T10:00:00Z",
132+
"updatedAt": "2024-03-05T10:00:00Z"
133+
},
134+
{
135+
"id": "10000000-0000-0000-0000-000000000012",
136+
"accountId": "00000000-0000-0000-0000-000000000002",
137+
"categoryId": null,
138+
"statementId": null,
139+
"amount": 25000,
140+
"date": "2024-03-05",
141+
"description": "Transfer from Everyday Checking",
142+
"transactionState": "COMPLETED",
143+
"createdAt": "2024-03-05T10:05:00Z",
144+
"updatedAt": "2024-03-05T10:05:00Z"
121145
}
122146
]

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
recurringTransactionEvent,
99
statement,
1010
transaction,
11+
transactionLink,
1112
transactionSplit
1213
} from './schemas/index.js';
1314
import { enums, LucaSchemas } from './enums.js';
@@ -30,6 +31,7 @@ const schemas = {
3031
recurringTransaction,
3132
recurringTransactionEvent,
3233
transaction,
34+
transactionLink,
3335
transactionSplit,
3436
enums: enumsSchema
3537
};
@@ -43,6 +45,7 @@ export const recurringTransactionSchema = schemas.recurringTransaction;
4345
export const recurringTransactionEventSchema =
4446
schemas.recurringTransactionEvent;
4547
export const transactionSchema = schemas.transaction;
48+
export const transactionLinkSchema = schemas.transactionLink;
4649
export const transactionSplitSchema = schemas.transactionSplit;
4750

4851
export {

src/lucaValidator.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import statementSchemaJson from './schemas/statement.json' with { type: 'json' }
99
import recurringTransactionSchemaJson from './schemas/recurringTransaction.json' with { type: 'json' };
1010
import recurringTransactionEventSchemaJson from './schemas/recurringTransactionEvent.json' with { type: 'json' };
1111
import transactionSchemaJson from './schemas/transaction.json' with { type: 'json' };
12+
import transactionLinkSchemaJson from './schemas/transactionLink.json' with { type: 'json' };
1213
import transactionSplitSchemaJson from './schemas/transactionSplit.json' with { type: 'json' };
1314

1415
const schemas = {
@@ -19,6 +20,7 @@ const schemas = {
1920
recurringTransaction: recurringTransactionSchemaJson,
2021
recurringTransactionEvent: recurringTransactionEventSchemaJson,
2122
transaction: transactionSchemaJson,
23+
transactionLink: transactionLinkSchemaJson,
2224
transactionSplit: transactionSplitSchemaJson
2325
};
2426

@@ -193,6 +195,7 @@ export function getDateFieldPaths(schemaKey) {
193195
* recurringTransactions: Array<string>,
194196
* recurringTransactionEvents: Array<string>,
195197
* transactions: Array<string>,
198+
* transactionLinks: Array<string>,
196199
* transactionSplits: Array<string>
197200
* }}
198201
*/
@@ -204,6 +207,7 @@ export function getDateFieldPathsByCollection() {
204207
recurringTransactions: getDateFieldPaths('recurringTransaction'),
205208
recurringTransactionEvents: getDateFieldPaths('recurringTransactionEvent'),
206209
transactions: getDateFieldPaths('transaction'),
210+
transactionLinks: getDateFieldPaths('transactionLink'),
207211
transactionSplits: getDateFieldPaths('transactionSplit')
208212
};
209213
}

src/schemas/enums.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"RECURRING_TRANSACTION": "recurringTransaction",
1212
"RECURRING_TRANSACTION_EVENT": "recurringTransactionEvent",
1313
"TRANSACTION": "transaction",
14+
"TRANSACTION_LINK": "transactionLink",
1415
"TRANSACTION_SPLIT": "transactionSplit"
1516
},
1617
"$defs": {

0 commit comments

Comments
 (0)