Skip to content

Changelog

Each package versions on its own, so each keeps a changelog of its own. Pick a language in the sidebar to read it.

1.19.0 (2026-09-12)

Breaking changes

  • sortNumeric, sortByObjectKey: The order no longer depends on the machine or on the package. A string is cut into runs of digits and runs of everything else, and the runs are compared in three passes over the whole string: the letters, then the accents on them, then upper against lower case. Whitespace sorts before punctuation, punctuation before numbers and numbers before letters, and a run of digits is compared by length before value, so a number too long for a number type still sorts correctly. The three packages now return the same order for the same input, which they never did. Intl.Collator used to decide it here, with an empty locale list, so the same array came back in a different order on a machine set to Swedish than on one set to English, and neither Dart nor Python matched either. Ordinary lists of file names and labels are unaffected, the ordering being identical to what Intl.Collator produced for them; a string starting with punctuation is where the two part company. Sort with Intl.Collator yourself if you need the collation of one named locale

Changes

  • durationParts: Added. Breaks a duration into its units and hands them back as { value, unit } rather than a string, so a duration can be written in a language this package does not know. duration labels the units in English and builds the plural by adding an s, which is a rule only English follows: Polish has three plural forms and Arabic six. Intl.DurationFormat turns the pieces into 14 Tage, 6 Stunden, 56 Minuten und 7 Sekunden. It takes the options of duration that decide which units are used, and duration is now built on it, so the two cannot disagree
  • fileSizeParts: Added. Splits a file size in bytes into the scaled number and the unit it belongs to, and hands both back rather than a string, so a size can be written in a language this package does not know. Intl.NumberFormat turns { value: 1.177, exponent: 2 } into 1,18 MB in German and 1,18 Mo in French, which no arrangement of the old return value could produce. The value is deliberately left unrounded, so the caller's own formatter rounds it once instead of rounding an already rounded number
  • fileSizeFormat: Two options were added as a fourth argument. standard picks the divisor and the unit names: jedec (the default) divides by 1024 and writes KB as it always has, iec divides by 1024 and writes KiB, and si divides by 1000 and writes kB. unitDisplay writes the unit as an abbreviation (1.18 MB) or as a whole word (1.18 Megabytes), taking the singular when the rounded number is one. Leaving both out returns exactly the string it returned before, which the tests now pin
  • fileSizeFormat: A size past the largest unit no longer runs off the end of the unit table. fileSizeFormat(1024 ** 9) returned 1 undefined, where the Dart and Python packages threw on the same input; the exponent is now held at the last unit, so it reads 1024 YB

1.18.0 (2026-08-29)

  • The package is now declared side-effect free, and the lookup tables that deburr, hasBadWords, sortNumeric, sortByObjectKey and numberFormat used to build at import time are built on the first call that needs them instead. A bundler has to keep any module that runs code when it is loaded, so import { arrUnique } from 'qsu' dragged two Intl.Collator instances, an Intl.NumberFormat and the deburr and hasBadWords tables along with it. Bundling a single function with esbuild now comes to 251 bytes rather than 2665, and importing the package in Node costs 22.7ms rather than 33.2ms. This changes no behavior and no API
  • Every category now has a subpath export of its own: qsu/array, qsu/date, qsu/format, qsu/math, qsu/misc, qsu/object, qsu/string, qsu/verify and qsu/web, plus qsu/node/crypto, qsu/node/file, qsu/node/misc, qsu/node/net and qsu/node/os under the Node.js runtime. Only ., ./types and ./node were reachable before, so anything not running a bundler had to load the whole root barrel (171 modules, 33.2ms in Node) to reach one function, where qsu/array costs 7.4ms. qsu/package.json is exported as well, for tools that read it
  • qsu/types now resolves. The subpath pointed at ./dist/types/global.* while the build emits ./dist/_types/global.*, so both import 'qsu/types' and the type-only import type { SlugOptions } from 'qsu/types' failed
  • truncate, truncateExpect: The length is now counted in code points, as pad already did, so a character outside the Basic Multilingual Plane counts as one in every language. A JavaScript string is indexed in UTF-16 units, so truncate('a👋b', 2) used to cut between the two halves of the emoji and hand back a broken character, and truncateExpect stopped at a different sentence than Python did on the same text
  • truncateExpect: endStringChar now takes an array as well as a single string, and defaults to the full stop as each script writes it (., , , ). Japanese and Chinese text used to come back untouched, because a text with no ASCII . in it split into one piece and the expected length was never reached. ! and ? are left out of the default on purpose, so that the same sentence is not split differently depending on the script it is written in. A longer ending character is matched before a shorter one, so . next to ... no longer cuts ... short

1.17.0 (2026-08-07)

  • unescapeHtml: Added. Turns the five entities escapeHtml produces back into their characters. The string is walked once rather than replaced five times in a row, so < comes back as the literal text < instead of being unescaped twice, and only those five entities are recognised, so   and ' are left as they are
  • escapeHtml: Added. Escapes &, <, >, " and ' so a value can be dropped into a page as text rather than read as markup. ' is written as &#39; rather than &apos;, which HTML 4 never defined. It lives in the web category, next to getSlug, and leaves escapeRegExp as the pattern-oriented one
  • objClone: Added. Copies an object, deeply by default and top level only with deep: false. Plain objects, arrays, Map and Set are rebuilt with their contents copied, Date and RegExp get a fresh copy, and a function or class instance is handed back as it is. A structure that points back at itself is rebuilt with the same shape rather than recursing until the stack runs out
  • objMerge: Added. Merges any number of objects into one new object, going down through nested objects, with the later source winning. Two plain objects under the same key are merged into a new object, so neither source is shared with the result or modified. Arrays are replaced whole rather than merged index by index as Lodash does, and nullNone is returned when an argument is not an object
  • objGet: Added. Reads a nested value out of an object by dot and/or bracket path (a.b.c, list[0], list[1].d), returning the fallback when the path is not there. A bracket may carry a quoted key, so ["a.b"] reads one key rather than walking two levels, and a stored nullNone counts as a value rather than a missing path
  • objPick: Added. Returns a new object containing only the listed keys, accepting a single key or an array of keys. Only the top level is inspected, and a key the object does not have is skipped rather than carried over as undefined
  • pad: Added. Pads a string until it reaches the given length, with one position option (start, end or both) covering what Lodash splits across pad, padStart and padEnd. both is the default and gives the extra character to the end, a multi-character char is repeated and truncated, and the length is counted in code points so an emoji counts as one in every language
  • strToConstantCase: Added. Converts a string to CONSTANT_CASE, uppercasing every word and joining them with an underscore. It splits with words, so XMLHttpRequest becomes XML_HTTP_REQUEST. JavaScript and Python apply the full Unicode case mapping where Dart applies the simple one, so straße becomes STRASSE here and STRAßE in Dart, which the documentation states rather than papering over
  • strToPascalCase: Added. Converts a string to PascalCase, giving every word an uppercase first letter and a lowercase rest. It splits with words, so XMLHttpRequest becomes XmlHttpRequest. capitalizeEachWords stays the one that keeps the original separators
  • strToKebabCase: Added. Converts a string to kebab-case, lowercasing every word and joining them with a hyphen. It splits with words, so XMLHttpRequest becomes xml-http-request. getSlug stays the URL-oriented one
  • strToSnakeCase: Added. Converts a string to snake_case, lowercasing every word and joining them with an underscore. It splits with words, so XMLHttpRequest becomes xml_http_request and abc12def becomes abc_12_def
  • strToCamelCase: Added. Converts a string to camelCase, lowercasing the first word and giving every word after it an uppercase first letter. It splits with words, so an acronym stays whole (XMLHttpRequest becomes xmlHttpRequest) and a run of digits is its own word (abc12def becomes abc12Def)
  • min: Added. Returns the smallest of the given numbers, accepting either n arguments or a single array exactly like sum. Values that are not numbers are skipped, and so is NaN, which would otherwise win by losing every comparison. An empty input returns nullNone
  • max: Added. Returns the largest of the given numbers, accepting either n arguments or a single array exactly like sum. Values that are not numbers are skipped, and so is NaN, which would otherwise win by losing every comparison. An empty input returns nullNone
  • floor: Added. Rounds a number down, to the given number of decimal places, a negative precision rounding down to tens, hundreds and so on. Rounding goes toward negative infinity, so floor(-4.006) is -5. The value is shifted through its shortest string representation, so floor(1.1, 1) is 1.1
  • ceil: Added. Rounds a number up, to the given number of decimal places, a negative precision rounding up to tens, hundreds and so on. Rounding goes toward positive infinity, so ceil(-4.006) is -4. The value is shifted through its shortest string representation, so ceil(1.1, 1) is 1.1 and not 1.2
  • round: Added. Rounds a number to the given number of decimal places, a negative precision rounding to tens, hundreds and so on. Ties go away from zero in every language, where the three disagree natively (0.5 is 1/1/0 and -1.5 is -1/-2/-2 in JavaScript/Dart/Python) and where Lodash sends them toward positive infinity. The value is shifted through its shortest string representation rather than multiplied by a power of ten, so round(1.005, 2) is 1.01 and not 1
  • clamp: Added. Restricts a number to an inclusive range, returning min below it and max above it. The upper bound is applied first, so min wins when the two are passed the wrong way round, matching Lodash rather than Dart's num.clamp, which throws

