df-script 1.4.0 → 1.5.0

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.
Files changed (95) hide show
  1. package/README.md +83 -34
  2. package/dist/api.d.ts +16 -8
  3. package/dist/api.js +11 -3
  4. package/dist/columnExpressions/ColumnExpr.d.ts +26 -364
  5. package/dist/columnExpressions/ColumnExpr.js +199 -3
  6. package/dist/columnExpressions/ExprBase.d.ts +8 -11
  7. package/dist/columnExpressions/ExprBase.js +20 -61
  8. package/dist/columnExpressions/constants.d.ts +2 -0
  9. package/dist/columnExpressions/constants.js +3 -1
  10. package/dist/columnExpressions/functions/coalesce.js +8 -4
  11. package/dist/columnExpressions/functions/element.d.ts +5 -0
  12. package/dist/columnExpressions/functions/element.js +11 -0
  13. package/dist/columnExpressions/functions/implode.d.ts +3 -0
  14. package/dist/columnExpressions/functions/implode.js +7 -0
  15. package/dist/columnExpressions/functions/lit.d.ts +3 -1
  16. package/dist/columnExpressions/functions/lit.js +8 -13
  17. package/dist/columnExpressions/functions/repeat.d.ts +47 -0
  18. package/dist/columnExpressions/functions/repeat.js +131 -0
  19. package/dist/columnExpressions/functions/seq_range.d.ts +31 -0
  20. package/dist/columnExpressions/functions/seq_range.js +91 -0
  21. package/dist/columnExpressions/functions/when.js +8 -4
  22. package/dist/columnExpressions/index.d.ts +5 -2
  23. package/dist/columnExpressions/index.js +5 -48
  24. package/dist/columnExpressions/mixins/AggregationExpr.d.ts +25 -50
  25. package/dist/columnExpressions/mixins/AggregationExpr.js +69 -165
  26. package/dist/columnExpressions/mixins/ArithmeticExpr.d.ts +41 -66
  27. package/dist/columnExpressions/mixins/ArithmeticExpr.js +126 -138
  28. package/dist/columnExpressions/mixins/BooleanExpr.d.ts +40 -0
  29. package/dist/columnExpressions/mixins/BooleanExpr.js +67 -0
  30. package/dist/columnExpressions/mixins/ComparisonExpr.d.ts +31 -60
  31. package/dist/columnExpressions/mixins/ComparisonExpr.js +231 -370
  32. package/dist/columnExpressions/mixins/ListExpr.d.ts +15 -39
  33. package/dist/columnExpressions/mixins/ListExpr.js +124 -148
  34. package/dist/columnExpressions/mixins/LogicalExpr.d.ts +7 -33
  35. package/dist/columnExpressions/mixins/LogicalExpr.js +48 -49
  36. package/dist/columnExpressions/mixins/ManipulationExpr.d.ts +6 -31
  37. package/dist/columnExpressions/mixins/ManipulationExpr.js +61 -25
  38. package/dist/columnExpressions/mixins/NumericExpr.d.ts +92 -0
  39. package/dist/columnExpressions/mixins/NumericExpr.js +259 -0
  40. package/dist/columnExpressions/mixins/StringExpr.d.ts +5 -30
  41. package/dist/columnExpressions/mixins/StringExpr.js +37 -49
  42. package/dist/columnExpressions/mixins/StructExpr.d.ts +16 -0
  43. package/dist/columnExpressions/mixins/StructExpr.js +139 -0
  44. package/dist/columnExpressions/mixins/TemporalExpr.d.ts +4 -30
  45. package/dist/columnExpressions/mixins/TemporalExpr.js +28 -29
  46. package/dist/columnExpressions/mixins/WindowExpr.d.ts +27 -49
  47. package/dist/columnExpressions/mixins/WindowExpr.js +144 -274
  48. package/dist/columnExpressions/utils.d.ts +4 -0
  49. package/dist/columnExpressions/utils.js +45 -0
  50. package/dist/dataframe/constants.d.ts +3 -0
  51. package/dist/dataframe/constants.js +4 -1
  52. package/dist/dataframe/dataframe.d.ts +27 -10
  53. package/dist/dataframe/dataframe.js +461 -220
  54. package/dist/dataframe/grouped/grouped.d.ts +2 -3
  55. package/dist/dataframe/grouped/grouped.js +17 -16
  56. package/dist/dataframe/types.d.ts +65 -1
  57. package/dist/dataframe/utils.d.ts +11 -3
  58. package/dist/dataframe/utils.js +101 -9
  59. package/dist/datatypes/DataType.d.ts +2 -1
  60. package/dist/datatypes/DataType.js +29 -0
  61. package/dist/datatypes/index.d.ts +2 -2
  62. package/dist/datatypes/types.d.ts +37 -36
  63. package/dist/datatypes/types.js +15 -11
  64. package/dist/exceptions/index.d.ts +2 -0
  65. package/dist/exceptions/index.js +4 -1
  66. package/dist/exceptions/utils.d.ts +1 -0
  67. package/dist/exceptions/utils.js +19 -0
  68. package/dist/functions/concat.d.ts +1 -2
  69. package/dist/functions/concat.js +24 -12
  70. package/dist/functions/index.d.ts +3 -1
  71. package/dist/functions/index.js +7 -15
  72. package/dist/functions/read_csv.d.ts +8 -0
  73. package/dist/functions/read_csv.js +53 -0
  74. package/dist/functions/read_json.d.ts +10 -0
  75. package/dist/functions/read_json.js +21 -0
  76. package/dist/functions/transpose.d.ts +7 -0
  77. package/dist/functions/transpose.js +84 -0
  78. package/dist/index.js +6 -1
  79. package/dist/types.d.ts +31 -5
  80. package/dist/utils/csv.d.ts +77 -0
  81. package/dist/utils/csv.js +313 -0
  82. package/dist/utils/date.js +5 -6
  83. package/dist/utils/guards.d.ts +0 -2
  84. package/dist/utils/guards.js +0 -31
  85. package/dist/utils/index.d.ts +2 -0
  86. package/dist/utils/index.js +2 -0
  87. package/dist/utils/json.d.ts +120 -2
  88. package/dist/utils/json.js +204 -20
  89. package/dist/utils/list.d.ts +173 -1
  90. package/dist/utils/list.js +255 -22
  91. package/dist/utils/number.d.ts +61 -28
  92. package/dist/utils/number.js +248 -70
  93. package/dist/utils/string.d.ts +5 -0
  94. package/dist/utils/string.js +89 -0
  95. package/package.json +5 -1
