@choksheak/ts-utils 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dateTimeStr.d.ts CHANGED
@@ -1,8 +1,82 @@
1
+ export type AnyDateTime = number | Date | string;
2
+ /**
3
+ * Convert a number (epoch milliseconds), string (parseable date/time), or
4
+ * Date object (no conversion) into a Date object.
5
+ */
6
+ export declare function toDate(ts: AnyDateTime): Date;
7
+ /**
8
+ * Returns a date in yyyy-MM format. E.g. '2000-01'.
9
+ *
10
+ * @param dt Specify a date object or default to the current date.
11
+ * @param separator Defaults to '-'.
12
+ */
13
+ export declare function yyyyMm(dt?: Date, separator?: string): string;
14
+ /**
15
+ * Returns a date in yyyy-MM-dd format. E.g. '2000-01-02'.
16
+ *
17
+ * @param dt Specify a date object or default to the current date.
18
+ * @param separator Defaults to '-'.
19
+ */
1
20
  export declare function yyyyMmDd(dt?: Date, separator?: string): string;
21
+ /**
22
+ * Returns a date in hh:mm format. E.g. '01:02'.
23
+ *
24
+ * @param dt Specify a date object or default to the current date/time.
25
+ * @param separator Defaults to ':'.
26
+ */
27
+ export declare function hhMm(dt?: Date, separator?: string): string;
28
+ /**
29
+ * Returns a date in hh:mm:ss format. E.g. '01:02:03'.
30
+ *
31
+ * @param dt Specify a date object or default to the current date/time.
32
+ * @param separator Defaults to ':'.
33
+ */
2
34
  export declare function hhMmSs(dt?: Date, separator?: string): string;
35
+ /**
36
+ * Returns a date in hh:mm:ss.SSS format. E.g. '01:02:03.004'.
37
+ *
38
+ * @param dt Specify a date object or default to the current date/time.
39
+ * @param timeSeparator Separator for hh/mm/ss. Defaults to ':'.
40
+ * @param msSeparator Separator before SSS. Defaults to '.'.
41
+ */
3
42
  export declare function hhMmSsMs(dt?: Date, timeSeparator?: string, msSeparator?: string): string;
43
+ /**
44
+ * Returns the timezone string for the given date. E.g. '+8', '-3.5'.
45
+ * Returns 'Z' for UTC.
46
+ *
47
+ * @param dt Specify a date object or default to the current date/time.
48
+ */
4
49
  export declare function tzShort(dt?: Date): string;
50
+ /**
51
+ * Returns the long month name, zero-indexed. E.g. 0 for 'January'.
52
+ *
53
+ * @param month Zero-indexed month.
54
+ * @param locales Specify the locale, e.g. 'en-US', new Intl.Locale("en-US").
55
+ */
5
56
  export declare function getLongMonthNameZeroIndexed(month: number, locales?: Intl.LocalesArgument): string;
57
+ /**
58
+ * Returns the long month name, one-indexed. E.g. 1 for 'January'.
59
+ *
60
+ * @param month One-indexed month.
61
+ * @param locales Specify the locale, e.g. 'en-US', new Intl.Locale("en-US").
62
+ */
6
63
  export declare function getLongMonthNameOneIndexed(month: number, locales?: Intl.LocalesArgument): string;
64
+ /**
65
+ * Returns the short month name, zero-indexed. E.g. 0 for 'Jan'.
66
+ *
67
+ * @param month Zero-indexed month.
68
+ * @param locales Specify the locale, e.g. 'en-US', new Intl.Locale("en-US").
69
+ */
7
70
  export declare function getShortMonthNameZeroIndexed(month: number, locales?: Intl.LocalesArgument): string;
71
+ /**
72
+ * Returns the short month name, one-indexed. E.g. 1 for 'Jan'.
73
+ *
74
+ * @param month One-indexed month.
75
+ * @param locales Specify the locale, e.g. 'en-US', new Intl.Locale("en-US").
76
+ */
8
77
  export declare function getShortMonthNameOneIndexed(month: number, locales?: Intl.LocalesArgument): string;
78
+ /**
79
+ * Returns a human-readable string date/time like '2025-01-01 22:31:16Z'.
80
+ * Excludes the milliseconds assuming it is not necessary for display.
81
+ */
82
+ export declare function getDisplayDateTime(ts: AnyDateTime): string;
package/dateTimeStr.js CHANGED
@@ -20,27 +20,43 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/dateTimeStr.ts
21
21
  var dateTimeStr_exports = {};
22
22
  __export(dateTimeStr_exports, {
23
+ getDisplayDateTime: () => getDisplayDateTime,
23
24
  getLongMonthNameOneIndexed: () => getLongMonthNameOneIndexed,
24
25
  getLongMonthNameZeroIndexed: () => getLongMonthNameZeroIndexed,
25
26
  getShortMonthNameOneIndexed: () => getShortMonthNameOneIndexed,
26
27
  getShortMonthNameZeroIndexed: () => getShortMonthNameZeroIndexed,
28
+ hhMm: () => hhMm,
27
29
  hhMmSs: () => hhMmSs,
28
30
  hhMmSsMs: () => hhMmSsMs,
31
+ toDate: () => toDate,
29
32
  tzShort: () => tzShort,
33
+ yyyyMm: () => yyyyMm,
30
34
  yyyyMmDd: () => yyyyMmDd
31
35
  });
32
36
  module.exports = __toCommonJS(dateTimeStr_exports);