1.16.0 (2026-08-04)

  • retry: Added. Runs the given function again on failure until it succeeds or the attempts run out, raising the last error if they all fail. times counts total attempts (default 3), delay waits between them and backoff multiplies that wait after each failure
  • throttle: Added. Limits how often a function may run to at most once per wait window, the counterpart of debounce. leading and trailing (both trueTrue by default) choose which edge of the window runs
  • objInvert: Added. Returns a new object with the keys and values swapped. Values are converted to text because keys are always strings, and the later entry wins when two share a value
  • objMapKeys: Added. Returns a new object whose keys are the values returned by the callback, with the values carried over untouched. The callback receives (value, key), and the later key wins when two map onto the same name
  • objPickBy: Added. Returns a new object containing only the entries for which the callback returns trueTrue. The callback receives (value, key), and only the top level is inspected
  • uncapitalizeFirst: Added. Converts the first letter of the entire string to lowercase, the inverse of capitalizeFirst. Only the first character is touched, so TEST becomes tEST
  • escapeRegExp: Added. Escapes every regular expression metacharacter (^ $ . * + ? ( ) [ ] { } | and \) so a value can be matched literally. - and # are left alone: they are special only inside a character class, and \- outside one is a syntax error in unicode mode
  • deburr: Added. Replaces accented Latin letters with their unaccented equivalents (déjà vu becomes deja vu), spelling out Æ, ß, Þ, Œ and IJ, and dropping combining marks. Covers the Latin-1 Supplement and Latin Extended-A blocks
  • words: Added. Splits a string into the words it is made of. Anything that is neither a letter nor a digit separates words, and camelCase boundaries, runs of capitals (XMLHttpRequest is XML, Http, Request) and runs of digits are split as well
  • arrIntersection: Added. Returns the values that are present in every one of the given arrays. The result is unique and keeps the order of the first array
  • arrDifference: Added. Returns the values of the first array that are not contained in any of the other arrays. Values are compared by value rather than by reference, so nested arrays and objects are matched as well
  • arrCompact: Added. Returns a new array with every falsy value removed (nullNone, undefined, falseFalse, 0, '', NaN). Empty arrays and objects are truthy and are kept

1.15.0 (2026-08-02)

  • BREAKING CHANGES: isValidFileName now rejects an empty name and any name carrying a control character (U+0000-U+001F or U+007F). NUL is the one that matters: it terminates the path in the system call underneath every filesystem, so a name carrying one was reported as valid and then silently truncated on the way to disk
  • BREAKING CHANGES: isValidFileName now rejects a name ending in a dot or a space on the Windows path. Windows strips it instead of reporting an error, so report. quietly becomes report and overwrites it. Unix keeps them, so they stay valid with unixType
  • BREAKING CHANGES: isValidFileName now measures its 255 limit in UTF-8 bytes rather than characters, which is what ext4, APFS and NTFS enforce. '가'.repeat(100) is 100 characters but 300 bytes and cannot be created. Counting characters also disagreed with the Python implementation, which counted code points where JavaScript counted UTF-16 units ('😀'.repeat(130) was invalid here and valid there)
  • BREAKING CHANGES: createDirectory now reports the error when a file already sits at the path. It asked only whether something was there and then answered that there was nothing to do, so no directory existed and nothing said so. The stat behind that check is gone as well, because mkdir with recursive is already a no-op for an existing directory
  • BREAKING CHANGES: createFile now creates any parent directory the path needs instead of failing with ENOENT, matching the Dart implementation
  • BREAKING CHANGES: createFile, deleteFile and moveFile now treat a path of nothing but whitespace as no path at all, matching the Dart implementation. createFile(' ') used to create a file literally named
  • BREAKING CHANGES: getFileInfo and getFileSize now throw the original filesystem error instead of a new Error carrying only its message. code, errno and path were dropped with it, so a caller could not tell ENOENT from EACCES, and the stack pointed at qsu rather than at the call. The unreachable fallback object both functions ended with has been removed
  • BREAKING CHANGES: toValidFilePath now resolves a leading .. against the root, so '../../etc/passwd' returns /etc/passwd instead of /../../etc/passwd
  • isFileHidden: Run attrib directly instead of handing a command line to a shell. A quote in a file name closed the quoting of attrib "<path>" and the rest of the name ran as a command, so scanning a directory could execute whatever a file in it was named. Running the program directly also halves the processes each call starts
  • isFileHidden: Read the attribute letters out of the column attrib prints them in. Removing the caller's path from the output failed whenever a relative path was given, because attrib answers with an absolute one, and any H in a directory name then read as hidden
  • tailFile: Read backwards from the end of the file a chunk at a time instead of walking it from the start, and stop shifting a length-sized array once per line. The old shape cost lines × length: on a 108 MB log the last 20,000 lines took 57 seconds and now take 0.02, and the last single line went from 0.29 seconds to 0.001
  • getCopyFileName: Accept a Set as well as an array, and read it as it is. Naming n files into one directory calls this n times, and rebuilding the set on every call made that loop quadratic — 16,000 names took 19 seconds through an array and 0.01 seconds through a reused Set
  • moveFile: Fall back to a copy and a remove when the operating system reports EXDEV. rename cannot cross a filesystem boundary, so moving out of the temporary directory, into a mounted volume or onto another drive failed outright
  • deleteAllFileFromDirectory: Delete up to 32 entries at a time instead of awaiting each one in turn
  • getFileHashFromPath: Read through pipeline with a 1 MB buffer instead of collecting data events at the 64 KB stream default
  • hasBadWords: Catch a banned word broken up by digits (ad1min, 사1과, 사123과), a common way of hiding a word in Korean. A digit that opens or closes a word is still read as a letter, so a number in front of a word (2시 발표) is not read away
  • Import every Node.js built-in through the node: prefix (node:crypto, node:fs, node:fs/promises, node:path, node:os, node:child_process, node:stream/promises). A bare specifier like crypto, path or os also names a real package on npm, so a bundler could resolve it to that package instead of the built-in, and webpack 4 and other older bundlers silently substituted a browser polyfill for it. The prefix marks these as built-ins unambiguously, so a bundler targeting the browser reports them rather than replacing them. The hash functions were also importing from crypto and node:crypto in the same file. This changes no behavior and no API: the browser-safe qsu root entry point never imported a built-in, and everything under qsu/node is imported from that subpath as before

1.14.0 (2026-07-28)

  • BREAKING CHANGES: logBox now requires a Node.js runtime and is imported from the qsu/node subpath. It uses node:util and process, so exporting it from the browser-safe root entry point could break bundlers
  • BREAKING CHANGES: objDeleteKeyByValue, objUpdate, objMergeNewKey, arrShuffle, arrMove, sortNumeric and sortByObjectKey no longer modify the argument they are given. They all return a new object or array, matching the Dart implementations. Object.assign(obj, {}) returns obj itself and Array.prototype.sort reorders in place, so the caller's data used to change underneath it
  • BREAKING CHANGES: numUnique now returns a millisecond timestamp combined with a per-millisecond sequence (16 digits) instead of a timestamp combined with a random number (18 digits). The old value exceeded Number.MAX_SAFE_INTEGER, so digits were rounded away and different draws collapsed onto the same number — 100 calls within one millisecond produced only 98 distinct values. Repeated calls in a process are now always unique and strictly increasing
  • BREAKING CHANGES: arrShuffle now returns an array when given a single element, instead of returning that element itself
  • BREAKING CHANGES: sortNumeric and sortByObjectKey now apply descending through the comparator instead of reversing the sorted result, so equal elements keep their relative order
  • BREAKING CHANGES: generateLicense now normalizes the type option correctly, so 'Apache 2.0', 'apache-2.0' and 'BSD 3' return the license they name instead of silently falling back to MIT (a missing character class in the normalizing regular expression made it a no-op)
  • BREAKING CHANGES: isEqual and isEqualStrict now compare objects instead of mistaking them for an argument list. Previously any two objects compared as equal (isEqual({a: 1}, {a: 2}) returned trueTrue). Passing the operands as an array still works
  • BREAKING CHANGES: safeParseInt now returns fallback when parsing fails (parseInt reports failure with NaN rather than throwing, so safeParseInt('abc', 99) returned NaN), and treats 0 as a valid input instead of a missing one
  • BREAKING CHANGES: numberFormat now groups the integer part as a string, so values beyond Number.MAX_SAFE_INTEGER keep every digit ('123456789012345678901' no longer becomes '123,456,789,012,345,680,000')
  • BREAKING CHANGES: encrypt now stores the authentication tag for AEAD algorithms (GCM, CCM, OCB, ChaCha20-Poly1305) as iv:authTag:encrypted. Ciphertext produced by these algorithms was previously impossible to decrypt. The iv:encrypted format for CBC and other non-AEAD algorithms is unchanged
  • BREAKING CHANGES: arrTo1dArray no longer throws on nullNone or plain objects (typeof null === 'object' made it spread a non-iterable); they are now kept as-is, matching the Dart and Python implementations
  • decrypt: Support AEAD algorithms by reading the authentication tag, and throw a clear error when the input is not in the format encrypt returns
  • debounce: Pass the caller's arguments through to the debounced function (func.apply(args) passed them as thisArg, so the function always received none), and stop a pending timer from keeping a Node process alive
  • capitalizeEverySentence: Fix sentences containing characters outside the BMP (emoji) overwriting the wrong character, because a code point array was indexed with UTF-16 offsets
  • strRandom: Stop appending the string 'undefined' to the candidate characters when additionalCharacters is omitted, which made u, n, d, e, f and i two to three times more likely
  • replaceBetween: Escape both delimiters correctly. A retained lastIndex on a /g regular expression left endChar unescaped, so replaceBetween('a(b)c', '(', ')') threw a syntax error
  • arrUnique: Stop throwing on arrays containing undefined or functions, which have no JSON representation
  • arrRepeat, arrTo1dArray: Stop overflowing the call stack on large arrays by pushing in a loop instead of spreading
  • fetchData: Fix bodyType: 'form-data' requests by leaving Content-Type unset, so fetch can supply the boundary parameter that multipart bodies require
  • is2dArray: Return on the first nested array instead of walking the whole array and allocating a new one
  • objToArray, objTo1d, objUpdate, objDeleteKeyByValue: Build the key list once per object instead of on every iteration, which made these O(n^2) (an object with 4,000 keys took over a second in objUpdate)
  • numberFormat, sortNumeric, sortByObjectKey: Reuse a single Intl.NumberFormat / Intl.Collator instance instead of constructing one per call
  • numUnique: Stop building an 89,999 element array on every call to pick a single number
  • Remove lib/verify/isUnique.ts, an empty file that was never exported but still emitted isUnique.js and isUnique.d.ts into the published build

