@luca-financial/luca-schema 3.0.0 → 3.0.2
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/CHANGELOG.md +18 -0
- package/README.md +5 -7
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -3
- package/dist/esm/lucaValidator.js +3 -77
- package/dist/esm/schemas/lucaSchema.json +2 -1
- package/dist/index.d.ts +2 -2
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.0.2] - 2026-02-14
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Export `SCHEMA_VERSION` for consumers to read the contract version without schema-shape coupling.
|
|
13
|
+
- Add tests to verify `SCHEMA_VERSION` matches `lucaSchema.properties.schemaVersion.const` and example schemaVersion values.
|
|
14
|
+
- Update README validator utilities docs to include `SCHEMA_VERSION`.
|
|
15
|
+
|
|
16
|
+
## [3.0.1] - 2026-02-14
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Pin `lucaSchema.properties.schemaVersion` to contract version `3.0.0`.
|
|
21
|
+
- Remove schema-side date repair helpers and date-fix metadata from validator APIs.
|
|
22
|
+
- Keep schema package focused on contract validation and migration utilities.
|
|
23
|
+
- Refresh dependency/tooling versions after `pnpm upgrade` (including `ajv`, `@babel/core`, `@babel/preset-env`, `prettier`, and `typescript-eslint`).
|
|
24
|
+
- Regenerate `pnpm-lock.yaml` to capture updated transitive dependency resolutions.
|
|
25
|
+
|
|
8
26
|
## [3.0.0] - 2026-02-13
|
|
9
27
|
|
|
10
28
|
### Changed
|
package/README.md
CHANGED
|
@@ -214,7 +214,7 @@ Validates the full ledger export.
|
|
|
214
214
|
|
|
215
215
|
```typescript
|
|
216
216
|
const lucaSchema = {
|
|
217
|
-
schemaVersion:
|
|
217
|
+
schemaVersion: '3.0.0';
|
|
218
218
|
categories: Category[];
|
|
219
219
|
accounts: Account[];
|
|
220
220
|
statements: Statement[];
|
|
@@ -231,10 +231,9 @@ This module exports helper utilities to inspect schemas and validate data:
|
|
|
231
231
|
|
|
232
232
|
```typescript
|
|
233
233
|
import {
|
|
234
|
+
SCHEMA_VERSION,
|
|
234
235
|
validate,
|
|
235
236
|
validateCollection,
|
|
236
|
-
normalizeDateString,
|
|
237
|
-
isDateStringFixable,
|
|
238
237
|
getDateFieldPaths,
|
|
239
238
|
getDateFieldPathsByCollection,
|
|
240
239
|
getValidFields,
|
|
@@ -246,10 +245,9 @@ import {
|
|
|
246
245
|
} from '@luca-financial/luca-schema';
|
|
247
246
|
```
|
|
248
247
|
|
|
249
|
-
- `
|
|
250
|
-
- `
|
|
251
|
-
- `
|
|
252
|
-
- `isDateStringFixable(value)` → `true` only for unambiguous slash date strings that can be safely normalized
|
|
248
|
+
- `SCHEMA_VERSION` → schema contract version from `lucaSchema.properties.schemaVersion.const`
|
|
249
|
+
- `validate(schemaKey, data)` → `{ valid: boolean, errors: AjvError[] }`
|
|
250
|
+
- `validateCollection(schemaKey, array)` → `{ valid: boolean, errors: [{ index, entity, errors }] }`
|
|
253
251
|
- `getDateFieldPaths(schemaKey)` → `string[]` of `format: date` fields for a schema key
|
|
254
252
|
- `getDateFieldPathsByCollection()` → `{ accounts, categories, statements, recurringTransactions, recurringTransactionEvents, transactions, transactionSplits }`
|
|
255
253
|
- `getValidFields(schemaKey)` → `Set<string>` of all fields (includes common fields when applicable)
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -673,9 +673,9 @@ export type Memo1 = string | null;
|
|
|
673
673
|
*/
|
|
674
674
|
export interface LucaSchema {
|
|
675
675
|
/**
|
|
676
|
-
* Schema version of the luca ledger
|
|
676
|
+
* Schema contract version of the luca ledger
|
|
677
677
|
*/
|
|
678
|
-
schemaVersion:
|
|
678
|
+
schemaVersion: '3.0.0';
|
|
679
679
|
/**
|
|
680
680
|
* List of categories
|
|
681
681
|
*/
|
package/dist/esm/index.js
CHANGED
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
validate,
|
|
21
21
|
validateCollection
|
|
22
22
|
} from './lucaValidator.js';
|
|
23
|
-
import { isDateStringFixable, normalizeDateString } from './dateUtils.js';
|
|
24
23
|
|
|
25
24
|
const schemas = {
|
|
26
25
|
account,
|
|
@@ -38,6 +37,7 @@ const schemas = {
|
|
|
38
37
|
export const accountSchema = schemas.account;
|
|
39
38
|
export const categorySchema = schemas.category;
|
|
40
39
|
export const lucaSchema = schemas.lucaSchema;
|
|
40
|
+
export const SCHEMA_VERSION = lucaSchemaJson.properties.schemaVersion.const;
|
|
41
41
|
export const statementSchema = schemas.statement;
|
|
42
42
|
export const recurringTransactionSchema = schemas.recurringTransaction;
|
|
43
43
|
export const recurringTransactionEventSchema =
|
|
@@ -51,8 +51,6 @@ export {
|
|
|
51
51
|
schemas,
|
|
52
52
|
validate,
|
|
53
53
|
validateCollection,
|
|
54
|
-
normalizeDateString,
|
|
55
|
-
isDateStringFixable,
|
|
56
54
|
getDateFieldPaths,
|
|
57
55
|
getDateFieldPathsByCollection,
|
|
58
56
|
getValidFields,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
2
2
|
import addFormats from 'ajv-formats';
|
|
3
|
-
import { isDateStringFixable, normalizeDateString } from './dateUtils.js';
|
|
4
3
|
import accountSchemaJson from './schemas/account.json' with { type: 'json' };
|
|
5
4
|
import categorySchemaJson from './schemas/category.json' with { type: 'json' };
|
|
6
5
|
import commonSchemaJson from './schemas/common.json' with { type: 'json' };
|
|
@@ -91,72 +90,6 @@ function isPlainObject(value) {
|
|
|
91
90
|
return proto === Object.prototype || proto === null;
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
function decodePointerToken(token) {
|
|
95
|
-
return token.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function getValueAtInstancePath(data, instancePath) {
|
|
99
|
-
if (!instancePath) return data;
|
|
100
|
-
if (typeof instancePath !== 'string' || !instancePath.startsWith('/')) {
|
|
101
|
-
return undefined;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const tokens = instancePath
|
|
105
|
-
.slice(1)
|
|
106
|
-
.split('/')
|
|
107
|
-
.filter(token => token.length > 0)
|
|
108
|
-
.map(decodePointerToken);
|
|
109
|
-
|
|
110
|
-
let current = data;
|
|
111
|
-
for (const token of tokens) {
|
|
112
|
-
if (current === null || current === undefined) return undefined;
|
|
113
|
-
if (Array.isArray(current)) {
|
|
114
|
-
const index = Number.parseInt(token, 10);
|
|
115
|
-
if (!Number.isInteger(index) || index < 0 || index >= current.length) {
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
current = current[index];
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if (typeof current !== 'object') return undefined;
|
|
122
|
-
current = current[token];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return current;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function createDateFormatIssue(error, data) {
|
|
129
|
-
const instancePath =
|
|
130
|
-
typeof error?.instancePath === 'string' ? error.instancePath : '';
|
|
131
|
-
const value = getValueAtInstancePath(data, instancePath);
|
|
132
|
-
const normalizedValue = normalizeDateString(value);
|
|
133
|
-
const fixable = isDateStringFixable(value);
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
instancePath,
|
|
137
|
-
schemaPath: typeof error?.schemaPath === 'string' ? error.schemaPath : '',
|
|
138
|
-
keyword: 'format',
|
|
139
|
-
format: 'date',
|
|
140
|
-
value,
|
|
141
|
-
fixable,
|
|
142
|
-
normalizedValue: fixable ? normalizedValue : null
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function buildValidationMetadata(errors, data) {
|
|
147
|
-
const dateFormatIssues = [];
|
|
148
|
-
for (const error of errors) {
|
|
149
|
-
if (error?.keyword !== 'format') continue;
|
|
150
|
-
if (error?.params?.format !== 'date') continue;
|
|
151
|
-
dateFormatIssues.push(createDateFormatIssue(error, data));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
dateFormatIssues,
|
|
156
|
-
hasFixableDateFormatIssues: dateFormatIssues.some(issue => issue.fixable)
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
93
|
function collectDatePathsFromSchemaFragment(schemaFragment, prefix = '') {
|
|
161
94
|
if (!schemaFragment || typeof schemaFragment !== 'object') return [];
|
|
162
95
|
|
|
@@ -193,8 +126,7 @@ export function validate(schemaKey, data) {
|
|
|
193
126
|
const errors = ajv.errors ?? [];
|
|
194
127
|
return {
|
|
195
128
|
valid: isValid,
|
|
196
|
-
errors
|
|
197
|
-
metadata: buildValidationMetadata(errors, data)
|
|
129
|
+
errors
|
|
198
130
|
};
|
|
199
131
|
}
|
|
200
132
|
|
|
@@ -317,20 +249,14 @@ export function validateCollection(schemaKey, arrayOfEntities) {
|
|
|
317
249
|
errors.push({
|
|
318
250
|
index,
|
|
319
251
|
entity,
|
|
320
|
-
errors: entityErrors
|
|
321
|
-
metadata: buildValidationMetadata(entityErrors, entity)
|
|
252
|
+
errors: entityErrors
|
|
322
253
|
});
|
|
323
254
|
}
|
|
324
255
|
});
|
|
325
256
|
|
|
326
257
|
return {
|
|
327
258
|
valid: errors.length === 0,
|
|
328
|
-
errors
|
|
329
|
-
metadata: {
|
|
330
|
-
hasFixableDateFormatIssues: errors.some(
|
|
331
|
-
entityError => entityError.metadata.hasFixableDateFormatIssues
|
|
332
|
-
)
|
|
333
|
-
}
|
|
259
|
+
errors
|
|
334
260
|
};
|
|
335
261
|
}
|
|
336
262
|
|
package/dist/index.d.ts
CHANGED
|
@@ -673,9 +673,9 @@ export type Memo1 = string | null;
|
|
|
673
673
|
*/
|
|
674
674
|
export interface LucaSchema {
|
|
675
675
|
/**
|
|
676
|
-
* Schema version of the luca ledger
|
|
676
|
+
* Schema contract version of the luca ledger
|
|
677
677
|
*/
|
|
678
|
-
schemaVersion:
|
|
678
|
+
schemaVersion: '3.0.0';
|
|
679
679
|
/**
|
|
680
680
|
* List of categories
|
|
681
681
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luca-financial/luca-schema",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Schemas for the Luca Ledger application",
|
|
5
5
|
"author": "Johnathan Aspinwall",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
@@ -71,14 +71,14 @@
|
|
|
71
71
|
"CHANGELOG.md"
|
|
72
72
|
],
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"ajv": "^8.
|
|
74
|
+
"ajv": "^8.18.0",
|
|
75
75
|
"ajv-formats": "^2.1.1"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@babel/cli": "^7.28.6",
|
|
79
|
-
"@babel/core": "^7.
|
|
79
|
+
"@babel/core": "^7.29.0",
|
|
80
80
|
"@babel/plugin-syntax-import-attributes": "^7.28.6",
|
|
81
|
-
"@babel/preset-env": "^7.
|
|
81
|
+
"@babel/preset-env": "^7.29.0",
|
|
82
82
|
"@babel/preset-typescript": "^7.28.5",
|
|
83
83
|
"@eslint/js": "^9.39.2",
|
|
84
84
|
"@types/jest": "^29.5.14",
|
|
@@ -93,12 +93,12 @@
|
|
|
93
93
|
"jest": "^29.7.0",
|
|
94
94
|
"json-schema-to-typescript": "^15.0.4",
|
|
95
95
|
"lint-staged": "^15.5.2",
|
|
96
|
-
"prettier": "^3.8.
|
|
96
|
+
"prettier": "^3.8.1",
|
|
97
97
|
"rimraf": "^5.0.10",
|
|
98
98
|
"ts-jest": "^29.4.6",
|
|
99
99
|
"ts-node": "^10.9.2",
|
|
100
100
|
"typescript": "^5.9.3",
|
|
101
|
-
"typescript-eslint": "^8.
|
|
101
|
+
"typescript-eslint": "^8.55.0"
|
|
102
102
|
},
|
|
103
103
|
"scripts": {
|
|
104
104
|
"build": "pnpm clean && pnpm generate:types && node scripts/build.js",
|