33
- function yyyyMmDd(dt = /* @__PURE__ */ new Date(), separator = "-") {
37
+ function toDate(ts) {
38
+ if (typeof ts === "number" || typeof ts === "string") {
39
+ return new Date(ts);
40
+ }
41
+ return ts;
42
+ }
43
+ function yyyyMm(dt = /* @__PURE__ */ new Date(), separator = "-") {
34
44
  const yr = dt.getFullYear();
35
45
  const mth = dt.getMonth() + 1;
46
+ return yr + separator + (mth < 10 ? "0" + mth : mth);
47
+ }
48
+ function yyyyMmDd(dt = /* @__PURE__ */ new Date(), separator = "-") {
36
49
  const day = dt.getDate();
37
- return yr + separator + (mth < 10 ? "0" + mth : mth) + separator + (day < 10 ? "0" + day : day);
50
+ return yyyyMm(dt, separator) + separator + (day < 10 ? "0" + day : day);
38
51
  }
39
- function hhMmSs(dt = /* @__PURE__ */ new Date(), separator = ":") {
52
+ function hhMm(dt = /* @__PURE__ */ new Date(), separator = ":") {
40
53
  const hr = dt.getHours();
41
54
  const min = dt.getMinutes();
55
+ return (hr < 10 ? "0" + hr : hr) + separator + (min < 10 ? "0" + min : min);
56
+ }
57
+ function hhMmSs(dt = /* @__PURE__ */ new Date(), separator = ":") {
42
58
  const sec = dt.getSeconds();
43
- return (hr < 10 ? "0" + hr : hr) + separator + (min < 10 ? "0" + min : min) + separator + (sec < 10 ? "0" + sec : sec);
59
+ return hhMm(dt, separator) + separator + (sec < 10 ? "0" + sec : sec);
44
60
  }
45
61
  function hhMmSsMs(dt = /* @__PURE__ */ new Date(), timeSeparator = ":", msSeparator = ".") {
46
62
  const ms = dt.getMilliseconds();
@@ -69,15 +85,24 @@ function getShortMonthNameZeroIndexed(month, locales = "default") {
69
85
  function getShortMonthNameOneIndexed(month, locales = "default") {
70
86
  return getShortMonthNameZeroIndexed(month - 1, locales);
71
87
  }
88
+ function getDisplayDateTime(ts) {
89
+ const iso = toDate(ts).toISOString();
90
+ const noMs = iso.slice(0, 19) + "Z";
91
+ return noMs.replace("T", " ");
92
+ }
72
93
  // Annotate the CommonJS export names for ESM import in node:
73
94
  0 && (module.exports = {
95
+ getDisplayDateTime,
74
96
  getLongMonthNameOneIndexed,
75
97
  getLongMonthNameZeroIndexed,
76
98
  getShortMonthNameOneIndexed,
77
99
  getShortMonthNameZeroIndexed,
100
+ hhMm,
78
101
  hhMmSs,
79
102
  hhMmSsMs,
103
+ toDate,
80
104
  tzShort,
105
+ yyyyMm,
81
106
  yyyyMmDd
82
107
  });
83
108
  //# sourceMappingURL=dateTimeStr.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/dateTimeStr.ts"],"sourcesContent":["export function yyyyMmDd(dt = new Date(), separator = \"-\"): string {\n const yr = dt.getFullYear();\n const mth = dt.getMonth() + 1;\n const day = dt.getDate();\n\n return (\n yr +\n separator +\n (mth < 10 ? \"0\" + mth : mth) +\n separator +\n (day < 10 ? \"0\" + day : day)\n );\n}\n\nexport function hhMmSs(dt = new Date(), separator = \":\"): string {\n const hr = dt.getHours();\n const min = dt.getMinutes();\n const sec = dt.getSeconds();\n\n return (\n (hr < 10 ? \"0\" + hr : hr) +\n separator +\n (min < 10 ? \"0\" + min : min) +\n separator +\n (sec < 10 ? \"0\" + sec : sec)\n );\n}\n\nexport function hhMmSsMs(\n dt = new Date(),\n timeSeparator = \":\",\n msSeparator = \".\",\n): string {\n const ms = dt.getMilliseconds();\n\n return (\n hhMmSs(dt, timeSeparator) +\n msSeparator +\n (ms < 10 ? \"00\" + ms : ms < 100 ? \"0\" + ms : ms)\n );\n}\n\nexport function tzShort(dt = new Date()): string {\n if (dt.getTimezoneOffset() === 0) {\n return \"Z\";\n }\n\n const tzHours = dt.getTimezoneOffset() / 60;\n return tzHours >= 0 ? \"+\" + tzHours : String(tzHours);\n}\n\nexport function getLongMonthNameZeroIndexed(\n month: number,\n locales: Intl.LocalesArgument = \"default\",\n): string {\n return new Date(2024, month, 15).toLocaleString(locales, {\n month: \"long\",\n });\n}\n\nexport function getLongMonthNameOneIndexed(\n month: number,\n locales: Intl.LocalesArgument = \"default\",\n): string {\n return getLongMonthNameZeroIndexed(month - 1, locales);\n}\n\nexport function getShortMonthNameZeroIndexed(\n month: number,\n locales: Intl.LocalesArgument = \"default\",\n): string {\n return new Date(2000, month, 15).toLocaleString(locales, {\n month: \"short\",\n });\n}\n\nexport function getShortMonthNameOneIndexed(\n month: number,\n locales: Intl.LocalesArgument = \"default\",\n): string {\n return getShortMonthNameZeroIndexed(month - 1, locales);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,SAAS,SAAS,KAAK,oBAAI,KAAK,GAAG,YAAY,KAAa;AACjE,QAAM,KAAK,GAAG,YAAY;AAC1B,QAAM,MAAM,GAAG,SAAS,IAAI;AAC5B,QAAM,MAAM,GAAG,QAAQ;AAEvB,SACE,KACA,aACC,MAAM,KAAK,MAAM,MAAM,OACxB,aACC,MAAM,KAAK,MAAM,MAAM;AAE5B;AAEO,SAAS,OAAO,KAAK,oBAAI,KAAK,GAAG,YAAY,KAAa;AAC/D,QAAM,KAAK,GAAG,SAAS;AACvB,QAAM,MAAM,GAAG,WAAW;AAC1B,QAAM,MAAM,GAAG,WAAW;AAE1B,UACG,KAAK,KAAK,MAAM,KAAK,MACtB,aACC,MAAM,KAAK,MAAM,MAAM,OACxB,aACC,MAAM,KAAK,MAAM,MAAM;AAE5B;AAEO,SAAS,SACd,KAAK,oBAAI,KAAK,GACd,gBAAgB,KAChB,cAAc,KACN;AACR,QAAM,KAAK,GAAG,gBAAgB;AAE9B,SACE,OAAO,IAAI,aAAa,IACxB,eACC,KAAK,KAAK,OAAO,KAAK,KAAK,MAAM,MAAM,KAAK;AAEjD;AAEO,SAAS,QAAQ,KAAK,oBAAI,KAAK,GAAW;AAC/C,MAAI,GAAG,kBAAkB,MAAM,GAAG;AAChC,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,GAAG,kBAAkB,IAAI;AACzC,SAAO,WAAW,IAAI,MAAM,UAAU,OAAO,OAAO;AACtD;AAEO,SAAS,4BACd,OACA,UAAgC,WACxB;AACR,SAAO,IAAI,KAAK,MAAM,OAAO,EAAE,EAAE,eAAe,SAAS;AAAA,IACvD,OAAO;AAAA,EACT,CAAC;AACH;AAEO,SAAS,2BACd,OACA,UAAgC,WACxB;AACR,SAAO,4BAA4B,QAAQ,GAAG,OAAO;AACvD;AAEO,SAAS,6BACd,OACA,UAAgC,WACxB;AACR,SAAO,IAAI,KAAK,KAAM,OAAO,EAAE,EAAE,eAAe,SAAS;AAAA,IACvD,OAAO;AAAA,EACT,CAAC;AACH;AAEO,SAAS,4BACd,OACA,UAAgC,WACxB;AACR,SAAO,6BAA6B,QAAQ,GAAG,OAAO;AACxD;","names":[]}
1
+ {"version":3,"sources":["../src/dateTimeStr.ts"],"sourcesContent":["export type AnyDateTime = number | Date | string;\n\n/**\n * Convert a number (epoch milliseconds), string (parseable date/time), or\n * Date object (no conversion) into a Date object.\n */\nexport function toDate(ts: AnyDateTime): Date {\n if (typeof ts === \"number\" || typeof ts === \"string\") {\n return new Date(ts);\n }\n\n return ts;\n}\n\n/**\n * Returns a date in yyyy-MM format. E.g. '2000-01'.\n *\n * @param dt Specify a date object or default to the current date.\n * @param separator Defaults to '-'.\n */\nexport function yyyyMm(dt = new Date(), separator = \"-\"): string {\n const yr = dt.getFullYear();\n const mth = dt.getMonth() + 1;\n\n return yr + separator + (mth < 10 ? \"0\" + mth : mth);\n}\n\n/**\n * Returns a date in yyyy-MM-dd format. E.g. '2000-01-02'.\n *\n * @param dt Specify a date object or default to the current date.\n * @param separator Defaults to '-'.\n */\nexport function yyyyMmDd(dt = new Date(), separator = \"-\"): string {\n const day = dt.getDate();\n\n return yyyyMm(dt, separator) + separator + (day < 10 ? \"0\" + day : day);\n}\n\n/**\n * Returns a date in hh:mm format. E.g. '01:02'.\n *\n * @param dt Specify a date object or default to the current date/time.\n * @param separator Defaults to ':'.\n */\nexport function hhMm(dt = new Date(), separator = \":\"): string {\n const hr = dt.getHours();\n const min = dt.getMinutes();\n\n return (hr < 10 ? \"0\" + hr : hr) + separator + (min < 10 ? \"0\" + min : min);\n}\n\n/**\n * Returns a date in hh:mm:ss format. E.g. '01:02:03'.\n *\n * @param dt Specify a date object or default to the current date/time.\n * @param separator Defaults to ':'.\n */\nexport function hhMmSs(dt = new Date(), separator = \":\"): string {\n const sec = dt.getSeconds();\n\n return hhMm(dt, separator) + separator + (sec < 10 ? \"0\" + sec : sec);\n}\n\n/**\n * Returns a date in hh:mm:ss.SSS format. E.g. '01:02:03.004'.\n *\n * @param dt Specify a date object or default to the current date/time.\n * @param timeSeparator Separator for hh/mm/ss. Defaults to ':'.\n * @param msSeparator Separator before SSS. Defaults to '.'.\n */\nexport function hhMmSsMs(\n dt = new Date(),\n timeSeparator = \":\",\n msSeparator = \".\",\n): string {\n const ms = dt.getMilliseconds();\n\n return (\n hhMmSs(dt, timeSeparator) +\n msSeparator +\n (ms < 10 ? \"00\" + ms : ms < 100 ? \"0\" + ms : ms)\n );\n}\n\n/**\n * Returns the timezone string for the given date. E.g. '+8', '-3.5'.\n * Returns 'Z' for UTC.\n *\n * @param dt Specify a date object or default to the current date/time.\n */\nexport function tzShort(dt = new Date()): string {\n if (dt.getTimezoneOffset() === 0) {\n return \"Z\";\n }\n\n const tzHours = dt.getTimezoneOffset() / 60;\n return tzHours >= 0 ? \"+\" + tzHours : String(tzHours);\n}\n\n/**\n * Returns the long month name, zero-indexed. E.g. 0 for 'January'.\n *\n * @param month Zero-indexed month.\n * @param locales Specify the locale, e.g. 'en-US', new Intl.Locale(\"en-US\").\n */\nexport function getLongMonthNameZeroIndexed(\n month: number,\n locales: Intl.LocalesArgument = \"default\",\n): string {\n return new Date(2024, month, 15).toLocaleString(locales, {\n month: \"long\",\n });\n}\n\n/**\n * Returns the long month name, one-indexed. E.g. 1 for 'January'.\n *\n * @param month One-indexed month.\n * @param locales Specify the locale, e.g. 'en-US', new Intl.Locale(\"en-US\").\n */\nexport function getLongMonthNameOneIndexed(\n month: number,\n locales: Intl.LocalesArgument = \"default\",\n): string {\n return getLongMonthNameZeroIndexed(month - 1, locales);\n}\n\n/**\n * Returns the short month name, zero-indexed. E.g. 0 for 'Jan'.\n *\n * @param month Zero-indexed month.\n * @param locales Specify the locale, e.g. 'en-US', new Intl.Locale(\"en-US\").\n */\nexport function getShortMonthNameZeroIndexed(\n month: number,\n locales: Intl.LocalesArgument = \"default\",\n): string {\n return new Date(2000, month, 15).toLocaleString(locales, {\n month: \"short\",\n });\n}\n\n/**\n * Returns the short month name, one-indexed. E.g. 1 for 'Jan'.\n *\n * @param month One-indexed month.\n * @param locales Specify the locale, e.g. 'en-US', new Intl.Locale(\"en-US\").\n */\nexport function getShortMonthNameOneIndexed(\n month: number,\n locales: Intl.LocalesArgument = \"default\",\n): string {\n return getShortMonthNameZeroIndexed(month - 1, locales);\n}\n\n/**\n * Returns a human-readable string date/time like '2025-01-01 22:31:16Z'.\n * Excludes the milliseconds assuming it is not necessary for display.\n */\nexport function getDisplayDateTime(ts: AnyDateTime) {\n const iso = toDate(ts).toISOString();\n const noMs = iso.slice(0, 19) + \"Z\";\n return noMs.replace(\"T\", \" \");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMO,SAAS,OAAO,IAAuB;AAC5C,MAAI,OAAO,OAAO,YAAY,OAAO,OAAO,UAAU;AACpD,WAAO,IAAI,KAAK,EAAE;AAAA,EACpB;AAEA,SAAO;AACT;AAQO,SAAS,OAAO,KAAK,oBAAI,KAAK,GAAG,YAAY,KAAa;AAC/D,QAAM,KAAK,GAAG,YAAY;AAC1B,QAAM,MAAM,GAAG,SAAS,IAAI;AAE5B,SAAO,KAAK,aAAa,MAAM,KAAK,MAAM,MAAM;AAClD;AAQO,SAAS,SAAS,KAAK,oBAAI,KAAK,GAAG,YAAY,KAAa;AACjE,QAAM,MAAM,GAAG,QAAQ;AAEvB,SAAO,OAAO,IAAI,SAAS,IAAI,aAAa,MAAM,KAAK,MAAM,MAAM;AACrE;AAQO,SAAS,KAAK,KAAK,oBAAI,KAAK,GAAG,YAAY,KAAa;AAC7D,QAAM,KAAK,GAAG,SAAS;AACvB,QAAM,MAAM,GAAG,WAAW;AAE1B,UAAQ,KAAK,KAAK,MAAM,KAAK,MAAM,aAAa,MAAM,KAAK,MAAM,MAAM;AACzE;AAQO,SAAS,OAAO,KAAK,oBAAI,KAAK,GAAG,YAAY,KAAa;AAC/D,QAAM,MAAM,GAAG,WAAW;AAE1B,SAAO,KAAK,IAAI,SAAS,IAAI,aAAa,MAAM,KAAK,MAAM,MAAM;AACnE;AASO,SAAS,SACd,KAAK,oBAAI,KAAK,GACd,gBAAgB,KAChB,cAAc,KACN;AACR,QAAM,KAAK,GAAG,gBAAgB;AAE9B,SACE,OAAO,IAAI,aAAa,IACxB,eACC,KAAK,KAAK,OAAO,KAAK,KAAK,MAAM,MAAM,KAAK;AAEjD;AAQO,SAAS,QAAQ,KAAK,oBAAI,KAAK,GAAW;AAC/C,MAAI,GAAG,kBAAkB,MAAM,GAAG;AAChC,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,GAAG,kBAAkB,IAAI;AACzC,SAAO,WAAW,IAAI,MAAM,UAAU,OAAO,OAAO;AACtD;AAQO,SAAS,4BACd,OACA,UAAgC,WACxB;AACR,SAAO,IAAI,KAAK,MAAM,OAAO,EAAE,EAAE,eAAe,SAAS;AAAA,IACvD,OAAO;AAAA,EACT,CAAC;AACH;AAQO,SAAS,2BACd,OACA,UAAgC,WACxB;AACR,SAAO,4BAA4B,QAAQ,GAAG,OAAO;AACvD;AAQO,SAAS,6BACd,OACA,UAAgC,WACxB;AACR,SAAO,IAAI,KAAK,KAAM,OAAO,EAAE,EAAE,eAAe,SAAS;AAAA,IACvD,OAAO;AAAA,EACT,CAAC;AACH;AAQO,SAAS,4BACd,OACA,UAAgC,WACxB;AACR,SAAO,6BAA6B,QAAQ,GAAG,OAAO;AACxD;AAMO,SAAS,mBAAmB,IAAiB;AAClD,QAAM,MAAM,OAAO,EAAE,EAAE,YAAY;AACnC,QAAM,OAAO,IAAI,MAAM,GAAG,EAAE,IAAI;AAChC,SAAO,KAAK,QAAQ,KAAK,GAAG;AAC9B;","names":[]}
package/duration.d.ts CHANGED
@@ -3,7 +3,8 @@
3
3
  * date and time durations.
4
4
  *
5
5
  * Note that month and year do not have fixed durations, and hence are excluded
6
- * from this file.
6
+ * from this file. Weeks have fixed durations, but are excluded because we
7
+ * use days as the max duration supported.
7
8
  */
8
9
  export type Duration = {
9
10
  days?: number;
@@ -12,7 +13,13 @@ export type Duration = {
12
13
  seconds?: number;
13
14
  milliseconds?: number;
14
15
  };
16
+ /**
17
+ * One of: days, hours, minutes, seconds, milliseconds
18
+ */
15
19
  export type DurationType = keyof Duration;
20
+ /**
21
+ * Order in which the duration type appears in the duration string.
22
+ */
16
23
  export declare const DURATION_TYPE_SEQUENCE: DurationType[];
17
24
  /**
18
25
  * Follows the same format as Intl.DurationFormat.prototype.format().
@@ -34,27 +41,32 @@ export type DurationSuffixType = keyof DurationSuffixMap;
34
41
  export declare const DURATION_STYLE_SUFFIX_MAP: Record<DurationType, DurationSuffixMap>;
35
42
  /**
36
43
  * Convert a milliseconds duration into a Duration object. If the given ms is
37
- * zero, then return an object with a single field of zero.
44
+ * zero, then return an object with a single field of zero with duration type
45
+ * of durationTypeForZero.
38
46
  *
39
- * durationTypeForZero - Defaults to 'milliseconds'
47
+ * @param durationTypeForZero Defaults to 'milliseconds'
40
48
  */
41
49
  export declare function msToDuration(ms: number, durationTypeForZero?: DurationType): Duration;
42
50
  /**
43
51
  * Returns the number of milliseconds for the given duration.
44
52
  */
45
53
  export declare function durationToMs(duration: Duration): number;
54
+ /**
55
+ * Convenience function to return a duration given an ms or Duration.
56
+ */
57
+ export declare function durationOrMsToMs(duration: number | Duration): number;
46
58
  /**
47
59
  * Format a given Duration object into a string. If the object has no fields,
48
60
  * then returns an empty string.
49
61
  *
50
- * style - Defaults to 'short'
62
+ * @param style Defaults to 'short'
51
63
  */
52
64
  export declare function formatDuration(duration: Duration, style?: DurationStyle): string;
53
65
  /**
54
66
  * Convert a millisecond duration into a human-readable duration string.
55
67
  *
56
- * options.durationTypeForZero - Defaults to 'milliseconds'
57
- * options.style - Defaults to 'short'
68
+ * @param options.durationTypeForZero - Defaults to 'milliseconds'
69
+ * @param options.style - Defaults to 'short'
58
70
  */
59
71
  export declare function readableDuration(ms: number, options?: {
60
72
  durationTypeForZero?: DurationType;
package/duration.js CHANGED
@@ -22,6 +22,7 @@ var duration_exports = {};
22
22
  __export(duration_exports, {
23
23
  DURATION_STYLE_SUFFIX_MAP: () => DURATION_STYLE_SUFFIX_MAP,
24
24
  DURATION_TYPE_SEQUENCE: () => DURATION_TYPE_SEQUENCE,
25
+ durationOrMsToMs: () => durationOrMsToMs,
25
26
  durationToMs: () => durationToMs,
26
27
  formatDuration: () => formatDuration,
27
28
  msToDuration: () => msToDuration,
@@ -143,6 +144,9 @@ function durationToMs(duration) {
143
144
  const msMs = (_e = duration.milliseconds) != null ? _e : 0;
144
145
  return daysMs + hoursMs + minsMs + secsMs + msMs;
145
146
  }
147
+ function durationOrMsToMs(duration) {
148
+ return typeof duration === "number" ? duration : durationToMs(duration);
149
+ }
146
150
  function formatDuration(duration, style) {
147
151
  style = style != null ? style : "short";
148
152
  const stylePlural = getDurationStyleForPlural(style);
@@ -166,6 +170,7 @@ function readableDuration(ms, options) {
166
170
  0 && (module.exports = {
167
171
  DURATION_STYLE_SUFFIX_MAP,
168
172
  DURATION_TYPE_SEQUENCE,
173
+ durationOrMsToMs,
169
174
  durationToMs,
170
175
  formatDuration,
171
176
  msToDuration,
package/duration.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/duration.ts","../src/timeConstants.ts"],"sourcesContent":["/**\n * Bunch of miscellaneous constants and utility functions related to handling\n * date and time durations.\n *\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file.\n */\n\nimport {\n MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n MINUTES_PER_HOUR,\n HOURS_PER_DAY,\n MS_PER_DAY,\n MS_PER_MINUTE,\n MS_PER_HOUR,\n} from \"./timeConstants\";\n\nexport type Duration = {\n days?: number;\n hours?: number;\n minutes?: number;\n seconds?: number;\n milliseconds?: number;\n};\n\nexport type DurationType = keyof Duration;\n\nexport const DURATION_TYPE_SEQUENCE: DurationType[] = [\n \"days\",\n \"hours\",\n \"minutes\",\n \"seconds\",\n \"milliseconds\",\n];\n\n/**\n * Follows the same format as Intl.DurationFormat.prototype.format().\n *\n * Short: 1 yr, 2 mths, 3 wks, 3 days, 4 hr, 5 min, 6 sec, 7 ms, 8 μs, 9 ns\n * Long: 1 year, 2 months, 3 weeks, 3 days, 4 hours, 5 minutes, 6 seconds,\n * 7 milliseconds, 8 microseconds, 9 nanoseconds\n * Narrow: 1y 2mo 3w 3d 4h 5m 6s 7ms 8μs 9ns\n */\nexport type DurationStyle = \"short\" | \"long\" | \"narrow\";\n\nexport type DurationSuffixMap = {\n short: string;\n shorts: string;\n long: string;\n longs: string;\n narrow: string;\n};\n\nexport type DurationSuffixType = keyof DurationSuffixMap;\n\nexport const DURATION_STYLE_SUFFIX_MAP: Record<\n DurationType,\n DurationSuffixMap\n> = {\n days: {\n short: \"day\",\n shorts: \"days\",\n long: \"day\",\n longs: \"days\",\n narrow: \"d\",\n },\n hours: {\n short: \"hr\",\n shorts: \"hrs\",\n long: \"hour\",\n longs: \"hours\",\n narrow: \"h\",\n },\n minutes: {\n short: \"min\",\n shorts: \"mins\",\n long: \"minute\",\n longs: \"minutes\",\n narrow: \"m\",\n },\n seconds: {\n short: \"sec\",\n shorts: \"secs\",\n long: \"second\",\n longs: \"seconds\",\n narrow: \"s\",\n },\n milliseconds: {\n short: \"ms\",\n shorts: \"ms\",\n long: \"millisecond\",\n longs: \"milliseconds\",\n narrow: \"ms\",\n },\n};\n\nfunction getDurationStyleForPlural(style: DurationStyle): DurationSuffixType {\n return style == \"short\" ? \"shorts\" : style === \"long\" ? \"longs\" : style;\n}\n\nfunction getValueAndUnitSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \"\" : \" \";\n}\n\nfunction getDurationTypeSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \" \" : \", \";\n}\n\n/**\n * Convert a milliseconds duration into a Duration object. If the given ms is\n * zero, then return an object with a single field of zero.\n *\n * durationTypeForZero - Defaults to 'milliseconds'\n */\nexport function msToDuration(\n ms: number,\n durationTypeForZero?: DurationType,\n): Duration {\n if (ms === 0) {\n durationTypeForZero = durationTypeForZero ?? \"milliseconds\";\n return { [durationTypeForZero]: 0 };\n }\n\n const duration: Duration = {};\n\n for (let i = 0; i < 1; i++) {\n let seconds = Math.floor(ms / MS_PER_SECOND);\n const millis = ms - seconds * MS_PER_SECOND;\n\n if (millis > 0) {\n duration[\"milliseconds\"] = millis;\n }\n\n if (seconds === 0) {\n break;\n }\n\n let minutes = Math.floor(seconds / SECONDS_PER_MINUTE);\n seconds -= minutes * SECONDS_PER_MINUTE;\n\n if (seconds > 0) {\n duration[\"seconds\"] = seconds;\n }\n\n if (minutes === 0) {\n break;\n }\n\n let hours = Math.floor(minutes / MINUTES_PER_HOUR);\n minutes -= hours * MINUTES_PER_HOUR;\n\n if (minutes > 0) {\n duration[\"minutes\"] = minutes;\n }\n\n if (hours === 0) {\n break;\n }\n\n const days = Math.floor(hours / HOURS_PER_DAY);\n hours -= days * HOURS_PER_DAY;\n\n if (hours > 0) {\n duration[\"hours\"] = hours;\n }\n\n if (days > 0) {\n duration[\"days\"] = days;\n }\n }\n\n return duration;\n}\n\n/**\n * Returns the number of milliseconds for the given duration.\n */\nexport function durationToMs(duration: Duration): number {\n const daysMs = (duration.days ?? 0) * MS_PER_DAY;\n const hoursMs = (duration.hours ?? 0) * MS_PER_HOUR;\n const minsMs = (duration.minutes ?? 0) * MS_PER_MINUTE;\n const secsMs = (duration.seconds ?? 0) * MS_PER_SECOND;\n const msMs = duration.milliseconds ?? 0;\n\n return daysMs + hoursMs + minsMs + secsMs + msMs;\n}\n\n/**\n * Format a given Duration object into a string. If the object has no fields,\n * then returns an empty string.\n *\n * style - Defaults to 'short'\n */\nexport function formatDuration(duration: Duration, style?: DurationStyle) {\n style = style ?? \"short\";\n const stylePlural = getDurationStyleForPlural(style);\n\n const space = getValueAndUnitSeparator(style);\n\n const a: string[] = [];\n\n for (const unit of DURATION_TYPE_SEQUENCE) {\n const value = duration[unit];\n if (value === undefined) continue;\n\n const suffixMap = DURATION_STYLE_SUFFIX_MAP[unit];\n const suffix = value === 1 ? suffixMap[style] : suffixMap[stylePlural];\n a.push(value + space + suffix);\n }\n\n const separator = getDurationTypeSeparator(style);\n return a.join(separator);\n}\n\n/**\n * Convert a millisecond duration into a human-readable duration string.\n *\n * options.durationTypeForZero - Defaults to 'milliseconds'\n * options.style - Defaults to 'short'\n */\nexport function readableDuration(\n ms: number,\n options?: { durationTypeForZero?: DurationType; style?: DurationStyle },\n): string {\n const duration = msToDuration(ms, options?.durationTypeForZero);\n\n return formatDuration(duration, options?.style);\n}\n","/**\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file.\n */\n\nexport const MS_PER_SECOND = 1000;\nexport const MS_PER_MINUTE = 60_000;\nexport const MS_PER_HOUR = 3_600_000;\nexport const MS_PER_DAY = 86_400_000;\nexport const MS_PER_WEEK = 604_800_000;\n\nexport const SECONDS_PER_MINUTE = 60;\nexport const SECONDS_PER_HOUR = 3_600;\nexport const SECONDS_PER_DAY = 86_400;\nexport const SECONDS_PER_WEEK = 604_800;\n\nexport const MINUTES_PER_HOUR = 60;\nexport const MINUTES_PER_DAY = 1440;\nexport const MINUTES_PER_WEEK = 10_080;\n\nexport const HOURS_PER_DAY = 24;\nexport const HOURS_PER_WEEK = 168;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/duration.ts","../src/timeConstants.ts"],"sourcesContent":["/**\n * Bunch of miscellaneous constants and utility functions related to handling\n * date and time durations.\n *\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file. Weeks have fixed durations, but are excluded because we\n * use days as the max duration supported.\n */\n\nimport {\n MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n MINUTES_PER_HOUR,\n HOURS_PER_DAY,\n MS_PER_DAY,\n MS_PER_MINUTE,\n MS_PER_HOUR,\n} from \"./timeConstants\";\n\nexport type Duration = {\n days?: number;\n hours?: number;\n minutes?: number;\n seconds?: number;\n milliseconds?: number;\n};\n\n/**\n * One of: days, hours, minutes, seconds, milliseconds\n */\nexport type DurationType = keyof Duration;\n\n/**\n * Order in which the duration type appears in the duration string.\n */\nexport const DURATION_TYPE_SEQUENCE: DurationType[] = [\n \"days\",\n \"hours\",\n \"minutes\",\n \"seconds\",\n \"milliseconds\",\n];\n\n/**\n * Follows the same format as Intl.DurationFormat.prototype.format().\n *\n * Short: 1 yr, 2 mths, 3 wks, 3 days, 4 hr, 5 min, 6 sec, 7 ms, 8 μs, 9 ns\n * Long: 1 year, 2 months, 3 weeks, 3 days, 4 hours, 5 minutes, 6 seconds,\n * 7 milliseconds, 8 microseconds, 9 nanoseconds\n * Narrow: 1y 2mo 3w 3d 4h 5m 6s 7ms 8μs 9ns\n */\nexport type DurationStyle = \"short\" | \"long\" | \"narrow\";\n\nexport type DurationSuffixMap = {\n short: string;\n shorts: string;\n long: string;\n longs: string;\n narrow: string;\n};\n\nexport type DurationSuffixType = keyof DurationSuffixMap;\n\nexport const DURATION_STYLE_SUFFIX_MAP: Record<\n DurationType,\n DurationSuffixMap\n> = {\n days: {\n short: \"day\",\n shorts: \"days\",\n long: \"day\",\n longs: \"days\",\n narrow: \"d\",\n },\n hours: {\n short: \"hr\",\n shorts: \"hrs\",\n long: \"hour\",\n longs: \"hours\",\n narrow: \"h\",\n },\n minutes: {\n short: \"min\",\n shorts: \"mins\",\n long: \"minute\",\n longs: \"minutes\",\n narrow: \"m\",\n },\n seconds: {\n short: \"sec\",\n shorts: \"secs\",\n long: \"second\",\n longs: \"seconds\",\n narrow: \"s\",\n },\n milliseconds: {\n short: \"ms\",\n shorts: \"ms\",\n long: \"millisecond\",\n longs: \"milliseconds\",\n narrow: \"ms\",\n },\n};\n\nfunction getDurationStyleForPlural(style: DurationStyle): DurationSuffixType {\n return style == \"short\" ? \"shorts\" : style === \"long\" ? \"longs\" : style;\n}\n\nfunction getValueAndUnitSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \"\" : \" \";\n}\n\nfunction getDurationTypeSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \" \" : \", \";\n}\n\n/**\n * Convert a milliseconds duration into a Duration object. If the given ms is\n * zero, then return an object with a single field of zero with duration type\n * of durationTypeForZero.\n *\n * @param durationTypeForZero Defaults to 'milliseconds'\n */\nexport function msToDuration(\n ms: number,\n durationTypeForZero?: DurationType,\n): Duration {\n if (ms === 0) {\n durationTypeForZero = durationTypeForZero ?? \"milliseconds\";\n return { [durationTypeForZero]: 0 };\n }\n\n const duration: Duration = {};\n\n for (let i = 0; i < 1; i++) {\n let seconds = Math.floor(ms / MS_PER_SECOND);\n const millis = ms - seconds * MS_PER_SECOND;\n\n if (millis > 0) {\n duration[\"milliseconds\"] = millis;\n }\n\n if (seconds === 0) {\n break;\n }\n\n let minutes = Math.floor(seconds / SECONDS_PER_MINUTE);\n seconds -= minutes * SECONDS_PER_MINUTE;\n\n if (seconds > 0) {\n duration[\"seconds\"] = seconds;\n }\n\n if (minutes === 0) {\n break;\n }\n\n let hours = Math.floor(minutes / MINUTES_PER_HOUR);\n minutes -= hours * MINUTES_PER_HOUR;\n\n if (minutes > 0) {\n duration[\"minutes\"] = minutes;\n }\n\n if (hours === 0) {\n break;\n }\n\n const days = Math.floor(hours / HOURS_PER_DAY);\n hours -= days * HOURS_PER_DAY;\n\n if (hours > 0) {\n duration[\"hours\"] = hours;\n }\n\n if (days > 0) {\n duration[\"days\"] = days;\n }\n }\n\n return duration;\n}\n\n/**\n * Returns the number of milliseconds for the given duration.\n */\nexport function durationToMs(duration: Duration): number {\n const daysMs = (duration.days ?? 0) * MS_PER_DAY;\n const hoursMs = (duration.hours ?? 0) * MS_PER_HOUR;\n const minsMs = (duration.minutes ?? 0) * MS_PER_MINUTE;\n const secsMs = (duration.seconds ?? 0) * MS_PER_SECOND;\n const msMs = duration.milliseconds ?? 0;\n\n return daysMs + hoursMs + minsMs + secsMs + msMs;\n}\n\n/**\n * Convenience function to return a duration given an ms or Duration.\n */\nexport function durationOrMsToMs(duration: number | Duration): number {\n return typeof duration === \"number\" ? duration : durationToMs(duration);\n}\n\n/**\n * Format a given Duration object into a string. If the object has no fields,\n * then returns an empty string.\n *\n * @param style Defaults to 'short'\n */\nexport function formatDuration(duration: Duration, style?: DurationStyle) {\n style = style ?? \"short\";\n const stylePlural = getDurationStyleForPlural(style);\n\n const space = getValueAndUnitSeparator(style);\n\n const a: string[] = [];\n\n for (const unit of DURATION_TYPE_SEQUENCE) {\n const value = duration[unit];\n if (value === undefined) continue;\n\n const suffixMap = DURATION_STYLE_SUFFIX_MAP[unit];\n const suffix = value === 1 ? suffixMap[style] : suffixMap[stylePlural];\n a.push(value + space + suffix);\n }\n\n const separator = getDurationTypeSeparator(style);\n return a.join(separator);\n}\n\n/**\n * Convert a millisecond duration into a human-readable duration string.\n *\n * @param options.durationTypeForZero - Defaults to 'milliseconds'\n * @param options.style - Defaults to 'short'\n */\nexport function readableDuration(\n ms: number,\n options?: { durationTypeForZero?: DurationType; style?: DurationStyle },\n): string {\n const duration = msToDuration(ms, options?.durationTypeForZero);\n\n return formatDuration(duration, options?.style);\n}\n","/**\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file.\n */\n\nexport const MS_PER_SECOND = 1000;\nexport const MS_PER_MINUTE = 60_000;\nexport const MS_PER_HOUR = 3_600_000;\nexport const MS_PER_DAY = 86_400_000;\nexport const MS_PER_WEEK = 604_800_000;\n\nexport const SECONDS_PER_MINUTE = 60;\nexport const SECONDS_PER_HOUR = 3_600;\nexport const SECONDS_PER_DAY = 86_400;\nexport const SECONDS_PER_WEEK = 604_800;\n\nexport const MINUTES_PER_HOUR = 60;\nexport const MINUTES_PER_DAY = 1440;\nexport const MINUTES_PER_WEEK = 10_080;\n\nexport const HOURS_PER_DAY = 24;\nexport const HOURS_PER_WEEK = 168;\n"],"mappings":"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","names":[]}
package/kvStore.d.ts CHANGED
@@ -10,12 +10,19 @@
10
10
  * How to use?
11
11
  * Just use the `kvStore` global constant like the local storage.
12
12
  */
13
+ import { Duration } from "./duration";
13
14
  /** One day in milliseconds. */
14
15
  export declare const MILLIS_PER_DAY = 86400000;
15
16
  /** 30 days in ms. */
16
17
  export declare const DEFAULT_EXPIRY_DELTA_MS: number;
17
18
  /** Do GC once per day. */
18
19
  export declare const GC_INTERVAL_MS = 86400000;
20
+ type StoredObject<T> = {
21
+ key: string;
22
+ value: T;
23
+ storedMs: number;
24
+ expiryMs: number;
25
+ };
19
26
  export declare class KVStoreField<T> {
20
27
  readonly store: KVStore;
21
28
  readonly key: string;
@@ -37,11 +44,12 @@ export declare class KVStore {
37
44
  constructor(dbName: string, dbVersion: number, defaultExpiryDeltaMs: number);
38
45
  private getOrCreateDb;
39
46
  private transact;
40
- set<T>(key: string, value: T, expireDeltaMs?: number): Promise<T>;
47
+ set<T>(key: string, value: T, expiryDeltaMs?: number | Duration): Promise<T>;
41
48
  /** Delete one or multiple keys. */
42
49
  delete(key: string | string[]): Promise<void>;
50
+ getStoredObject<T>(key: string): Promise<StoredObject<T> | undefined>;
43
51
  get<T>(key: string): Promise<T | undefined>;
44
- forEach(callback: (key: string, value: unknown, expireMs: number) => void | Promise<void>): Promise<void>;
52
+ forEach(callback: (key: string, value: unknown, expiryMs: number) => void | Promise<void>): Promise<void>;
45
53
  /** Cannot be a getter because this needs to be async. */
46
54
  size(): Promise<number>;
47
55
  clear(): Promise<void>;
@@ -61,3 +69,26 @@ export declare class KVStore {
61
69
  * you want, but most likely you will only need one store instance.
62
70
  */
63
71
  export declare const kvStore: KVStore;
72
+ /**
73
+ * Class to represent one key in the store with a default expiration.
74
+ */
75
+ declare class KvStoreItem<T> {
76
+ readonly key: string;
77
+ readonly defaultExpiryDeltaMs: number;
78
+ readonly store: KVStore;
79
+ constructor(key: string, defaultExpiryDeltaMs: number, store?: KVStore);
80
+ /**
81
+ * Example usage:
82
+ *
83
+ * const { value, storedMs, expiryMs } = await myKvItem.getStoredObject();
84
+ */
85
+ getStoredObject(): Promise<StoredObject<T> | undefined>;
86
+ get(): Promise<T | undefined>;
87
+ set(value: T, expiryDeltaMs?: number): Promise<void>;
88
+ delete(): Promise<void>;
89
+ }
90
+ /**
91
+ * Create a KV store item with a key and a default expiration.
92
+ */
93
+ export declare function kvStoreItem<T>(key: string, defaultExpiration: number | Duration): KvStoreItem<T>;
94
+ export {};
package/kvStore.js CHANGED
@@ -45,9 +45,32 @@ __export(kvStore_exports, {
45
45
  KVStore: () => KVStore,
46
46
  KVStoreField: () => KVStoreField,
47
47
  MILLIS_PER_DAY: () => MILLIS_PER_DAY,
48
- kvStore: () => kvStore
48
+ kvStore: () => kvStore,
49
+ kvStoreItem: () => kvStoreItem
49
50
  });
50
51
  module.exports = __toCommonJS(kvStore_exports);
52
+
53
+ // src/timeConstants.ts
54
+ var MS_PER_SECOND = 1e3;
55
+ var MS_PER_MINUTE = 6e4;
56
+ var MS_PER_HOUR = 36e5;
57
+ var MS_PER_DAY = 864e5;
58
+
59
+ // src/duration.ts
60
+ function durationToMs(duration) {
61
+ var _a, _b, _c, _d, _e;
62
+ const daysMs = ((_a = duration.days) != null ? _a : 0) * MS_PER_DAY;
63
+ const hoursMs = ((_b = duration.hours) != null ? _b : 0) * MS_PER_HOUR;
64
+ const minsMs = ((_c = duration.minutes) != null ? _c : 0) * MS_PER_MINUTE;
65
+ const secsMs = ((_d = duration.seconds) != null ? _d : 0) * MS_PER_SECOND;
66
+ const msMs = (_e = duration.milliseconds) != null ? _e : 0;
67
+ return daysMs + hoursMs + minsMs + secsMs + msMs;
68
+ }
69
+ function durationOrMsToMs(duration) {
70
+ return typeof duration === "number" ? duration : durationToMs(duration);
71
+ }
72
+
73
+ // src/kvStore.ts
51
74
  var DEFAULT_DB_NAME = "KVStore";
52
75
  var DEFAULT_DB_VERSION = 1;
53
76
  var STORE_NAME = "kvStore";
@@ -55,7 +78,7 @@ var MILLIS_PER_DAY = 864e5;
55
78
  var DEFAULT_EXPIRY_DELTA_MS = MILLIS_PER_DAY * 30;
56
79
  var GC_INTERVAL_MS = MILLIS_PER_DAY;
57
80
  function validateStoredObject(obj) {
58
- if (!obj || typeof obj !== "object" || !("key" in obj) || typeof obj.key !== "string" || !("value" in obj) || obj.value === void 0 || !("expireMs" in obj) || typeof obj.expireMs !== "number" || Date.now() >= obj.expireMs) {
81
+ if (!obj || typeof obj !== "object" || !("key" in obj) || typeof obj.key !== "string" || !("value" in obj) || !("storedMs" in obj) || typeof obj.storedMs !== "number" || obj.value === void 0 || !("expiryMs" in obj) || typeof obj.expiryMs !== "number" || Date.now() >= obj.expiryMs) {
59
82
  return void 0;
60
83
  }
61
84
  return obj;
@@ -128,11 +151,13 @@ var KVStore = class {
128
151
  });
129
152
  }
130
153
  set(_0, _1) {
131
- return __async(this, arguments, function* (key, value, expireDeltaMs = this.defaultExpiryDeltaMs) {
154
+ return __async(this, arguments, function* (key, value, expiryDeltaMs = this.defaultExpiryDeltaMs) {
155
+ const nowMs = Date.now();
132
156
  const obj = {
133
157
  key,
134
158
  value,
135
- expireMs: Date.now() + expireDeltaMs
159
+ storedMs: nowMs,
160
+ expiryMs: nowMs + durationOrMsToMs(expiryDeltaMs)
136
161
  };
137
162
  return yield this.transact(
138
163
  "readwrite",
@@ -166,7 +191,7 @@ var KVStore = class {
166
191
  );
167
192
  });
168
193
  }
169
- get(key) {
194
+ getStoredObject(key) {
170
195
  return __async(this, null, function* () {
171
196
  const stored = yield this.transact(
172
197
  "readonly",
@@ -187,7 +212,7 @@ var KVStore = class {
187
212
  this.gc();
188
213
  return void 0;
189
214
  }
190
- return obj.value;
215
+ return obj;
191
216
  } catch (e) {
192
217
  console.error(`Invalid kv value: ${key}=${JSON.stringify(stored)}:`, e);
193
218
  yield this.delete(key);
@@ -196,6 +221,12 @@ var KVStore = class {
196
221
  }
197
222
  });
198
223
  }
224
+ get(key) {
225
+ return __async(this, null, function* () {
226
+ const obj = yield this.getStoredObject(key);
227
+ return obj == null ? void 0 : obj.value;
228
+ });
229
+ }
199
230
  forEach(callback) {
200
231
  return __async(this, null, function* () {
201
232
  yield this.transact("readonly", (objectStore, resolve, reject) => {
@@ -206,7 +237,7 @@ var KVStore = class {
206
237
  if (cursor.key) {
207
238
  const obj = validateStoredObject(cursor.value);
208
239
  if (obj) {
209
- yield callback(String(cursor.key), obj.value, obj.expireMs);
240
+ yield callback(String(cursor.key), obj.value, obj.expiryMs);
210
241
  } else {
211
242
  yield callback(String(cursor.key), void 0, 0);
212
243
  }
@@ -242,8 +273,8 @@ var KVStore = class {
242
273
  asMap() {
243
274
  return __async(this, null, function* () {
244
275
  const map = /* @__PURE__ */ new Map();
245
- yield this.forEach((key, value, expireMs) => {
246
- map.set(key, { value, expireMs });
276
+ yield this.forEach((key, value, expiryMs) => {
277
+ map.set(key, { value, expiryMs });
247
278
  });
248
279
  return map;
249
280
  });
@@ -278,8 +309,8 @@ var KVStore = class {
278
309
  this.lastGcMs = Date.now();
279
310
  const keysToDelete = [];
280
311
  yield this.forEach(
281
- (key, value, expireMs) => __async(this, null, function* () {
282
- if (value === void 0 || Date.now() >= expireMs) {
312
+ (key, value, expiryMs) => __async(this, null, function* () {
313
+ if (value === void 0 || Date.now() >= expiryMs) {
283
314
  keysToDelete.push(key);
284
315
  }
285
316
  })
@@ -303,6 +334,42 @@ var kvStore = new KVStore(
303
334
  DEFAULT_DB_VERSION,
304
335
  DEFAULT_EXPIRY_DELTA_MS
305
336
  );
337
+ var KvStoreItem = class {
338
+ constructor(key, defaultExpiryDeltaMs, store = kvStore) {
339
+ this.key = key;
340
+ this.defaultExpiryDeltaMs = defaultExpiryDeltaMs;
341
+ this.store = store;
342
+ }
343
+ /**
344
+ * Example usage:
345
+ *
346
+ * const { value, storedMs, expiryMs } = await myKvItem.getStoredObject();
347
+ */
348
+ getStoredObject() {
349
+ return __async(this, null, function* () {
350
+ return yield this.store.getStoredObject(this.key);
351
+ });
352
+ }
353
+ get() {
354
+ return __async(this, null, function* () {
355
+ return yield this.store.get(this.key);
356
+ });
357
+ }
358
+ set(_0) {
359
+ return __async(this, arguments, function* (value, expiryDeltaMs = this.defaultExpiryDeltaMs) {
360
+ yield this.store.set(this.key, value, expiryDeltaMs);
361
+ });
362
+ }
363
+ delete() {
364
+ return __async(this, null, function* () {
365
+ yield this.store.delete(this.key);
366
+ });
367
+ }
368
+ };
369
+ function kvStoreItem(key, defaultExpiration) {
370
+ const defaultExpiryDeltaMs = durationOrMsToMs(defaultExpiration);
371
+ return new KvStoreItem(key, defaultExpiryDeltaMs);
372
+ }
306
373
  // Annotate the CommonJS export names for ESM import in node:
307
374
  0 && (module.exports = {
308
375
  DEFAULT_EXPIRY_DELTA_MS,
@@ -310,6 +377,7 @@ var kvStore = new KVStore(
310
377
  KVStore,
311
378
  KVStoreField,
312
379
  MILLIS_PER_DAY,
313
- kvStore
380
+ kvStore,
381
+ kvStoreItem
314
382
  });
315
383
  //# sourceMappingURL=kvStore.js.map
package/kvStore.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/kvStore.ts"],"sourcesContent":["/**\n * Indexed DB key-value store with support for auto-expirations.\n *\n * Why use this?\n * 1. No need to worry about running out of storage.\n * 2. Extremely simple interface to use indexed DBs.\n * 3. Auto-expirations frees you from worrying about data clean-up.\n * 4. Any serializable data type can be stored (except undefined).\n *\n * How to use?\n * Just use the `kvStore` global constant like the local storage.\n */\n\n// Updating the DB name will cause all old entries to be gone.\nconst DEFAULT_DB_NAME = \"KVStore\";\n\n// Updating the version will cause all old entries to be gone.\nconst DEFAULT_DB_VERSION = 1;\n\n// Use a constant store name to keep things simple.\nconst STORE_NAME = \"kvStore\";\n\n/** One day in milliseconds. */\nexport const MILLIS_PER_DAY = 86_400_000;\n\n/** 30 days in ms. */\nexport const DEFAULT_EXPIRY_DELTA_MS = MILLIS_PER_DAY * 30;\n\n/** Do GC once per day. */\nexport const GC_INTERVAL_MS = MILLIS_PER_DAY;\n\ntype StoredObject<T> = {\n key: string;\n value: T;\n expireMs: number;\n};\n\n/**\n * Parse a stored value string. Returns undefined if invalid or expired.\n * Throws an error if the string cannot be parsed as JSON.\n */\nfunction validateStoredObject<T>(\n obj: StoredObject<T>,\n): StoredObject<T> | undefined {\n if (\n !obj ||\n typeof obj !== \"object\" ||\n !(\"key\" in obj) ||\n typeof obj.key !== \"string\" ||\n !(\"value\" in obj) ||\n obj.value === undefined ||\n !(\"expireMs\" in obj) ||\n typeof obj.expireMs !== \"number\" ||\n Date.now() >= obj.expireMs\n ) {\n return undefined;\n }\n\n return obj;\n}\n\n/** Add an `onerror` handler to the request. */\nfunction withOnError<T extends IDBRequest | IDBTransaction>(\n request: T,\n reject: (reason?: unknown) => void,\n): T {\n request.onerror = (event) => {\n reject(event);\n };\n\n return request;\n}\n\nexport class KVStoreField<T> {\n public constructor(\n public readonly store: KVStore,\n public readonly key: string,\n ) {}\n\n public get(): Promise<T | undefined> {\n return this.store.get(this.key);\n }\n\n public set(t: T): Promise<T> {\n return this.store.set(this.key, t);\n }\n\n public delete(): Promise<void> {\n return this.store.delete(this.key);\n }\n}\n\n/**\n * You can create multiple KVStores if you want, but most likely you will only\n * need to use the default `kvStore` instance.\n */\nexport class KVStore {\n // We'll init the DB only on first use.\n private db: IDBDatabase | undefined;\n\n // Local storage key name for the last GC completed timestamp.\n public readonly gcMsStorageKey: string;\n\n public constructor(\n public readonly dbName: string,\n public readonly dbVersion: number,\n public readonly defaultExpiryDeltaMs: number,\n ) {\n this.gcMsStorageKey = `__kvStore:lastGcMs:${dbName}:v${dbVersion}:${STORE_NAME}`;\n }\n\n private async getOrCreateDb() {\n if (!this.db) {\n this.db = await new Promise<IDBDatabase>((resolve, reject) => {\n const request = withOnError(\n globalThis.indexedDB.open(this.dbName, this.dbVersion),\n reject,\n );\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as unknown as { result: IDBDatabase })\n .result;\n\n // Create the store on DB init.\n const objectStore = db.createObjectStore(STORE_NAME, {\n keyPath: \"key\",\n });\n\n objectStore.createIndex(\"key\", \"key\", {\n unique: true,\n });\n };\n\n request.onsuccess = (event) => {\n const db = (event.target as unknown as { result: IDBDatabase })\n .result;\n resolve(db);\n };\n });\n }\n\n return this.db;\n }\n\n private async transact<T>(\n mode: IDBTransactionMode,\n callback: (\n objectStore: IDBObjectStore,\n resolve: (t: T) => void,\n reject: (reason?: unknown) => void,\n ) => void,\n ): Promise<T> {\n const db = await this.getOrCreateDb();\n\n return await new Promise<T>((resolve, reject) => {\n const transaction = withOnError(db.transaction(STORE_NAME, mode), reject);\n\n transaction.onabort = (event) => {\n reject(event);\n };\n\n const objectStore = transaction.objectStore(STORE_NAME);\n\n callback(objectStore, resolve, reject);\n });\n }\n\n public async set<T>(\n key: string,\n value: T,\n expireDeltaMs: number = this.defaultExpiryDeltaMs,\n ): Promise<T> {\n const obj = {\n key,\n value,\n expireMs: Date.now() + expireDeltaMs,\n };\n\n return await this.transact<T>(\n \"readwrite\",\n (objectStore, resolve, reject) => {\n const request = withOnError(objectStore.put(obj), reject);\n\n request.onsuccess = () => {\n resolve(value);\n\n this.gc(); // check GC on every write\n };\n },\n );\n }\n\n /** Delete one or multiple keys. */\n public async delete(key: string | string[]): Promise<void> {\n return await this.transact<void>(\n \"readwrite\",\n (objectStore, resolve, reject) => {\n objectStore.transaction.oncomplete = () => {\n resolve();\n };\n\n if (typeof key === \"string\") {\n withOnError(objectStore.delete(key), reject);\n } else {\n for (const k of key) {\n withOnError(objectStore.delete(k), reject);\n }\n }\n },\n );\n }\n\n public async get<T>(key: string): Promise<T | undefined> {\n const stored = await this.transact<StoredObject<T> | undefined>(\n \"readonly\",\n (objectStore, resolve, reject) => {\n const request = withOnError(objectStore.get(key), reject);\n\n request.onsuccess = () => {\n resolve(request.result);\n };\n },\n );\n\n if (!stored) {\n return undefined;\n }\n\n try {\n const obj = validateStoredObject(stored);\n if (!obj) {\n await this.delete(key);\n\n this.gc(); // check GC on every read of an expired key\n\n return undefined;\n }\n\n return obj.value;\n } catch (e) {\n console.error(`Invalid kv value: ${key}=${JSON.stringify(stored)}:`, e);\n await this.delete(key);\n\n this.gc(); // check GC on every read of an invalid key\n\n return undefined;\n }\n }\n\n public async forEach(\n callback: (\n key: string,\n value: unknown,\n expireMs: number,\n ) => void | Promise<void>,\n ): Promise<void> {\n await this.transact<void>(\"readonly\", (objectStore, resolve, reject) => {\n const request = withOnError(objectStore.openCursor(), reject);\n\n request.onsuccess = async (event) => {\n const cursor = (\n event.target as unknown as { result: IDBCursorWithValue }\n ).result;\n\n if (cursor) {\n if (cursor.key) {\n const obj = validateStoredObject(cursor.value);\n if (obj) {\n await callback(String(cursor.key), obj.value, obj.expireMs);\n } else {\n await callback(String(cursor.key), undefined, 0);\n }\n }\n cursor.continue();\n } else {\n resolve();\n }\n };\n });\n }\n\n /** Cannot be a getter because this needs to be async. */\n public async size() {\n let count = 0;\n await this.forEach(() => {\n count++;\n });\n return count;\n }\n\n public async clear() {\n const keys: string[] = [];\n await this.forEach((key) => {\n keys.push(key);\n });\n\n await this.delete(keys);\n }\n\n /** Mainly for debugging dumps. */\n public async asMap(): Promise<Map<string, unknown>> {\n const map = new Map<string, unknown>();\n await this.forEach((key, value, expireMs) => {\n map.set(key, { value, expireMs });\n });\n return map;\n }\n\n public get lastGcMs(): number {\n const lastGcMsStr = globalThis.localStorage.getItem(this.gcMsStorageKey);\n if (!lastGcMsStr) return 0;\n\n const ms = Number(lastGcMsStr);\n return isNaN(ms) ? 0 : ms;\n }\n\n public set lastGcMs(ms: number) {\n globalThis.localStorage.setItem(this.gcMsStorageKey, String(ms));\n }\n\n /** Perform garbage-collection if due. */\n public async gc() {\n const lastGcMs = this.lastGcMs;\n\n // Set initial timestamp - no need GC now.\n if (!lastGcMs) {\n this.lastGcMs = Date.now();\n return;\n }\n\n if (Date.now() < lastGcMs + GC_INTERVAL_MS) {\n return; // not due for next GC yet\n }\n\n // GC is due now, so run it.\n await this.gcNow();\n }\n\n /** Perform garbage-collection immediately without checking. */\n public async gcNow() {\n console.log(`Starting kvStore GC on ${this.dbName} v${this.dbVersion}...`);\n\n // Prevent concurrent GC runs.\n this.lastGcMs = Date.now();\n\n const keysToDelete: string[] = [];\n await this.forEach(\n async (key: string, value: unknown, expireMs: number) => {\n if (value === undefined || Date.now() >= expireMs) {\n keysToDelete.push(key);\n }\n },\n );\n\n if (keysToDelete.length) {\n await this.delete(keysToDelete);\n }\n\n console.log(\n `Finished kvStore GC on ${this.dbName} v${this.dbVersion} ` +\n `- deleted ${keysToDelete.length} keys`,\n );\n\n // Mark the end time as last GC time.\n this.lastGcMs = Date.now();\n }\n\n /** Get an independent store item with a locked key and value type. */\n public field<T>(key: string) {\n return new KVStoreField<T>(this, key);\n }\n}\n\n/**\n * Default KV store ready for immediate use. You can create new instances if\n * you want, but most likely you will only need one store instance.\n */\nexport const kvStore = new KVStore(\n DEFAULT_DB_NAME,\n DEFAULT_DB_VERSION,\n DEFAULT_EXPIRY_DELTA_MS,\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,IAAM,kBAAkB;AAGxB,IAAM,qBAAqB;AAG3B,IAAM,aAAa;AAGZ,IAAM,iBAAiB;AAGvB,IAAM,0BAA0B,iBAAiB;AAGjD,IAAM,iBAAiB;AAY9B,SAAS,qBACP,KAC6B;AAC7B,MACE,CAAC,OACD,OAAO,QAAQ,YACf,EAAE,SAAS,QACX,OAAO,IAAI,QAAQ,YACnB,EAAE,WAAW,QACb,IAAI,UAAU,UACd,EAAE,cAAc,QAChB,OAAO,IAAI,aAAa,YACxB,KAAK,IAAI,KAAK,IAAI,UAClB;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAGA,SAAS,YACP,SACA,QACG;AACH,UAAQ,UAAU,CAAC,UAAU;AAC3B,WAAO,KAAK;AAAA,EACd;AAEA,SAAO;AACT;AAEO,IAAM,eAAN,MAAsB;AAAA,EACpB,YACW,OACA,KAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAEI,MAA8B;AACnC,WAAO,KAAK,MAAM,IAAI,KAAK,GAAG;AAAA,EAChC;AAAA,EAEO,IAAI,GAAkB;AAC3B,WAAO,KAAK,MAAM,IAAI,KAAK,KAAK,CAAC;AAAA,EACnC;AAAA,EAEO,SAAwB;AAC7B,WAAO,KAAK,MAAM,OAAO,KAAK,GAAG;AAAA,EACnC;AACF;AAMO,IAAM,UAAN,MAAc;AAAA,EAOZ,YACW,QACA,WACA,sBAChB;AAHgB;AACA;AACA;AAEhB,SAAK,iBAAiB,sBAAsB,MAAM,KAAK,SAAS,IAAI,UAAU;AAAA,EAChF;AAAA,EAEc,gBAAgB;AAAA;AAC5B,UAAI,CAAC,KAAK,IAAI;AACZ,aAAK,KAAK,MAAM,IAAI,QAAqB,CAAC,SAAS,WAAW;AAC5D,gBAAM,UAAU;AAAA,YACd,WAAW,UAAU,KAAK,KAAK,QAAQ,KAAK,SAAS;AAAA,YACrD;AAAA,UACF;AAEA,kBAAQ,kBAAkB,CAAC,UAAU;AACnC,kBAAM,KAAM,MAAM,OACf;AAGH,kBAAM,cAAc,GAAG,kBAAkB,YAAY;AAAA,cACnD,SAAS;AAAA,YACX,CAAC;AAED,wBAAY,YAAY,OAAO,OAAO;AAAA,cACpC,QAAQ;AAAA,YACV,CAAC;AAAA,UACH;AAEA,kBAAQ,YAAY,CAAC,UAAU;AAC7B,kBAAM,KAAM,MAAM,OACf;AACH,oBAAQ,EAAE;AAAA,UACZ;AAAA,QACF,CAAC;AAAA,MACH;AAEA,aAAO,KAAK;AAAA,IACd;AAAA;AAAA,EAEc,SACZ,MACA,UAKY;AAAA;AACZ,YAAM,KAAK,MAAM,KAAK,cAAc;AAEpC,aAAO,MAAM,IAAI,QAAW,CAAC,SAAS,WAAW;AAC/C,cAAM,cAAc,YAAY,GAAG,YAAY,YAAY,IAAI,GAAG,MAAM;AAExE,oBAAY,UAAU,CAAC,UAAU;AAC/B,iBAAO,KAAK;AAAA,QACd;AAEA,cAAM,cAAc,YAAY,YAAY,UAAU;AAEtD,iBAAS,aAAa,SAAS,MAAM;AAAA,MACvC,CAAC;AAAA,IACH;AAAA;AAAA,EAEa,IACX,IACA,IAEY;AAAA,+CAHZ,KACA,OACA,gBAAwB,KAAK,sBACjB;AACZ,YAAM,MAAM;AAAA,QACV;AAAA,QACA;AAAA,QACA,UAAU,KAAK,IAAI,IAAI;AAAA,MACzB;AAEA,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACA,CAAC,aAAa,SAAS,WAAW;AAChC,gBAAM,UAAU,YAAY,YAAY,IAAI,GAAG,GAAG,MAAM;AAExD,kBAAQ,YAAY,MAAM;AACxB,oBAAQ,KAAK;AAEb,iBAAK,GAAG;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA;AAAA,EAGa,OAAO,KAAuC;AAAA;AACzD,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACA,CAAC,aAAa,SAAS,WAAW;AAChC,sBAAY,YAAY,aAAa,MAAM;AACzC,oBAAQ;AAAA,UACV;AAEA,cAAI,OAAO,QAAQ,UAAU;AAC3B,wBAAY,YAAY,OAAO,GAAG,GAAG,MAAM;AAAA,UAC7C,OAAO;AACL,uBAAW,KAAK,KAAK;AACnB,0BAAY,YAAY,OAAO,CAAC,GAAG,MAAM;AAAA,YAC3C;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA,EAEa,IAAO,KAAqC;AAAA;AACvD,YAAM,SAAS,MAAM,KAAK;AAAA,QACxB;AAAA,QACA,CAAC,aAAa,SAAS,WAAW;AAChC,gBAAM,UAAU,YAAY,YAAY,IAAI,GAAG,GAAG,MAAM;AAExD,kBAAQ,YAAY,MAAM;AACxB,oBAAQ,QAAQ,MAAM;AAAA,UACxB;AAAA,QACF;AAAA,MACF;AAEA,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,MACT;AAEA,UAAI;AACF,cAAM,MAAM,qBAAqB,MAAM;AACvC,YAAI,CAAC,KAAK;AACR,gBAAM,KAAK,OAAO,GAAG;AAErB,eAAK,GAAG;AAER,iBAAO;AAAA,QACT;AAEA,eAAO,IAAI;AAAA,MACb,SAAS,GAAG;AACV,gBAAQ,MAAM,qBAAqB,GAAG,IAAI,KAAK,UAAU,MAAM,CAAC,KAAK,CAAC;AACtE,cAAM,KAAK,OAAO,GAAG;AAErB,aAAK,GAAG;AAER,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA,EAEa,QACX,UAKe;AAAA;AACf,YAAM,KAAK,SAAe,YAAY,CAAC,aAAa,SAAS,WAAW;AACtE,cAAM,UAAU,YAAY,YAAY,WAAW,GAAG,MAAM;AAE5D,gBAAQ,YAAY,CAAO,UAAU;AACnC,gBAAM,SACJ,MAAM,OACN;AAEF,cAAI,QAAQ;AACV,gBAAI,OAAO,KAAK;AACd,oBAAM,MAAM,qBAAqB,OAAO,KAAK;AAC7C,kBAAI,KAAK;AACP,sBAAM,SAAS,OAAO,OAAO,GAAG,GAAG,IAAI,OAAO,IAAI,QAAQ;AAAA,cAC5D,OAAO;AACL,sBAAM,SAAS,OAAO,OAAO,GAAG,GAAG,QAAW,CAAC;AAAA,cACjD;AAAA,YACF;AACA,mBAAO,SAAS;AAAA,UAClB,OAAO;AACL,oBAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA;AAAA;AAAA,EAGa,OAAO;AAAA;AAClB,UAAI,QAAQ;AACZ,YAAM,KAAK,QAAQ,MAAM;AACvB;AAAA,MACF,CAAC;AACD,aAAO;AAAA,IACT;AAAA;AAAA,EAEa,QAAQ;AAAA;AACnB,YAAM,OAAiB,CAAC;AACxB,YAAM,KAAK,QAAQ,CAAC,QAAQ;AAC1B,aAAK,KAAK,GAAG;AAAA,MACf,CAAC;AAED,YAAM,KAAK,OAAO,IAAI;AAAA,IACxB;AAAA;AAAA;AAAA,EAGa,QAAuC;AAAA;AAClD,YAAM,MAAM,oBAAI,IAAqB;AACrC,YAAM,KAAK,QAAQ,CAAC,KAAK,OAAO,aAAa;AAC3C,YAAI,IAAI,KAAK,EAAE,OAAO,SAAS,CAAC;AAAA,MAClC,CAAC;AACD,aAAO;AAAA,IACT;AAAA;AAAA,EAEA,IAAW,WAAmB;AAC5B,UAAM,cAAc,WAAW,aAAa,QAAQ,KAAK,cAAc;AACvE,QAAI,CAAC,YAAa,QAAO;AAEzB,UAAM,KAAK,OAAO,WAAW;AAC7B,WAAO,MAAM,EAAE,IAAI,IAAI;AAAA,EACzB;AAAA,EAEA,IAAW,SAAS,IAAY;AAC9B,eAAW,aAAa,QAAQ,KAAK,gBAAgB,OAAO,EAAE,CAAC;AAAA,EACjE;AAAA;AAAA,EAGa,KAAK;AAAA;AAChB,YAAM,WAAW,KAAK;AAGtB,UAAI,CAAC,UAAU;AACb,aAAK,WAAW,KAAK,IAAI;AACzB;AAAA,MACF;AAEA,UAAI,KAAK,IAAI,IAAI,WAAW,gBAAgB;AAC1C;AAAA,MACF;AAGA,YAAM,KAAK,MAAM;AAAA,IACnB;AAAA;AAAA;AAAA,EAGa,QAAQ;AAAA;AACnB,cAAQ,IAAI,0BAA0B,KAAK,MAAM,KAAK,KAAK,SAAS,KAAK;AAGzE,WAAK,WAAW,KAAK,IAAI;AAEzB,YAAM,eAAyB,CAAC;AAChC,YAAM,KAAK;AAAA,QACT,CAAO,KAAa,OAAgB,aAAqB;AACvD,cAAI,UAAU,UAAa,KAAK,IAAI,KAAK,UAAU;AACjD,yBAAa,KAAK,GAAG;AAAA,UACvB;AAAA,QACF;AAAA,MACF;AAEA,UAAI,aAAa,QAAQ;AACvB,cAAM,KAAK,OAAO,YAAY;AAAA,MAChC;AAEA,cAAQ;AAAA,QACN,0BAA0B,KAAK,MAAM,KAAK,KAAK,SAAS,cACzC,aAAa,MAAM;AAAA,MACpC;AAGA,WAAK,WAAW,KAAK,IAAI;AAAA,IAC3B;AAAA;AAAA;AAAA,EAGO,MAAS,KAAa;AAC3B,WAAO,IAAI,aAAgB,MAAM,GAAG;AAAA,EACtC;AACF;AAMO,IAAM,UAAU,IAAI;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/kvStore.ts","../src/timeConstants.ts","../src/duration.ts"],"sourcesContent":["/**\n * Indexed DB key-value store with support for auto-expirations.\n *\n * Why use this?\n * 1. No need to worry about running out of storage.\n * 2. Extremely simple interface to use indexed DBs.\n * 3. Auto-expirations frees you from worrying about data clean-up.\n * 4. Any serializable data type can be stored (except undefined).\n *\n * How to use?\n * Just use the `kvStore` global constant like the local storage.\n */\n\nimport { Duration, durationOrMsToMs } from \"./duration\";\n\n// Updating the DB name will cause all old entries to be gone.\nconst DEFAULT_DB_NAME = \"KVStore\";\n\n// Updating the version will cause all old entries to be gone.\nconst DEFAULT_DB_VERSION = 1;\n\n// Use a constant store name to keep things simple.\nconst STORE_NAME = \"kvStore\";\n\n/** One day in milliseconds. */\nexport const MILLIS_PER_DAY = 86_400_000;\n\n/** 30 days in ms. */\nexport const DEFAULT_EXPIRY_DELTA_MS = MILLIS_PER_DAY * 30;\n\n/** Do GC once per day. */\nexport const GC_INTERVAL_MS = MILLIS_PER_DAY;\n\ntype StoredObject<T> = {\n key: string;\n value: T;\n storedMs: number;\n expiryMs: number;\n};\n\n/**\n * Parse a stored value string. Returns undefined if invalid or expired.\n * Throws an error if the string cannot be parsed as JSON.\n */\nfunction validateStoredObject<T>(\n obj: StoredObject<T>,\n): StoredObject<T> | undefined {\n if (\n !obj ||\n typeof obj !== \"object\" ||\n !(\"key\" in obj) ||\n typeof obj.key !== \"string\" ||\n !(\"value\" in obj) ||\n !(\"storedMs\" in obj) ||\n typeof obj.storedMs !== \"number\" ||\n obj.value === undefined ||\n !(\"expiryMs\" in obj) ||\n typeof obj.expiryMs !== \"number\" ||\n Date.now() >= obj.expiryMs\n ) {\n return undefined;\n }\n\n return obj;\n}\n\n/** Add an `onerror` handler to the request. */\nfunction withOnError<T extends IDBRequest | IDBTransaction>(\n request: T,\n reject: (reason?: unknown) => void,\n): T {\n request.onerror = (event) => {\n reject(event);\n };\n\n return request;\n}\n\nexport class KVStoreField<T> {\n public constructor(\n public readonly store: KVStore,\n public readonly key: string,\n ) {}\n\n public get(): Promise<T | undefined> {\n return this.store.get(this.key);\n }\n\n public set(t: T): Promise<T> {\n return this.store.set(this.key, t);\n }\n\n public delete(): Promise<void> {\n return this.store.delete(this.key);\n }\n}\n\n/**\n * You can create multiple KVStores if you want, but most likely you will only\n * need to use the default `kvStore` instance.\n */\nexport class KVStore {\n // We'll init the DB only on first use.\n private db: IDBDatabase | undefined;\n\n // Local storage key name for the last GC completed timestamp.\n public readonly gcMsStorageKey: string;\n\n public constructor(\n public readonly dbName: string,\n public readonly dbVersion: number,\n public readonly defaultExpiryDeltaMs: number,\n ) {\n this.gcMsStorageKey = `__kvStore:lastGcMs:${dbName}:v${dbVersion}:${STORE_NAME}`;\n }\n\n private async getOrCreateDb() {\n if (!this.db) {\n this.db = await new Promise<IDBDatabase>((resolve, reject) => {\n const request = withOnError(\n globalThis.indexedDB.open(this.dbName, this.dbVersion),\n reject,\n );\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as unknown as { result: IDBDatabase })\n .result;\n\n // Create the store on DB init.\n const objectStore = db.createObjectStore(STORE_NAME, {\n keyPath: \"key\",\n });\n\n objectStore.createIndex(\"key\", \"key\", {\n unique: true,\n });\n };\n\n request.onsuccess = (event) => {\n const db = (event.target as unknown as { result: IDBDatabase })\n .result;\n resolve(db);\n };\n });\n }\n\n return this.db;\n }\n\n private async transact<T>(\n mode: IDBTransactionMode,\n callback: (\n objectStore: IDBObjectStore,\n resolve: (t: T) => void,\n reject: (reason?: unknown) => void,\n ) => void,\n ): Promise<T> {\n const db = await this.getOrCreateDb();\n\n return await new Promise<T>((resolve, reject) => {\n const transaction = withOnError(db.transaction(STORE_NAME, mode), reject);\n\n transaction.onabort = (event) => {\n reject(event);\n };\n\n const objectStore = transaction.objectStore(STORE_NAME);\n\n callback(objectStore, resolve, reject);\n });\n }\n\n public async set<T>(\n key: string,\n value: T,\n expiryDeltaMs: number | Duration = this.defaultExpiryDeltaMs,\n ): Promise<T> {\n const nowMs = Date.now();\n const obj: StoredObject<T> = {\n key,\n value,\n storedMs: nowMs,\n expiryMs: nowMs + durationOrMsToMs(expiryDeltaMs),\n };\n\n return await this.transact<T>(\n \"readwrite\",\n (objectStore, resolve, reject) => {\n const request = withOnError(objectStore.put(obj), reject);\n\n request.onsuccess = () => {\n resolve(value);\n\n this.gc(); // check GC on every write\n };\n },\n );\n }\n\n /** Delete one or multiple keys. */\n public async delete(key: string | string[]): Promise<void> {\n return await this.transact<void>(\n \"readwrite\",\n (objectStore, resolve, reject) => {\n objectStore.transaction.oncomplete = () => {\n resolve();\n };\n\n if (typeof key === \"string\") {\n withOnError(objectStore.delete(key), reject);\n } else {\n for (const k of key) {\n withOnError(objectStore.delete(k), reject);\n }\n }\n },\n );\n }\n\n public async getStoredObject<T>(\n key: string,\n ): Promise<StoredObject<T> | undefined> {\n const stored = await this.transact<StoredObject<T> | undefined>(\n \"readonly\",\n (objectStore, resolve, reject) => {\n const request = withOnError(objectStore.get(key), reject);\n\n request.onsuccess = () => {\n resolve(request.result);\n };\n },\n );\n\n if (!stored) {\n return undefined;\n }\n\n try {\n const obj = validateStoredObject(stored);\n if (!obj) {\n await this.delete(key);\n\n this.gc(); // check GC on every read of an expired key\n\n return undefined;\n }\n\n return obj;\n } catch (e) {\n console.error(`Invalid kv value: ${key}=${JSON.stringify(stored)}:`, e);\n await this.delete(key);\n\n this.gc(); // check GC on every read of an invalid key\n\n return undefined;\n }\n }\n\n public async get<T>(key: string): Promise<T | undefined> {\n const obj = await this.getStoredObject<T>(key);\n\n return obj?.value;\n }\n\n public async forEach(\n callback: (\n key: string,\n value: unknown,\n expiryMs: number,\n ) => void | Promise<void>,\n ): Promise<void> {\n await this.transact<void>(\"readonly\", (objectStore, resolve, reject) => {\n const request = withOnError(objectStore.openCursor(), reject);\n\n request.onsuccess = async (event) => {\n const cursor = (\n event.target as unknown as { result: IDBCursorWithValue }\n ).result;\n\n if (cursor) {\n if (cursor.key) {\n const obj = validateStoredObject(cursor.value);\n if (obj) {\n await callback(String(cursor.key), obj.value, obj.expiryMs);\n } else {\n await callback(String(cursor.key), undefined, 0);\n }\n }\n cursor.continue();\n } else {\n resolve();\n }\n };\n });\n }\n\n /** Cannot be a getter because this needs to be async. */\n public async size() {\n let count = 0;\n await this.forEach(() => {\n count++;\n });\n return count;\n }\n\n public async clear() {\n const keys: string[] = [];\n await this.forEach((key) => {\n keys.push(key);\n });\n\n await this.delete(keys);\n }\n\n /** Mainly for debugging dumps. */\n public async asMap(): Promise<Map<string, unknown>> {\n const map = new Map<string, unknown>();\n await this.forEach((key, value, expiryMs) => {\n map.set(key, { value, expiryMs });\n });\n return map;\n }\n\n public get lastGcMs(): number {\n const lastGcMsStr = globalThis.localStorage.getItem(this.gcMsStorageKey);\n if (!lastGcMsStr) return 0;\n\n const ms = Number(lastGcMsStr);\n return isNaN(ms) ? 0 : ms;\n }\n\n public set lastGcMs(ms: number) {\n globalThis.localStorage.setItem(this.gcMsStorageKey, String(ms));\n }\n\n /** Perform garbage-collection if due. */\n public async gc() {\n const lastGcMs = this.lastGcMs;\n\n // Set initial timestamp - no need GC now.\n if (!lastGcMs) {\n this.lastGcMs = Date.now();\n return;\n }\n\n if (Date.now() < lastGcMs + GC_INTERVAL_MS) {\n return; // not due for next GC yet\n }\n\n // GC is due now, so run it.\n await this.gcNow();\n }\n\n /** Perform garbage-collection immediately without checking. */\n public async gcNow() {\n console.log(`Starting kvStore GC on ${this.dbName} v${this.dbVersion}...`);\n\n // Prevent concurrent GC runs.\n this.lastGcMs = Date.now();\n\n const keysToDelete: string[] = [];\n await this.forEach(\n async (key: string, value: unknown, expiryMs: number) => {\n if (value === undefined || Date.now() >= expiryMs) {\n keysToDelete.push(key);\n }\n },\n );\n\n if (keysToDelete.length) {\n await this.delete(keysToDelete);\n }\n\n console.log(\n `Finished kvStore GC on ${this.dbName} v${this.dbVersion} ` +\n `- deleted ${keysToDelete.length} keys`,\n );\n\n // Mark the end time as last GC time.\n this.lastGcMs = Date.now();\n }\n\n /** Get an independent store item with a locked key and value type. */\n public field<T>(key: string) {\n return new KVStoreField<T>(this, key);\n }\n}\n\n/**\n * Default KV store ready for immediate use. You can create new instances if\n * you want, but most likely you will only need one store instance.\n */\nexport const kvStore = new KVStore(\n DEFAULT_DB_NAME,\n DEFAULT_DB_VERSION,\n DEFAULT_EXPIRY_DELTA_MS,\n);\n\n/**\n * Class to represent one key in the store with a default expiration.\n */\nclass KvStoreItem<T> {\n public constructor(\n public readonly key: string,\n public readonly defaultExpiryDeltaMs: number,\n public readonly store = kvStore,\n ) {}\n\n /**\n * Example usage:\n *\n * const { value, storedMs, expiryMs } = await myKvItem.getStoredObject();\n */\n public async getStoredObject(): Promise<StoredObject<T> | undefined> {\n return await this.store.getStoredObject(this.key);\n }\n\n public async get(): Promise<T | undefined> {\n return await this.store.get(this.key);\n }\n\n public async set(\n value: T,\n expiryDeltaMs: number = this.defaultExpiryDeltaMs,\n ): Promise<void> {\n await this.store.set(this.key, value, expiryDeltaMs);\n }\n\n public async delete(): Promise<void> {\n await this.store.delete(this.key);\n }\n}\n\n/**\n * Create a KV store item with a key and a default expiration.\n */\nexport function kvStoreItem<T>(\n key: string,\n defaultExpiration: number | Duration,\n): KvStoreItem<T> {\n const defaultExpiryDeltaMs = durationOrMsToMs(defaultExpiration);\n\n return new KvStoreItem<T>(key, defaultExpiryDeltaMs);\n}\n","/**\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file.\n */\n\nexport const MS_PER_SECOND = 1000;\nexport const MS_PER_MINUTE = 60_000;\nexport const MS_PER_HOUR = 3_600_000;\nexport const MS_PER_DAY = 86_400_000;\nexport const MS_PER_WEEK = 604_800_000;\n\nexport const SECONDS_PER_MINUTE = 60;\nexport const SECONDS_PER_HOUR = 3_600;\nexport const SECONDS_PER_DAY = 86_400;\nexport const SECONDS_PER_WEEK = 604_800;\n\nexport const MINUTES_PER_HOUR = 60;\nexport const MINUTES_PER_DAY = 1440;\nexport const MINUTES_PER_WEEK = 10_080;\n\nexport const HOURS_PER_DAY = 24;\nexport const HOURS_PER_WEEK = 168;\n","/**\n * Bunch of miscellaneous constants and utility functions related to handling\n * date and time durations.\n *\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file. Weeks have fixed durations, but are excluded because we\n * use days as the max duration supported.\n */\n\nimport {\n MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n MINUTES_PER_HOUR,\n HOURS_PER_DAY,\n MS_PER_DAY,\n MS_PER_MINUTE,\n MS_PER_HOUR,\n} from \"./timeConstants\";\n\nexport type Duration = {\n days?: number;\n hours?: number;\n minutes?: number;\n seconds?: number;\n milliseconds?: number;\n};\n\n/**\n * One of: days, hours, minutes, seconds, milliseconds\n */\nexport type DurationType = keyof Duration;\n\n/**\n * Order in which the duration type appears in the duration string.\n */\nexport const DURATION_TYPE_SEQUENCE: DurationType[] = [\n \"days\",\n \"hours\",\n \"minutes\",\n \"seconds\",\n \"milliseconds\",\n];\n\n/**\n * Follows the same format as Intl.DurationFormat.prototype.format().\n *\n * Short: 1 yr, 2 mths, 3 wks, 3 days, 4 hr, 5 min, 6 sec, 7 ms, 8 μs, 9 ns\n * Long: 1 year, 2 months, 3 weeks, 3 days, 4 hours, 5 minutes, 6 seconds,\n * 7 milliseconds, 8 microseconds, 9 nanoseconds\n * Narrow: 1y 2mo 3w 3d 4h 5m 6s 7ms 8μs 9ns\n */\nexport type DurationStyle = \"short\" | \"long\" | \"narrow\";\n\nexport type DurationSuffixMap = {\n short: string;\n shorts: string;\n long: string;\n longs: string;\n narrow: string;\n};\n\nexport type DurationSuffixType = keyof DurationSuffixMap;\n\nexport const DURATION_STYLE_SUFFIX_MAP: Record<\n DurationType,\n DurationSuffixMap\n> = {\n days: {\n short: \"day\",\n shorts: \"days\",\n long: \"day\",\n longs: \"days\",\n narrow: \"d\",\n },\n hours: {\n short: \"hr\",\n shorts: \"hrs\",\n long: \"hour\",\n longs: \"hours\",\n narrow: \"h\",\n },\n minutes: {\n short: \"min\",\n shorts: \"mins\",\n long: \"minute\",\n longs: \"minutes\",\n narrow: \"m\",\n },\n seconds: {\n short: \"sec\",\n shorts: \"secs\",\n long: \"second\",\n longs: \"seconds\",\n narrow: \"s\",\n },\n milliseconds: {\n short: \"ms\",\n shorts: \"ms\",\n long: \"millisecond\",\n longs: \"milliseconds\",\n narrow: \"ms\",\n },\n};\n\nfunction getDurationStyleForPlural(style: DurationStyle): DurationSuffixType {\n return style == \"short\" ? \"shorts\" : style === \"long\" ? \"longs\" : style;\n}\n\nfunction getValueAndUnitSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \"\" : \" \";\n}\n\nfunction getDurationTypeSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \" \" : \", \";\n}\n\n/**\n * Convert a milliseconds duration into a Duration object. If the given ms is\n * zero, then return an object with a single field of zero with duration type\n * of durationTypeForZero.\n *\n * @param durationTypeForZero Defaults to 'milliseconds'\n */\nexport function msToDuration(\n ms: number,\n durationTypeForZero?: DurationType,\n): Duration {\n if (ms === 0) {\n durationTypeForZero = durationTypeForZero ?? \"milliseconds\";\n return { [durationTypeForZero]: 0 };\n }\n\n const duration: Duration = {};\n\n for (let i = 0; i < 1; i++) {\n let seconds = Math.floor(ms / MS_PER_SECOND);\n const millis = ms - seconds * MS_PER_SECOND;\n\n if (millis > 0) {\n duration[\"milliseconds\"] = millis;\n }\n\n if (seconds === 0) {\n break;\n }\n\n let minutes = Math.floor(seconds / SECONDS_PER_MINUTE);\n seconds -= minutes * SECONDS_PER_MINUTE;\n\n if (seconds > 0) {\n duration[\"seconds\"] = seconds;\n }\n\n if (minutes === 0) {\n break;\n }\n\n let hours = Math.floor(minutes / MINUTES_PER_HOUR);\n minutes -= hours * MINUTES_PER_HOUR;\n\n if (minutes > 0) {\n duration[\"minutes\"] = minutes;\n }\n\n if (hours === 0) {\n break;\n }\n\n const days = Math.floor(hours / HOURS_PER_DAY);\n hours -= days * HOURS_PER_DAY;\n\n if (hours > 0) {\n duration[\"hours\"] = hours;\n }\n\n if (days > 0) {\n duration[\"days\"] = days;\n }\n }\n\n return duration;\n}\n\n/**\n * Returns the number of milliseconds for the given duration.\n */\nexport function durationToMs(duration: Duration): number {\n const daysMs = (duration.days ?? 0) * MS_PER_DAY;\n const hoursMs = (duration.hours ?? 0) * MS_PER_HOUR;\n const minsMs = (duration.minutes ?? 0) * MS_PER_MINUTE;\n const secsMs = (duration.seconds ?? 0) * MS_PER_SECOND;\n const msMs = duration.milliseconds ?? 0;\n\n return daysMs + hoursMs + minsMs + secsMs + msMs;\n}\n\n/**\n * Convenience function to return a duration given an ms or Duration.\n */\nexport function durationOrMsToMs(duration: number | Duration): number {\n return typeof duration === \"number\" ? duration : durationToMs(duration);\n}\n\n/**\n * Format a given Duration object into a string. If the object has no fields,\n * then returns an empty string.\n *\n * @param style Defaults to 'short'\n */\nexport function formatDuration(duration: Duration, style?: DurationStyle) {\n style = style ?? \"short\";\n const stylePlural = getDurationStyleForPlural(style);\n\n const space = getValueAndUnitSeparator(style);\n\n const a: string[] = [];\n\n for (const unit of DURATION_TYPE_SEQUENCE) {\n const value = duration[unit];\n if (value === undefined) continue;\n\n const suffixMap = DURATION_STYLE_SUFFIX_MAP[unit];\n const suffix = value === 1 ? suffixMap[style] : suffixMap[stylePlural];\n a.push(value + space + suffix);\n }\n\n const separator = getDurationTypeSeparator(style);\n return a.join(separator);\n}\n\n/**\n * Convert a millisecond duration into a human-readable duration string.\n *\n * @param options.durationTypeForZero - Defaults to 'milliseconds'\n * @param options.style - Defaults to 'short'\n */\nexport function readableDuration(\n ms: number,\n options?: { durationTypeForZero?: DurationType; style?: DurationStyle },\n): string {\n const duration = msToDuration(ms, options?.durationTypeForZero);\n\n return formatDuration(duration, options?.style);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKO,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AACtB,IAAM,cAAc;AACpB,IAAM,aAAa;;;ACkLnB,SAAS,aAAa,UAA4B;AA1LzD;AA2LE,QAAM,WAAU,cAAS,SAAT,YAAiB,KAAK;AACtC,QAAM,YAAW,cAAS,UAAT,YAAkB,KAAK;AACxC,QAAM,WAAU,cAAS,YAAT,YAAoB,KAAK;AACzC,QAAM,WAAU,cAAS,YAAT,YAAoB,KAAK;AACzC,QAAM,QAAO,cAAS,iBAAT,YAAyB;AAEtC,SAAO,SAAS,UAAU,SAAS,SAAS;AAC9C;AAKO,SAAS,iBAAiB,UAAqC;AACpE,SAAO,OAAO,aAAa,WAAW,WAAW,aAAa,QAAQ;AACxE;;;AFzLA,IAAM,kBAAkB;AAGxB,IAAM,qBAAqB;AAG3B,IAAM,aAAa;AAGZ,IAAM,iBAAiB;AAGvB,IAAM,0BAA0B,iBAAiB;AAGjD,IAAM,iBAAiB;AAa9B,SAAS,qBACP,KAC6B;AAC7B,MACE,CAAC,OACD,OAAO,QAAQ,YACf,EAAE,SAAS,QACX,OAAO,IAAI,QAAQ,YACnB,EAAE,WAAW,QACb,EAAE,cAAc,QAChB,OAAO,IAAI,aAAa,YACxB,IAAI,UAAU,UACd,EAAE,cAAc,QAChB,OAAO,IAAI,aAAa,YACxB,KAAK,IAAI,KAAK,IAAI,UAClB;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAGA,SAAS,YACP,SACA,QACG;AACH,UAAQ,UAAU,CAAC,UAAU;AAC3B,WAAO,KAAK;AAAA,EACd;AAEA,SAAO;AACT;AAEO,IAAM,eAAN,MAAsB;AAAA,EACpB,YACW,OACA,KAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAEI,MAA8B;AACnC,WAAO,KAAK,MAAM,IAAI,KAAK,GAAG;AAAA,EAChC;AAAA,EAEO,IAAI,GAAkB;AAC3B,WAAO,KAAK,MAAM,IAAI,KAAK,KAAK,CAAC;AAAA,EACnC;AAAA,EAEO,SAAwB;AAC7B,WAAO,KAAK,MAAM,OAAO,KAAK,GAAG;AAAA,EACnC;AACF;AAMO,IAAM,UAAN,MAAc;AAAA,EAOZ,YACW,QACA,WACA,sBAChB;AAHgB;AACA;AACA;AAEhB,SAAK,iBAAiB,sBAAsB,MAAM,KAAK,SAAS,IAAI,UAAU;AAAA,EAChF;AAAA,EAEc,gBAAgB;AAAA;AAC5B,UAAI,CAAC,KAAK,IAAI;AACZ,aAAK,KAAK,MAAM,IAAI,QAAqB,CAAC,SAAS,WAAW;AAC5D,gBAAM,UAAU;AAAA,YACd,WAAW,UAAU,KAAK,KAAK,QAAQ,KAAK,SAAS;AAAA,YACrD;AAAA,UACF;AAEA,kBAAQ,kBAAkB,CAAC,UAAU;AACnC,kBAAM,KAAM,MAAM,OACf;AAGH,kBAAM,cAAc,GAAG,kBAAkB,YAAY;AAAA,cACnD,SAAS;AAAA,YACX,CAAC;AAED,wBAAY,YAAY,OAAO,OAAO;AAAA,cACpC,QAAQ;AAAA,YACV,CAAC;AAAA,UACH;AAEA,kBAAQ,YAAY,CAAC,UAAU;AAC7B,kBAAM,KAAM,MAAM,OACf;AACH,oBAAQ,EAAE;AAAA,UACZ;AAAA,QACF,CAAC;AAAA,MACH;AAEA,aAAO,KAAK;AAAA,IACd;AAAA;AAAA,EAEc,SACZ,MACA,UAKY;AAAA;AACZ,YAAM,KAAK,MAAM,KAAK,cAAc;AAEpC,aAAO,MAAM,IAAI,QAAW,CAAC,SAAS,WAAW;AAC/C,cAAM,cAAc,YAAY,GAAG,YAAY,YAAY,IAAI,GAAG,MAAM;AAExE,oBAAY,UAAU,CAAC,UAAU;AAC/B,iBAAO,KAAK;AAAA,QACd;AAEA,cAAM,cAAc,YAAY,YAAY,UAAU;AAEtD,iBAAS,aAAa,SAAS,MAAM;AAAA,MACvC,CAAC;AAAA,IACH;AAAA;AAAA,EAEa,IACX,IACA,IAEY;AAAA,+CAHZ,KACA,OACA,gBAAmC,KAAK,sBAC5B;AACZ,YAAM,QAAQ,KAAK,IAAI;AACvB,YAAM,MAAuB;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV,UAAU,QAAQ,iBAAiB,aAAa;AAAA,MAClD;AAEA,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACA,CAAC,aAAa,SAAS,WAAW;AAChC,gBAAM,UAAU,YAAY,YAAY,IAAI,GAAG,GAAG,MAAM;AAExD,kBAAQ,YAAY,MAAM;AACxB,oBAAQ,KAAK;AAEb,iBAAK,GAAG;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA;AAAA,EAGa,OAAO,KAAuC;AAAA;AACzD,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACA,CAAC,aAAa,SAAS,WAAW;AAChC,sBAAY,YAAY,aAAa,MAAM;AACzC,oBAAQ;AAAA,UACV;AAEA,cAAI,OAAO,QAAQ,UAAU;AAC3B,wBAAY,YAAY,OAAO,GAAG,GAAG,MAAM;AAAA,UAC7C,OAAO;AACL,uBAAW,KAAK,KAAK;AACnB,0BAAY,YAAY,OAAO,CAAC,GAAG,MAAM;AAAA,YAC3C;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA,EAEa,gBACX,KACsC;AAAA;AACtC,YAAM,SAAS,MAAM,KAAK;AAAA,QACxB;AAAA,QACA,CAAC,aAAa,SAAS,WAAW;AAChC,gBAAM,UAAU,YAAY,YAAY,IAAI,GAAG,GAAG,MAAM;AAExD,kBAAQ,YAAY,MAAM;AACxB,oBAAQ,QAAQ,MAAM;AAAA,UACxB;AAAA,QACF;AAAA,MACF;AAEA,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,MACT;AAEA,UAAI;AACF,cAAM,MAAM,qBAAqB,MAAM;AACvC,YAAI,CAAC,KAAK;AACR,gBAAM,KAAK,OAAO,GAAG;AAErB,eAAK,GAAG;AAER,iBAAO;AAAA,QACT;AAEA,eAAO;AAAA,MACT,SAAS,GAAG;AACV,gBAAQ,MAAM,qBAAqB,GAAG,IAAI,KAAK,UAAU,MAAM,CAAC,KAAK,CAAC;AACtE,cAAM,KAAK,OAAO,GAAG;AAErB,aAAK,GAAG;AAER,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA,EAEa,IAAO,KAAqC;AAAA;AACvD,YAAM,MAAM,MAAM,KAAK,gBAAmB,GAAG;AAE7C,aAAO,2BAAK;AAAA,IACd;AAAA;AAAA,EAEa,QACX,UAKe;AAAA;AACf,YAAM,KAAK,SAAe,YAAY,CAAC,aAAa,SAAS,WAAW;AACtE,cAAM,UAAU,YAAY,YAAY,WAAW,GAAG,MAAM;AAE5D,gBAAQ,YAAY,CAAO,UAAU;AACnC,gBAAM,SACJ,MAAM,OACN;AAEF,cAAI,QAAQ;AACV,gBAAI,OAAO,KAAK;AACd,oBAAM,MAAM,qBAAqB,OAAO,KAAK;AAC7C,kBAAI,KAAK;AACP,sBAAM,SAAS,OAAO,OAAO,GAAG,GAAG,IAAI,OAAO,IAAI,QAAQ;AAAA,cAC5D,OAAO;AACL,sBAAM,SAAS,OAAO,OAAO,GAAG,GAAG,QAAW,CAAC;AAAA,cACjD;AAAA,YACF;AACA,mBAAO,SAAS;AAAA,UAClB,OAAO;AACL,oBAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA;AAAA;AAAA,EAGa,OAAO;AAAA;AAClB,UAAI,QAAQ;AACZ,YAAM,KAAK,QAAQ,MAAM;AACvB;AAAA,MACF,CAAC;AACD,aAAO;AAAA,IACT;AAAA;AAAA,EAEa,QAAQ;AAAA;AACnB,YAAM,OAAiB,CAAC;AACxB,YAAM,KAAK,QAAQ,CAAC,QAAQ;AAC1B,aAAK,KAAK,GAAG;AAAA,MACf,CAAC;AAED,YAAM,KAAK,OAAO,IAAI;AAAA,IACxB;AAAA;AAAA;AAAA,EAGa,QAAuC;AAAA;AAClD,YAAM,MAAM,oBAAI,IAAqB;AACrC,YAAM,KAAK,QAAQ,CAAC,KAAK,OAAO,aAAa;AAC3C,YAAI,IAAI,KAAK,EAAE,OAAO,SAAS,CAAC;AAAA,MAClC,CAAC;AACD,aAAO;AAAA,IACT;AAAA;AAAA,EAEA,IAAW,WAAmB;AAC5B,UAAM,cAAc,WAAW,aAAa,QAAQ,KAAK,cAAc;AACvE,QAAI,CAAC,YAAa,QAAO;AAEzB,UAAM,KAAK,OAAO,WAAW;AAC7B,WAAO,MAAM,EAAE,IAAI,IAAI;AAAA,EACzB;AAAA,EAEA,IAAW,SAAS,IAAY;AAC9B,eAAW,aAAa,QAAQ,KAAK,gBAAgB,OAAO,EAAE,CAAC;AAAA,EACjE;AAAA;AAAA,EAGa,KAAK;AAAA;AAChB,YAAM,WAAW,KAAK;AAGtB,UAAI,CAAC,UAAU;AACb,aAAK,WAAW,KAAK,IAAI;AACzB;AAAA,MACF;AAEA,UAAI,KAAK,IAAI,IAAI,WAAW,gBAAgB;AAC1C;AAAA,MACF;AAGA,YAAM,KAAK,MAAM;AAAA,IACnB;AAAA;AAAA;AAAA,EAGa,QAAQ;AAAA;AACnB,cAAQ,IAAI,0BAA0B,KAAK,MAAM,KAAK,KAAK,SAAS,KAAK;AAGzE,WAAK,WAAW,KAAK,IAAI;AAEzB,YAAM,eAAyB,CAAC;AAChC,YAAM,KAAK;AAAA,QACT,CAAO,KAAa,OAAgB,aAAqB;AACvD,cAAI,UAAU,UAAa,KAAK,IAAI,KAAK,UAAU;AACjD,yBAAa,KAAK,GAAG;AAAA,UACvB;AAAA,QACF;AAAA,MACF;AAEA,UAAI,aAAa,QAAQ;AACvB,cAAM,KAAK,OAAO,YAAY;AAAA,MAChC;AAEA,cAAQ;AAAA,QACN,0BAA0B,KAAK,MAAM,KAAK,KAAK,SAAS,cACzC,aAAa,MAAM;AAAA,MACpC;AAGA,WAAK,WAAW,KAAK,IAAI;AAAA,IAC3B;AAAA;AAAA;AAAA,EAGO,MAAS,KAAa;AAC3B,WAAO,IAAI,aAAgB,MAAM,GAAG;AAAA,EACtC;AACF;AAMO,IAAM,UAAU,IAAI;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF;AAKA,IAAM,cAAN,MAAqB;AAAA,EACZ,YACW,KACA,sBACA,QAAQ,SACxB;AAHgB;AACA;AACA;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,kBAAwD;AAAA;AACnE,aAAO,MAAM,KAAK,MAAM,gBAAgB,KAAK,GAAG;AAAA,IAClD;AAAA;AAAA,EAEa,MAA8B;AAAA;AACzC,aAAO,MAAM,KAAK,MAAM,IAAI,KAAK,GAAG;AAAA,IACtC;AAAA;AAAA,EAEa,IACX,IAEe;AAAA,+CAFf,OACA,gBAAwB,KAAK,sBACd;AACf,YAAM,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO,aAAa;AAAA,IACrD;AAAA;AAAA,EAEa,SAAwB;AAAA;AACnC,YAAM,KAAK,MAAM,OAAO,KAAK,GAAG;AAAA,IAClC;AAAA;AACF;AAKO,SAAS,YACd,KACA,mBACgB;AAChB,QAAM,uBAAuB,iBAAiB,iBAAiB;AAE/D,SAAO,IAAI,YAAe,KAAK,oBAAoB;AACrD;","names":[]}