@parsrun/core 0.2.6 → 0.2.9

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/dist/decimal.d.ts CHANGED
@@ -1,7 +1,9 @@
1
+ import DecimalJS from 'decimal.js';
2
+
1
3
  /**
2
4
  * @parsrun/core - Decimal Utilities
3
5
  * Precise decimal calculations for financial and quantity operations.
4
- * Edge-compatible - no external dependencies.
6
+ * Edge-compatible - wraps decimal.js for arbitrary precision arithmetic.
5
7
  *
6
8
  * @example
7
9
  * ```typescript
@@ -15,14 +17,20 @@
15
17
  * const total = price.mul(quantity).round(2);
16
18
  * console.log(total.toString()); // '59.97'
17
19
  *
20
+ * // Precision info
21
+ * const d = new Decimal('123.45');
22
+ * d.precision(); // 5 (total significant digits)
23
+ * d.decimalPlaces(); // 2 (digits after decimal point)
24
+ *
18
25
  * // Static helpers
19
26
  * const sum = Decimal.sum(['10.50', '20.25', '15.75']);
20
27
  * const avg = Decimal.avg([100, 200, 300]);
21
28
  * ```
22
29
  */
30
+
23
31
  /**
24
32
  * Decimal class for precise arithmetic operations.
25
- * Uses string-based representation internally to avoid floating point issues.
33
+ * Wraps decimal.js to provide arbitrary precision decimal arithmetic.
26
34
  *
27
35
  * @example
28
36
  * ```typescript
@@ -33,10 +41,8 @@
33
41
  * ```
34
42
  */
35
43
  declare class Decimal {
36
- private value;
37
- constructor(value: number | string | Decimal);
38
- private normalizeNumber;
39
- private normalizeString;
44
+ private readonly _value;
45
+ constructor(value: number | string | Decimal | DecimalJS);
40
46
  /**
41
47
  * Add another value to this decimal.
42
48
  * @param other - Value to add
@@ -159,8 +165,39 @@ declare class Decimal {
159
165
  * @returns True if value is negative
160
166
  */
161
167
  isNegative(): boolean;
168
+ /**
169
+ * Check if this decimal is an integer (no decimal places).
170
+ * @returns True if value is an integer
171
+ */
172
+ isInteger(): boolean;
173
+ /**
174
+ * Get the number of decimal places (digits after the decimal point).
175
+ * @returns Number of decimal places
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * new Decimal('123.45').decimalPlaces(); // 2
180
+ * new Decimal('100').decimalPlaces(); // 0
181
+ * new Decimal('1.500').decimalPlaces(); // 1 (trailing zeros removed)
182
+ * ```
183
+ */
184
+ decimalPlaces(): number;
185
+ /**
186
+ * Get the precision (total number of significant digits).
187
+ * @param includeZeros - If true, include trailing zeros in the count
188
+ * @returns The precision
189
+ *
190
+ * @example
191
+ * ```typescript
192
+ * new Decimal('123.45').precision(); // 5
193
+ * new Decimal('100').precision(); // 1
194
+ * new Decimal('100').precision(true); // 3
195
+ * ```
196
+ */
197
+ precision(includeZeros?: boolean): number;
162
198
  /**
163
199
  * Convert this decimal to a JavaScript number.
200
+ * Warning: May lose precision for very large or very precise numbers.
164
201
  * @returns The numeric value
165
202
  */
166
203
  toNumber(): number;
@@ -180,6 +217,12 @@ declare class Decimal {
180
217
  * @returns The string value for JSON serialization
181
218
  */
182
219
  toJSON(): string;
220
+ /**
221
+ * Get the underlying decimal.js instance.
222
+ * Useful for advanced operations not covered by this wrapper.
223
+ * @returns The underlying DecimalJS instance
224
+ */
225
+ toDecimalJS(): DecimalJS;
183
226
  /**
184
227
  * Create a Decimal from a value (alias for constructor).
185
228
  * @param value - The value to convert
@@ -212,6 +255,18 @@ declare class Decimal {
212
255
  * @throws Error if no values provided
213
256
  */
214
257
  static max(...values: (number | string | Decimal)[]): Decimal;
258
+ /**
259
+ * Check if a value is a valid decimal representation.
260
+ * @param value - The value to check
261
+ * @returns True if the value can be converted to a Decimal
262
+ */
263
+ static isValid(value: unknown): boolean;
264
+ /**
265
+ * Create a Decimal or return null if the value is invalid.
266
+ * @param value - The value to convert
267
+ * @returns A Decimal or null
268
+ */
269
+ static tryParse(value: unknown): Decimal | null;
215
270
  }
216
271
  /**
217
272
  * Utility functions for working with decimals in database operations.
@@ -296,12 +351,27 @@ declare const DecimalUtils: {
296
351
  */
297
352
  prepareForDatabase<T extends Record<string, unknown>>(data: T, decimalFields: string[]): T;
298
353
  /**
299
- * Parse an object from database by converting decimal string fields to JavaScript numbers.
354
+ * Parse an object from database by converting decimal string fields to Decimal instances.
300
355
  * @param data - The object from database
301
- * @param decimalFields - Array of field names that should be converted from decimal strings
302
- * @returns A new object with specified fields converted to numbers
356
+ * @param decimalFields - Array of field names that should be converted to Decimal
357
+ * @returns A new object with specified fields converted to Decimal instances
303
358
  */
304
359
  parseFromDatabase<T extends Record<string, unknown>>(data: T, decimalFields: string[]): T;
360
+ /**
361
+ * Validate that a value matches the specified precision and scale.
362
+ * @param value - The value to validate
363
+ * @param precision - Total number of digits (integer + decimal)
364
+ * @param scale - Number of decimal places
365
+ * @returns An error message if invalid, or null if valid
366
+ *
367
+ * @example
368
+ * ```typescript
369
+ * DecimalUtils.validate('123.45', 5, 2); // null (valid)
370
+ * DecimalUtils.validate('123.456', 5, 2); // "max 2 decimal places allowed"
371
+ * DecimalUtils.validate('1234.56', 5, 2); // "max 3 integer digits allowed"
372
+ * ```
373
+ */
374
+ validate(value: number | string | Decimal, precision: number, scale: number): string | null;
305
375
  };