1.13.2 (2026-07-26)

  • hasBadWords: Improve hasBadWords method

1.13.1 (2026-07-26)

  • hasBadWords: Add hasBadWords method

1.13.0 (2026-07-26)

  • BREAKING CHANGES: duration now hides milliseconds by default (enable with withMilliSeconds) and uses grammatically correct plurals (e.g. 0 Hours, 1 Hour)
  • BREAKING CHANGES: getFilePathLevel no longer counts a trailing separator as an extra level (/home/user/ now returns the same level as /home/user)
  • BREAKING CHANGES: getCopyFileName now preserves the original file extension casing (e.g. Report.PDF copies to Report (1).PDF instead of Report (1).pdf)
  • BREAKING CHANGES: isValidFileName now validates the whole name including its extension (so hello.:txt is invalid) and rejects Windows device names (CON, NUL, COM1-COM9, LPT1-LPT9, etc.)
  • BREAKING CHANGES: createFileWithDummy now creates an empty file for a size of 0 instead of throwing, and throws a clearer error for a negative size
  • BREAKING CHANGES: getParentFilePath now returns the root (/ or \) for an empty or single-segment path instead of /.
  • BREAKING CHANGES: toValidFilePath now returns the root (/ or \) for a path that collapses to nothing instead of /.
  • isFileExists: Follow symlinks so a dangling link reports as missing on every platform (previously returned trueTrue for a dangling link on Windows), matching the Dart and Python implementations
  • duration: Support Month (30 days) and Year (365 days) units, and add withMilliSeconds, maxUnitCount, and unit (single-unit) options
  • logBox: Add logBox method
  • getParsedInfoFromAddress: Add getParsedInfoFromAddress method
  • getSlug: Add getSlug method

1.12.2 (2026-06-06)

  • arrPick: Add arrPick method

1.12.1 (2026-05-21)

  • capitalizeEachWords: If the natural option is not enabled, characters that are already uppercase will not be converted to lowercase.

1.12.0 (2026-04-14)

  • BREAKING CHANGES: strToNumberHash has renamed to numberHash
  • md5Hash, sha1Hash, sha256Hash: Add an encoding option for hash functions
  • fetchData: Minor improvements
  • getUptime: Add getUptime method
  • sha512Hash: Add sha512Hash method

1.11.6 (2026-04-12)

  • fetchData: Minor improvements

1.11.5 (2026-03-27)

  • getCopyFileName: Add getCopyFileName method

1.11.4 (2026-03-27)

  • net.fetchData: Add fetchData method

1.11.3 (2026-03-26)

  • numberFormat: Fix zero value

1.11.2 (2026-03-26)

  • BREAKING CHANGES: numRandom has renamed to numPick
  • numUnique: Add numUnique method
  • getCpu: Add getCpu method
  • getFileName: Fix incorrect directory name with include dot character
  • numberFormat: Fix where negative numbers were not handled properly, and now return an empty string instead of 0 when the value is null

1.11.1 (2026-01-18)

  • getGroupKeys: Add getGroupKeys method

1.11.0 (2026-01-09)

  • BREAKING CHANGES: The isWindows argument is no longer used in getFileExtension.
  • getFileExtension: Performance improvements and cleanups
  • getFileName: Performance improvements and cleanups
  • toValidFilePath: Performance improvements and cleanups
  • getParentFilePath: Performance improvements and cleanups
  • joinFilePath: Performance improvements and cleanups

1.10.4 (2025-11-25)

  • getFileSize: Add getFileSize method
  • getRamSize: Add getRamSize method
  • headFile, tailFile: Use better head/tail logic
  • fileSizeFormat: Add ceil argument to the fileSizeFormat method

1.10.3 (2025-11-04)

  • BREAKING CHANGES: getFileSize has renamed to fileSizeFormat

1.10.2 (2025-10-15)

  • getHostname: Add getHostname method
  • getStrBytes: Add getStrBytes method

1.10.1 (2025-06-08)

  • isEmail: Add onlyLowerCase parameter
  • getFileHash: This function has been renamed to getFileHashFromPath. Also, getFileHashFromStream has been added, which can take a ReadableStream and hash it.

1.10.0 (2025-03-12)

  • The machini packages have now been merged into the qsu package

1.9.3 (2025-03-08)

  • generateLicense: Add bsd3 license

1.9.2 (2025-03-06)

  • Update README.md

1.9.1 (2025-03-01)

  • Fix import issue

1.9.0 (2025-03-01)

  • BREAKING CHANGES: The utility functions related to file, crypto that use Node.js modules have been separated out and should use import * from 'qsu/node' instead of import * from 'qsu' to use them. These modules do not need to be installed separately.
  • Rename export name server to node

1.8.3 (2025-03-01) - DEPRECATED

  • Fix import issue

1.8.2 (2025-03-01) - DEPRECATED

  • Fix import issue

1.8.1 (2025-03-01) - DEPRECATED

  • Fix import issue

1.8.0 (2025-03-01) - DEPRECATED

  • BREAKING CHANGES: The utility functions related to file, crypto that use Node.js modules have been separated out and should use import * from 'qsu/server' instead of import * from 'qsu' to use them. These modules do not need to be installed separately.

1.7.2 (2025-03-01)

  • numberFormat: Fix decimal point format
  • tailFile, headFile: Correct line-break detection in Windows OS
  • Clarify node module descriptions

1.7.1 (2025-02-28)

  • Update documentations

1.7.0 (2025-02-27)

  • BREAKING CHANGES: The qsu-fs and qsu-web packages have now been merged into the qsu package, and all functions in the family package can now be used by installing only qsu. For more information, please refer to the documentation.
  • BREAKING CHANGES: fileExt, fileName, and fileSize have been moved to the file category and renamed to getFileExtension, getFileName, and getFileSize, respectively.
  • Separate files by function to strengthen tree shaking

1.6.5 (2025-02-23)

  • numberFormat: Need to handle decimal points
  • truncateExpect: Fix incorrect characters being added when all strings are displayed

1.6.4 (2024-12-20)

  • isTrueMinimumNumberOfTimes: Use any type (fix build)

1.6.3 (2024-12-20)

  • objMergeNewKey: Added options to customize behavior for arrays

1.6.2 (2024-12-08)

  • Fix import crypto module

1.6.1 (2024-12-07)

  • Fix import of type declaration files
  • Fix critical import issue

1.6.0 (2024-12-06)

NOTE: This version is broken. Please use 1.6.1 or later.

  • BREAKING CHANGES: The qsu package no longer uses classes, so if you want to import the entire module at once, you must use something like import * as _ from 'qsu'. (_ -> * as _)
  • BREAKING CHANGES: The objectTo1d method have been renamed to objTo1d
  • Separate files for each module purpose. Improved tree-shaking.

1.5.0 (2024-10-24)

  • BREAKING CHANGES: The md5, sha1, and sha256 methods have been renamed to md5Hash, sha1Hash, and sha256Hash.
  • objMergeNewKey: Add objMergeNewKey method

1.4.2 (2024-06-25)

  • isObject: use more accurate detect logic

1.4.1 (2024-05-05)

  • safeJSONParse: Add safeJSONParse method
  • safeParseInt: Add safeParseInt method

1.4.0 (2024-04-14)

  • BREAKING CHANGES: Removed the msToTime and secToTime methods, which are unstable and have been replaced with the duration method to provide a more stable utility.
  • duration: Add duration method

1.3.8 (2024-04-12)

  • objectTo1d: Add objectTo1d method
  • Strictly check object types on some methods

1.3.7 (2024-04-07)

  • trim: handle error when value is nullNone

1.3.6 (2024-04-07)

  • BREAKING CHANGES: The trim, Now there is no second argument, and the default behavior is to remove leading and trailing spaces, and change spaces in more than two letters to spaces in the sentence
  • BREAKING CHANGES: The getPlatform method has been deleted

1.3.5 (2024-03-31)

  • numberFormat: allow string type parameter
  • isTrueMinimumNumberOfTimes: Add isTrueMinimumNumberOfTimes method

1.3.4 (2024-03-19)

  • objDeleteKeyByValue: Add objDeleteKeyByValue method
  • objUpdate: Add objUpdate method
  • arrGroupByMaxCount: Add arrGroupByMaxCount method

1.3.3 (2024-03-05)

  • objFindItemRecursiveByKey: Add objFindItemRecursiveByKey method
  • urlJoin: Add urlJoin method
  • objToArray: Add objToArray method

