diff --git a/.all-contributorsrc b/.all-contributorsrc
new file mode 100644
index 00000000..d0cbda79
--- /dev/null
+++ b/.all-contributorsrc
@@ -0,0 +1,25 @@
+{
+ "files": [
+ "README.md"
+ ],
+ "imageSize": 100,
+ "commit": false,
+ "contributors": [
+ {
+ "login": "sandstreamdevelopment",
+ "name": "sandstreamdevelopment",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/44231396?v=4",
+ "profile": "https://github.com/sandstreamdevelopment",
+ "contributions": [
+ "business",
+ "financial",
+ "ideas"
+ ]
+ }
+ ],
+ "contributorsPerLine": 7,
+ "projectName": "std",
+ "projectOwner": "sandstreamdev",
+ "repoType": "github",
+ "repoHost": "https://github.com"
+}
diff --git a/.gitignore b/.gitignore
index ad46b308..2b0ef73d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,3 +59,6 @@ typings/
# next.js build output
.next
+
+index.cjs.js
+index.umd.js
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 00000000..eb4647a4
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1 @@
+regenerate.js
diff --git a/README.md b/README.md
index 0d24f20d..3584d1ef 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,19 @@
-# std
\ No newline at end of file
+# std
+
+[](#contributors)
+
+## Contributors ✨
+
+Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+
+
+
+
+
+
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
diff --git a/array/any.js b/array/any.js
new file mode 100644
index 00000000..f9a2b66c
--- /dev/null
+++ b/array/any.js
@@ -0,0 +1 @@
+export default xs => xs && xs.length > 0;
diff --git a/array/are.js b/array/are.js
new file mode 100644
index 00000000..ba83b30e
--- /dev/null
+++ b/array/are.js
@@ -0,0 +1,3 @@
+import is from "./is.js";
+
+export default (...xs) => xs.every(is);
diff --git a/array/difference.js b/array/difference.js
new file mode 100644
index 00000000..63a40ba6
--- /dev/null
+++ b/array/difference.js
@@ -0,0 +1,5 @@
+export default (xs, ys) => {
+ const zs = new Set(ys);
+
+ return xs.filter(x => !zs.has(x));
+};
diff --git a/array/differs.js b/array/differs.js
new file mode 100644
index 00000000..1fac6689
--- /dev/null
+++ b/array/differs.js
@@ -0,0 +1,5 @@
+export default (xs, ys) =>
+ (!xs && ys) ||
+ (!ys && xs) ||
+ xs.length !== ys.length ||
+ xs.some((x, index) => x !== ys[index]);
diff --git a/array/duplicates.js b/array/duplicates.js
new file mode 100644
index 00000000..f3ff72f7
--- /dev/null
+++ b/array/duplicates.js
@@ -0,0 +1,2 @@
+export default xs =>
+ xs.filter((value, index, self) => self.indexOf(value) !== index);
diff --git a/array/empty.js b/array/empty.js
new file mode 100644
index 00000000..d6d1738d
--- /dev/null
+++ b/array/empty.js
@@ -0,0 +1 @@
+export default [];
diff --git a/array/exact.js b/array/exact.js
new file mode 100644
index 00000000..c5080095
--- /dev/null
+++ b/array/exact.js
@@ -0,0 +1,3 @@
+import range from "./range.js";
+
+export default n => xs => range(n).map(index => xs[index]);
diff --git a/array/except.js b/array/except.js
new file mode 100644
index 00000000..b721e9d4
--- /dev/null
+++ b/array/except.js
@@ -0,0 +1 @@
+export default y => xs => xs.filter(x => x !== y);
diff --git a/array/filterInPlace.js b/array/filterInPlace.js
new file mode 100644
index 00000000..8b065931
--- /dev/null
+++ b/array/filterInPlace.js
@@ -0,0 +1,15 @@
+export default f => xs => {
+ let i = 0;
+ let j = 0;
+
+ while (i < xs.length) {
+ const value = xs[i];
+ if (f(value, i, xs)) {
+ xs[j++] = value;
+ }
+ i++;
+ }
+
+ xs.length = j;
+ return xs;
+};
diff --git a/array/find.js b/array/find.js
new file mode 100644
index 00000000..9e4018a0
--- /dev/null
+++ b/array/find.js
@@ -0,0 +1,4 @@
+export default (predicate, fallback) => xs => {
+ const target = xs.find(predicate);
+ return target !== undefined ? target : fallback;
+};
diff --git a/array/first.js b/array/first.js
new file mode 100644
index 00000000..42b96520
--- /dev/null
+++ b/array/first.js
@@ -0,0 +1 @@
+export default ([x]) => x;
diff --git a/array/flatMap.js b/array/flatMap.js
new file mode 100644
index 00000000..00184cb9
--- /dev/null
+++ b/array/flatMap.js
@@ -0,0 +1 @@
+export default f => xs => xs.reduce((ys, y) => ys.concat(f(y)), []);
diff --git a/array/flatten.js b/array/flatten.js
new file mode 100644
index 00000000..936f23dd
--- /dev/null
+++ b/array/flatten.js
@@ -0,0 +1 @@
+export default xs => [].concat(...xs);
diff --git a/array/index.js b/array/index.js
new file mode 100644
index 00000000..db09ff73
--- /dev/null
+++ b/array/index.js
@@ -0,0 +1,126 @@
+import any from "./any.js";
+import are from "./are.js";
+import difference from "./difference.js";
+import differs from "./differs.js";
+import duplicates from "./duplicates.js";
+import empty from "./empty.js";
+import exact from "./exact.js";
+import except from "./except.js";
+import filterInPlace from "./filterInPlace.js";
+import find from "./find.js";
+import first from "./first.js";
+import flatMap from "./flatMap.js";
+import flatten from "./flatten.js";
+import intersection from "./intersection.js";
+import is from "./is.js";
+import last from "./last.js";
+import lengthDiffers from "./lengthDiffers.js";
+import map from "./map.js";
+import midpoint from "./midpoint.js";
+import minMax from "./minMax.js";
+import multiple from "./multiple.js";
+import none from "./none.js";
+import partition from "./partition.js";
+import range from "./range.js";
+import repeat from "./repeat.js";
+import reverse from "./reverse.js";
+import reverseIf from "./reverseIf.js";
+import rotate from "./rotate.js";
+import second from "./second.js";
+import secondToLast from "./secondToLast.js";
+import shift from "./shift.js";
+import shuffle from "./shuffle.js";
+import shuffleInPlace from "./shuffleInPlace.js";
+import single from "./single.js";
+import sort from "./sort.js";
+import sum from "./sum.js";
+import transpose from "./transpose.js";
+import unique from "./unique.js";
+import zip from "./zip.js";
+import zipWith from "./zipWith.js";
+
+export {
+ any,
+ are,
+ difference,
+ differs,
+ duplicates,
+ empty,
+ exact,
+ except,
+ filterInPlace,
+ find,
+ first,
+ flatMap,
+ flatten,
+ intersection,
+ is,
+ last,
+ lengthDiffers,
+ map,
+ midpoint,
+ minMax,
+ multiple,
+ none,
+ partition,
+ range,
+ repeat,
+ reverse,
+ reverseIf,
+ rotate,
+ second,
+ secondToLast,
+ shift,
+ shuffle,
+ shuffleInPlace,
+ single,
+ sort,
+ sum,
+ transpose,
+ unique,
+ zip,
+ zipWith
+};
+
+export default {
+ any,
+ are,
+ difference,
+ differs,
+ duplicates,
+ empty,
+ exact,
+ except,
+ filterInPlace,
+ find,
+ first,
+ flatMap,
+ flatten,
+ intersection,
+ is,
+ last,
+ lengthDiffers,
+ map,
+ midpoint,
+ minMax,
+ multiple,
+ none,
+ partition,
+ range,
+ repeat,
+ reverse,
+ reverseIf,
+ rotate,
+ second,
+ secondToLast,
+ shift,
+ shuffle,
+ shuffleInPlace,
+ single,
+ sort,
+ sum,
+ transpose,
+ unique,
+ zip,
+ zipWith
+};
diff --git a/array/intersection.js b/array/intersection.js
new file mode 100644
index 00000000..647ae519
--- /dev/null
+++ b/array/intersection.js
@@ -0,0 +1 @@
+export default (xs, ys) => xs.filter(value => ys.includes(value));
diff --git a/array/is.js b/array/is.js
new file mode 100644
index 00000000..b0f97bbe
--- /dev/null
+++ b/array/is.js
@@ -0,0 +1 @@
+export default value => Array.isArray(value);
diff --git a/array/last.js b/array/last.js
new file mode 100644
index 00000000..8302bdbb
--- /dev/null
+++ b/array/last.js
@@ -0,0 +1 @@
+export default xs => xs[xs.length - 1];
diff --git a/array/lengthDiffers.js b/array/lengthDiffers.js
new file mode 100644
index 00000000..f8019d11
--- /dev/null
+++ b/array/lengthDiffers.js
@@ -0,0 +1 @@
+export default (a, b) => a.length !== b.length;
diff --git a/array/map.js b/array/map.js
new file mode 100644
index 00000000..a61d5d13
--- /dev/null
+++ b/array/map.js
@@ -0,0 +1,4 @@
+export default (...fs) => {
+ const f = x => fs.reduce((x, f) => f(x), x);
+ return x => x.map(f);
+};
diff --git a/array/midpoint.js b/array/midpoint.js
new file mode 100644
index 00000000..6acdb881
--- /dev/null
+++ b/array/midpoint.js
@@ -0,0 +1 @@
+export default xs => xs[Math.floor(xs.length / 2)];
diff --git a/array/minMax.js b/array/minMax.js
new file mode 100644
index 00000000..a1129efa
--- /dev/null
+++ b/array/minMax.js
@@ -0,0 +1,7 @@
+const { max, min } = Math;
+
+export default ([head, ...tail]) =>
+ tail.reduce(([min, max], current) => [min(min, current), max(max, current)], [
+ head,
+ head
+ ]);
diff --git a/array/multiple.js b/array/multiple.js
new file mode 100644
index 00000000..19d1cdb0
--- /dev/null
+++ b/array/multiple.js
@@ -0,0 +1 @@
+export default xs => xs.length > 1;
diff --git a/array/none.js b/array/none.js
new file mode 100644
index 00000000..307b11e6
--- /dev/null
+++ b/array/none.js
@@ -0,0 +1,3 @@
+import any from "./any.js";
+
+export default xs => !any(xs);
diff --git a/array/partition.js b/array/partition.js
new file mode 100644
index 00000000..039a6ce7
--- /dev/null
+++ b/array/partition.js
@@ -0,0 +1,8 @@
+export default predicate => xs =>
+ xs.reduce(
+ ([left, right], current) => {
+ const pass = predicate(current);
+ return pass ? [left, [...right, current]] : [[...left, current], right];
+ },
+ [[], []]
+ );
diff --git a/array/range.js b/array/range.js
new file mode 100644
index 00000000..837dd682
--- /dev/null
+++ b/array/range.js
@@ -0,0 +1,4 @@
+export default n =>
+ Array(n)
+ .fill()
+ .map((_, index) => index);
diff --git a/array/repeat.js b/array/repeat.js
new file mode 100644
index 00000000..e90082e8
--- /dev/null
+++ b/array/repeat.js
@@ -0,0 +1 @@
+export default n => value => Array(n).fill(value);
diff --git a/array/reverse.js b/array/reverse.js
new file mode 100644
index 00000000..75a00a68
--- /dev/null
+++ b/array/reverse.js
@@ -0,0 +1 @@
+export default xs => [...xs].reverse();
diff --git a/array/reverseIf.js b/array/reverseIf.js
new file mode 100644
index 00000000..dacd1541
--- /dev/null
+++ b/array/reverseIf.js
@@ -0,0 +1,3 @@
+import reverse from "./reverse.js";
+
+export default predicate => xs => (predicate ? reverse(xs) : xs);
diff --git a/array/rotate.js b/array/rotate.js
new file mode 100644
index 00000000..32a160cd
--- /dev/null
+++ b/array/rotate.js
@@ -0,0 +1,8 @@
+export default array => angle => {
+ const margin = Math.PI / 8;
+ const angleWithMargin = angle + margin;
+ const unit = Math.PI / 4;
+ const ratio = angleWithMargin / unit;
+ const offset = Math.floor(ratio);
+ return shift(offset)(array);
+};
diff --git a/array/second.js b/array/second.js
new file mode 100644
index 00000000..41953d02
--- /dev/null
+++ b/array/second.js
@@ -0,0 +1 @@
+export default ([, x]) => x;
diff --git a/array/secondToLast.js b/array/secondToLast.js
new file mode 100644
index 00000000..134e4b9c
--- /dev/null
+++ b/array/secondToLast.js
@@ -0,0 +1 @@
+export default xs => xs[xs.length - 2];
diff --git a/array/shift.js b/array/shift.js
new file mode 100644
index 00000000..b0cc0987
--- /dev/null
+++ b/array/shift.js
@@ -0,0 +1,2 @@
+export default n => xs =>
+ xs.map((_, index) => xs[(index + (n % xs.length) + xs.length) % xs.length]);
diff --git a/array/shuffle.js b/array/shuffle.js
new file mode 100644
index 00000000..66db53c0
--- /dev/null
+++ b/array/shuffle.js
@@ -0,0 +1,3 @@
+import shuffleInPlace from "./shuffleInPlace.js";
+
+export default xs => shuffleInPlace([...xs]);
diff --git a/array/shuffleInPlace.js b/array/shuffleInPlace.js
new file mode 100644
index 00000000..ae39da13
--- /dev/null
+++ b/array/shuffleInPlace.js
@@ -0,0 +1,8 @@
+export default xs => {
+ for (let i = 0; i < xs.length; i++) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [xs[i], xs[j]] = [xs[j], xs[i]];
+ }
+
+ return xs;
+};
diff --git a/array/single.js b/array/single.js
new file mode 100644
index 00000000..bdc53c3f
--- /dev/null
+++ b/array/single.js
@@ -0,0 +1 @@
+export default xs => xs.length === 1;
diff --git a/array/sort.js b/array/sort.js
new file mode 100644
index 00000000..645230d6
--- /dev/null
+++ b/array/sort.js
@@ -0,0 +1 @@
+export default f => xs => [...xs].sort(f);
diff --git a/array/sum.js b/array/sum.js
new file mode 100644
index 00000000..3d49a4b1
--- /dev/null
+++ b/array/sum.js
@@ -0,0 +1,3 @@
+const add = (a, b) => a + b;
+
+export default xs => xs.reduce(add, 0);
diff --git a/array/transpose.js b/array/transpose.js
new file mode 100644
index 00000000..9da83662
--- /dev/null
+++ b/array/transpose.js
@@ -0,0 +1 @@
+export default xs => Object.keys(xs[0]).map(key => [xs.map(x => x[key]), key]);
diff --git a/array/unique.js b/array/unique.js
new file mode 100644
index 00000000..f03af709
--- /dev/null
+++ b/array/unique.js
@@ -0,0 +1 @@
+export default xs => [...new Set(xs)];
diff --git a/array/zip.js b/array/zip.js
new file mode 100644
index 00000000..dcd8e433
--- /dev/null
+++ b/array/zip.js
@@ -0,0 +1,3 @@
+import zipWith from "./zipWith.js";
+
+export default zipWith((x, y) => [x, y]);
diff --git a/array/zipWith.js b/array/zipWith.js
new file mode 100644
index 00000000..7cd6a442
--- /dev/null
+++ b/array/zipWith.js
@@ -0,0 +1 @@
+export default f => (xs, ys) => xs.map((x, index) => f(x, ys[index]));
diff --git a/async/debounce.js b/async/debounce.js
new file mode 100644
index 00000000..11423a1a
--- /dev/null
+++ b/async/debounce.js
@@ -0,0 +1,15 @@
+export default (f, wait) => {
+ let timeout;
+
+ return (...args) => {
+ const resolve = () => {
+ timeout = null;
+
+ f(...args);
+ };
+
+ clearTimeout(timeout);
+
+ timeout = setTimeout(resolve, wait);
+ };
+};
diff --git a/async/delay.js b/async/delay.js
new file mode 100644
index 00000000..0c7bc064
--- /dev/null
+++ b/async/delay.js
@@ -0,0 +1,2 @@
+export default duration =>
+ new Promise(resolve => setTimeout(resolve, duration));
diff --git a/async/index.js b/async/index.js
new file mode 100644
index 00000000..8c3a3d37
--- /dev/null
+++ b/async/index.js
@@ -0,0 +1,7 @@
+import debounce from "./debounce.js";
+import delay from "./delay.js";
+import sequence from "./sequence.js";
+
+export { debounce, delay, sequence };
+
+export default { debounce, delay, sequence };
diff --git a/async/sequence.js b/async/sequence.js
new file mode 100644
index 00000000..f689e49c
--- /dev/null
+++ b/async/sequence.js
@@ -0,0 +1,14 @@
+export default async tasks => {
+ const results = tasks.map(_ => undefined);
+
+ await tasks.reduce((chain, current, i) => {
+ return chain.then(() =>
+ current().then(x => {
+ results[i] = x;
+ return x;
+ })
+ );
+ }, Promise.resolve());
+
+ return results;
+};
diff --git a/date/byDateWithFallback.js b/date/byDateWithFallback.js
new file mode 100644
index 00000000..3c6c5296
--- /dev/null
+++ b/date/byDateWithFallback.js
@@ -0,0 +1,18 @@
+export default now => (
+ { endedAt: aEnd, startedAt: aStart },
+ { endedAt: bEnd, startedAt: bStart }
+) => {
+ const aEndDate = new Date(aEnd || now);
+ const aStartDate = new Date(aStart || now);
+ const bEndDate = new Date(bEnd || now);
+ const bStartDate = new Date(bStart || now);
+ const aEndDateValue = aEndDate.valueOf();
+ const aStartDateValue = aStartDate.valueOf();
+ const bEndDateValue = bEndDate.valueOf();
+ const bStartDateValue = bStartDate.valueOf();
+
+ const startDateDifference = aStartDateValue - bStartDateValue;
+ const startDatesEqual = startDateDifference === 0;
+
+ return startDatesEqual ? aEndDateValue - bEndDateValue : startDateDifference;
+};
diff --git a/date/clamp.js b/date/clamp.js
new file mode 100644
index 00000000..70e2c31a
--- /dev/null
+++ b/date/clamp.js
@@ -0,0 +1,7 @@
+export default (min, max) => dateStringOrDate => {
+ const date = new Date(dateStringOrDate);
+ const clamped = new Date(
+ Math.min(max.valueOf(), Math.max(min.valueOf(), date.valueOf()))
+ );
+ return clamped;
+};
diff --git a/date/dateDiff.js b/date/dateDiff.js
new file mode 100644
index 00000000..2e4d2ce6
--- /dev/null
+++ b/date/dateDiff.js
@@ -0,0 +1,6 @@
+export default (a, b) => {
+ const d1 = new Date(a);
+ const d2 = new Date(b);
+
+ return d1.valueOf() - d2.valueOf();
+};
diff --git a/date/dateInRange.js b/date/dateInRange.js
new file mode 100644
index 00000000..29997ef7
--- /dev/null
+++ b/date/dateInRange.js
@@ -0,0 +1,7 @@
+export default (from, to) => (date = new Date()) => {
+ const dateTime = new Date(date).getTime();
+ const fromTime = new Date(from).getTime();
+ const toTime = new Date(to).getTime();
+
+ return dateTime >= fromTime && dateTime <= toTime;
+};
diff --git a/date/dayRange.js b/date/dayRange.js
new file mode 100644
index 00000000..2cea4450
--- /dev/null
+++ b/date/dayRange.js
@@ -0,0 +1,24 @@
+import endOfDay from "./endOfDay.js";
+import offsetByBit from "./offsetByBit.js";
+import splitDateTime from "./splitDateTime.js";
+import startOfDay from "./startOfDay.js";
+import toISO from "./toISO.js";
+import toISOFromLocalDateTime from "./toISOFromLocalDateTime.js";
+
+export default ({
+ iso = false,
+ local = true,
+ now = new Date(),
+ timezoneOffset = 0
+}) => date => {
+ const convert = iso ? toISO : toISOFromLocalDateTime;
+
+ const [start] = splitDateTime(
+ convert(startOfDay(date || now, timezoneOffset, local))
+ );
+ const [end] = splitDateTime(
+ convert(offsetByBit(endOfDay(date || now, timezoneOffset, local)))
+ );
+
+ return [start, end];
+};
diff --git a/date/daysInMonths.js b/date/daysInMonths.js
new file mode 100644
index 00000000..2ad5657f
--- /dev/null
+++ b/date/daysInMonths.js
@@ -0,0 +1,14 @@
+export default leapYear => [
+ 31,
+ leapYear ? 29 : 28,
+ 31,
+ 30,
+ 31,
+ 30,
+ 31,
+ 31,
+ 30,
+ 31,
+ 30,
+ 31
+];
diff --git a/date/daysInYear.js b/date/daysInYear.js
new file mode 100644
index 00000000..6df73661
--- /dev/null
+++ b/date/daysInYear.js
@@ -0,0 +1,5 @@
+import sum from "../array/sum.js";
+import daysInMonths from "./daysInMonths.js";
+import leapYear from "./leapYear.js";
+
+export default year => sum(daysInMonths(leapYear(year)));
diff --git a/date/displayMonth.js b/date/displayMonth.js
new file mode 100644
index 00000000..83a1e0b6
--- /dev/null
+++ b/date/displayMonth.js
@@ -0,0 +1,3 @@
+import monthNames from "./monthNames";
+
+export default monthIndex => monthNames[monthIndex % 12];
diff --git a/date/displayTime.js b/date/displayTime.js
new file mode 100644
index 00000000..fc78bc18
--- /dev/null
+++ b/date/displayTime.js
@@ -0,0 +1,14 @@
+export default (source, showSeconds) => {
+ const [hours, minutes, seconds] = source.map(_ => _ + "");
+
+ const padded = [
+ hours.padStart(2, "0"),
+ minutes.padStart(2, "0"),
+ seconds.padStart(2, "0")
+ ];
+
+ const [paddedHours, paddedMinutes] = padded;
+ const parts = showSeconds ? padded : [paddedHours, paddedMinutes];
+
+ return parts.join(":");
+};
diff --git a/date/endOfDay.js b/date/endOfDay.js
new file mode 100644
index 00000000..475fed53
--- /dev/null
+++ b/date/endOfDay.js
@@ -0,0 +1,9 @@
+import toLocalDateTime from "./toLocalDateTime.js";
+
+export default (date, timezoneOffset = 0, local = true) => {
+ const newDate = new Date(date);
+ newDate.setHours(24, 0, 0, 0);
+ return local
+ ? toLocalDateTime(newDate, timezoneOffset + newDate.getTimezoneOffset())
+ : newDate;
+};
diff --git a/date/formatDate.js b/date/formatDate.js
new file mode 100644
index 00000000..af16eabc
--- /dev/null
+++ b/date/formatDate.js
@@ -0,0 +1,19 @@
+import toLocalDateTime from "./toLocalDateTime.js";
+
+export default (sourceDate, timezoneOffset = 0) => {
+ const localDate = toLocalDateTime(sourceDate, timezoneOffset);
+
+ const [m, a, y] = [
+ localDate.getUTCMonth() + 1,
+ localDate.getUTCDate(),
+ localDate.getUTCFullYear()
+ ].map(_ => _ + "");
+
+ const date = [
+ y.padStart(4, "0"),
+ m.padStart(2, "0"),
+ a.padStart(2, "0")
+ ].join("-");
+
+ return date;
+};
diff --git a/date/formatDateTime.js b/date/formatDateTime.js
new file mode 100644
index 00000000..6aeb2266
--- /dev/null
+++ b/date/formatDateTime.js
@@ -0,0 +1,8 @@
+import formatDate from "./formatDate.js";
+import formatTime from "./formatTime.js";
+
+export default (sourceDate, showSeconds = false, timezoneOffset = 0) => {
+ const date = formatDate(sourceDate, timezoneOffset);
+ const time = formatTime(sourceDate, showSeconds, timezoneOffset);
+ return `${date} ${time}`;
+};
diff --git a/date/formatDisplayDate.js b/date/formatDisplayDate.js
new file mode 100644
index 00000000..c3d06b8a
--- /dev/null
+++ b/date/formatDisplayDate.js
@@ -0,0 +1,13 @@
+import toLocalDateTime from "./toLocalDateTime.js";
+import displayMonth from "./displayMonth.js";
+
+export default (sourceDate, showDay = false, timezoneOffset = 0) => {
+ const localDate = toLocalDateTime(sourceDate, timezoneOffset);
+ const day = localDate.getDate();
+ const monthIndex = localDate.getMonth();
+ const year = localDate.getFullYear();
+
+ return [showDay && day, displayMonth(monthIndex), year]
+ .filter(Boolean)
+ .join(" ");
+};
diff --git a/date/formatDuration.js b/date/formatDuration.js
new file mode 100644
index 00000000..1a8ef6f9
--- /dev/null
+++ b/date/formatDuration.js
@@ -0,0 +1,16 @@
+import fromHours from "./fromHours.js";
+import fromMinutes from "./fromMinutes.js";
+import toHours from "./toHours.js";
+import toMinutes from "./toMinutes.js";
+import toSeconds from "./toSeconds.js";
+import displayTime from "./displayTime.js";
+
+export default (duration, showSeconds = false) => {
+ const hours = Math.floor(toHours(duration));
+ const minutes = Math.floor(toMinutes(duration - fromHours(hours)));
+ const seconds = Math.floor(
+ toSeconds(duration - fromHours(hours) - fromMinutes(minutes))
+ );
+
+ return displayTime([hours, minutes, seconds], showSeconds);
+};
diff --git a/date/formatTime.js b/date/formatTime.js
new file mode 100644
index 00000000..6c01768f
--- /dev/null
+++ b/date/formatTime.js
@@ -0,0 +1,14 @@
+import toLocalDateTime from "./toLocalDateTime.js";
+import displayTime from "./displayTime.js";
+
+export default (sourceDate, showSeconds = false, timezoneOffset = 0) => {
+ const localDate = toLocalDateTime(sourceDate, timezoneOffset);
+
+ const source = [
+ localDate.getUTCHours(),
+ localDate.getUTCMinutes(),
+ localDate.getUTCSeconds()
+ ];
+
+ return displayTime(source, showSeconds);
+};
diff --git a/date/fromDays.js b/date/fromDays.js
new file mode 100644
index 00000000..7961f32d
--- /dev/null
+++ b/date/fromDays.js
@@ -0,0 +1,3 @@
+import fromHours from "./fromHours.js";
+
+export default days => fromHours(days * 24);
diff --git a/date/fromHours.js b/date/fromHours.js
new file mode 100644
index 00000000..07aba8a0
--- /dev/null
+++ b/date/fromHours.js
@@ -0,0 +1,3 @@
+import fromMinutes from "./fromMinutes.js";
+
+export default hours => fromMinutes(hours * 60);
diff --git a/date/fromMinutes.js b/date/fromMinutes.js
new file mode 100644
index 00000000..53408542
--- /dev/null
+++ b/date/fromMinutes.js
@@ -0,0 +1,3 @@
+import fromSeconds from "./fromSeconds.js";
+
+export default minutes => fromSeconds(minutes * 60);
diff --git a/date/fromSeconds.js b/date/fromSeconds.js
new file mode 100644
index 00000000..c0770d78
--- /dev/null
+++ b/date/fromSeconds.js
@@ -0,0 +1 @@
+export default seconds => seconds * 1000;
diff --git a/date/index.js b/date/index.js
new file mode 100644
index 00000000..bdaf6cbc
--- /dev/null
+++ b/date/index.js
@@ -0,0 +1,117 @@
+import byDateWithFallback from "./byDateWithFallback.js";
+import clamp from "./clamp.js";
+import dateDiff from "./dateDiff.js";
+import dateInRange from "./dateInRange.js";
+import dayRange from "./dayRange.js";
+import daysInMonths from "./daysInMonths.js";
+import daysInYear from "./daysInYear.js";
+import displayMonth from "./displayMonth.js";
+import displayTime from "./displayTime.js";
+import endOfDay from "./endOfDay.js";
+import formatDate from "./formatDate.js";
+import formatDateTime from "./formatDateTime.js";
+import formatDisplayDate from "./formatDisplayDate.js";
+import formatDuration from "./formatDuration.js";
+import formatTime from "./formatTime.js";
+import fromDays from "./fromDays.js";
+import fromHours from "./fromHours.js";
+import fromMinutes from "./fromMinutes.js";
+import fromSeconds from "./fromSeconds.js";
+import joinDateTime from "./joinDateTime.js";
+import leapYear from "./leapYear.js";
+import monthNames from "./monthNames.js";
+import offsetByBit from "./offsetByBit.js";
+import parseHourMinutePair from "./parseHourMinutePair.js";
+import splitDateTime from "./splitDateTime.js";
+import startOfDay from "./startOfDay.js";
+import subtractDays from "./subtractDays.js";
+import toDate from "./toDate.js";
+import toDates from "./toDates.js";
+import toDays from "./toDays.js";
+import toHours from "./toHours.js";
+import toISO from "./toISO.js";
+import toISOFromLocalDateTime from "./toISOFromLocalDateTime.js";
+import toLocalDateTime from "./toLocalDateTime.js";
+import toMinutes from "./toMinutes.js";
+import toSeconds from "./toSeconds.js";
+import valid from "./valid.js";
+
+export {
+ byDateWithFallback,
+ clamp,
+ dateDiff,
+ dateInRange,
+ dayRange,
+ daysInMonths,
+ daysInYear,
+ displayMonth,
+ displayTime,
+ endOfDay,
+ formatDate,
+ formatDateTime,
+ formatDisplayDate,
+ formatDuration,
+ formatTime,
+ fromDays,
+ fromHours,
+ fromMinutes,
+ fromSeconds,
+ joinDateTime,
+ leapYear,
+ monthNames,
+ offsetByBit,
+ parseHourMinutePair,
+ splitDateTime,
+ startOfDay,
+ subtractDays,
+ toDate,
+ toDates,
+ toDays,
+ toHours,
+ toISO,
+ toISOFromLocalDateTime,
+ toLocalDateTime,
+ toMinutes,
+ toSeconds,
+ valid
+};
+
+export default {
+ byDateWithFallback,
+ clamp,
+ dateDiff,
+ dateInRange,
+ dayRange,
+ daysInMonths,
+ daysInYear,
+ displayMonth,
+ displayTime,
+ endOfDay,
+ formatDate,
+ formatDateTime,
+ formatDisplayDate,
+ formatDuration,
+ formatTime,
+ fromDays,
+ fromHours,
+ fromMinutes,
+ fromSeconds,
+ joinDateTime,
+ leapYear,
+ monthNames,
+ offsetByBit,
+ parseHourMinutePair,
+ splitDateTime,
+ startOfDay,
+ subtractDays,
+ toDate,
+ toDates,
+ toDays,
+ toHours,
+ toISO,
+ toISOFromLocalDateTime,
+ toLocalDateTime,
+ toMinutes,
+ toSeconds,
+ valid
+};
diff --git a/date/joinDateTime.js b/date/joinDateTime.js
new file mode 100644
index 00000000..328ef7c9
--- /dev/null
+++ b/date/joinDateTime.js
@@ -0,0 +1 @@
+export default (...xs) => xs.join("T");
diff --git a/date/leapYear.js b/date/leapYear.js
new file mode 100644
index 00000000..62ab3314
--- /dev/null
+++ b/date/leapYear.js
@@ -0,0 +1 @@
+export default year => new Date(year, 1, 29).getMonth() === 1;
diff --git a/date/monthNames.js b/date/monthNames.js
new file mode 100644
index 00000000..c870f165
--- /dev/null
+++ b/date/monthNames.js
@@ -0,0 +1,14 @@
+export default [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December"
+];
diff --git a/date/offsetByBit.js b/date/offsetByBit.js
new file mode 100644
index 00000000..6df9dd14
--- /dev/null
+++ b/date/offsetByBit.js
@@ -0,0 +1,5 @@
+import fromSeconds from "./fromSeconds";
+
+const SECOND = fromSeconds(1);
+
+export default date => new Date(date - SECOND);
diff --git a/date/parseHourMinutePair.js b/date/parseHourMinutePair.js
new file mode 100644
index 00000000..5862c201
--- /dev/null
+++ b/date/parseHourMinutePair.js
@@ -0,0 +1,6 @@
+export default (text = "") => {
+ const [hoursString, minutesString] = text.split(":");
+ const hours = parseInt(hoursString || 0, 10);
+ const minutes = parseInt(minutesString || 0, 10);
+ return [hours, minutes];
+};
diff --git a/date/splitDateTime.js b/date/splitDateTime.js
new file mode 100644
index 00000000..0b871afb
--- /dev/null
+++ b/date/splitDateTime.js
@@ -0,0 +1 @@
+export default dateTimeString => dateTimeString.split("T");
diff --git a/date/startOfDay.js b/date/startOfDay.js
new file mode 100644
index 00000000..0e9d3395
--- /dev/null
+++ b/date/startOfDay.js
@@ -0,0 +1,9 @@
+import toLocalDateTime from "./toLocalDateTime.js";
+
+export default (date, timezoneOffset = 0, local = true) => {
+ const newDate = new Date(date);
+ newDate.setHours(0, 0, 0, 0);
+ return local
+ ? toLocalDateTime(newDate, timezoneOffset + newDate.getTimezoneOffset())
+ : newDate;
+};
diff --git a/date/subtractDays.js b/date/subtractDays.js
new file mode 100644
index 00000000..3dbbb505
--- /dev/null
+++ b/date/subtractDays.js
@@ -0,0 +1,7 @@
+export default (sourceDate, numberOfDays) => {
+ const date = new Date(sourceDate);
+
+ date.setDate(date.getDate() - numberOfDays);
+
+ return date;
+};
diff --git a/date/toDate.js b/date/toDate.js
new file mode 100644
index 00000000..04b7bb21
--- /dev/null
+++ b/date/toDate.js
@@ -0,0 +1,11 @@
+export default date => {
+ const day = date.getDate();
+ const month = date.getMonth() + 1;
+ const year = date.getFullYear();
+
+ return [
+ `${year}`.padStart(4, "0"),
+ `${month}`.padStart(2, "0"),
+ `${day}`.padStart(2, "0")
+ ].join("-");
+};
diff --git a/date/toDates.js b/date/toDates.js
new file mode 100644
index 00000000..8fb2e180
--- /dev/null
+++ b/date/toDates.js
@@ -0,0 +1 @@
+export default xs => xs.map(x => new Date(x));
diff --git a/date/toDays.js b/date/toDays.js
new file mode 100644
index 00000000..ba5e7a90
--- /dev/null
+++ b/date/toDays.js
@@ -0,0 +1,3 @@
+import toHours from "./toHours.js";
+
+export default milliseconds => toHours(milliseconds) / 24;
diff --git a/date/toHours.js b/date/toHours.js
new file mode 100644
index 00000000..5ed4eadf
--- /dev/null
+++ b/date/toHours.js
@@ -0,0 +1,3 @@
+import toMinutes from "./toMinutes.js";
+
+export default milliseconds => toMinutes(milliseconds) / 60;
diff --git a/date/toISO.js b/date/toISO.js
new file mode 100644
index 00000000..9ac56e00
--- /dev/null
+++ b/date/toISO.js
@@ -0,0 +1 @@
+export default x => x.toISOString();
diff --git a/date/toISOFromLocalDateTime.js b/date/toISOFromLocalDateTime.js
new file mode 100644
index 00000000..6d8480d3
--- /dev/null
+++ b/date/toISOFromLocalDateTime.js
@@ -0,0 +1,6 @@
+import fromMinutes from "./fromMinutes.js";
+
+export default date =>
+ new Date(
+ date.valueOf() - fromMinutes(date.getTimezoneOffset())
+ ).toISOString();
diff --git a/date/toLocalDateTime.js b/date/toLocalDateTime.js
new file mode 100644
index 00000000..90379f1a
--- /dev/null
+++ b/date/toLocalDateTime.js
@@ -0,0 +1,4 @@
+import fromMinutes from "./fromMinutes.js";
+
+export default (date, timezoneOffset = 0) =>
+ new Date(date.valueOf() - fromMinutes(timezoneOffset));
diff --git a/date/toMinutes.js b/date/toMinutes.js
new file mode 100644
index 00000000..30cfa8ae
--- /dev/null
+++ b/date/toMinutes.js
@@ -0,0 +1,3 @@
+import toSeconds from "./toSeconds.js";
+
+export default milliseconds => toSeconds(milliseconds) / 60;
diff --git a/date/toSeconds.js b/date/toSeconds.js
new file mode 100644
index 00000000..39f86994
--- /dev/null
+++ b/date/toSeconds.js
@@ -0,0 +1 @@
+export default milliseconds => milliseconds / 1000;
diff --git a/date/valid.js b/date/valid.js
new file mode 100644
index 00000000..dcd6e7fe
--- /dev/null
+++ b/date/valid.js
@@ -0,0 +1 @@
+export default date => date && date instanceof Date && !Number.isNaN(date);
diff --git a/debug/assert.js b/debug/assert.js
new file mode 100644
index 00000000..38668ae7
--- /dev/null
+++ b/debug/assert.js
@@ -0,0 +1,52 @@
+import isNumber from "../is/number.js";
+import isInteger from "../is/integer.js";
+import isByte from "../is/byte.js";
+import isNormal from "../is/normal.js";
+import isString from "../is/string.js";
+import isDefined from "../is/defined.js";
+
+const assert = (condition, callbackOrMessage) => {
+ if (!condition) {
+ if (typeof callbackOrMessage === "function") {
+ callbackOrMessage();
+ } else {
+ throw new TypeError(
+ typeof callbackOrMessage === "string"
+ ? callbackOrMessage
+ : "Assertion failed!"
+ );
+ }
+ }
+};
+
+export const throws = f => {
+ try {
+ f();
+ return undefined;
+ } catch (error) {
+ return error;
+ }
+};
+
+export const assertNumber = x =>
+ assert(isNumber(x), `Value must be a valid number but it is ${typeof x}.`);
+
+export const assertInteger = x =>
+ assertNumber(x) && assert(isInteger(x), "Value must be an integer.");
+
+export const assertByte = x =>
+ assertInteger(x) && assert(isByte(x), "Value must be a byte.");
+
+export const assertNormal = x =>
+ assertNumber(x) &&
+ assert(
+ isNormal(x),
+ `Value must be a number in range of 0 to 1 inclusive but it is ${x}.`
+ );
+
+export const assertString = x => assert(isString(x), "Value must be a string.");
+
+export const assertIsDefined = (x, message = "Value must be defined.") =>
+ assert(isDefined(x), message);
+
+export default assert;
diff --git a/debug/diff.js b/debug/diff.js
new file mode 100644
index 00000000..1aa28eaf
--- /dev/null
+++ b/debug/diff.js
@@ -0,0 +1,85 @@
+import filter from "../object/filter.js";
+import none from "../object/none.js";
+
+import isDefined from "../is/defined.js";
+import isArray from "../is/array.js";
+import isDate from "../is/date.js";
+import isFunction from "../is/function.js";
+import isObject from "../is/object.js";
+
+export const VALUE_CREATED = "+";
+export const VALUE_DELETED = "-";
+export const VALUE_UNCHANGED = "=";
+export const VALUE_UPDATED = "~";
+
+const isValue = x => !isObject(x) && !isArray(x);
+
+const compareValues = (value1, value2) => {
+ if (value1 === value2) {
+ return VALUE_UNCHANGED;
+ }
+
+ if (
+ isDate(value1) &&
+ isDate(value2) &&
+ value1.getTime() === value2.getTime()
+ ) {
+ return VALUE_UNCHANGED;
+ }
+
+ if (!isDefined(value1)) {
+ return VALUE_CREATED;
+ }
+
+ if (!isDefined(value2)) {
+ return VALUE_DELETED;
+ }
+
+ return VALUE_UPDATED;
+};
+
+const diff = (obj1, obj2) => {
+ if (isFunction(obj1) || isFunction(obj2)) {
+ throw "Invalid argument. Function given, object expected.";
+ }
+
+ if (isValue(obj1) || isValue(obj2)) {
+ const comparisonResult = compareValues(obj1, obj2);
+ return comparisonResult !== VALUE_UNCHANGED
+ ? {
+ type: comparisonResult,
+ data: [obj1, obj2]
+ }
+ : null;
+ }
+
+ const result = {};
+
+ for (const key in obj1) {
+ if (isFunction(obj1[key])) {
+ continue;
+ }
+
+ let value2 = undefined;
+
+ if (isDefined(obj2[key])) {
+ value2 = obj2[key];
+ }
+
+ result[key] = diff(obj1[key], value2);
+ }
+
+ for (const key in obj2) {
+ if (isFunction(obj2[key]) || isDefined(result[key])) {
+ continue;
+ }
+
+ result[key] = diff(undefined, obj2[key]);
+ }
+
+ return filter(
+ value => value !== null && !(value && isObject(value) && none(value))
+ )(result);
+};
+
+export default diff;
diff --git a/debug/index.js b/debug/index.js
new file mode 100644
index 00000000..f85332b7
--- /dev/null
+++ b/debug/index.js
@@ -0,0 +1,6 @@
+import assert from "./assert.js";
+import diff from "./diff.js";
+
+export { assert, diff };
+
+export default { assert, diff };
diff --git a/encoding/base64url.js b/encoding/base64url.js
new file mode 100644
index 00000000..b053ef7b
--- /dev/null
+++ b/encoding/base64url.js
@@ -0,0 +1,13 @@
+export const encode = _ =>
+ btoa(_)
+ .replace(/=/g, "")
+ .replace(/\+/g, "-")
+ .replace(/\//g, "_");
+
+export const toBase64Url = base64 =>
+ base64.replace(/\+/g, "-").replace(/\//g, "_");
+
+export const fromBase64Url = base64 =>
+ base64.replace(/-/g, "+").replace(/_/g, "/");
+
+export default { encode };
diff --git a/encoding/index.js b/encoding/index.js
new file mode 100644
index 00000000..78553ab2
--- /dev/null
+++ b/encoding/index.js
@@ -0,0 +1,5 @@
+import base64url from "./base64url.js";
+
+export { base64url };
+
+export default { base64url };
diff --git a/file/index.js b/file/index.js
new file mode 100644
index 00000000..bbddc8ea
--- /dev/null
+++ b/file/index.js
@@ -0,0 +1,5 @@
+import validName from "./validName.js";
+
+export { validName };
+
+export default { validName };
diff --git a/file/validName.js b/file/validName.js
new file mode 100644
index 00000000..57046aac
--- /dev/null
+++ b/file/validName.js
@@ -0,0 +1,5 @@
+export default name => {
+ const forbiddenCharacters = /[<>:"\/\\|?*\x00-\x1F]/g;
+ const forbiddenNames = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
+ return !forbiddenCharacters.test(name) && !forbiddenNames.test(name);
+};
diff --git a/function/compose.js b/function/compose.js
new file mode 100644
index 00000000..5fd7d4cc
--- /dev/null
+++ b/function/compose.js
@@ -0,0 +1 @@
+export default (...fs) => x => fs.reduceRight((x, f) => f(x), x);
diff --git a/function/constant.js b/function/constant.js
new file mode 100644
index 00000000..3487c0dd
--- /dev/null
+++ b/function/constant.js
@@ -0,0 +1 @@
+export default x => () => x;
diff --git a/function/identity.js b/function/identity.js
new file mode 100644
index 00000000..9a800599
--- /dev/null
+++ b/function/identity.js
@@ -0,0 +1 @@
+export default x => x;
diff --git a/function/index.js b/function/index.js
new file mode 100644
index 00000000..cec9a17c
--- /dev/null
+++ b/function/index.js
@@ -0,0 +1,39 @@
+import compose from "./compose.js";
+import constant from "./constant.js";
+import identity from "./identity.js";
+import memoize from "./memoize.js";
+import memoizeShallow from "./memoizeShallow.js";
+import memoizeWith from "./memoizeWith.js";
+import noOp from "./noOp.js";
+import not from "./not.js";
+import pipe from "./pipe.js";
+import when from "./when.js";
+import whenTrue from "./whenTrue.js";
+
+export {
+ compose,
+ constant,
+ identity,
+ memoize,
+ memoizeShallow,
+ memoizeWith,
+ noOp,
+ not,
+ pipe,
+ when,
+ whenTrue
+};
+
+export default {
+ compose,
+ constant,
+ identity,
+ memoize,
+ memoizeShallow,
+ memoizeWith,
+ noOp,
+ not,
+ pipe,
+ when,
+ whenTrue
+};
diff --git a/function/memoize.js b/function/memoize.js
new file mode 100644
index 00000000..4ef07411
--- /dev/null
+++ b/function/memoize.js
@@ -0,0 +1,4 @@
+import equals from "../object/equals.js";
+import memoizeWith from "./memoizeWith.js";
+
+export default memoizeWith(equals);
diff --git a/function/memoizeShallow.js b/function/memoizeShallow.js
new file mode 100644
index 00000000..31528d8d
--- /dev/null
+++ b/function/memoizeShallow.js
@@ -0,0 +1,6 @@
+import memoizeWith from "./memoizeWith";
+
+const equalsShallow = (xs, ys) =>
+ xs.length === ys.length && xs.every((x, index) => x === ys[index]);
+
+export default memoizeWith(equalsShallow);
diff --git a/function/memoizeWith.js b/function/memoizeWith.js
new file mode 100644
index 00000000..83481bb0
--- /dev/null
+++ b/function/memoizeWith.js
@@ -0,0 +1,15 @@
+export default equals => f => {
+ let memoized = undefined;
+ let memoizedArgs = undefined;
+
+ return (...args) => {
+ if (memoized && equals(args, memoizedArgs)) {
+ return memoized;
+ }
+
+ memoized = f(...args);
+ memoizedArgs = args;
+
+ return memoized;
+ };
+};
diff --git a/function/noOp.js b/function/noOp.js
new file mode 100644
index 00000000..2d1ec238
--- /dev/null
+++ b/function/noOp.js
@@ -0,0 +1 @@
+export default () => {};
diff --git a/function/not.js b/function/not.js
new file mode 100644
index 00000000..c3411fce
--- /dev/null
+++ b/function/not.js
@@ -0,0 +1 @@
+export default f => (...args) => !f(...args);
diff --git a/function/pipe.js b/function/pipe.js
new file mode 100644
index 00000000..57e1e197
--- /dev/null
+++ b/function/pipe.js
@@ -0,0 +1 @@
+export default (...fs) => x => fs.reduce((x, f) => f(x), x);
diff --git a/function/when.js b/function/when.js
new file mode 100644
index 00000000..c5212ead
--- /dev/null
+++ b/function/when.js
@@ -0,0 +1,5 @@
+export default predicate => action => (...args) => {
+ if (predicate(...args)) {
+ return action(...args);
+ }
+};
diff --git a/function/whenTrue.js b/function/whenTrue.js
new file mode 100644
index 00000000..0870c5cb
--- /dev/null
+++ b/function/whenTrue.js
@@ -0,0 +1,3 @@
+import when from "./when.js";
+
+export default when(x => x === true);
diff --git a/index.js b/index.js
new file mode 100644
index 00000000..a908ee7f
--- /dev/null
+++ b/index.js
@@ -0,0 +1,54 @@
+import array from "./array/index.js";
+import async from "./async/index.js";
+import date from "./date/index.js";
+import debug from "./debug/index.js";
+import encoding from "./encoding/index.js";
+import file from "./file/index.js";
+import _function from "./function/index.js";
+import is from "./is/index.js";
+import math from "./math/index.js";
+import object from "./object/index.js";
+import query from "./query/index.js";
+import range from "./range/index.js";
+import regex from "./regex/index.js";
+import string from "./string/index.js";
+import vector2 from "./vector2/index.js";
+import web from "./web/index.js";
+
+export {
+ array,
+ async,
+ date,
+ debug,
+ encoding,
+ file,
+ _function,
+ is,
+ math,
+ object,
+ query,
+ range,
+ regex,
+ string,
+ vector2,
+ web
+};
+
+export default {
+ array,
+ async,
+ date,
+ debug,
+ encoding,
+ file,
+ _function,
+ is,
+ math,
+ object,
+ query,
+ range,
+ regex,
+ string,
+ vector2,
+ web
+};
diff --git a/is/array.js b/is/array.js
new file mode 100644
index 00000000..11c376b1
--- /dev/null
+++ b/is/array.js
@@ -0,0 +1 @@
+export default x => Array.isArray(x);
diff --git a/is/byte.js b/is/byte.js
new file mode 100644
index 00000000..b25231df
--- /dev/null
+++ b/is/byte.js
@@ -0,0 +1,3 @@
+import integer from "./integer.js";
+
+export default x => integer(x) && x >= 0 && x <= 255;
diff --git a/is/date.js b/is/date.js
new file mode 100644
index 00000000..7d89cb83
--- /dev/null
+++ b/is/date.js
@@ -0,0 +1 @@
+export default _ => ({}.toString.apply(x) === "[object Date]");
diff --git a/is/defined.js b/is/defined.js
new file mode 100644
index 00000000..23ddccd1
--- /dev/null
+++ b/is/defined.js
@@ -0,0 +1 @@
+export default x => x !== undefined;
diff --git a/is/function.js b/is/function.js
new file mode 100644
index 00000000..e136d4cc
--- /dev/null
+++ b/is/function.js
@@ -0,0 +1 @@
+export default x => typeof x === "function";
diff --git a/is/index.js b/is/index.js
new file mode 100644
index 00000000..2880557e
--- /dev/null
+++ b/is/index.js
@@ -0,0 +1,36 @@
+import array from "./array.js";
+import byte from "./byte.js";
+import date from "./date.js";
+import defined from "./defined.js";
+import _function from "./function.js";
+import integer from "./integer.js";
+import normal from "./normal.js";
+import number from "./number.js";
+import object from "./object.js";
+import string from "./string.js";
+
+export {
+ array,
+ byte,
+ date,
+ defined,
+ _function,
+ integer,
+ normal,
+ number,
+ object,
+ string
+};
+
+export default {
+ array,
+ byte,
+ date,
+ defined,
+ _function,
+ integer,
+ normal,
+ number,
+ object,
+ string
+};
diff --git a/is/integer.js b/is/integer.js
new file mode 100644
index 00000000..b79db0fd
--- /dev/null
+++ b/is/integer.js
@@ -0,0 +1,3 @@
+import number from "./number.js";
+
+export default x => number(x) && Math.floor(x) === x;
diff --git a/is/normal.js b/is/normal.js
new file mode 100644
index 00000000..284b2b93
--- /dev/null
+++ b/is/normal.js
@@ -0,0 +1,3 @@
+import number from "./number.js";
+
+export default x => number(x) && x >= 0 && x <= 1;
diff --git a/is/number.js b/is/number.js
new file mode 100644
index 00000000..71557dcf
--- /dev/null
+++ b/is/number.js
@@ -0,0 +1 @@
+export default x => typeof x === "number" && !Number.isNaN(x);
diff --git a/is/object.js b/is/object.js
new file mode 100644
index 00000000..0f50c776
--- /dev/null
+++ b/is/object.js
@@ -0,0 +1 @@
+export default x => ({}.toString.apply(x) === "[object Object]");
diff --git a/is/string.js b/is/string.js
new file mode 100644
index 00000000..a330f049
--- /dev/null
+++ b/is/string.js
@@ -0,0 +1 @@
+export default x => typeof x === "string";
diff --git a/math/add.js b/math/add.js
new file mode 100644
index 00000000..fcbea415
--- /dev/null
+++ b/math/add.js
@@ -0,0 +1 @@
+export default (a, b) => a + b;
diff --git a/math/average.js b/math/average.js
new file mode 100644
index 00000000..a75f2b0f
--- /dev/null
+++ b/math/average.js
@@ -0,0 +1,3 @@
+import sum from "../array/sum.js";
+
+export default xs => sum(xs) / xs.length;
diff --git a/math/ceilToNearestPowerOfTwo.js b/math/ceilToNearestPowerOfTwo.js
new file mode 100644
index 00000000..8f367cf6
--- /dev/null
+++ b/math/ceilToNearestPowerOfTwo.js
@@ -0,0 +1 @@
+export default x => Math.pow(2, Math.ceil(Math.log(x) / Math.log(2)));
diff --git a/math/clamp.js b/math/clamp.js
new file mode 100644
index 00000000..a1ca7f90
--- /dev/null
+++ b/math/clamp.js
@@ -0,0 +1 @@
+export default (min, max) => x => Math.max(min, Math.min(max, x));
diff --git a/math/clampNormal.js b/math/clampNormal.js
new file mode 100644
index 00000000..5be86e87
--- /dev/null
+++ b/math/clampNormal.js
@@ -0,0 +1,3 @@
+import clamp from "./clamp.js";
+
+export default clamp(0, 1);
diff --git a/math/clampPercentage.js b/math/clampPercentage.js
new file mode 100644
index 00000000..d331be17
--- /dev/null
+++ b/math/clampPercentage.js
@@ -0,0 +1,3 @@
+import clamp from "./clamp.js";
+
+export default clamp(0, 100);
diff --git a/math/delta.js b/math/delta.js
new file mode 100644
index 00000000..0f32a1e9
--- /dev/null
+++ b/math/delta.js
@@ -0,0 +1 @@
+export default (a, b) => Math.abs(a - b);
diff --git a/math/inRectangleRange.js b/math/inRectangleRange.js
new file mode 100644
index 00000000..690b6a9d
--- /dev/null
+++ b/math/inRectangleRange.js
@@ -0,0 +1,2 @@
+export default (width, height) => (x, y) =>
+ x >= 0 && x <= width && y >= 0 && y <= height;
diff --git a/math/index.js b/math/index.js
new file mode 100644
index 00000000..596c8fe1
--- /dev/null
+++ b/math/index.js
@@ -0,0 +1,54 @@
+import add from "./add.js";
+import average from "./average.js";
+import ceilToNearestPowerOfTwo from "./ceilToNearestPowerOfTwo.js";
+import clamp from "./clamp.js";
+import clampNormal from "./clampNormal.js";
+import clampPercentage from "./clampPercentage.js";
+import delta from "./delta.js";
+import inRectangleRange from "./inRectangleRange.js";
+import lerp from "./lerp.js";
+import maximumBy from "./maximumBy.js";
+import median from "./median.js";
+import minMax from "./minMax.js";
+import safeNormalize from "./safeNormalize.js";
+import sameSign from "./sameSign.js";
+import standardDeviation from "./standardDeviation.js";
+import subtract from "./subtract.js";
+
+export {
+ add,
+ average,
+ ceilToNearestPowerOfTwo,
+ clamp,
+ clampNormal,
+ clampPercentage,
+ delta,
+ inRectangleRange,
+ lerp,
+ maximumBy,
+ median,
+ minMax,
+ safeNormalize,
+ sameSign,
+ standardDeviation,
+ subtract
+};
+
+export default {
+ add,
+ average,
+ ceilToNearestPowerOfTwo,
+ clamp,
+ clampNormal,
+ clampPercentage,
+ delta,
+ inRectangleRange,
+ lerp,
+ maximumBy,
+ median,
+ minMax,
+ safeNormalize,
+ sameSign,
+ standardDeviation,
+ subtract
+};
diff --git a/math/lerp.js b/math/lerp.js
new file mode 100644
index 00000000..82ee6796
--- /dev/null
+++ b/math/lerp.js
@@ -0,0 +1 @@
+export default t => (a, b) => a * t + b * (1 - t);
diff --git a/math/maximumBy.js b/math/maximumBy.js
new file mode 100644
index 00000000..00f7e73b
--- /dev/null
+++ b/math/maximumBy.js
@@ -0,0 +1,2 @@
+export default f => xs =>
+ xs.reduce((acc, curr) => (f(curr) > f(acc) ? curr : acc));
diff --git a/math/median.js b/math/median.js
new file mode 100644
index 00000000..4a4e6fad
--- /dev/null
+++ b/math/median.js
@@ -0,0 +1,13 @@
+import sort from "../array/sort.js";
+import subtract from "./subtract.js";
+
+export default xs => {
+ const sorted = sort(subtract)(xs);
+ const middle = Math.floor(sorted.length / 2);
+
+ if (sorted.length % 2 === 0) {
+ return (sorted[middle - 1] + sorted[middle]) / 2;
+ }
+
+ return sorted[middle];
+};
diff --git a/math/minMax.js b/math/minMax.js
new file mode 100644
index 00000000..e5b943ce
--- /dev/null
+++ b/math/minMax.js
@@ -0,0 +1 @@
+export default ([a, b]) => (a > b ? [b, a] : [a, b]);
diff --git a/math/safeNormalize.js b/math/safeNormalize.js
new file mode 100644
index 00000000..cc7a2b57
--- /dev/null
+++ b/math/safeNormalize.js
@@ -0,0 +1 @@
+export default x => (x !== 0 ? x / Math.abs(x) : 0);
diff --git a/math/sameSign.js b/math/sameSign.js
new file mode 100644
index 00000000..ab69865e
--- /dev/null
+++ b/math/sameSign.js
@@ -0,0 +1,9 @@
+const filterOutZeros = xs => xs.filter(_ => _ !== 0);
+
+export default xs => {
+ const filteredXs = filterOutZeros(xs);
+
+ return (
+ Math.abs(filteredXs.map(safeNormalize).reduce(add, 0)) === filteredXs.length
+ );
+};
diff --git a/math/standardDeviation.js b/math/standardDeviation.js
new file mode 100644
index 00000000..3202d84b
--- /dev/null
+++ b/math/standardDeviation.js
@@ -0,0 +1,8 @@
+export default (xs, origin = average(data)) => {
+ const sumOfSquareDifferences = xs.reduce(
+ (squareDiffs, x) => squareDiffs + Math.pow(x - origin, 2),
+ 0
+ );
+
+ return Math.sqrt(sumOfSquareDifferences / (xs.length - 1));
+};
diff --git a/math/subtract.js b/math/subtract.js
new file mode 100644
index 00000000..ae5f7e86
--- /dev/null
+++ b/math/subtract.js
@@ -0,0 +1 @@
+export default (a, b) => a - b;
diff --git a/object/any.js b/object/any.js
new file mode 100644
index 00000000..0d0edf9b
--- /dev/null
+++ b/object/any.js
@@ -0,0 +1,3 @@
+import length from "./length.js";
+
+export default xs => xs && length(xs) > 0;
diff --git a/object/apply.js b/object/apply.js
new file mode 100644
index 00000000..93526e28
--- /dev/null
+++ b/object/apply.js
@@ -0,0 +1,5 @@
+import entries from "./entries.js";
+import fromEntries from "./fromEntries.js";
+
+export default fs => (...xs) =>
+ fromEntries(entries(fs).map(([key, value]) => [key, value(...xs)]));
diff --git a/object/empty.js b/object/empty.js
new file mode 100644
index 00000000..ff8b4c56
--- /dev/null
+++ b/object/empty.js
@@ -0,0 +1 @@
+export default {};
diff --git a/object/entries.js b/object/entries.js
new file mode 100644
index 00000000..dc4dd62e
--- /dev/null
+++ b/object/entries.js
@@ -0,0 +1,2 @@
+export default Object.entries ||
+ (object => Object.keys(object).map(key => [key, object[key]]));
diff --git a/object/enumerable.js b/object/enumerable.js
new file mode 100644
index 00000000..a1e0d87a
--- /dev/null
+++ b/object/enumerable.js
@@ -0,0 +1,2 @@
+export default (...xs) =>
+ xs.reduce((acc, curr) => ({ ...acc, [curr]: curr }), {});
diff --git a/object/equals.js b/object/equals.js
new file mode 100644
index 00000000..b5d64c4e
--- /dev/null
+++ b/object/equals.js
@@ -0,0 +1,29 @@
+import isObject from "../is/object.js";
+import areArrays from "../array/are.js";
+import lengthDiffers from "../array/lengthDiffers.js";
+
+const keySet = (a, b) => [...new Set([...keys(a), ...keys(b)])];
+
+export const equalsDeep = (a, b) => {
+ if (areArrays(a, b)) {
+ return (
+ !lengthDiffers(a, b) && a.every(a, (_, key) => equalsDeep(_, b[key]))
+ );
+ }
+ return isObject(a) && isObject(b)
+ ? a === b || keySet(a, b).every(key => equalsDeep(a[key], b[key]))
+ : a === b;
+};
+
+export const equalsDeepWith = f => (a, b) => {
+ if (areArrays(a, b)) {
+ return (
+ !lengthDiffers(a, b) && a.every((_, key) => equalsDeepWith(f)(_, b[key]))
+ );
+ }
+ return isObject(a) && isObject(b)
+ ? a === b || keySet(a, b).every(key => equalsDeepWith(f)(a[key], b[key]))
+ : f(a, b);
+};
+
+export default equalsDeep;
diff --git a/object/filter.js b/object/filter.js
new file mode 100644
index 00000000..9209dcae
--- /dev/null
+++ b/object/filter.js
@@ -0,0 +1,5 @@
+import entries from "./entries.js";
+import fromEntries from "./fromEntries.js";
+
+export default f => xs =>
+ fromEntries(entries(xs).filter(([key, value]) => f(value, key, xs)));
diff --git a/object/find.js b/object/find.js
new file mode 100644
index 00000000..bbf04ad0
--- /dev/null
+++ b/object/find.js
@@ -0,0 +1,4 @@
+import entries from "./entries.js";
+
+export default predicate => xs =>
+ entries(xs).find(([key, value]) => predicate(value, key, xs));
diff --git a/object/findKey.js b/object/findKey.js
new file mode 100644
index 00000000..0947503b
--- /dev/null
+++ b/object/findKey.js
@@ -0,0 +1 @@
+export default predicate => xs => (find(predicate)(xs) || [])[0];
diff --git a/object/findValue.js b/object/findValue.js
new file mode 100644
index 00000000..849893a0
--- /dev/null
+++ b/object/findValue.js
@@ -0,0 +1 @@
+export default predicate => xs => (find(predicate)(xs) || [])[1];
diff --git a/object/first.js b/object/first.js
new file mode 100644
index 00000000..9fafa098
--- /dev/null
+++ b/object/first.js
@@ -0,0 +1 @@
+export default xs => Object.values(xs)[0];
diff --git a/object/flatMapValues.js b/object/flatMapValues.js
new file mode 100644
index 00000000..3318507f
--- /dev/null
+++ b/object/flatMapValues.js
@@ -0,0 +1,5 @@
+import flatMap from "../array/flatMap.js";
+import entries from "./entries.js";
+
+export default f => xs =>
+ flatMap(([key, value]) => f(value, key, xs))(entries(xs));
diff --git a/object/fromEntries.js b/object/fromEntries.js
new file mode 100644
index 00000000..6b566045
--- /dev/null
+++ b/object/fromEntries.js
@@ -0,0 +1,6 @@
+export default Object.fromEntries ||
+ (keyValuePairs =>
+ keyValuePairs.reduce(
+ (acc, [key, value]) => ({ ...acc, [key]: value }),
+ {}
+ ));
diff --git a/object/groupBy.js b/object/groupBy.js
new file mode 100644
index 00000000..25eb4b32
--- /dev/null
+++ b/object/groupBy.js
@@ -0,0 +1,5 @@
+export default selector => xs =>
+ xs.reduce((acc, x) => {
+ const key = selector(x);
+ return { ...acc, [key]: [...(acc[key] || []), x] };
+ }, {});
diff --git a/object/hasKey.js b/object/hasKey.js
new file mode 100644
index 00000000..5d9b2b74
--- /dev/null
+++ b/object/hasKey.js
@@ -0,0 +1,2 @@
+export default key => xs =>
+ xs ? Object.prototype.hasOwnProperty.call(xs, key) : false;
diff --git a/object/index.js b/object/index.js
new file mode 100644
index 00000000..ed337bb8
--- /dev/null
+++ b/object/index.js
@@ -0,0 +1,72 @@
+import any from "./any.js";
+import apply from "./apply.js";
+import empty from "./empty.js";
+import entries from "./entries.js";
+import enumerable from "./enumerable.js";
+import equals from "./equals.js";
+import filter from "./filter.js";
+import find from "./find.js";
+import findKey from "./findKey.js";
+import findValue from "./findValue.js";
+import first from "./first.js";
+import flatMapValues from "./flatMapValues.js";
+import fromEntries from "./fromEntries.js";
+import groupBy from "./groupBy.js";
+import hasKey from "./hasKey.js";
+import length from "./length.js";
+import map from "./map.js";
+import mapEntries from "./mapEntries.js";
+import mapKeys from "./mapKeys.js";
+import mapValues from "./mapValues.js";
+import none from "./none.js";
+import sort from "./sort.js";
+
+export {
+ any,
+ apply,
+ empty,
+ entries,
+ enumerable,
+ equals,
+ filter,
+ find,
+ findKey,
+ findValue,
+ first,
+ flatMapValues,
+ fromEntries,
+ groupBy,
+ hasKey,
+ length,
+ map,
+ mapEntries,
+ mapKeys,
+ mapValues,
+ none,
+ sort
+};
+
+export default {
+ any,
+ apply,
+ empty,
+ entries,
+ enumerable,
+ equals,
+ filter,
+ find,
+ findKey,
+ findValue,
+ first,
+ flatMapValues,
+ fromEntries,
+ groupBy,
+ hasKey,
+ length,
+ map,
+ mapEntries,
+ mapKeys,
+ mapValues,
+ none,
+ sort
+};
diff --git a/object/length.js b/object/length.js
new file mode 100644
index 00000000..186433f4
--- /dev/null
+++ b/object/length.js
@@ -0,0 +1 @@
+export default xs => Object.keys(xs).length;
diff --git a/object/map.js b/object/map.js
new file mode 100644
index 00000000..8590e36b
--- /dev/null
+++ b/object/map.js
@@ -0,0 +1,4 @@
+import fromEntries from "./fromEntries.js";
+import mapEntries from "./mapEntries.js";
+
+export default f => xs => fromEntries(mapEntries(f)(xs));
diff --git a/object/mapEntries.js b/object/mapEntries.js
new file mode 100644
index 00000000..854de72e
--- /dev/null
+++ b/object/mapEntries.js
@@ -0,0 +1,4 @@
+import entries from "./entries.js";
+
+export default f => xs =>
+ entries(xs).map(([key, value]) => [key, f(value, key, xs)]);
diff --git a/object/mapKeys.js b/object/mapKeys.js
new file mode 100644
index 00000000..a8f5465b
--- /dev/null
+++ b/object/mapKeys.js
@@ -0,0 +1,5 @@
+import entries from "./entries.js";
+import fromEntries from "./fromEntries.js";
+
+export default f => xs =>
+ fromEntries(entries(xs).map(([key, value]) => [f(value, key, xs), value]));
diff --git a/object/mapValues.js b/object/mapValues.js
new file mode 100644
index 00000000..8230ab66
--- /dev/null
+++ b/object/mapValues.js
@@ -0,0 +1,3 @@
+import entries from "./entries.js";
+
+export default f => xs => entries(xs).map(([key, value]) => f(value, key, xs));
diff --git a/object/none.js b/object/none.js
new file mode 100644
index 00000000..307b11e6
--- /dev/null
+++ b/object/none.js
@@ -0,0 +1,3 @@
+import any from "./any.js";
+
+export default xs => !any(xs);
diff --git a/object/sort.js b/object/sort.js
new file mode 100644
index 00000000..a52812d3
--- /dev/null
+++ b/object/sort.js
@@ -0,0 +1,5 @@
+import entries from "./entries.js";
+import fromEntries from "./fromEntries.js";
+
+export default f => xs =>
+ fromEntries(sort(([, a], [, b]) => f(a, b))(entries(xs)));
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..a56ea7c2
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,43 @@
+{
+ "name": "@sandstreamdev/std",
+ "version": "0.1.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@types/estree": {
+ "version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "12.7.9",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.9.tgz",
+ "integrity": "sha512-P57oKTJ/vYivL2BCfxCC5tQjlS8qW31pbOL6qt99Yrjm95YdHgNZwjrTTjMBh+C2/y6PXIX4oz253+jUzxKKfQ==",
+ "dev": true
+ },
+ "acorn": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
+ "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
+ "dev": true
+ },
+ "prettier": {
+ "version": "1.18.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
+ "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
+ "dev": true
+ },
+ "rollup": {
+ "version": "1.22.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.22.0.tgz",
+ "integrity": "sha512-x4l4ZrV/Mr/x/jvFTmwROdEAhbZjx16yDRTVSKWh/i4oJDuW2dVEbECT853mybYCz7BAitU8ElGlhx7dNjw3qQ==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "*",
+ "@types/node": "*",
+ "acorn": "^7.1.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..44bbcc25
--- /dev/null
+++ b/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@sandstreamdev/std",
+ "version": "0.1.0",
+ "description": "",
+ "type": "module",
+ "module": "index.js",
+ "main": "index.cjs.js",
+ "browser": "index.umd.js",
+ "sideEffects": false,
+ "scripts": {
+ "build": "npm run regenerate && rollup -c",
+ "prepare": "npm run build",
+ "prettier:check": "npm run prettier -- --check",
+ "prettier:fix": "npm run prettier -- --write",
+ "prettier": "prettier \"**/*.{js,json,md}\"",
+ "regenerate": "node --experimental-modules regenerate.js && npm run prettier:fix"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/sandstreamdev/std.git"
+ },
+ "keywords": [],
+ "author": {
+ "name": "Sandstream Development",
+ "url": "https://www.sandstream.pl/"
+ },
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/sandstreamdev/std/issues"
+ },
+ "homepage": "https://github.com/sandstreamdev/std#readme",
+ "devDependencies": {
+ "prettier": "^1.18.2",
+ "rollup": "^1.22.0"
+ }
+}
diff --git a/query/index.js b/query/index.js
new file mode 100644
index 00000000..558489ce
--- /dev/null
+++ b/query/index.js
@@ -0,0 +1,8 @@
+import parse from "./parse.js";
+import parsePathname from "./parsePathname.js";
+import read from "./read.js";
+import serialize from "./serialize.js";
+
+export { parse, parsePathname, read, serialize };
+
+export default { parse, parsePathname, read, serialize };
diff --git a/query/parse.js b/query/parse.js
new file mode 100644
index 00000000..3e2fb4f6
--- /dev/null
+++ b/query/parse.js
@@ -0,0 +1,17 @@
+import fromEntries from "../object/fromEntries.js";
+import startsWith from "../string/startsWith.js";
+
+const startsWithQuestionMark = startsWith("?");
+
+const queryFromMaybeSearchString = x =>
+ startsWithQuestionMark(x) ? x.substring(1) : x;
+
+export default (xs = "") =>
+ fromEntries(
+ queryFromMaybeSearchString(xs)
+ .split("&")
+ .map(xs => {
+ const [key, value] = xs.split("=");
+ return [key, value !== undefined ? decodeURIComponent(value) : true];
+ })
+ );
diff --git a/query/parsePathname.js b/query/parsePathname.js
new file mode 100644
index 00000000..1924d222
--- /dev/null
+++ b/query/parsePathname.js
@@ -0,0 +1,32 @@
+const extractPath = url => {
+ const match = url.match(/^(https?:)?\/\/[^/]*/);
+ return match == null ? url : url.substring(match[0].length);
+};
+
+export default url => {
+ let pathname = extractPath(url);
+ let search = "";
+ let hash = "";
+
+ const hashIndex = pathname.indexOf("#");
+ if (hashIndex !== -1) {
+ hash = pathname.substring(hashIndex);
+ pathname = pathname.substring(0, hashIndex);
+ }
+
+ const searchIndex = pathname.indexOf("?");
+ if (searchIndex !== -1) {
+ search = pathname.substring(searchIndex);
+ pathname = pathname.substring(0, searchIndex);
+ }
+
+ if (pathname === "") {
+ pathname = "/";
+ }
+
+ return {
+ pathname,
+ search,
+ hash
+ };
+};
diff --git a/query/read.js b/query/read.js
new file mode 100644
index 00000000..799c7496
--- /dev/null
+++ b/query/read.js
@@ -0,0 +1,5 @@
+export default source =>
+ [...new URLSearchParams(source).entries()].reduce(
+ (q, [k, v]) => ({ ...q, ...{ [k]: v } }),
+ {}
+ );
diff --git a/query/serialize.js b/query/serialize.js
new file mode 100644
index 00000000..12f23f5f
--- /dev/null
+++ b/query/serialize.js
@@ -0,0 +1,8 @@
+import entries from "../object/entries.js";
+
+export default (xs = {}) =>
+ entries(xs)
+ .filter(([, value]) => Boolean(value) || value === 0)
+ .map(pair => pair.map(encodeURIComponent))
+ .reduce((acc, [key, value]) => [...acc, `${key}=${value}`], [])
+ .join("&");
diff --git a/range/empty.js b/range/empty.js
new file mode 100644
index 00000000..71065567
--- /dev/null
+++ b/range/empty.js
@@ -0,0 +1 @@
+export default ([min, max]) => min === max;
diff --git a/range/equals.js b/range/equals.js
new file mode 100644
index 00000000..ab5c1860
--- /dev/null
+++ b/range/equals.js
@@ -0,0 +1 @@
+export default ([a, b], [c, d]) => a === c && b === d;
diff --git a/range/index.js b/range/index.js
new file mode 100644
index 00000000..a0e7a341
--- /dev/null
+++ b/range/index.js
@@ -0,0 +1,8 @@
+import empty from "./empty.js";
+import equals from "./equals.js";
+import length from "./length.js";
+import split from "./split.js";
+
+export { empty, equals, length, split };
+
+export default { empty, equals, length, split };
diff --git a/range/length.js b/range/length.js
new file mode 100644
index 00000000..3961ffcd
--- /dev/null
+++ b/range/length.js
@@ -0,0 +1 @@
+export default ([min, max]) => max - min;
diff --git a/range/split.js b/range/split.js
new file mode 100644
index 00000000..05ed309d
--- /dev/null
+++ b/range/split.js
@@ -0,0 +1,40 @@
+import isNumber from "../is/number.js";
+import clamp from "../math/clamp.js";
+import empty from "./empty.js";
+
+const split = (used, sourceRange = [-Infinity, Infinity]) => range => {
+ if (empty(range) || !range.every(isNumber)) {
+ return [];
+ }
+
+ if (!used || used.length === 0) {
+ return [range];
+ }
+
+ const [x, ...xs] = used;
+ const [usedMin, usedMax] = x;
+
+ if (empty(x)) {
+ return split(xs, sourceRange)(range);
+ }
+
+ const [sourceMin, sourceMax] = sourceRange;
+ const clampToSourceRange = clamp(...sourceRange);
+
+ const freeLeft = [sourceMin, usedMin].map(clampToSourceRange);
+ const freeRight = [usedMax, sourceMax].map(clampToSourceRange);
+
+ const clampLeft = clamp(...freeLeft);
+ const clampedLeft = range.map(clampLeft);
+
+ const lefts = split(xs, sourceRange)(clampedLeft);
+
+ const clampRight = clamp(...freeRight);
+ const clampedRight = range.map(clampRight);
+
+ const rights = split(xs, sourceRange)(clampedRight);
+
+ return [...lefts, ...rights].filter(range => !empty(range));
+};
+
+export default split;
diff --git a/regenerate.js b/regenerate.js
new file mode 100644
index 00000000..a6c3baaf
--- /dev/null
+++ b/regenerate.js
@@ -0,0 +1,88 @@
+import { promises } from "fs";
+import path from "path";
+
+const { readdir: readDirectoryAsync, writeFile: writeFileAsync } = promises;
+
+const [, , cwd = process.cwd()] = process.argv;
+
+const mapping = {
+ function: "_function"
+};
+
+const ignoredFiles = [
+ ".all-contributorsrc",
+ ".gitignore",
+ ".npmignore",
+ "index.cjs.js",
+ "index.js",
+ "index.umd.js",
+ "LICENSE",
+ "package-lock.json",
+ "package.json",
+ "README.md",
+ "regenerate.js",
+ "rollup.config.js"
+];
+
+const ignoredDirectories = [".git", "node_modules"];
+
+const identifier = name => mapping[name] || name;
+
+const main = async cwd => {
+ console.log(`Indexing files in ${cwd}...`);
+
+ const entries = await readDirectoryAsync(cwd, { withFileTypes: true });
+ const files = entries
+ .filter(x => x.isFile())
+ .map(x => x.name)
+ .filter(x => !ignoredFiles.includes(x));
+ const directories = entries
+ .filter(x => x.isDirectory())
+ .map(x => x.name)
+ .filter(x => !ignoredDirectories.includes(x));
+
+ for (const directory of directories) {
+ await main(path.join(cwd, directory));
+ }
+
+ const submodules = files.map(filePath => {
+ const { base: fileName } = path.parse(filePath);
+ const splitted = fileName.split(".");
+ const id = identifier(splitted.slice(0, splitted.length - 1).join("_"));
+ const extension = `.${splitted[splitted.length - 1]}`;
+
+ return [fileName, id, extension];
+ });
+
+ const dependencies = [
+ ...submodules,
+ ...directories.map(x => [`${x}/index.js`, identifier(x), ""])
+ ];
+
+ const importDeclarations = dependencies
+ .map(([fileName, id]) =>
+ id !== "index" ? `import ${id} from './${fileName}'` : ""
+ )
+ .join("\r\n");
+
+ const exportDeclarationBody = dependencies
+ .map(([, id]) => (id !== "index" ? id : ""))
+ .join(", ");
+
+ const exportDeclaration = `export { ${exportDeclarationBody} }`;
+ const defaultExport = `export default { ${exportDeclarationBody} }`;
+
+ console.log(`Indexed files in ${cwd}:`);
+
+ const moduleContents = [
+ importDeclarations,
+ exportDeclaration,
+ defaultExport
+ ].join("\r\n\r\n");
+
+ console.log(moduleContents);
+
+ await writeFileAsync(path.join(cwd, "index.js"), moduleContents);
+};
+
+main(cwd);
diff --git a/regex/escape.js b/regex/escape.js
new file mode 100644
index 00000000..b9233880
--- /dev/null
+++ b/regex/escape.js
@@ -0,0 +1 @@
+export default string => string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
diff --git a/regex/index.js b/regex/index.js
new file mode 100644
index 00000000..231e89cb
--- /dev/null
+++ b/regex/index.js
@@ -0,0 +1,5 @@
+import escape from "./escape.js";
+
+export { escape };
+
+export default { escape };
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 00000000..3dabf215
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,16 @@
+export default {
+ input: "index.js",
+ output: [
+ {
+ exports: "named",
+ file: "index.cjs.js",
+ format: "cjs"
+ },
+ {
+ exports: "named",
+ file: "index.umd.js",
+ format: "umd",
+ name: "@sandstreamdev/std"
+ }
+ ]
+};
diff --git a/string/containsWhitespace.js b/string/containsWhitespace.js
new file mode 100644
index 00000000..a2a288b0
--- /dev/null
+++ b/string/containsWhitespace.js
@@ -0,0 +1 @@
+export default x => /\s/.test(x);
diff --git a/string/empty.js b/string/empty.js
new file mode 100644
index 00000000..9cf3c27e
--- /dev/null
+++ b/string/empty.js
@@ -0,0 +1 @@
+export default "";
diff --git a/string/firstToLower.js b/string/firstToLower.js
new file mode 100644
index 00000000..0a54fdd4
--- /dev/null
+++ b/string/firstToLower.js
@@ -0,0 +1 @@
+export default ([first, ...rest]) => [first.toLowerCase(), ...rest].join("");
diff --git a/string/firstToUpper.js b/string/firstToUpper.js
new file mode 100644
index 00000000..7b9b4cba
--- /dev/null
+++ b/string/firstToUpper.js
@@ -0,0 +1 @@
+export default ([first, ...rest]) => [first.toUpperCase(), ...rest].join("");
diff --git a/string/includes.js b/string/includes.js
new file mode 100644
index 00000000..e9c27210
--- /dev/null
+++ b/string/includes.js
@@ -0,0 +1 @@
+export default search => xs => xs.indexOf(search) !== -1;
diff --git a/string/index.js b/string/index.js
new file mode 100644
index 00000000..af2f7705
--- /dev/null
+++ b/string/index.js
@@ -0,0 +1,30 @@
+import containsWhitespace from "./containsWhitespace.js";
+import empty from "./empty.js";
+import firstToLower from "./firstToLower.js";
+import firstToUpper from "./firstToUpper.js";
+import includes from "./includes.js";
+import nbsp from "./nbsp.js";
+import nonEmpty from "./nonEmpty.js";
+import startsWith from "./startsWith.js";
+
+export {
+ containsWhitespace,
+ empty,
+ firstToLower,
+ firstToUpper,
+ includes,
+ nbsp,
+ nonEmpty,
+ startsWith
+};
+
+export default {
+ containsWhitespace,
+ empty,
+ firstToLower,
+ firstToUpper,
+ includes,
+ nbsp,
+ nonEmpty,
+ startsWith
+};
diff --git a/string/nbsp.js b/string/nbsp.js
new file mode 100644
index 00000000..f5632c43
--- /dev/null
+++ b/string/nbsp.js
@@ -0,0 +1 @@
+export default "\u00A0";
diff --git a/string/nonEmpty.js b/string/nonEmpty.js
new file mode 100644
index 00000000..a66c5d72
--- /dev/null
+++ b/string/nonEmpty.js
@@ -0,0 +1 @@
+export default x => x && x.trim();
diff --git a/string/startsWith.js b/string/startsWith.js
new file mode 100644
index 00000000..8997cd33
--- /dev/null
+++ b/string/startsWith.js
@@ -0,0 +1 @@
+export default prefix => xs => xs.indexOf(prefix) === 0;
diff --git a/vector2/add.js b/vector2/add.js
new file mode 100644
index 00000000..6995faae
--- /dev/null
+++ b/vector2/add.js
@@ -0,0 +1 @@
+export default ([x1, y1], [x2, y2]) => [x1 + x2, y1 + y2];
diff --git a/vector2/convertSpace.js b/vector2/convertSpace.js
new file mode 100644
index 00000000..7d50c019
--- /dev/null
+++ b/vector2/convertSpace.js
@@ -0,0 +1,6 @@
+import mul from "./mul.js";
+
+export default space => ([x, y]) => {
+ const [outX, outY] = mul(space, [x, y]);
+ return [outX, outY];
+};
diff --git a/vector2/cross.js b/vector2/cross.js
new file mode 100644
index 00000000..327ae4d7
--- /dev/null
+++ b/vector2/cross.js
@@ -0,0 +1 @@
+export default ([a, b], [c, d]) => a * d - b * c;
diff --git a/vector2/dot.js b/vector2/dot.js
new file mode 100644
index 00000000..c18e85fe
--- /dev/null
+++ b/vector2/dot.js
@@ -0,0 +1 @@
+export default ([a, b], [c, d]) => a * c + b * d;
diff --git a/vector2/index.js b/vector2/index.js
new file mode 100644
index 00000000..b88aa8d8
--- /dev/null
+++ b/vector2/index.js
@@ -0,0 +1,48 @@
+import add from "./add.js";
+import convertSpace from "./convertSpace.js";
+import cross from "./cross.js";
+import dot from "./dot.js";
+import length from "./length.js";
+import mul from "./mul.js";
+import multiply from "./multiply.js";
+import normalize from "./normalize.js";
+import reflect from "./reflect.js";
+import rotate from "./rotate.js";
+import scale from "./scale.js";
+import sub from "./sub.js";
+import transform from "./transform.js";
+import translate from "./translate.js";
+
+export {
+ add,
+ convertSpace,
+ cross,
+ dot,
+ length,
+ mul,
+ multiply,
+ normalize,
+ reflect,
+ rotate,
+ scale,
+ sub,
+ transform,
+ translate
+};
+
+export default {
+ add,
+ convertSpace,
+ cross,
+ dot,
+ length,
+ mul,
+ multiply,
+ normalize,
+ reflect,
+ rotate,
+ scale,
+ sub,
+ transform,
+ translate
+};
diff --git a/vector2/length.js b/vector2/length.js
new file mode 100644
index 00000000..ac12d13b
--- /dev/null
+++ b/vector2/length.js
@@ -0,0 +1 @@
+export default ([x, y]) => Math.sqrt(x ** 2 + y ** 2);
diff --git a/vector2/mul.js b/vector2/mul.js
new file mode 100644
index 00000000..7ce615f1
--- /dev/null
+++ b/vector2/mul.js
@@ -0,0 +1,4 @@
+export default (matrix, point) => [
+ matrix.a * point[0] + matrix.c * point[1] + matrix.e,
+ matrix.b * point[0] + matrix.d * point[1] + matrix.f
+];
diff --git a/vector2/multiply.js b/vector2/multiply.js
new file mode 100644
index 00000000..3102465f
--- /dev/null
+++ b/vector2/multiply.js
@@ -0,0 +1,8 @@
+export default (m1, m2) => ({
+ a: m1.a * m2.a + m1.c * m2.b,
+ c: m1.a * m2.c + m1.c * m2.d,
+ e: m1.a * m2.e + m1.c * m2.f + m1.e,
+ b: m1.b * m2.a + m1.d * m2.b,
+ d: m1.b * m2.c + m1.d * m2.d,
+ f: m1.b * m2.e + m1.d * m2.f + m1.f
+});
diff --git a/vector2/normalize.js b/vector2/normalize.js
new file mode 100644
index 00000000..c00359af
--- /dev/null
+++ b/vector2/normalize.js
@@ -0,0 +1,6 @@
+import length from "./length.js";
+
+export default vector => {
+ const magnitude = length(vector);
+ return magnitude !== 0 ? vector.map(_ => _ / magnitude) : vector;
+};
diff --git a/vector2/reflect.js b/vector2/reflect.js
new file mode 100644
index 00000000..6ee82a82
--- /dev/null
+++ b/vector2/reflect.js
@@ -0,0 +1,4 @@
+import sub from "./sub.js";
+import dot from "./dot.js";
+
+export default (a, v) => sub(v, a.map(_ => (_ * 2 * dot(v, a)) / dot(a, a)));
diff --git a/vector2/rotate.js b/vector2/rotate.js
new file mode 100644
index 00000000..2d1dc708
--- /dev/null
+++ b/vector2/rotate.js
@@ -0,0 +1,20 @@
+import transform from "./transform.js";
+import translate from "./translate.js";
+
+const { cos, sin } = Math;
+
+export default (angle = 0, cx = 0, cy = 0) => {
+ const cosAngle = cos(angle);
+ const sinAngle = sin(angle);
+
+ const rotationMatrix = {
+ a: cosAngle,
+ c: -sinAngle,
+ e: 0,
+ b: sinAngle,
+ d: cosAngle,
+ f: 0
+ };
+
+ return transform(translate(cx, cy), rotationMatrix, translate(-cx, -cy));
+};
diff --git a/vector2/scale.js b/vector2/scale.js
new file mode 100644
index 00000000..d4aa6aba
--- /dev/null
+++ b/vector2/scale.js
@@ -0,0 +1,8 @@
+export default (sx = 1, sy = sx) => ({
+ a: sx,
+ c: 0,
+ e: 0,
+ b: 0,
+ d: sy,
+ f: 0
+});
diff --git a/vector2/sub.js b/vector2/sub.js
new file mode 100644
index 00000000..5f40a0c4
--- /dev/null
+++ b/vector2/sub.js
@@ -0,0 +1 @@
+export default ([x1, y1], [x2, y2]) => [x1 - x2, y1 - y2];
diff --git a/vector2/transform.js b/vector2/transform.js
new file mode 100644
index 00000000..0fc27235
--- /dev/null
+++ b/vector2/transform.js
@@ -0,0 +1 @@
+export default (...matrices) => matrices.reduce(multiply);
diff --git a/vector2/translate.js b/vector2/translate.js
new file mode 100644
index 00000000..2c2048ad
--- /dev/null
+++ b/vector2/translate.js
@@ -0,0 +1,8 @@
+export default (tx = 0, ty = 0) => ({
+ a: 1,
+ c: 0,
+ e: tx,
+ b: 0,
+ d: 1,
+ f: ty
+});
diff --git a/web/classNames.js b/web/classNames.js
new file mode 100644
index 00000000..190d8ed5
--- /dev/null
+++ b/web/classNames.js
@@ -0,0 +1,16 @@
+import entries from "../object/entries.js";
+import isString from "../is/string.js";
+
+const booleanKeys = x =>
+ entries(x)
+ .filter(([, value]) => Boolean(value))
+ .map(([key]) => key);
+
+export default (...xs) =>
+ xs
+ .filter(Boolean)
+ .reduce((acc, curr) => {
+ const names = isString(curr) ? [curr] : booleanKeys(curr);
+ return [...acc, ...names];
+ }, [])
+ .join(" ");
diff --git a/web/events/cancel.js b/web/events/cancel.js
new file mode 100644
index 00000000..e2c2c023
--- /dev/null
+++ b/web/events/cancel.js
@@ -0,0 +1,8 @@
+import prevent from "./prevent.js";
+import stop from "./stop.js";
+
+export default event => {
+ prevent(event);
+ stop(event);
+ return false;
+};
diff --git a/web/events/index.js b/web/events/index.js
new file mode 100644
index 00000000..0809d31a
--- /dev/null
+++ b/web/events/index.js
@@ -0,0 +1,8 @@
+import cancel from "./cancel.js";
+import openInNewTabIntent from "./openInNewTabIntent.js";
+import prevent from "./prevent.js";
+import stop from "./stop.js";
+
+export { cancel, openInNewTabIntent, prevent, stop };
+
+export default { cancel, openInNewTabIntent, prevent, stop };
diff --git a/web/events/openInNewTabIntent.js b/web/events/openInNewTabIntent.js
new file mode 100644
index 00000000..bf0f619f
--- /dev/null
+++ b/web/events/openInNewTabIntent.js
@@ -0,0 +1,2 @@
+export default ({ button, ctrlKey, metaKey, shiftKey }) =>
+ ctrlKey || shiftKey || metaKey || button === 1;
diff --git a/web/events/prevent.js b/web/events/prevent.js
new file mode 100644
index 00000000..f475eecb
--- /dev/null
+++ b/web/events/prevent.js
@@ -0,0 +1,4 @@
+export default event => {
+ event.preventDefault();
+ return false;
+};
diff --git a/web/events/stop.js b/web/events/stop.js
new file mode 100644
index 00000000..cadf59cc
--- /dev/null
+++ b/web/events/stop.js
@@ -0,0 +1,4 @@
+export default event => {
+ event.stopPropagation();
+ return false;
+};
diff --git a/web/index.js b/web/index.js
new file mode 100644
index 00000000..e75e558b
--- /dev/null
+++ b/web/index.js
@@ -0,0 +1,6 @@
+import classNames from "./classNames.js";
+import events from "./events/index.js";
+
+export { classNames, events };
+
+export default { classNames, events };