306
376
  /**
307
377
  * Shorthand function for creating a Decimal instance.
package/dist/decimal.js CHANGED
@@ -1,38 +1,30 @@
1
1
  // src/decimal.ts
2
- var PRECISION = 20;
2
+ import DecimalJS from "decimal.js";
3
+ DecimalJS.set({
4
+ precision: 40,
5
+ rounding: DecimalJS.ROUND_HALF_UP,
6
+ toExpNeg: -9,
7
+ toExpPos: 21
8
+ });
3
9
  var Decimal = class _Decimal {
4
- value;
10
+ _value;
5
11
  constructor(value) {
6
12
  if (value instanceof _Decimal) {
7
- this.value = value.value;
8
- } else if (typeof value === "number") {
9
- this.value = this.normalizeNumber(value);
13
+ this._value = value._value;
14
+ } else if (value instanceof DecimalJS) {
15
+ this._value = value;
10
16
  } else {
11
- this.value = this.normalizeString(value);
17
+ this._value = new DecimalJS(value);
12
18
  }
13
19
  }
14
- normalizeNumber(n) {
15
- if (!isFinite(n)) {
16
- throw new Error(`Invalid number: ${n}`);
17
- }
18
- return n.toFixed(PRECISION).replace(/\.?0+$/, "") || "0";
19
- }
20
- normalizeString(s) {
21
- const trimmed = s.trim();
22
- if (!/^-?\d*\.?\d+$/.test(trimmed)) {
23
- throw new Error(`Invalid decimal string: ${s}`);
24
- }
25
- return trimmed.replace(/^(-?)0+(?=\d)/, "$1").replace(/\.?0+$/, "") || "0";
26
- }
27
20
  /**
28
21
  * Add another value to this decimal.
29
22
  * @param other - Value to add
30
23
  * @returns A new Decimal with the result
31
24
  */