1.3.2 (2023-12-28)

  • strToNumberHash: Add strToNumberHash method
  • objToQueryString: Add objToQueryString method
  • objToPrettyStr: Add objToPrettyStr method

1.3.1 (2023-11-08)

  • encrypt, decrypt: Add toBase64 params for result string encoding
  • createDateListFromRange: Use regex instead of string check
  • getPlatform: Android is not linux os (This method has now been removed in version 1.3.6)

1.3.0 (2023-09-27)

  • objectId: Add objectId method
  • sortByObjectKey: Add sortByObjectKey method
  • sortNumeric: Add sortNumeric method
  • Documentation improvements

1.2.3 (2023-09-15)

  • truncateExpect: do not add a closing character to the last character for sentences without a closing character

1.2.2 (2023-08-15)

  • replaceBetween: Add replaceBetween method

1.2.1 (2023-08-07)

  • capitalizeEverySentence: Add capitalizeEverySentence method
  • arrUnique: Use fast algorithm for 2d array unique
  • debounce: Add debounce method

1.2.0 (2023-06-29)

BREAKING CHANGES: The isBotAgent, license methods were separated from qsu to the qsu-web package. These methods are no longer available after version 1.2.0.

  • Explore the qsu-web package: %DEPRECATED%
  • Also, I've split the documentation page into the following sites: https://qsu.cdget.com

1.1.8 (2023-05-13)

  • strToAscii: Add strToAscii method
  • truncateExpect: Add truncateExpect method

1.1.7 (2023-03-17)

  • Node.js 12 version deprecation
  • removeSpecialChar: Using exceptionCharacters instead of withoutSpace

1.1.6 (2023-02-28)

  • isValidDate: Only the yyyy-mm-dd format can be verified
  • dateToYYYYMMDD: Add dateToYYYYMMDD method
  • createDateListFromRange: Add createDateListFromRange method
  • arrCount: Add arrCount method

1.1.5 (2023-02-07)

  • isEmail: Add isEmail method
  • sub: Add sub method
  • div: Add div method

1.1.4 (2022-12-22)

  • arrTo1dArray: Add arrTo1dArray method
  • isObject: Add isObject method
  • arrRepeat: Add arrRepeat method
  • isValidDate: Rename isRealDate to isValidDate

1.1.3 (2022-10-23)

  • funcTimes: Add funcTimes method
  • getPlatform: Add getPlatform method (This method has now been removed in version 1.3.6)
  • sum, mul, split: Fix type error
  • arrUnique, capitalizeEachWords, strBlindRandom: Fix correct use static method
  • Support named import
  • Change test script to TypeScript

1.1.2 (2022-10-20)

  • trim: Add new trim method
  • fileSize: When byte is null, returns 0 bytes
  • strCount: Use indexOf instead of regular expression to use better performance
  • strNumberOf: Rename method name to strCount
  • Add prettier and reformat all codes
  • Change require nodejs version to >= 12
  • Remove unused ts-node package
  • Upgrade package dependencies

1.1.1 (2022-10-08)

  • Upgrade package dependencies

1.1.0 (2022-09-03)

  • Reduced bundle size due to minify executable code
  • isBotAgent: Remove duplicate string

1.0.9 (2022-08-15)

  • str: Handling of null str values

1.0.8 (2022-08-15)

  • Add GitHub workflows
  • truncate: Return empty string when str is null
  • fileName: Resolves Windows's path regardless of system environment

1.0.7 (2022-07-24)

  • Add CHANGELOG.md to .npmignore

1.0.6 (2022-07-24)

  • isBotAgent: Add chrome-lighthouse in bot lists
  • split: Fix incorrect return type
  • isEqual: Add new isEqual method
  • isEqualStrict: Add new isEqualStrict method
  • Import only the methods needed in the path and crypto module

1.0.5 (2022-06-23)

  • contains: When the length of the str parameter value of a string type is 0, no error is thrown and false is returned

1.0.4 (2022-06-16)

BREAKING CHANGES: convertDate is no longer supported due to the removal of moment as a dependent module.

The today method has changed its usage. We no longer support custom date formats.

  • split: Add new split method
  • today: Remove dependent modules, change parameters to use pure code
  • convertDate: Remove method
  • encrypt, decrypt: Add basic validation check (more fix)

1.0.3 (2022-05-24)

  • encrypt, decrypt: Add basic validation check

1.0.2 (2022-05-23)

  • encrypt decrypt: Add basic validation check
  • strBlindRandom: Override the deprecated substr method

1.0.1 (2022-05-12)

  • Minimize bundle size and clean up code

1.0.0 (2022-05-09)

  • First version release

0.0.1 ~ 0.5.5 (2021-03-16 ~ 2022-04-09)

  • This is for the Alpha release and is not recommended for use

1.7.0 (2026-09-12)

Breaking changes

  • sortNumeric: The order no longer depends on the package. A string is cut into runs of digits and runs of everything else, and the runs are compared in three passes over the whole string: the letters, then the accents on them, then upper against lower case. Whitespace sorts before punctuation, punctuation before numbers and numbers before letters, and a run of digits is compared by length before value, so a number too long for a number type still sorts correctly. The three packages now return the same order for the same input, which they never did. compareNatural from package:collection used to decide it, which compared code points, so Apple, Banana and Zebra all came before apple, and File-3.txt came before file-1.txt

Changes

  • package:collection is no longer a dependency. compareNatural was the only thing this package used it for, and sortNumeric no longer calls it, so the package now rests on path, crypto and unorm_dart alone
  • durationParts: Added. Breaks a duration into its units and hands them back as a List<DurationPart> rather than a string, so a duration can be written in a language this package does not know. duration labels the units in English and builds the plural by adding an s, which is a rule only English follows: Polish has three plural forms and Arabic six. It takes the named parameters of duration that decide which units are used, and duration is now built on it, so the two cannot disagree
  • fileSizeParts: Added. Splits a file size in bytes into the scaled number and the unit it belongs to, returning a FileSizeParts rather than a string, so a size can be written in a language this package does not know. Hand value to a NumberFormat from intl and take the unit name from exponent; the value is deliberately left unrounded, so that formatter rounds it once instead of rounding an already rounded number
  • fileSizeFormat: Two named parameters were added. standard picks the divisor and the unit names: jedec (the default) divides by 1024 and writes KB as it always has, iec divides by 1024 and writes KiB, and si divides by 1000 and writes kB. unitDisplay writes the unit as an abbreviation (1.18 MB) or as a whole word (1.18 Megabytes), taking the singular when the rounded number is one. Leaving both out returns exactly the string it returned before, which the tests now pin
  • fileSizeFormat: A size past the largest unit no longer throws a RangeError by indexing past the end of the unit table. The exponent is held at the last unit instead, matching what the JavaScript and Python packages now do

1.6.0 (2026-08-29)

  • truncate, truncateExpect: The length is now counted in code points, as pad already did, so a character outside the Basic Multilingual Plane counts as one in every language. A Dart string is indexed in UTF-16 units, so truncate('a👋b', 2) used to cut between the two halves of the emoji and hand back a broken character, and truncateExpect stopped at a different sentence than Python did on the same text
  • truncateExpect: endStringChar now takes a List<String> as well as a single String, and defaults to the full stop as each script writes it (., , , ). Japanese and Chinese text used to come back untouched, because a text with no ASCII . in it split into one piece and the expected length was never reached. ! and ? are left out of the default on purpose, so that the same sentence is not split differently depending on the script it is written in. A longer ending character is matched before a shorter one, so . next to ... no longer cuts ... short