@@ -1,32 +1,39 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BIGINT_RANGES = exports.INT_RANGES = exports.FLOAT_RANGES = exports.FLOAT_32_BOUNDARY = void 0;
3
+ exports.BIGINT_RANGES = exports.INT_RANGES = void 0;
4
4
  exports.isValidNumber = isValidNumber;
5
5
  exports.toValidNumber = toValidNumber;
6
- exports.clamp = clamp;
6
+ exports.formatNumber = formatNumber;
7
7
  exports.isValidFloat = isValidFloat;
8
8
  exports.toValidFloat = toValidFloat;
9
9
  exports.isValidInt = isValidInt;
10
- exports.isValidBigInt = isValidBigInt;
11
10
  exports.toValidInt = toValidInt;
11
+ exports.isValidBigInt = isValidBigInt;
12
12
  exports.toValidBigInt = toValidBigInt;
13
13
  exports.isValidDecimal = isValidDecimal;
14
14
  exports.toValidDecimal = toValidDecimal;
15
+ exports.clamp = clamp;
15
16
  exports.mulberry32 = mulberry32;
17
+ exports.computeMedian = computeMedian;
18
+ exports.computeQuantile = computeQuantile;
19
+ exports.computeMode = computeMode;
20
+ const guards_1 = require("./guards");
21
+ const list_1 = require("./list");
16
22
  const NUMERIC_CLEAN_REGEX = /[,\s_]/g;