32
25
  add(other) {
33
- const a = parseFloat(this.value);
34
- const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
35
- return new _Decimal(a + b);
26
+ const otherValue = other instanceof _Decimal ? other._value : other;
27
+ return new _Decimal(this._value.plus(otherValue));
36
28
  }
37
29
  /**
38
30
  * Subtract a value from this decimal.
@@ -40,9 +32,8 @@ var Decimal = class _Decimal {
40
32
  * @returns A new Decimal with the result
41
33
  */
42
34
  sub(other) {
43
- const a = parseFloat(this.value);
44
- const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
45
- return new _Decimal(a - b);
35
+ const otherValue = other instanceof _Decimal ? other._value : other;
36
+ return new _Decimal(this._value.minus(otherValue));
46
37
  }
47
38
  /**
48
39
  * Multiply this decimal by another value.
@@ -50,9 +41,8 @@ var Decimal = class _Decimal {
50
41
  * @returns A new Decimal with the result
51
42
  */
52
43
  mul(other) {
53
- const a = parseFloat(this.value);
54
- const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
55
- return new _Decimal(a * b);
44
+ const otherValue = other instanceof _Decimal ? other._value : other;
45
+ return new _Decimal(this._value.times(otherValue));
56
46
  }
57
47
  /**
58
48
  * Divide this decimal by another value.
@@ -61,12 +51,12 @@ var Decimal = class _Decimal {
61
51
  * @throws Error if dividing by zero
62
52
  */