1.5.0 (2026-08-07)

  • unescapeHtml: Added. Turns the five entities escapeHtml produces back into their characters. The string is walked once rather than replaced five times in a row, so &amp;lt; comes back as the literal text &lt; instead of being unescaped twice, and only those five entities are recognised, so &nbsp; and &#x27; are left as they are

  • escapeHtml: Added. Escapes &, <, >, " and ' so a value can be dropped into a page as text rather than read as markup. ' is written as &#39; rather than &apos;, which HTML 4 never defined. It lives in the web category, next to getSlug, and leaves escapeRegExp as the pattern-oriented one

  • objClone: Added. Copies an object, deeply by default and top level only with deep: false. A Map, List and Set are rebuilt with their contents copied, while a DateTime (immutable) or a class instance is handed back as it is. A structure that points back at itself is rebuilt with the same shape rather than recursing until the stack runs out

  • objMerge: Added. Merges any number of objects into one new object, going down through nested maps, with the later source winning. Two maps under the same key are merged into a new map, so neither source is shared with the result or modified. Lists are replaced whole rather than merged index by index as Lodash does, and nullNone is returned when an entry is not a map

  • objGet: Added. Reads a nested value out of an object by dot and/or bracket path (a.b.c, list[0], list[1].d), returning the fallback when the path is not there. A bracket may carry a quoted key, so ["a.b"] reads one key rather than walking two levels, and a stored nullNone counts as a value rather than a missing path

  • objPick: Added. Returns a new object containing only the listed keys, accepting a single key or a list of keys. Only the top level is inspected, and a key the map does not have is skipped rather than carried over as nullNone

  • pad: Added. Pads a string until it reaches the given length, with one position named parameter (start, end or both) covering what Lodash splits across pad, padStart and padEnd. both is the default and gives the extra character to the end, a multi-character char is repeated and truncated, and the length is counted in code points so an emoji counts as one in every language

  • strToConstantCase: Added. Converts a string to CONSTANT_CASE, uppercasing every word and joining them with an underscore. It splits with words, so XMLHttpRequest becomes XML_HTTP_REQUEST. Dart applies the simple Unicode case mapping where JavaScript and Python apply the full one, so straße becomes STRAßE here and STRASSE there, which the documentation states rather than papering over

  • strToPascalCase: Added. Converts a string to PascalCase, giving every word an uppercase first letter and a lowercase rest. It splits with words, so XMLHttpRequest becomes XmlHttpRequest. capitalizeEachWords stays the one that keeps the original separators

  • strToKebabCase: Added. Converts a string to kebab-case, lowercasing every word and joining them with a hyphen. It splits with words, so XMLHttpRequest becomes xml-http-request. getSlug stays the URL-oriented one

  • strToSnakeCase: Added. Converts a string to snake_case, lowercasing every word and joining them with an underscore. It splits with words, so XMLHttpRequest becomes xml_http_request and abc12def becomes abc_12_def

  • strToCamelCase: Added. Converts a string to camelCase, lowercasing the first word and giving every word after it an uppercase first letter. It splits with words, so an acronym stays whole (XMLHttpRequest becomes xmlHttpRequest) and a run of digits is its own word (abc12def becomes abc12Def)

  • min: Added. Returns the smallest of the given numbers, taking a single array exactly like sum. NaN is skipped, because it loses every comparison and would otherwise win by being seen first, and an empty list returns nullNone. It shadows min from dart:math, so a file that needs both has to import one of them with a prefix

  • max: Added. Returns the largest of the given numbers, taking a single array exactly like sum. NaN is skipped, because it loses every comparison and would otherwise win by being seen first, and an empty list returns nullNone. It shadows max from dart:math, so a file that needs both has to import one of them with a prefix

  • floor: Added. Rounds a number down, to the given number of decimal places, a negative precision rounding down to tens, hundreds and so on. Rounding goes toward negative infinity, so floor(-4.006) is -5. The value is shifted through its shortest string representation, so floor(1.1, 1) is 1.1, and a whole result is handed back as an int

  • ceil: Added. Rounds a number up, to the given number of decimal places, a negative precision rounding up to tens, hundreds and so on. Rounding goes toward positive infinity, so ceil(-4.006) is -4. The value is shifted through its shortest string representation, so ceil(1.1, 1) is 1.1 and not 1.2, and a whole result is handed back as an int

  • round: Added. Rounds a number to the given number of decimal places, a negative precision rounding to tens, hundreds and so on. Ties go away from zero, which is what num.round already does but not what JavaScript's Math.round or Lodash do. The value is shifted through its shortest string representation rather than multiplied by a power of ten, so round(1.005, 2) is 1.01 and not 1, and a whole result is handed back as an int so round(1234, -2) is 1200 rather than 1200.0

  • clamp: Added. Restricts a number to an inclusive range, returning min below it and max above it. The upper bound is applied first, so min wins when the two are passed the wrong way round, where the built-in num.clamp throws on an inverted range instead

  • retry: Added. Runs the given function again on failure until it succeeds or the attempts run out, rethrowing the last error with its original stack trace if they all fail. times counts total attempts (default 3), delay waits between them and backoff multiplies that wait after each failure

  • throttle: Added. Limits how often a function may run to at most once per wait window, the counterpart of debounce. leading and trailing (both trueTrue by default) choose which edge of the window runs

  • objInvert: Added. Returns a new object with the keys and values swapped. Values are converted to text because keys are always strings, a whole double losing its fractional part so the result matches the JavaScript implementation, and the later entry wins when two share a value

  • objMapKeys: Added. Returns a new object whose keys are the values returned by the callback, with the values carried over untouched. The callback receives (value, key), and the later key wins when two map onto the same name

  • objPickBy: Added. Returns a new object containing only the entries for which the callback returns trueTrue. The callback receives (value, key), and only the top level is inspected

  • uncapitalizeFirst: Added. Converts the first letter of the entire string to lowercase, the inverse of capitalizeFirst. Only the first character is touched, so TEST becomes tEST

  • escapeRegExp: Added. Escapes every regular expression metacharacter (^ $ . * + ? ( ) [ ] { } | and \) so a value can be matched literally. - and # are left alone: they are special only inside a character class. The private helper behind removeSpecialChar and replaceBetween, which does also escape - and / because its result lands inside a character class, is now named _escapeRegExpInClass to keep the two apart

  • deburr: Added. Replaces accented Latin letters with their unaccented equivalents (déjà vu becomes deja vu), spelling out Æ, ß, Þ, Œ and IJ, and dropping combining marks. Covers the Latin-1 Supplement and Latin Extended-A blocks

  • words: Added. Splits a string into the words it is made of. Anything that is neither a letter nor a digit separates words, and camelCase boundaries, runs of capitals (XMLHttpRequest is XML, Http, Request) and runs of digits are split as well

  • arrIntersection: Added. Returns the values that are present in every one of the given arrays. The result is unique and keeps the order of the first array

  • arrDifference: Added. Returns the values of the first array that are not contained in any of the other arrays. Values are compared by value rather than by identity, so nested lists and maps are matched as well

  • arrCompact: Added. Returns a new array with every falsy value removed (nullNone, falseFalse, 0, '', NaN). An empty list and an empty map are kept, matching the JavaScript implementation

1.4.0 (2026-08-)

  • BREAKING CHANGES: isValidFileName now rejects an empty name and any name carrying a control character (U+0000-U+001F or U+007F). NUL is the one that matters: it terminates the path in the system call underneath every filesystem, so a name carrying one was reported as valid and then silently truncated on the way to disk
  • BREAKING CHANGES: isValidFileName now rejects a name ending in a dot or a space on the Windows path. Windows strips it instead of reporting an error, so report. quietly becomes report and overwrites it. Unix keeps them, so they stay valid with unixType
  • BREAKING CHANGES: isValidFileName now measures its 255 limit in UTF-8 bytes rather than characters, which is what ext4, APFS and NTFS enforce. '가' * 100 is 100 characters but 300 bytes and cannot be created
  • BREAKING CHANGES: headFile and tailFile now replace malformed UTF-8 with U+FFFD instead of throwing a FormatException, matching the JavaScript and Python implementations. One bad byte in a log file no longer stops it from being read
  • BREAKING CHANGES: headFile and tailFile now keep a leading byte order mark. Dart's UTF-8 decoder drops it, which silently changed text that JavaScript and Python both return whole
  • BREAKING CHANGES: moveFile now moves a directory as well as a file, with everything inside it. File(path).rename reports an error on a directory, so the entity is opened as what it actually is
  • BREAKING CHANGES: getFileInfo and getFileSize now throw the FileSystemException as it is instead of wrapping it in Exception(err.toString()), which dropped osError and path and left a caller unable to tell a missing file from a permission error. headFile and tailFile no longer wrap theirs either
  • BREAKING CHANGES: toValidFilePath now resolves a leading .. against the root, so '../../etc/passwd' returns /etc/passwd instead of /../../etc/passwd
  • BREAKING CHANGES: getCopyFileName now takes an Iterable<String> instead of a List<String>, and reads a Set as it is. Naming n files into one directory calls this n times, and rebuilding the set on every call made that loop quadratic — 16,000 names took 21 seconds through a List and 0.01 seconds through a reused Set
  • tailFile: Read backwards from the end of the file a chunk at a time instead of streaming it from the start. On a 108 MB log the last line took 0.73 seconds and now takes 0.002
  • headFile: Read forwards a chunk at a time and stop as soon as enough lines are in hand, rather than running the whole file through a stream transformer
  • moveFile: Fall back to a copy and a remove when the operating system reports a cross-device error. rename cannot cross a filesystem boundary, so moving out of the temporary directory, into a mounted volume or onto another drive failed outright
  • isFileExists: Answer with a single FileSystemEntity.type call rather than asking File.exists and then Directory.exists, which cost two system calls for every directory
  • createDirectory: Drop the exists call that ran before every create. create is already a no-op for an existing directory and already reports a file in the way
  • deleteAllFileFromDirectory: Delete up to 32 entries at a time instead of awaiting each one in turn
  • hasBadWords: Catch a banned word broken up by digits (ad1min, 사1과, 사123과), a common way of hiding a word in Korean. A digit that opens or closes a word is still read as a letter, so a number in front of a word (2시 발표) is not read away