17
23
  const VALID_DECIMAL_REGEX = /^[+-]?\d+(?:\.\d+)?$/;
18
- // ============================================================================
19
- // /** Generic Number Helpers */
20
- // ============================================================================
21
- function isValidNumber(v) {
22
- return typeof v === "number" && !Number.isNaN(v) && Number.isFinite(v);
24
+ function isValidNumber(v, options) {
25
+ if (typeof v !== "number")
26
+ return false;
27
+ if (options && options.allowNonFiniteNumbers)
28
+ return true;
29
+ return !Number.isNaN(v) && Number.isFinite(v);
23
30
  }
24
- function toValidNumber(v) {
31
+ function toValidNumber(v, options) {
25
32
  if (v == null)
26
33
  return null;
27
34
  if (typeof v === "symbol")
28
35
  return null;
29
- if (isValidNumber(v)) {
36
+ if (isValidNumber(v, options)) {
30
37
  return v;
31
38
  }
32
39
  if (typeof v === "boolean") {
@@ -34,51 +41,104 @@ function toValidNumber(v) {
34
41
  }
35
42
  if (typeof v === "bigint") {
36
43
  const n = Number(v);
37
- return isValidNumber(n) ? n : null;
44
+ return isValidNumber(n, options) ? n : null;
38
45
  }
39
46
  if (v instanceof Date) {
40
47
  const t = v.getTime();
41
- return isValidNumber(t) ? t : null;
48
+ return isValidNumber(t, options) ? t : null;
42
49
  }
43
50
  if (typeof v === "string") {
44
51
  const clean = v.trim().replace(NUMERIC_CLEAN_REGEX, "");
45
52
  if (clean === "")
46
53
  return null;
47
54
  const n = Number(clean);
48
- return isValidNumber(n) ? n : null;
55
+ if (Number.isNaN(n)) {
56
+ if (options?.allowNonFiniteNumbers && clean.toLowerCase() === "nan") {
57
+ return NaN;
58
+ }
59
+ return null;
60
+ }
61
+ return isValidNumber(n, options) ? n : null;
49
62
  }
50
63
  return null;
51
64
  }
52
- function clamp(opts) {
53
- const { val, min, max } = opts;
54
- if (val < min)
55
- return min;
56
- if (val > max)
57
- return max;
58
- return val;
65
+ /**
66
+ * Formats any numeric value (number, bigint, numeric string, Date, etc.) for output,
67
+ * handling NaN, Infinity, scientific notation, precision, and accounting formats.
68
+ */
69
+ function formatNumber({ locale = "en-US", accountingNegatives = false, fallback = "NaN", ...intlOpts } = {}) {
70
+ if (intlOpts.useGrouping === undefined) {
71
+ intlOpts.useGrouping = false;
72
+ }
73
+ if (intlOpts.maximumFractionDigits === undefined && intlOpts.maximumSignificantDigits === undefined) {
74
+ intlOpts.maximumFractionDigits = 20;
75
+ }
76
+ const formatter = new Intl.NumberFormat(locale, intlOpts);
77
+ return (value) => {
78
+ if (typeof value === "bigint") {
79
+ if (accountingNegatives && value < 0n) {
80
+ return `(${formatter.format(-value)})`;
81
+ }
82
+ return formatter.format(value);
83
+ }
84
+ const num = toValidNumber(value, { allowNonFiniteNumbers: true });
85
+ if (num === null || Number.isNaN(num)) {
86
+ return fallback;
87
+ }
88
+ if (!Number.isFinite(num)) {
89
+ if (accountingNegatives && num === -Infinity) {
90
+ return "(Infinity)";
91
+ }
92
+ return String(num);
93
+ }
94
+ if (accountingNegatives && num < 0) {
95
+ return `(${formatter.format(Math.abs(num))})`;
96
+ }
97
+ return formatter.format(num);
98
+ };
59
99
  }
60
- exports.FLOAT_32_BOUNDARY = 3.4028234663852886e+38;
61
- exports.FLOAT_RANGES = {
62
- Float32: { min: -exports.FLOAT_32_BOUNDARY, max: exports.FLOAT_32_BOUNDARY },
63
- Float64: { min: -Number.MAX_VALUE, max: Number.MAX_VALUE }
64
- };
65
- function isValidFloat(v, precision) {
66
- if (!isValidNumber(v))
100
+ function isValidFloat(v, { floatPrecision, allowNonFiniteNumbers = false, floatScientific = false } = {}) {
101
+ let num;
102
+ if (typeof v === "number") {
103
+ num = v;
104
+ }
105
+ else if (floatScientific && typeof v === "string") {
106
+ const parsed = parseFloat(v);
107
+ if (Number.isNaN(parsed))
108
+ return false;
109
+ num = parsed;
110
+ }
111
+ else {
112
+ return false;
113
+ }
114
+ if (floatPrecision === "Float32") {
115
+ num = Math.fround(num);
116
+ }
117
+ if (!allowNonFiniteNumbers && (Number.isNaN(num) || !Number.isFinite(num))) {
67
118
  return false;
68
- if (precision) {
69
- const limits = exports.FLOAT_RANGES[precision];
70
- return v >= limits.min && v <= limits.max;
71
119
  }
72
120
  return true;
73
121
  }
74
- function toValidFloat(v, precision = "Float64") {
75
- const num = toValidNumber(v);
122
+ function toValidFloat(v, { floatPrecision = "Float64", allowNonFiniteNumbers = true, floatScientific = false } = {}) {
123
+ let num = toValidNumber(v, { allowNonFiniteNumbers });
124
+ if (num === null && floatScientific && typeof v === "string") {
125
+ const parsed = parseFloat(v);
126
+ if (isValidNumber(parsed, { allowNonFiniteNumbers })) {
127
+ num = parsed;
128
+ }
129
+ }
76
130
  if (num === null)
77
131
  return null;
78
- return precision === "Float32" ? Math.fround(num) : num;
132
+ if (floatPrecision === "Float32") {
133
+ num = Math.fround(num);
134
+ }
135
+ if (!allowNonFiniteNumbers && !Number.isFinite(num)) {
136
+ return null;
137
+ }
138
+ return num;
79
139
  }
80
140
  // ============================================================================
81
- // /** Integer & BigInt Functions */
141
+ // /** Integer Functions */
82
142
  // ============================================================================
83
143
  exports.INT_RANGES = {
84
144
  Int8: { min: -128, max: 127 },
@@ -88,10 +148,6 @@ exports.INT_RANGES = {
88
148
  UInt16: { min: 0, max: 65535 },
89
149
  UInt32: { min: 0, max: 4294967295 }
90
150
  };
91
- exports.BIGINT_RANGES = {
92
- Int64: { min: -9223372036854775808n, max: 9223372036854775807n },
93
- UInt64: { min: 0n, max: 18446744073709551615n }
94
- };
95
151
  function isValidInt(v, range) {
96
152
  if (!isValidNumber(v))
97
153
  return false;
@@ -102,20 +158,11 @@ function isValidInt(v, range) {
102
158
  const limits = typeof range === "string" ? exports.INT_RANGES[range] : range;
103
159
  return v >= limits.min && v <= limits.max;
104
160
  }
105
- function isValidBigInt(v, range) {
106
- if (typeof v !== "bigint")
107
- return false;
108
- if (!range)
109
- return true;
110
- const limits = typeof range === "string" ? exports.BIGINT_RANGES[range] : range;
111
- return v >= limits.min && v <= limits.max;
112
- }
113
- function toValidInt(v, range = "Int32", opts = {}) {
161
+ function toValidInt(v, { range = "Int32", coerce = "truncate" } = {}) {
114
162
  const num = toValidNumber(v);
115
163
  if (num === null)
116
164
  return null;
117
165
  let n = num;
118
- const coerce = opts.coerce || "truncate";
119
166
  switch (coerce) {
120
167
  case "round":
121
168
  n = Math.round(n);
@@ -131,9 +178,24 @@ function toValidInt(v, range = "Int32", opts = {}) {
131
178
  break;
132
179
  }
133
180
  const limits = typeof range === "string" ? exports.INT_RANGES[range] : range;
134
- return clamp({ val: n, min: limits.min, max: limits.max });
181
+ return clamp(n, { min: limits.min, max: limits.max });
182
+ }
183
+ // ============================================================================
184
+ // /** BigInt Functions */
185
+ // ============================================================================
186
+ exports.BIGINT_RANGES = {
187
+ Int64: { min: -9223372036854775808n, max: 9223372036854775807n },
188
+ UInt64: { min: 0n, max: 18446744073709551615n }
189
+ };
190
+ function isValidBigInt(v, range) {
191
+ if (typeof v !== "bigint")
192
+ return false;
193
+ if (!range)
194
+ return true;
195
+ const limits = typeof range === "string" ? exports.BIGINT_RANGES[range] : range;
196
+ return v >= limits.min && v <= limits.max;
135
197
  }
136
- function toValidBigInt(v, range = "Int64") {
198
+ function toValidBigInt(v, { range = "Int64", truncate = false } = {}) {
137
199
  if (v == null)
138
200
  return null;
139
201
  if (typeof v === "symbol")
@@ -150,64 +212,104 @@ function toValidBigInt(v, range = "Int64") {
150
212
  if (clean === "")
151
213
  return null;
152
214
  if (!VALID_DECIMAL_REGEX.test(clean)) {
153
- const num = toValidNumber(v);
215
+ const num = toValidNumber(clean);
154
216
  if (num === null)
155
217
  return null;
218
+ if (!truncate && !Number.isInteger(num))
219
+ return null;
156
220
  bigintVal = BigInt(Math.trunc(num));
157
221
  }
158
222
  else {
159
- bigintVal = BigInt(clean.split(".")[0]);
223
+ if (!truncate && clean.includes("."))
224
+ return null;
225
+ const dotIdx = clean.indexOf(".");
226
+ bigintVal = BigInt(dotIdx !== -1 ? clean.slice(0, dotIdx) : clean);
160
227
  }
161
228
  }
162
229
  else {
163
230
  const num = toValidNumber(v);
164
231
  if (num === null)
165
232
  return null;
233
+ if (!truncate && !Number.isInteger(num))
234
+ return null;
166
235
  bigintVal = BigInt(Math.trunc(num));
167
236
  }
168
237
  const limits = typeof range === "string" ? exports.BIGINT_RANGES[range] : range;
169
- return clamp({ val: bigintVal, min: limits.min, max: limits.max });
238
+ return clamp(bigintVal, { min: limits.min, max: limits.max });
239
+ }
240
+ function getDecimalMaxVal(precision, scale) {
241
+ const integerDigits = precision - scale;
242
+ const maxVal = Math.pow(10, integerDigits) - Math.pow(10, -scale);
243
+ return maxVal > 0 ? maxVal : null;
170
244
  }
171
- function isValidDecimal(v, opts = {}) {
245
+ function roundToScale(v, scale) {
246
+ const str = v.toString();
247
+ if (str.includes("e")) {
248
+ const factor = Math.pow(10, scale);
249
+ return Math.round(v * factor) / factor;
250
+ }
251
+ return Number(Math.round(Number(str + "e" + scale)) + "e-" + scale);
252
+ }
253
+ function isValidDecimal(v, { precision, scale = 0 } = {}) {
172
254
  if (!isValidNumber(v))
173
255
  return false;
174
- const { precision, scale = 0 } = opts;
175
256
  if (precision !== undefined) {
176
- const integerDigits = precision - scale;
177
- const maxVal = Math.pow(10, integerDigits) - Math.pow(10, -scale);
178
- if (integerDigits > 0 && Math.abs(v) > maxVal) {
257
+ const maxVal = getDecimalMaxVal(precision, scale);
258
+ if (maxVal !== null && Math.abs(v) > maxVal) {
179
259
  return false;
180
260
  }
181
261
  }
182
262
  if (scale !== undefined) {
183
- const str = v.toString();
184
- const dotIdx = str.indexOf(".");
185
- if (dotIdx !== -1 && str.slice(dotIdx + 1).length > scale) {
263
+ const rounded = roundToScale(v, scale);
264
+ if (Math.abs(v - rounded) > 1e-12) {
186
265
  return false;
187
266
  }
188
267
  }
189
268
  return true;
190
269
  }
191
- function toValidDecimal(v, opts = {}) {
270
+ function toValidDecimal(v, { precision, scale } = {}) {
192
271
  const num = toValidNumber(v);
193
272
  if (num === null)
194
273
  return null;
195
- const { precision, scale } = opts;
196
274
  let n = num;
197
275
  if (scale !== undefined) {
198
- const multiplier = Math.pow(10, scale);
199
- n = Math.round(n * multiplier) / multiplier;
276
+ n = roundToScale(n, scale);
200
277
  }
201
278
  if (precision !== undefined) {
202
- const scaleVal = scale || 0;
203
- const integerDigits = precision - scaleVal;
204
- if (integerDigits > 0) {
205
- const maxVal = Math.pow(10, integerDigits) - Math.pow(10, -scaleVal);
206
- n = clamp({ val: n, min: -maxVal, max: maxVal });
279
+ const scaleVal = scale ?? 0;
280
+ const maxVal = getDecimalMaxVal(precision, scaleVal);
281
+ if (maxVal !== null) {
282
+ n = clamp(n, { min: -maxVal, max: maxVal });
207
283
  }
208
284
  }
209
285
  return n;
210
286
  }
287
+ // ============================================================================
288
+ // /** Math & Stats Functions */
289
+ // ============================================================================
290
+ function clamp(val, { min = null, max = null, safe = true } = {}) {
291
+ if (min !== null && max !== null && min > max) {
292
+ return min;
293
+ }
294
+ let v = val;
295
+ if (safe && typeof v === "number") {
296
+ if (Number.isNaN(v)) {
297
+ // NaN can't be compared — coerce to the nearest boundary, preferring min
298
+ v = min !== null ? min : (max !== null ? max : val);
299
+ }
300
+ else if (v === Infinity) {
301
+ v = max !== null ? max : val;
302
+ }
303
+ else if (v === -Infinity) {
304
+ v = min !== null ? min : val;
305
+ }
306
+ }
307
+ if (min !== null && v < min)
308
+ return min;
309
+ if (max !== null && v > max)
310
+ return max;
311
+ return v;
312
+ }
211
313
  /**
212
314
  * Creates a seedable pseudo-random number generator using the Mulberry32 PRNG algorithm.
213
315
  * Returns a function that generates a pseudo-random float in the range [0, 1).
@@ -221,3 +323,79 @@ function mulberry32(seed) {
221
323
  return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
222
324
  };
223
325
  }
326
+ function getSortedValidNumbers(values) {
327
+ const len = values.length;
328
+ let validCount = 0;
329
+ const nums = new Float64Array(len);
330
+ for (let i = 0; i < len; i++) {
331
+ const val = values[i];
332
+ // Only include actual numbers that aren't NaN (but preserve Infinity for bounds)
333
+ if (typeof val === "number" && !Number.isNaN(val)) {
334
+ nums[validCount++] = val;
335
+ }
336
+ }
337
+ if (validCount === 0)
338
+ return null;
339
+ const validNums = nums.subarray(0, validCount);
340
+ validNums.sort();
341
+ return validNums;
342
+ }
343
+ /**
344
+ * Computes the median of a numeric array, filtering out non-numeric and NaN values.
345
+ * Returns null if no valid numbers remain.
346
+ */
347
+ function computeMedian(values) {
348
+ const validNums = getSortedValidNumbers(values);
349
+ if (!validNums)
350
+ return null;
351
+ const len = validNums.length;
352
+ const mid = Math.floor(len / 2);
353
+ return len % 2 !== 0 ? validNums[mid] : (validNums[mid - 1] + validNums[mid]) / 2;
354
+ }
355
+ /**
356
+ * Computes the quantile of a numeric array using linear interpolation, filtering out non-numeric and NaN values.
357
+ * q must be in [0, 1]. Returns null if no valid numbers remain or q is out of bounds.
358
+ */
359
+ function computeQuantile(values, q) {
360
+ if (q < 0 || q > 1)
361
+ return null;
362
+ const validNums = getSortedValidNumbers(values);
363
+ if (!validNums)
364
+ return null;
365
+ const len = validNums.length;
366
+ const idx = q * (len - 1);
367
+ const low = Math.floor(idx);
368
+ const high = Math.ceil(idx);
369
+ if (low === high)
370
+ return validNums[low];
371
+ return validNums[low] + (idx - low) * (validNums[high] - validNums[low]);
372
+ }
373
+ /**
374
+ * Computes the mode(s) of an array, filtering out null/undefined values.
375
+ * Returns an array of the most frequent values, sorted, or null if empty/no mode.
376
+ */
377
+ function computeMode(values) {
378
+ if (!(0, guards_1.isArrayOrTypedArray)(values) || values.length === 0)
379
+ return null;
380
+ const counts = new Map();
381
+ const len = values.length;
382
+ let max = 0;
383
+ let modes = [];
384
+ for (let i = 0; i < len; i++) {
385
+ const val = values[i];
386
+ if (val == null)
387
+ continue;
388
+ const c = (counts.get(val) ?? 0) + 1;
389
+ counts.set(val, c);
390
+ if (c > max) {
391
+ max = c;
392
+ modes = [val];
393
+ }
394
+ else if (c === max) {
395
+ modes.push(val);
396
+ }
397
+ }
398
+ if (modes.length === 0)
399
+ return null;
400
+ return (0, list_1.sortList)(modes);
401
+ }
@@ -50,3 +50,8 @@ export type StripCharsOptions = {
50
50
  * Strips characters from the start, end, or both ends of a string.
51
51
  */
52
52
  export declare function stripChars(str: string | null | undefined, characters?: string | RegExp | null, { mode, returnStringOnNull, maxScanStart, maxScanEnd, maxMatchesStart, maxMatchesEnd, trimFirst, stringOptions: { literal, caseInsensitive } }?: StripCharsOptions): string | null;
53
+ export declare function isBlankString(v: unknown): v is string;
54
+ export declare function toCanonicalString(val: any, { depth, maxDepth }?: {
55
+ depth?: number;
56
+ maxDepth?: number;
57
+ }): string;
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.stripChars = stripChars;
4
+ exports.isBlankString = isBlankString;
5
+ exports.toCanonicalString = toCanonicalString;
6
+ const guards_1 = require("./guards");
7
+ const date_1 = require("./date");
4
8
  /**
5
9
  * Strips characters from the start, end, or both ends of a string.
6
10
  */
@@ -118,3 +122,88 @@ function stripChars(str, characters = null, { mode = "both", returnStringOnNull
118
122
  }
119
123
  return (returnStringOnNull || result !== "") ? result : null;
120
124
  }
125
+ function isBlankString(v) {
126
+ if (typeof v === "string") {
127
+ return v.trim().length === 0;
128
+ }
129
+ if (v instanceof String) {
130
+ return v.valueOf().trim().length === 0;
131
+ }
132
+ return false;
133
+ }
134
+ function toCanonicalString(val, { depth = 0, maxDepth = 50 } = {}) {
135
+ if (depth > maxDepth) {
136
+ return "v:circular";
137
+ }
138
+ if (val === null) {
139
+ return "v:null";
140
+ }
141
+ if (val === undefined) {
142
+ return "v:undefined";
143
+ }
144
+ if (val instanceof Date) {
145
+ return (0, date_1.isValidDateObj)(val) ? `d:${val.getTime()}` : "d:invalid";
146
+ }
147
+ if ((0, guards_1.isTypedArray)(val)) {
148
+ return `u:${val.constructor.name}:${val.toString()}`;
149
+ }
150
+ if (Array.isArray(val)) {
151
+ const len = val.length;
152
+ const parts = new Array(len);
153
+ const nextOpt = { depth: depth + 1, maxDepth };
154
+ for (let i = 0; i < len; i++) {
155
+ parts[i] = toCanonicalString(val[i], nextOpt);
156
+ }
157
+ return `a:[${parts.join(",")}]`;
158
+ }
159
+ if (val instanceof Set) {
160
+ const arr = Array.from(val);
161
+ const len = arr.length;
162
+ const parts = new Array(len);
163
+ const nextOpt = { depth: depth + 1, maxDepth };
164
+ for (let i = 0; i < len; i++) {
165
+ parts[i] = toCanonicalString(arr[i], nextOpt);
166
+ }
167
+ parts.sort();
168
+ return `set:[${parts.join(",")}]`;
169
+ }
170
+ if (val instanceof Map) {
171
+ const keys = Array.from(val.keys());
172
+ const len = keys.length;
173
+ const parts = new Array(len);
174
+ const nextOpt = { depth: depth + 1, maxDepth };
175
+ for (let i = 0; i < len; i++) {
176
+ const k = keys[i];
177
+ parts[i] = `${toCanonicalString(k, nextOpt)}:${toCanonicalString(val.get(k), nextOpt)}`;
178
+ }
179
+ parts.sort();
180
+ return `map:{${parts.join(",")}}`;
181
+ }
182
+ if (typeof val === "object" && typeof val.toJSON === "function") {
183
+ return `j:${toCanonicalString(val.toJSON(), { depth: depth + 1, maxDepth })}`;
184
+ }
185
+ if ((0, guards_1.isPlainObj)(val)) {
186
+ const keys = Object.keys(val).sort();
187
+ const len = keys.length;
188
+ const parts = new Array(len);
189
+ const nextOpt = { depth: depth + 1, maxDepth };
190
+ for (let i = 0; i < len; i++) {
191
+ const k = keys[i];
192
+ parts[i] = `${k}:${toCanonicalString(val[k], nextOpt)}`;
193
+ }
194
+ return `o:{${parts.join(",")}}`;
195
+ }
196
+ if (val instanceof RegExp) {
197
+ return `r:${val.toString()}`;
198
+ }
199
+ if (typeof val === "function") {
200
+ return `f:${val.toString()}`;
201
+ }
202
+ if (typeof val === "string") {
203
+ return `s:${val}`;
204
+ }
205
+ if (typeof val === "symbol") {
206
+ return `y:${val.toString()}`;
207
+ }
208
+ return `${typeof val}:${val}`;
209
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "df-script",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "A zero-dependency, high-performance, expression-based DataFrame library for TypeScript/JavaScript.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,6 +26,10 @@
26
26
  ],
27
27
  "author": "Trent Morris",
28
28
  "license": "MIT",
29
+ "funding": {
30
+ "type": "custom",
31
+ "url": "https://github.com/trentamorris/df-script/blob/main/DONATIONS.md"
32
+ },
29
33
  "devDependencies": {
30
34
  "typescript": "^5.4.5",
31
35
  "tsx": "^4.15.5",