@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 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: string;
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
- - `validate(schemaKey, data)` → `{ valid: boolean, errors: AjvError[], metadata: { dateFormatIssues, hasFixableDateFormatIssues } }`
250
- - `validateCollection(schemaKey, array)` → `{ valid: boolean, errors: [{ index, entity, errors, metadata }], metadata: { hasFixableDateFormatIssues } }`
251
- - `normalizeDateString(value)` → normalized `YYYY-MM-DD` for unambiguous date strings (`YYYY-MM-DD` or `YYYY/MM/DD`), else `null`
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)
@@ -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: string;
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
 
@@ -17,7 +17,8 @@
17
17
  "properties": {
18
18
  "schemaVersion": {
19
19
  "type": "string",
20
- "description": "Schema version of the luca ledger"
20
+ "const": "3.0.0",
21
+ "description": "Schema contract version of the luca ledger"
21
22
  },
22
23
  "categories": {
23
24
  "type": "array",
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: string;
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.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.17.1",
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.28.6",
79
+ "@babel/core": "^7.29.0",
80
80
  "@babel/plugin-syntax-import-attributes": "^7.28.6",
81
- "@babel/preset-env": "^7.28.6",
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.0",
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.53.0"
101
+ "typescript-eslint": "^8.55.0"
102
102
  },
103
103
  "scripts": {
104
104
  "build": "pnpm clean && pnpm generate:types && node scripts/build.js",