1.3.0 (2026-07-28)

  • BREAKING CHANGES: numberHash now returns the low 32 bits as a signed value, so it can be negative as documented and matches the JavaScript and Python implementations (numberHash('k10000') is -1184917978, not 3110049318)
  • BREAKING CHANGES: The base64url hash encoding is now unpadded, and binary now returns the raw digest as latin-1 characters instead of a string of 0s and 1s, both matching the JavaScript and Python implementations
  • BREAKING CHANGES: truncateExpect no longer inserts the literal text nullNone into the result when endStringChar is omitted (truncateExpect('Hi. Bye.', 3) returned 'Hinull')
  • BREAKING CHANGES: numUnique now returns a millisecond timestamp combined with a per-millisecond sequence (16 digits) instead of a timestamp combined with a random number (18 digits). Repeated calls within a process are now always unique and strictly increasing
  • BREAKING CHANGES: isValidDate now rejects years 0100-1599, which the JavaScript and Python implementations also reject. Two-digit years 16-99 and four-digit years 1600-9999 remain valid
  • BREAKING CHANGES: dayDiff now returns the absolute difference, so swapping the arguments no longer flips the sign
  • BREAKING CHANGES: arrMove no longer modifies the list it is given; it returns a new one
  • BREAKING CHANGES: strRandom returns an empty string and funcTimes returns an empty list for a non-positive count, instead of throwing, matching the JavaScript and Python implementations
  • BREAKING CHANGES: objTo1d now rejects a nullNone separator, which used to be interpolated into every nested key as the literal text nullNone
  • BREAKING CHANGES: isMatchPathname now throws for an empty matcher list instead of quietly returning falseFalse
  • strUnique: Deduplicate by code point, so characters outside the BMP (emoji) are no longer broken apart
  • capitalizeFirst, capitalizeEachWords: Return an empty string instead of throwing a RangeError on empty input
  • replaceBetween: Escape the whole delimiter, so multi-character delimiters produce a valid pattern; replaceWith now defaults to an empty string as documented
  • removeSpecialChar, removeLocalePrefix: Escape the caller's characters before building the pattern, so values like ']' or zh.CN are matched literally instead of being interpreted as a pattern
  • isMatchPathname, removeLocalePrefix: Accept any iterable, not only List<String>. A List<dynamic> (what JSON decoding produces) used to be stringified whole and never matched
  • md5Hash, sha1Hash, sha256Hash, sha512Hash: Fall back to hex when encoding is explicitly nullNone instead of throwing
  • isBotAgent: Remove 84 of the 172 alternatives that were substrings of another one (bot already matches naverbot, bingbot, ...) and could never change the outcome — verified identical on 200,000 inputs. Roughly halves the matching cost
  • isBotAgent, isMobile, getSlug, removeSpecialChar, replaceBetween, trim, capitalizeEverySentence, getParsedInfoFromAddress: Compile regular expressions once instead of on every call — getSlug was building three per character
  • objectId, strShuffle, strRandom, numPick: Reuse a single Random instance instead of constructing one per draw
  • BREAKING CHANGES: getParentFilePath now handles relative paths (relative/path -> /relative), UNC paths, and trailing separators correctly
  • BREAKING CHANGES: toValidFilePath now resolves . and .. segments and preserves the UNC \\ prefix
  • BREAKING CHANGES: getFilePathLevel no longer counts a trailing separator as an extra level (/home/user/ now returns the same level as /home/user)
  • BREAKING CHANGES: getCopyFileName now preserves the original file extension casing (e.g. Report.PDF copies to Report (1).PDF instead of Report (1).pdf)
  • BREAKING CHANGES: isValidFileName now validates the whole name including its extension (so hello.:txt is invalid) and rejects Windows device names (CON, NUL, COM1-COM9, LPT1-LPT9, etc.)
  • BREAKING CHANGES: createFileWithDummy now throws for a negative size instead of returning falseFalse
  • BREAKING CHANGES: createDirectory, moveFile, and createFile now propagate filesystem errors instead of silently ignoring them
  • duration: Add duration method
  • arrPick: Add arrPick method
  • getParsedInfoFromAddress: Add getParsedInfoFromAddress method
  • getSlug: Add getSlug method
  • hasBadWords: Add hasBadWords method
  • capitalizeEachWords: If the natural option is not enabled, characters that are already uppercase will not be converted to lowercase

1.2.0 (2026-04-14)

  • BREAKING CHANGES: strToNumberHash has renamed to numberHash
  • md5Hash, sha1Hash, sha256Hash: Add an encoding option for hash functions
  • Add sortNumeric method
  • Add sha512Hash method

1.1.12 (2026-03-31)

  • BREAKING CHANGES: numRandom has renamed to numPick
  • getFileName: Fix incorrect directory name with include dot character
  • Add getCopyFileName method
  • Add div method
  • Add mul method
  • Add sub method
  • Add sum method
  • Add createDateListFromRange method
  • Add dateToYYYYMMDD method
  • Add dayDiff method
  • Add isValidDate method
  • Add today method

1.1.11 (2025-12-10)

  • Add split method
  • Add isMobile method
  • Add objDeleteKeyByValue method

1.1.10 (2025-11-25)

  • Fix package dependencies

1.1.9 (2025-11-25)

  • Add getFileSize method
  • Add normalizeFile method
  • Add headFile method
  • Add tailFile method
  • Add removeLocalePrefix method
  • Add isMatchPathname method
  • Add isBotAgent method
  • Add ceil argument to the fileSizeFormat method

1.1.8 (2025-11-13)

  • Add createDirectory method
  • Add getParentFilePath method
  • Add deleteFile method
  • Add createFile method
  • Add deleteAllFileFromDirectory method
  • Add moveFile method
  • Add createFileWithDummy method
  • Add getFileInfo method
  • Add joinFilePath method
  • Add getFileHashFromPath method

1.1.7 (2025-11-03)

  • BREAKING CHANGES: getFileSize has renamed to fileSizeFormat
  • BREAKING CHANGES: safeJSONParse: 'fallback' parameters has changed to named parameter
  • BREAKING CHANGES: objToArray: 'recursive' parameters has changed to named parameter
  • Add getFileName and getFileExtension methods
  • Add isFileExists method
  • Add isValidFileName method
  • Add toPosixFilePath method
  • Add getFilePathLevel method
  • Add toValidFilePath method

1.1.6 (2025-10-15)

  • isEmail: add onlyLowerCase parameter
  • Add console method
  • Add getStrBytes method

1.1.5 (2025-03-06)

  • Update README.md

1.1.4 (2025-02-28)

  • Update documentation

1.1.3 (2025-02-14)

  • Fix isUrl parameters

1.1.2 (2025-02-14)

  • Add debounce method
  • Add isUrl method
  • Add isObject method
  • Add isEqual method
  • Add isEqualStrict method
  • Add isEmpty method

1.1.1 (2024-11-26)

  • Fix objTo1d parameters

1.1.0 (2024-11-26)

  • Add arrCount method
  • Add between method
  • Add arrGroupByMaxCount method
  • Add numPick method
  • Add len method
  • Add isTrueMinimumNumberOfTimes method
  • Add objToQueryString method
  • Add objToArray method
  • Add objTo1d method

1.0.0 (2024-10-19)

  • Add fileSize method
  • Add fileExt method
  • Add safeParseInt method
  • Add isEmail method
  • Add fileName method
  • Add safeJSONParse method
  • Add md5Hash method
  • Add sha1Hash method
  • Add sha256Hash method
  • Add encodeBase64 method
  • Add decodeBase64 method
  • Add strToNumberHash method
  • Add objectId method

0.0.4 (2024-10-02)

  • Add average method
  • Add arrMove method
  • Add arrTo1dArray method
  • Add arrRepeat method

0.0.3 (2024-10-02)

  • Add strShuffle method
  • Add strRandom method
  • Add truncateExpect method
  • Add strUnique method
  • Add strToAscii method
  • Add urlJoin method
  • Add arrWithDefault method
  • Add arrWithNumber method
  • Add funcTimes method
  • Add is2dArray method
  • Add arrUnique method

0.0.2 (2024-09-10)

  • Add trim method
  • Add replaceBetween method
  • Add removeNewLine method
  • Add capitalizeEverySentence method
  • Add contains method
  • Add capitalizeEachWords method
  • Add strCount method
  • Add sleep method
  • Add arrShuffle method
  • Add removeSpecialChar method

0.0.1 (2024-09-02) - Not for Production

  • Initial release

1.4.0 (2026-09-12)

Breaking changes

  • sortNumeric, sortByObjectKey: The order no longer depends on the machine or on the package. A string is cut into runs of digits and runs of everything else, and the runs are compared in three passes over the whole string: the letters, then the accents on them, then upper against lower case. Whitespace sorts before punctuation, punctuation before numbers and numbers before letters, and a run of digits is compared by length before value, so a number too long for a number type still sorts correctly. The three packages now return the same order for the same input, which they never did. The key used to compare code points, so Apple, Banana and Zebra all came before apple, and File-3.txt came before file-1.txt

Changes

  • durationParts: Added. Breaks a duration into its units and hands them back as a list of {'value', 'unit'} rather than a string, so a duration can be written in a language this package does not know. duration labels the units in English and builds the plural by adding an s, which is a rule only English follows: Polish has three plural forms and Arabic six. babel's format_unit knows the plural rules for each language. It takes the options of duration that decide which units are used, and duration is now built on it, so the two cannot disagree
  • fileSizeParts: Added. Splits a file size in bytes into the scaled number and the unit it belongs to, returning a dict rather than a string, so a size can be written in a language this package does not know. Hand value to babel's format_decimal and take the unit name from exponent; the value is deliberately left unrounded, so that formatter rounds it once instead of rounding an already rounded number
  • fileSizeFormat: Two keyword arguments were added. standard picks the divisor and the unit names: jedec (the default) divides by 1024 and writes KB as it always has, iec divides by 1024 and writes KiB, and si divides by 1000 and writes kB. unitDisplay writes the unit as an abbreviation (1.18 MB) or as a whole word (1.18 Megabytes), taking the singular when the rounded number is one. Leaving both out returns exactly the string it returned before, which the tests now pin
  • fileSizeFormat: A size past the largest unit no longer raises IndexError by indexing past the end of the unit table. fileSizeFormat(1024 ** 9) now reads 1024 YB, matching what the JavaScript and Dart packages now do