63
53
  div(other) {
64
- const a = parseFloat(this.value);
65
- const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
66
- if (b === 0) {
54
+ const otherValue = other instanceof _Decimal ? other._value : other;
55
+ const divisor = new DecimalJS(otherValue);
56
+ if (divisor.isZero()) {
67
57
  throw new Error("Division by zero");
68
58
  }
69
- return new _Decimal(a / b);
59
+ return new _Decimal(this._value.dividedBy(divisor));
70
60
  }
71
61
  /**
72
62
  * Get the modulo (remainder) of dividing this decimal by another value.
@@ -74,9 +64,8 @@ var Decimal = class _Decimal {
74
64
  * @returns A new Decimal with the remainder
75
65
  */
76
66
  mod(other) {
77
- const a = parseFloat(this.value);
78
- const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
79
- return new _Decimal(a % b);
67
+ const otherValue = other instanceof _Decimal ? other._value : other;
68
+ return new _Decimal(this._value.modulo(otherValue));
80
69
  }
81
70
  /**
82
71
  * Raise this decimal to a power.
@@ -84,8 +73,7 @@ var Decimal = class _Decimal {
84
73
  * @returns A new Decimal with the result
85
74
  */
86
75
  pow(exp) {
87
- const a = parseFloat(this.value);
88
- return new _Decimal(Math.pow(a, exp));
76
+ return new _Decimal(this._value.pow(exp));
89
77
  }
90
78
  /**
91
79
  * Calculate the square root of this decimal.
@@ -93,27 +81,24 @@ var Decimal = class _Decimal {
93
81
  * @throws Error if the value is negative
94
82
  */
95
83
  sqrt() {
96
- const a = parseFloat(this.value);
97
- if (a < 0) {
84
+ if (this._value.isNegative()) {
98
85
  throw new Error("Square root of negative number");
99
86
  }
100
- return new _Decimal(Math.sqrt(a));
87
+ return new _Decimal(this._value.sqrt());
101
88
  }
102
89
  /**
103
90
  * Get the absolute value of this decimal.
104
91
  * @returns A new Decimal with the absolute value
105
92
  */
106
93
  abs() {
107
- const a = parseFloat(this.value);
108
- return new _Decimal(Math.abs(a));
94
+ return new _Decimal(this._value.abs());
109
95
  }
110
96
  /**
111
97
  * Negate this decimal (multiply by -1).
112
98
  * @returns A new Decimal with the negated value
113
99
  */
114
100
  neg() {
115
- const a = parseFloat(this.value);
116
- return new _Decimal(-a);
101
+ return new _Decimal(this._value.negated());
117
102
  }
118
103
  /**
119
104
  * Round to the specified number of decimal places using standard rounding.
@@ -121,9 +106,7 @@ var Decimal = class _Decimal {
121
106
  * @returns A new Decimal with the rounded value
122
107
  */
123
108
  round(decimals = 0) {
124
- const a = parseFloat(this.value);
125
- const factor = Math.pow(10, decimals);
126
- return new _Decimal(Math.round(a * factor) / factor);
109
+ return new _Decimal(this._value.toDecimalPlaces(decimals, DecimalJS.ROUND_HALF_UP));
127
110
  }
128
111
  /**
129
112
  * Round down to the specified number of decimal places.
@@ -131,9 +114,7 @@ var Decimal = class _Decimal {
131
114
  * @returns A new Decimal with the floored value
132
115
  */
133
116
  floor(decimals = 0) {
134
- const a = parseFloat(this.value);
135
- const factor = Math.pow(10, decimals);
136
- return new _Decimal(Math.floor(a * factor) / factor);
117
+ return new _Decimal(this._value.toDecimalPlaces(decimals, DecimalJS.ROUND_FLOOR));
137
118
  }
138
119
  /**
139
120
  * Round up to the specified number of decimal places.
@@ -141,9 +122,7 @@ var Decimal = class _Decimal {
141
122
  * @returns A new Decimal with the ceiled value
142
123
  */
143
124
  ceil(decimals = 0) {
144
- const a = parseFloat(this.value);
145
- const factor = Math.pow(10, decimals);
146
- return new _Decimal(Math.ceil(a * factor) / factor);
125
+ return new _Decimal(this._value.toDecimalPlaces(decimals, DecimalJS.ROUND_CEIL));
147
126
  }
148
127
  /**
149
128
  * Compare this decimal to another value.
@@ -151,11 +130,8 @@ var Decimal = class _Decimal {
151
130
  * @returns -1 if less, 0 if equal, 1 if greater
152
131
  */
153
132
  cmp(other) {
154
- const a = parseFloat(this.value);
155
- const b = parseFloat(other instanceof _Decimal ? other.value : String(other));
156
- if (a < b) return -1;
157
- if (a > b) return 1;
158
- return 0;
133
+ const otherValue = other instanceof _Decimal ? other._value : other;
134
+ return this._value.comparedTo(otherValue);
159
135
  }
160
136
  /**
161
137
  * Check if this decimal equals another value.
@@ -163,7 +139,8 @@ var Decimal = class _Decimal {
163
139
  * @returns True if values are equal
164
140
  */
165
141
  eq(other) {
166
- return this.cmp(other) === 0;
142
+ const otherValue = other instanceof _Decimal ? other._value : other;
143
+ return this._value.equals(otherValue);
167
144
  }
168
145
  /**
169
146
  * Check if this decimal is greater than another value.
@@ -171,7 +148,8 @@ var Decimal = class _Decimal {
171
148
  * @returns True if this is greater
172
149
  */
173
150
  gt(other) {
174
- return this.cmp(other) === 1;
151
+ const otherValue = other instanceof _Decimal ? other._value : other;
152
+ return this._value.greaterThan(otherValue);
175
153
  }
176
154
  /**
177
155
  * Check if this decimal is greater than or equal to another value.
@@ -179,7 +157,8 @@ var Decimal = class _Decimal {
179
157
  * @returns True if this is greater or equal
180
158
  */
181
159
  gte(other) {
182
- return this.cmp(other) >= 0;
160
+ const otherValue = other instanceof _Decimal ? other._value : other;
161
+ return this._value.greaterThanOrEqualTo(otherValue);
183
162
  }
184
163
  /**
185
164
  * Check if this decimal is less than another value.
@@ -187,7 +166,8 @@ var Decimal = class _Decimal {
187
166
  * @returns True if this is less
188
167
  */
189
168
  lt(other) {
190
- return this.cmp(other) === -1;
169
+ const otherValue = other instanceof _Decimal ? other._value : other;
170
+ return this._value.lessThan(otherValue);
191
171
  }
192
172
  /**
193
173
  * Check if this decimal is less than or equal to another value.
@@ -195,42 +175,80 @@ var Decimal = class _Decimal {
195
175
  * @returns True if this is less or equal
196
176
  */
197
177
  lte(other) {
198
- return this.cmp(other) <= 0;
178
+ const otherValue = other instanceof _Decimal ? other._value : other;
179
+ return this._value.lessThanOrEqualTo(otherValue);
199
180
  }
200
181
  /**
201
182
  * Check if this decimal is exactly zero.
202
183
  * @returns True if value is zero
203
184
  */
204
185
  isZero() {
205
- return parseFloat(this.value) === 0;
186
+ return this._value.isZero();
206
187
  }
207
188
  /**
208
189
  * Check if this decimal is positive (greater than zero).
209
190
  * @returns True if value is positive
210
191
  */
211
192
  isPositive() {
212
- return parseFloat(this.value) > 0;
193
+ return this._value.isPositive() && !this._value.isZero();
213
194
  }
214
195
  /**
215
196
  * Check if this decimal is negative (less than zero).
216
197
  * @returns True if value is negative
217
198
  */
218
199
  isNegative() {
219
- return parseFloat(this.value) < 0;
200
+ return this._value.isNegative();
201
+ }
202
+ /**
203
+ * Check if this decimal is an integer (no decimal places).
204
+ * @returns True if value is an integer
205
+ */
206
+ isInteger() {
207
+ return this._value.isInteger();
208
+ }
209
+ /**
210
+ * Get the number of decimal places (digits after the decimal point).
211
+ * @returns Number of decimal places
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * new Decimal('123.45').decimalPlaces(); // 2
216
+ * new Decimal('100').decimalPlaces(); // 0
217
+ * new Decimal('1.500').decimalPlaces(); // 1 (trailing zeros removed)
218
+ * ```
219
+ */
220
+ decimalPlaces() {
221
+ return this._value.decimalPlaces();
222
+ }
223
+ /**
224
+ * Get the precision (total number of significant digits).
225
+ * @param includeZeros - If true, include trailing zeros in the count
226
+ * @returns The precision
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * new Decimal('123.45').precision(); // 5
231
+ * new Decimal('100').precision(); // 1
232
+ * new Decimal('100').precision(true); // 3
233
+ * ```
234
+ */
235
+ precision(includeZeros = false) {
236
+ return this._value.precision(includeZeros);
220
237
  }
221
238
  /**
222
239
  * Convert this decimal to a JavaScript number.
240
+ * Warning: May lose precision for very large or very precise numbers.
223
241
  * @returns The numeric value
224
242
  */
225
243
  toNumber() {
226
- return parseFloat(this.value);
244
+ return this._value.toNumber();
227
245
  }
228
246
  /**
229
247
  * Convert this decimal to its string representation.
230
248
  * @returns The string value
231
249
  */
232
250
  toString() {
233
- return this.value;
251
+ return this._value.toString();
234
252
  }
235
253
  /**
236
254
  * Format this decimal with a fixed number of decimal places.
@@ -238,14 +256,22 @@ var Decimal = class _Decimal {
238
256
  * @returns Formatted string
239
257
  */
240
258
  toFixed(decimals = 2) {
241
- return parseFloat(this.value).toFixed(decimals);
259
+ return this._value.toFixed(decimals);
242
260
  }
243
261
  /**
244
262
  * Convert to JSON (returns string representation for serialization).
245
263
  * @returns The string value for JSON serialization
246
264
  */
247
265
  toJSON() {
248
- return this.value;
266
+ return this._value.toString();
267
+ }
268
+ /**
269
+ * Get the underlying decimal.js instance.
270
+ * Useful for advanced operations not covered by this wrapper.
271
+ * @returns The underlying DecimalJS instance
272
+ */
273
+ toDecimalJS() {
274
+ return this._value;
249
275
  }
250
276
  /**
251
277
  * Create a Decimal from a value (alias for constructor).
@@ -301,6 +327,30 @@ var Decimal = class _Decimal {
301
327
  return d.gt(max) ? d : max;
302
328
  }, new _Decimal(values[0]));
303
329
  }
330
+ /**
331
+ * Check if a value is a valid decimal representation.
332
+ * @param value - The value to check
333
+ * @returns True if the value can be converted to a Decimal
334
+ */
335
+ static isValid(value) {
336
+ if (value === null || value === void 0) return false;
337
+ if (value instanceof _Decimal) return true;
338
+ try {
339
+ new DecimalJS(value);
340
+ return true;
341
+ } catch {
342
+ return false;
343
+ }
344
+ }
345
+ /**
346
+ * Create a Decimal or return null if the value is invalid.
347
+ * @param value - The value to convert
348
+ * @returns A Decimal or null
349
+ */
350
+ static tryParse(value) {
351
+ if (!_Decimal.isValid(value)) return null;
352
+ return new _Decimal(value);
353
+ }
304
354
  };
305
355
  var DecimalUtils = {
306
356
  /**
@@ -393,30 +443,62 @@ var DecimalUtils = {
393
443
  for (const field of decimalFields) {
394
444
  if (field in result && result[field] !== void 0 && result[field] !== null) {
395
445
  const value = result[field];
396
- if (typeof value === "number") {
446
+ if (typeof value === "number" || typeof value === "string") {
397
447
  result[field] = DecimalUtils.toDecimalString(value);
448
+ } else if (value instanceof Decimal) {
449
+ result[field] = value.toString();
398
450
  }
399
451
  }
400
452
  }
401
453
  return result;
402
454
  },
403
455
  /**
404
- * Parse an object from database by converting decimal string fields to JavaScript numbers.
456
+ * Parse an object from database by converting decimal string fields to Decimal instances.
405
457
  * @param data - The object from database
406
- * @param decimalFields - Array of field names that should be converted from decimal strings
407
- * @returns A new object with specified fields converted to numbers
458
+ * @param decimalFields - Array of field names that should be converted to Decimal
459
+ * @returns A new object with specified fields converted to Decimal instances
408
460
  */
409
461
  parseFromDatabase(data, decimalFields) {
410
462
  const result = { ...data };
411
463
  for (const field of decimalFields) {
412
464
  if (field in result && result[field] !== void 0 && result[field] !== null) {
413
465
  const value = result[field];
414
- if (typeof value === "string") {
415
- result[field] = DecimalUtils.fromDecimalString(value);
466
+ if (typeof value === "string" || typeof value === "number") {
467
+ result[field] = new Decimal(value);
416
468
  }
417
469
  }
418
470
  }
419
471
  return result;
472
+ },
473
+ /**
474
+ * Validate that a value matches the specified precision and scale.
475
+ * @param value - The value to validate
476
+ * @param precision - Total number of digits (integer + decimal)
477
+ * @param scale - Number of decimal places
478
+ * @returns An error message if invalid, or null if valid
479
+ *
480
+ * @example
481
+ * ```typescript
482
+ * DecimalUtils.validate('123.45', 5, 2); // null (valid)
483
+ * DecimalUtils.validate('123.456', 5, 2); // "max 2 decimal places allowed"
484
+ * DecimalUtils.validate('1234.56', 5, 2); // "max 3 integer digits allowed"
485
+ * ```
486
+ */
487
+ validate(value, precision, scale) {
488
+ const d = value instanceof Decimal ? value : new Decimal(value);
489
+ const maxIntDigits = precision - scale;
490
+ if (d.decimalPlaces() > scale) {
491
+ return `max ${scale} decimal places allowed`;
492
+ }
493
+ const absValue = d.abs();
494
+ if (!absValue.isZero()) {
495
+ const integerPart = absValue.floor(0);
496
+ const intDigits = integerPart.isZero() ? 0 : integerPart.precision(true);
497
+ if (intDigits > maxIntDigits) {
498
+ return `max ${maxIntDigits} integer digits allowed`;
499
+ }
500
+ }
501
+ return null;
420
502
  }
421
503
  };
422
504
  function decimal(value) {