1.3.0 (2026-08-29)

  • The package now ships a py.typed marker, so a type checker reads the annotations every function already carried. Without it those annotations were ignored in an installed package (PEP 561), and because functions are imported on demand, a checker following from qsu.array import arrUnique landed on the module of that name rather than the function in it. Each category now spells its re-exports out under if TYPE_CHECKING, which costs nothing at runtime, so qsu.arrUnique, from qsu.array import arrUnique and from qsu.array.arrUnique import arrUnique all carry the real signature
  • Categories and functions are now imported on first access. import qsu used to pull in all 170 function modules, and with them cryptography, subprocess and urllib, whatever the caller went on to use; reaching for a single function through its own module cost exactly the same, because importing a submodule runs the package __init__ first. It now takes 1.6ms rather than 61ms and loads 18 modules rather than 286, and the first call into a category pays for that category alone. __all__ and its order, from qsu import *, from qsu.array import arrUnique and dir() all answer as before, and every name still resolves to one object whether it is read from qsu or from its category
  • truncateExpect: endStringChar now takes a list as well as a single string, and defaults to the full stop as each script writes it (., , , ). Japanese and Chinese text used to come back untouched, because a text with no ASCII . in it split into one piece and the expected length was never reached. ! and ? are left out of the default on purpose, so that the same sentence is not split differently depending on the script it is written in. A longer ending character is matched before a shorter one, so . next to ... no longer cuts ... short

1.2.0 (2026-08-07)

  • unescapeHtml: Added. Turns the five entities escapeHtml produces back into their characters. The string is walked once rather than replaced five times in a row, so &amp;lt; comes back as the literal text &lt; instead of being unescaped twice, and only those five entities are recognised, so &nbsp; and &#x27; are left as they are
  • escapeHtml: Added. Escapes &, <, >, " and ' so a value can be dropped into a page as text rather than read as markup. ' is written as &#39; where the built-in html.escape writes &#x27;, so this is not a wrapper around it. It lives in the web category, next to getSlug, and leaves escapeRegExp as the pattern-oriented one
  • objClone: Added. Copies an object, deeply by default and top level only with deep: False. A dict, list and tuple are rebuilt with their contents copied and a set gets a fresh copy, while a datetime (immutable) or a class instance is handed back as it is. A structure that points back at itself is rebuilt with the same shape rather than recursing until the recursion limit is hit
  • objMerge: Added. Merges any number of objects into one new object, going down through nested dicts, with the later source winning. Two dicts under the same key are merged into a new dict, so neither source is shared with the result or modified. Lists are replaced whole rather than merged index by index as Lodash does, and None is returned when an argument is not a dict
  • objGet: Added. Reads a nested value out of an object by dot and/or bracket path (a.b.c, list[0], list[1].d), returning the fallback when the path is not there. A bracket may carry a quoted key, so ["a.b"] reads one key rather than walking two levels, and a stored None counts as a value rather than a missing path
  • objPick: Added. Returns a new object containing only the listed keys, accepting a single key or a list of keys. Only the top level is inspected, and a key the dict does not have is skipped rather than carried over as None
  • pad: Added. Pads a string until it reaches the given length, with one position option (start, end or both) covering what Lodash splits across pad, padStart and padEnd. both is the default and gives the extra character to the end, a multi-character char is repeated and truncated, and the length is counted in code points so an emoji counts as one in every language
  • strToConstantCase: Added. Converts a string to CONSTANT_CASE, uppercasing every word and joining them with an underscore. It splits with words, so XMLHttpRequest becomes XML_HTTP_REQUEST. Python and JavaScript apply the full Unicode case mapping where Dart applies the simple one, so straße becomes STRASSE here and STRAßE in Dart, which the documentation states rather than papering over
  • strToPascalCase: Added. Converts a string to PascalCase, giving every word an uppercase first letter and a lowercase rest. It splits with words, so XMLHttpRequest becomes XmlHttpRequest. capitalizeEachWords stays the one that keeps the original separators
  • strToKebabCase: Added. Converts a string to kebab-case, lowercasing every word and joining them with a hyphen. It splits with words, so XMLHttpRequest becomes xml-http-request. getSlug stays the URL-oriented one
  • strToSnakeCase: Added. Converts a string to snake_case, lowercasing every word and joining them with an underscore. It splits with words, so XMLHttpRequest becomes xml_http_request and abc12def becomes abc_12_def
  • strToCamelCase: Added. Converts a string to camelCase, lowercasing the first word and giving every word after it an uppercase first letter. It splits with words, so an acronym stays whole (XMLHttpRequest becomes xmlHttpRequest) and a run of digits is its own word (abc12def becomes abc12Def)
  • min: Added. Returns the smallest of the given numbers, accepting either n arguments or a single list exactly like sum. Values that are not numbers are skipped, bool among them, and so is nan, which would otherwise win by losing every comparison. An empty input returns None
  • max: Added. Returns the largest of the given numbers, accepting either n arguments or a single list exactly like sum. Values that are not numbers are skipped, bool among them, and so is nan, which would otherwise win by losing every comparison. An empty input returns None
  • floor: Added. Rounds a number down, to the given number of decimal places, a negative precision rounding down to tens, hundreds and so on. Rounding goes toward negative infinity, so floor(-4.006) is -5. The value is read through Decimal(str(value)) and shifted by its exponent, so floor(1.1, 1) is 1.1
  • ceil: Added. Rounds a number up, to the given number of decimal places, a negative precision rounding up to tens, hundreds and so on. Rounding goes toward positive infinity, so ceil(-4.006) is -4. The value is read through Decimal(str(value)) and shifted by its exponent, so ceil(1.1, 1) is 1.1 and not 1.2
  • round: Added. Rounds a number to the given number of decimal places, a negative precision rounding to tens, hundreds and so on. Ties go away from zero rather than to the nearest even number, so unlike the built-in round it answers 1 for 0.5 and 3 for 2.5, matching the JavaScript and Dart implementations. The value is read through Decimal(str(value)) and shifted by its exponent rather than multiplied by a power of ten, so round(1.005, 2) is 1.01 and not 1
  • clamp: Added. Restricts a number to an inclusive range, returning min below it and max above it. The upper bound is applied first, so min wins when the two are passed the wrong way round, matching Lodash rather than Dart's num.clamp, which throws
  • retry: Added. Runs the given function again on failure until it succeeds or the attempts run out, raising the last error if they all fail. times counts total attempts (default 3), delay waits between them and backoff multiplies that wait after each failure. It is synchronous and waits with time.sleep, as sleep already does. BaseException is not caught, so KeyboardInterrupt still stops the loop
  • throttle: Added. Limits how often a function may run to at most once per wait window, the counterpart of debounce. leading and trailing (both True by default) choose which edge of the window runs. The trailing call is scheduled on a background thread, as debounce already does
  • objInvert: Added. Returns a new object with the keys and values swapped. Values are converted to text because keys are always strings — None becomes nullNone and True becomes trueTrue, and a whole float loses its fractional part, so the result matches the JavaScript implementation — and the later entry wins when two share a value
  • objMapKeys: Added. Returns a new object whose keys are the values returned by the callback, with the values carried over untouched. The callback receives (value, key), and the later key wins when two map onto the same name
  • objPickBy: Added. Returns a new object containing only the entries for which the callback returns trueTrue. The callback receives (value, key), and only the top level is inspected
  • uncapitalizeFirst: Added. Converts the first letter of the entire string to lowercase, the inverse of capitalizeFirst. Only the first character is touched, so TEST becomes tEST
  • escapeRegExp: Added. Escapes every regular expression metacharacter (^ $ . * + ? ( ) [ ] { } | and \) so a value can be matched literally. Unlike re.escape it leaves -, # and whitespace alone, because those are special only inside a character class or in verbose mode, and escaping them would not match the JavaScript implementation
  • deburr: Added. Replaces accented Latin letters with their unaccented equivalents (déjà vu becomes deja vu), spelling out Æ, ß, Þ, Œ and IJ, and dropping combining marks. Covers the Latin-1 Supplement and Latin Extended-A blocks
  • words: Added. Splits a string into the words it is made of. Anything that is neither a letter nor a digit separates words, and camelCase boundaries, runs of capitals (XMLHttpRequest is XML, Http, Request) and runs of digits are split as well
  • arrIntersection: Added. Returns the values that are present in every one of the given arrays. The result is unique and keeps the order of the first array
  • arrDifference: Added. Returns the values of the first array that are not contained in any of the other arrays. Values are compared by value rather than by identity, so nested lists and dicts are matched as well
  • arrCompact: Added. Returns a new array with every falsy value removed (None, False, 0, '', nan). An empty list and an empty dict are kept, matching the JavaScript implementation

1.1.0 (2026-08-04)

  • BREAKING CHANGES: isValidFileName now rejects an empty name and any name carrying a control character (U+0000-U+001F or U+007F). NUL is the one that matters: it terminates the path in the system call underneath every filesystem, so a name carrying one was reported as valid and then silently truncated on the way to disk
  • BREAKING CHANGES: isValidFileName now rejects a name ending in a dot or a space on the Windows path. Windows strips it instead of reporting an error, so report. quietly becomes report and overwrites it. Unix keeps them, so they stay valid with unixType
  • BREAKING CHANGES: isValidFileName now measures its 255 limit in UTF-8 bytes rather than characters, which is what ext4, APFS and NTFS enforce. '가' * 100 is 100 characters but 300 bytes and cannot be created. Counting characters also disagreed with the JavaScript and Dart implementations, which count UTF-16 units where Python counted code points ('😀' * 130 was valid here and invalid there)
  • BREAKING CHANGES: headFile and tailFile now replace malformed UTF-8 with U+FFFD instead of raising UnicodeDecodeError, matching the JavaScript and Dart implementations. One bad byte in a log file no longer stops it from being read
  • BREAKING CHANGES: headFile and tailFile now break a line on a lone \r as well as on \n and \r\n, matching Node's readline and Dart's LineSplitter. A file written on a pre-OS X Mac used to come back as a single line
  • BREAKING CHANGES: createDirectory now reports the error when a file already sits at the path. It asked only whether something was there and then answered that there was nothing to do, so no directory existed and nothing said so
  • BREAKING CHANGES: createFile now creates any parent directory the path needs instead of raising FileNotFoundError, matching the Dart implementation
  • BREAKING CHANGES: createFile, deleteFile and moveFile now treat a path of nothing but whitespace as no path at all, matching the Dart implementation. createFile(' ') used to create a file literally named
  • BREAKING CHANGES: getFileInfo and getFileSize now raise the original OSError instead of a plain Exception carrying only its text. errno, strerror and filename were dropped with it, so a caller could not tell a missing file from a permission error. The unreachable return both functions ended with has been removed
  • BREAKING CHANGES: getFileInfo now builds dirname with os.path, which follows the host platform, instead of always splitting on /. A Windows path used to come back whole
  • BREAKING CHANGES: toValidFilePath now resolves a leading .. against the root, so '../../etc/passwd' returns /etc/passwd instead of /../../etc/passwd
  • isFileHidden: Read the attribute letters out of the column attrib prints them in. Removing the caller's path from the output failed whenever a relative path was given, because attrib answers with an absolute one, and any H in a directory name then read as hidden
  • headFile: Read the file in chunks instead of pulling all of it into memory with a single read(). Asking for the first line of a 108 MB log held 476 MB at once and now holds a chunk
  • tailFile: Read backwards from the end of the file a chunk at a time instead of walking it from the start, and stop popping the front of a length-sized list once per line. The old shape cost lines × length: on a 108 MB log the last 20,000 lines took 6.8 seconds and now take 0.06, and the last single line went from 0.28 seconds to 0.001
  • getCopyFileName: Accept a set as well as a list, and read it as it is. Naming n files into one directory calls this n times, and rebuilding the set on every call made that loop quadratic — 16,000 names took 11 seconds through a list and 0.03 seconds through a reused set
  • moveFile: Fall back to a copy and a remove when the operating system reports EXDEV. os.rename cannot cross a filesystem boundary, so moving out of the temporary directory, into a mounted volume or onto another drive failed outright
  • isFileExists: Drop the os.access call whose result was thrown away, halving the system calls this makes
  • getFileInfo: Read the directory flag out of the stat result already in hand instead of asking the filesystem a second time through os.path.isdir
  • createDirectory: Drop the isFileExists call that ran before every makedirs. makedirs with exist_ok is already a no-op for an existing directory
  • hasBadWords: Catch a banned word broken up by digits (ad1min, 사1과, 사123과), a common way of hiding a word in Korean. A digit that opens or closes a word is still read as a letter, so a number in front of a word (2시 발표) is not read away

1.0.0 (2026-07-28)

  • BREAKING CHANGES: encrypt and decrypt now honour the algorithm argument. Every algorithm silently produced AES-CBC before, so a value such as aes-256-gcm was accepted but ignored and the output did not match the JavaScript implementation. AEAD modes (GCM) now carry the authentication tag as iv:authTag:encrypted; the iv:encrypted format for CBC is unchanged. The key length is validated against the algorithm, as it is in JavaScript
  • BREAKING CHANGES: decrypt now validates PKCS7 padding, so decrypting with the wrong key raises instead of quietly returning an empty string
  • BREAKING CHANGES: generateLicense now normalizes the type argument correctly, so 'Apache 2.0', 'apache-2.0' and 'BSD 3' return the license they name instead of silently falling back to MIT (a missing character class in the normalizing regular expression made it a no-op)
  • BREAKING CHANGES: numberHash and strToAscii now iterate UTF-16 code units, like the JavaScript and Dart implementations. ord() returns a code point, so characters outside the BMP produced different values ('😀' hashed to 128512 instead of 1772899)
  • BREAKING CHANGES: numUnique now returns a millisecond timestamp combined with a per-millisecond sequence (16 digits) instead of a timestamp combined with a random number (18 digits). Repeated calls within a process are now always unique and strictly increasing
  • BREAKING CHANGES: isEqual and isEqualStrict now compare dicts instead of mistaking them for an argument list. Iterating a dict yielded its keys, so every dict comparison returned False. Passing the operands as a list or tuple still works
  • BREAKING CHANGES: objDeleteKeyByValue, objUpdate, arrShuffle, arrMove, sortNumeric and sortByObjectKey no longer modify the argument they are given; they all return a new dict or list
  • BREAKING CHANGES: arrShuffle now returns a list when given a single element, instead of returning that element itself
  • BREAKING CHANGES: sortNumeric and sortByObjectKey now apply descending through the sort key instead of reversing the sorted result, so equal elements keep their relative order
  • BREAKING CHANGES: safeParseInt now treats 0 as a valid input instead of a missing one, so safeParseInt(0, 99) returns 0
  • BREAKING CHANGES: trim now returns None for any non-string input instead of raising an AttributeError on truthy values such as trim(123)
  • strBlindRandom: Keep the result the same length as the input. The character that was checked and the character that was masked were one position apart, and the index could land past the end of the string and append instead of mask
  • generateLicense: Accept an options dict as the first positional argument, like the rest of the package
  • is2dArray: Return on the first nested list instead of building a filtered copy of the whole list (640ms to 0ms on a 100,000 element list)
  • isBotAgent: Remove 84 of the 172 alternatives that were substrings of another one (bot already matches naverbot, bingbot, ...) and could never change the outcome — verified identical on 200,000 inputs. Roughly halves the matching cost
  • numUnique: Stop building an 89,999 element list on every call to pick a single number
  • capitalizeEachWords: Look the stop words up in a frozenset instead of scanning a list through contains
  • BREAKING CHANGES: duration now hides milliseconds by default (enable with withMilliSeconds) and uses grammatically correct plurals (e.g. 0 Hours, 1 Hour)
  • BREAKING CHANGES: getFilePathLevel no longer counts a trailing separator as an extra level (/home/user/ now returns the same level as /home/user)
  • BREAKING CHANGES: getCopyFileName now preserves the original file extension casing (e.g. Report.PDF copies to Report (1).PDF instead of Report (1).pdf)
  • BREAKING CHANGES: isValidFileName now validates the whole name including its extension (so hello.:txt is invalid) and rejects Windows device names (CON, NUL, COM1-COM9, LPT1-LPT9, etc.)
  • BREAKING CHANGES: createFileWithDummy now creates an empty file for a size of 0 instead of throwing, and throws a clearer error for a negative size
  • BREAKING CHANGES: getParentFilePath now returns the root (/ or \) for an empty or single-segment path instead of /.
  • BREAKING CHANGES: toValidFilePath now returns the root (/ or \) for a path that collapses to nothing instead of /.
  • duration: Support Month (30 days) and Year (365 days) units, and add withMilliSeconds, maxUnitCount, and unit (single-unit) options
  • getParsedInfoFromAddress: Add getParsedInfoFromAddress method
  • getSlug: Add getSlug method
  • hasBadWords: Add hasBadWords method

0.1.0 (2026-06-16)

  • Initial release of the Python package
  • Add string utilities: capitalizeEachWords, capitalizeEverySentence, capitalizeFirst, getGroupKeys, getStrBytes, removeNewLine, removeSpecialChar, replaceBetween, split, strBlindRandom, strCount, strRandom, strShuffle, strToAscii, strUnique, trim, truncate, truncateExpect, urlJoin
  • Add array utilities: arrCount, arrGroupByMaxCount, arrMove, arrPick, arrRepeat, arrShuffle, arrTo1dArray, arrUnique, arrWithDefault, arrWithNumber, average, sortByObjectKey, sortNumeric
  • Add object utilities: objDeleteKeyByValue, objFindItemRecursiveByKey, objMergeNewKey, objTo1d, objToArray, objToPrettyStr, objToQueryString, objUpdate
  • Add date utilities: createDateListFromRange, dateToYYYYMMDD, dayDiff, isValidDate, today
  • Add format utilities: duration, fileSizeFormat, numberFormat, safeJSONParse, safeParseInt
  • Add math utilities: div, mul, numPick, numUnique, sub, sum
  • Add verify utilities: between, contains, is2dArray, isEmail, isEmpty, isEqual, isEqualStrict, isObject, isTrueMinimumNumberOfTimes, isUrl, len
  • Add web utilities: generateLicense, isBotAgent, isMatchPathname, isMobile, removeLocalePrefix
  • Add misc utilities: debounce, funcTimes, logBox, sleep (async functions are implemented synchronously)
  • Add crypto utilities: decodeBase64, decrypt, encodeBase64, encrypt, md5Hash, numberHash, objectId, sha1Hash, sha256Hash, sha512Hash (encrypt/decrypt use the cryptography package)
  • Add file utilities: createDirectory, createFile, createFileWithDummy, deleteAllFileFromDirectory, deleteFile, getCopyFileName, getFileExtension, getFileHashFromPath, getFileHashFromStream, getFileInfo, getFileName, getFilePathLevel, getFileSize, getParentFilePath, headFile, isFileExists, isFileHidden, isValidFileName, joinFilePath, moveFile, normalizeFile, tailFile, toPosixFilePath, toValidFilePath
  • Add os utilities: getCpu, getHostname, getMachineId, getRamSize, getSid, getUptime, runCommand
  • Add net utility: fetchData

Released under the MIT License