@choksheak/ts-utils 0.1.5 → 0.1.6

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.
@@ -0,0 +1,14 @@
1
+ export declare class DateTimeStr {
2
+ static yyyyMmDd(dt?: Date, separator?: string): string;
3
+ static hhMmSs(dt?: Date, separator?: string): string;
4
+ static hhMmSsMs(dt?: Date, timeSeparator?: string, msSeparator?: string): string;
5
+ static tz(dt?: Date): string;
6
+ /** Full local date/time string. */
7
+ static local(dt?: Date, dateSeparator?: string, dtSeparator?: string, timeSeparator?: string, msSeparator?: string): string;
8
+ /** Use the default ISO string function to keep things simple here. */
9
+ static utc(dt?: Date): string;
10
+ /** Default full local date+time string with full & concise info. */
11
+ static get now(): string;
12
+ /** Default full UTC date+time string with full & concise info. */
13
+ static get utcNow(): string;
14
+ }
package/dateTimeStr.js ADDED
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/dateTimeStr.ts
21
+ var dateTimeStr_exports = {};
22
+ __export(dateTimeStr_exports, {
23
+ DateTimeStr: () => DateTimeStr
24
+ });
25
+ module.exports = __toCommonJS(dateTimeStr_exports);
26
+ var DateTimeStr = class _DateTimeStr {
27
+ static yyyyMmDd(dt = /* @__PURE__ */ new Date(), separator = "-") {
28
+ const yr = dt.getFullYear();
29
+ const mth = dt.getMonth() + 1;
30
+ const day = dt.getDate();
31
+ return yr + separator + (mth < 10 ? "0" + mth : mth) + separator + (day < 10 ? "0" + day : day);
32
+ }
33
+ static hhMmSs(dt = /* @__PURE__ */ new Date(), separator = ":") {
34
+ const hr = dt.getHours();
35
+ const min = dt.getMinutes();
36
+ const sec = dt.getSeconds();
37
+ return (hr < 10 ? "0" + hr : hr) + separator + (min < 10 ? "0" + min : min) + separator + (sec < 10 ? "0" + sec : sec);
38
+ }
39
+ static hhMmSsMs(dt = /* @__PURE__ */ new Date(), timeSeparator = ":", msSeparator = ".") {
40
+ const ms = dt.getMilliseconds();
41
+ return _DateTimeStr.hhMmSs(dt, timeSeparator) + msSeparator + (ms < 10 ? "00" + ms : ms < 100 ? "0" + ms : ms);
42
+ }
43
+ static tz(dt = /* @__PURE__ */ new Date()) {
44
+ if (dt.getTimezoneOffset() === 0) {
45
+ return "Z";
46
+ }
47
+ const tzHours = dt.getTimezoneOffset() / 60;
48
+ return tzHours >= 0 ? "+" + tzHours : String(tzHours);
49
+ }
50
+ /** Full local date/time string. */
51
+ static local(dt = /* @__PURE__ */ new Date(), dateSeparator = "-", dtSeparator = " ", timeSeparator = ":", msSeparator = ".") {
52
+ return _DateTimeStr.yyyyMmDd(dt, dateSeparator) + dtSeparator + _DateTimeStr.hhMmSsMs(dt, timeSeparator, msSeparator) + _DateTimeStr.tz(dt);
53
+ }
54
+ /** Use the default ISO string function to keep things simple here. */
55
+ static utc(dt = /* @__PURE__ */ new Date()) {
56
+ return dt.toISOString();
57
+ }
58
+ /** Default full local date+time string with full & concise info. */
59
+ static get now() {
60
+ return _DateTimeStr.local();
61
+ }
62
+ /** Default full UTC date+time string with full & concise info. */
63
+ static get utcNow() {
64
+ return _DateTimeStr.utc();
65
+ }
66
+ };
67
+ // Annotate the CommonJS export names for ESM import in node:
68
+ 0 && (module.exports = {
69
+ DateTimeStr
70
+ });
71
+ //# sourceMappingURL=dateTimeStr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dateTimeStr.ts"],"sourcesContent":["export class DateTimeStr {\n public static 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\n public static 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\n public static hhMmSsMs(\n dt = new Date(),\n timeSeparator = \":\",\n msSeparator = \".\",\n ): string {\n const ms = dt.getMilliseconds();\n\n return (\n DateTimeStr.hhMmSs(dt, timeSeparator) +\n msSeparator +\n (ms < 10 ? \"00\" + ms : ms < 100 ? \"0\" + ms : ms)\n );\n }\n\n public static tz(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 /** Full local date/time string. */\n public static local(\n dt = new Date(),\n dateSeparator = \"-\",\n dtSeparator = \" \",\n timeSeparator = \":\",\n msSeparator = \".\",\n ): string {\n return (\n DateTimeStr.yyyyMmDd(dt, dateSeparator) +\n dtSeparator +\n DateTimeStr.hhMmSsMs(dt, timeSeparator, msSeparator) +\n DateTimeStr.tz(dt)\n );\n }\n\n /** Use the default ISO string function to keep things simple here. */\n public static utc(dt = new Date()) {\n return dt.toISOString();\n }\n\n /** Default full local date+time string with full & concise info. */\n public static get now() {\n return DateTimeStr.local();\n }\n\n /** Default full UTC date+time string with full & concise info. */\n public static get utcNow() {\n return DateTimeStr.utc();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,IAAM,cAAN,MAAM,aAAY;AAAA,EACvB,OAAc,SAAS,KAAK,oBAAI,KAAK,GAAG,YAAY,KAAa;AAC/D,UAAM,KAAK,GAAG,YAAY;AAC1B,UAAM,MAAM,GAAG,SAAS,IAAI;AAC5B,UAAM,MAAM,GAAG,QAAQ;AAEvB,WACE,KACA,aACC,MAAM,KAAK,MAAM,MAAM,OACxB,aACC,MAAM,KAAK,MAAM,MAAM;AAAA,EAE5B;AAAA,EAEA,OAAc,OAAO,KAAK,oBAAI,KAAK,GAAG,YAAY,KAAa;AAC7D,UAAM,KAAK,GAAG,SAAS;AACvB,UAAM,MAAM,GAAG,WAAW;AAC1B,UAAM,MAAM,GAAG,WAAW;AAE1B,YACG,KAAK,KAAK,MAAM,KAAK,MACtB,aACC,MAAM,KAAK,MAAM,MAAM,OACxB,aACC,MAAM,KAAK,MAAM,MAAM;AAAA,EAE5B;AAAA,EAEA,OAAc,SACZ,KAAK,oBAAI,KAAK,GACd,gBAAgB,KAChB,cAAc,KACN;AACR,UAAM,KAAK,GAAG,gBAAgB;AAE9B,WACE,aAAY,OAAO,IAAI,aAAa,IACpC,eACC,KAAK,KAAK,OAAO,KAAK,KAAK,MAAM,MAAM,KAAK;AAAA,EAEjD;AAAA,EAEA,OAAc,GAAG,KAAK,oBAAI,KAAK,GAAW;AACxC,QAAI,GAAG,kBAAkB,MAAM,GAAG;AAChC,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,GAAG,kBAAkB,IAAI;AACzC,WAAO,WAAW,IAAI,MAAM,UAAU,OAAO,OAAO;AAAA,EACtD;AAAA;AAAA,EAGA,OAAc,MACZ,KAAK,oBAAI,KAAK,GACd,gBAAgB,KAChB,cAAc,KACd,gBAAgB,KAChB,cAAc,KACN;AACR,WACE,aAAY,SAAS,IAAI,aAAa,IACtC,cACA,aAAY,SAAS,IAAI,eAAe,WAAW,IACnD,aAAY,GAAG,EAAE;AAAA,EAErB;AAAA;AAAA,EAGA,OAAc,IAAI,KAAK,oBAAI,KAAK,GAAG;AACjC,WAAO,GAAG,YAAY;AAAA,EACxB;AAAA;AAAA,EAGA,WAAkB,MAAM;AACtB,WAAO,aAAY,MAAM;AAAA,EAC3B;AAAA;AAAA,EAGA,WAAkB,SAAS;AACzB,WAAO,aAAY,IAAI;AAAA,EACzB;AACF;","names":[]}
package/isEmpty.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Returns true if `t` is empty.
3
+ */
4
+ export declare function isEmpty(t: unknown): boolean;
package/isEmpty.js ADDED
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/isEmpty.ts
21
+ var isEmpty_exports = {};
22
+ __export(isEmpty_exports, {
23
+ isEmpty: () => isEmpty
24
+ });
25
+ module.exports = __toCommonJS(isEmpty_exports);
26
+ function isEmpty(t) {
27
+ if (!t) {
28
+ return true;
29
+ }
30
+ if (typeof t !== "object") {
31
+ return false;
32
+ }
33
+ if ("length" in t) {
34
+ return t.length === 0;
35
+ }
36
+ if ("size" in t) {
37
+ return t.size === 0;
38
+ }
39
+ for (const k in t) {
40
+ return false;
41
+ }
42
+ return true;
43
+ }
44
+ // Annotate the CommonJS export names for ESM import in node:
45
+ 0 && (module.exports = {
46
+ isEmpty
47
+ });
48
+ //# sourceMappingURL=isEmpty.js.map
package/isEmpty.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/isEmpty.ts"],"sourcesContent":["/**\n * Returns true if `t` is empty.\n */\nexport function isEmpty(t: unknown): boolean {\n // Anything falsy is considered empty.\n if (!t) {\n return true;\n }\n\n // Arrays are also of type `object`.\n if (typeof t !== \"object\") {\n return false;\n }\n\n // `length` includes arrays as well.\n if (\"length\" in t) {\n return t.length === 0;\n }\n\n // `size` is for Set, Map, Blob etc.\n if (\"size\" in t) {\n return t.size === 0;\n }\n\n // Super fast check for object emptiness.\n // https://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object\n for (const k in t) {\n return false;\n }\n\n return true;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,SAAS,QAAQ,GAAqB;AAE3C,MAAI,CAAC,GAAG;AACN,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO;AAAA,EACT;AAGA,MAAI,YAAY,GAAG;AACjB,WAAO,EAAE,WAAW;AAAA,EACtB;AAGA,MAAI,UAAU,GAAG;AACf,WAAO,EAAE,SAAS;AAAA,EACpB;AAIA,aAAW,KAAK,GAAG;AACjB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;","names":[]}
package/kvStore.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Indexed DB key-value store with support for auto-expirations.
3
+ *
4
+ * Why use this?
5
+ * 1. No need to worry about running out of storage.
6
+ * 2. Extremely simple interface to use indexed DBs.
7
+ * 3. Auto-expirations frees you from worrying about data clean-up.
8
+ * 4. Any serializable data type can be stored (except undefined).
9
+ *
10
+ * How to use?
11
+ * Just use the `kvStore` global constant like the local storage.
12
+ */
13
+ /** One day in milliseconds. */
14
+ export declare const MILLIS_PER_DAY = 86400000;
15
+ /** 30 days in ms. */
16
+ export declare const DEFAULT_EXPIRY_DELTA_MS: number;
17
+ /** Do GC once per day. */
18
+ export declare const GC_INTERVAL_MS = 86400000;
19
+ export declare class KVStoreField<T> {
20
+ readonly store: KVStore;
21
+ readonly key: string;
22
+ constructor(store: KVStore, key: string);
23
+ get(): Promise<T | undefined>;
24
+ set(t: T): Promise<T>;
25
+ delete(): Promise<void>;
26
+ }
27
+ /**
28
+ * You can create multiple KVStores if you want, but most likely you will only
29
+ * need to use the default `kvStore` instance.
30
+ */
31
+ export declare class KVStore {
32
+ readonly dbName: string;
33
+ readonly dbVersion: number;
34
+ readonly defaultExpiryDeltaMs: number;
35
+ private db;
36
+ readonly gcMsStorageKey: string;
37
+ constructor(dbName: string, dbVersion: number, defaultExpiryDeltaMs: number);
38
+ private getOrCreateDb;
39
+ private transact;
40
+ set<T>(key: string, value: T, expireDeltaMs?: number): Promise<T>;
41
+ /** Delete one or multiple keys. */
42
+ delete(key: string | string[]): Promise<void>;
43
+ get<T>(key: string): Promise<T | undefined>;
44
+ forEach(callback: (key: string, value: unknown, expireMs: number) => void | Promise<void>): Promise<void>;
45
+ /** Cannot be a getter because this needs to be async. */
46
+ size(): Promise<number>;
47
+ clear(): Promise<void>;
48
+ /** Mainly for debugging dumps. */
49
+ asMap(): Promise<Map<string, unknown>>;
50
+ get lastGcMs(): number;
51
+ set lastGcMs(ms: number);
52
+ /** Perform garbage-collection if due. */
53
+ gc(): Promise<void>;
54
+ /** Perform garbage-collection immediately without checking. */
55
+ gcNow(): Promise<void>;
56
+ /** Get an independent store item with a locked key and value type. */
57
+ field<T>(key: string): KVStoreField<T>;
58
+ }
59
+ /**
60
+ * Default KV store ready for immediate use. You can create new instances if
61
+ * you want, but most likely you will only need one store instance.
62
+ */
63
+ export declare const kvStore: KVStore;
package/kvStore.js ADDED
@@ -0,0 +1,315 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var __async = (__this, __arguments, generator) => {
20
+ return new Promise((resolve, reject) => {
21
+ var fulfilled = (value) => {
22
+ try {
23
+ step(generator.next(value));
24
+ } catch (e) {
25
+ reject(e);
26
+ }
27
+ };
28
+ var rejected = (value) => {
29
+ try {
30
+ step(generator.throw(value));
31
+ } catch (e) {
32
+ reject(e);
33
+ }
34
+ };
35
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
36
+ step((generator = generator.apply(__this, __arguments)).next());
37
+ });
38
+ };
39
+
40
+ // src/kvStore.ts
41
+ var kvStore_exports = {};
42
+ __export(kvStore_exports, {
43
+ DEFAULT_EXPIRY_DELTA_MS: () => DEFAULT_EXPIRY_DELTA_MS,
44
+ GC_INTERVAL_MS: () => GC_INTERVAL_MS,
45
+ KVStore: () => KVStore,
46
+ KVStoreField: () => KVStoreField,
47
+ MILLIS_PER_DAY: () => MILLIS_PER_DAY,
48
+ kvStore: () => kvStore
49
+ });
50
+ module.exports = __toCommonJS(kvStore_exports);
51
+ var DEFAULT_DB_NAME = "KVStore";
52
+ var DEFAULT_DB_VERSION = 1;
53
+ var STORE_NAME = "kvStore";
54
+ var MILLIS_PER_DAY = 864e5;
55
+ var DEFAULT_EXPIRY_DELTA_MS = MILLIS_PER_DAY * 30;
56
+ var GC_INTERVAL_MS = MILLIS_PER_DAY;
57
+ 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) {
59
+ return void 0;
60
+ }
61
+ return obj;
62
+ }
63
+ function withOnError(request, reject) {
64
+ request.onerror = (event) => {
65
+ reject(event);
66
+ };
67
+ return request;
68
+ }
69
+ var KVStoreField = class {
70
+ constructor(store, key) {
71
+ this.store = store;
72
+ this.key = key;
73
+ }
74
+ get() {
75
+ return this.store.get(this.key);
76
+ }
77
+ set(t) {
78
+ return this.store.set(this.key, t);
79
+ }
80
+ delete() {
81
+ return this.store.delete(this.key);
82
+ }
83
+ };
84
+ var KVStore = class {
85
+ constructor(dbName, dbVersion, defaultExpiryDeltaMs) {
86
+ this.dbName = dbName;
87
+ this.dbVersion = dbVersion;
88
+ this.defaultExpiryDeltaMs = defaultExpiryDeltaMs;
89
+ this.gcMsStorageKey = `__kvStore:lastGcMs:${dbName}:v${dbVersion}:${STORE_NAME}`;
90
+ }
91
+ getOrCreateDb() {
92
+ return __async(this, null, function* () {
93
+ if (!this.db) {
94
+ this.db = yield new Promise((resolve, reject) => {
95
+ const request = withOnError(
96
+ globalThis.indexedDB.open(this.dbName, this.dbVersion),
97
+ reject
98
+ );
99
+ request.onupgradeneeded = (event) => {
100
+ const db = event.target.result;
101
+ const objectStore = db.createObjectStore(STORE_NAME, {
102
+ keyPath: "key"
103
+ });
104
+ objectStore.createIndex("key", "key", {
105
+ unique: true
106
+ });
107
+ };
108
+ request.onsuccess = (event) => {
109
+ const db = event.target.result;
110
+ resolve(db);
111
+ };
112
+ });
113
+ }
114
+ return this.db;
115
+ });
116
+ }
117
+ transact(mode, callback) {
118
+ return __async(this, null, function* () {
119
+ const db = yield this.getOrCreateDb();
120
+ return yield new Promise((resolve, reject) => {
121
+ const transaction = withOnError(db.transaction(STORE_NAME, mode), reject);
122
+ transaction.onabort = (event) => {
123
+ reject(event);
124
+ };
125
+ const objectStore = transaction.objectStore(STORE_NAME);
126
+ callback(objectStore, resolve, reject);
127
+ });
128
+ });
129
+ }
130
+ set(_0, _1) {
131
+ return __async(this, arguments, function* (key, value, expireDeltaMs = this.defaultExpiryDeltaMs) {
132
+ const obj = {
133
+ key,
134
+ value,
135
+ expireMs: Date.now() + expireDeltaMs
136
+ };
137
+ return yield this.transact(
138
+ "readwrite",
139
+ (objectStore, resolve, reject) => {
140
+ const request = withOnError(objectStore.put(obj), reject);
141
+ request.onsuccess = () => {
142
+ resolve(value);
143
+ this.gc();
144
+ };
145
+ }
146
+ );
147
+ });
148
+ }
149
+ /** Delete one or multiple keys. */
150
+ delete(key) {
151
+ return __async(this, null, function* () {
152
+ return yield this.transact(
153
+ "readwrite",
154
+ (objectStore, resolve, reject) => {
155
+ objectStore.transaction.oncomplete = () => {
156
+ resolve();
157
+ };
158
+ if (typeof key === "string") {
159
+ withOnError(objectStore.delete(key), reject);
160
+ } else {
161
+ for (const k of key) {
162
+ withOnError(objectStore.delete(k), reject);
163
+ }
164
+ }
165
+ }
166
+ );
167
+ });
168
+ }
169
+ get(key) {
170
+ return __async(this, null, function* () {
171
+ const stored = yield this.transact(
172
+ "readonly",
173
+ (objectStore, resolve, reject) => {
174
+ const request = withOnError(objectStore.get(key), reject);
175
+ request.onsuccess = () => {
176
+ resolve(request.result);
177
+ };
178
+ }
179
+ );
180
+ if (!stored) {
181
+ return void 0;
182
+ }
183
+ try {
184
+ const obj = validateStoredObject(stored);
185
+ if (!obj) {
186
+ yield this.delete(key);
187
+ this.gc();
188
+ return void 0;
189
+ }
190
+ return obj.value;
191
+ } catch (e) {
192
+ console.error(`Invalid kv value: ${key}=${JSON.stringify(stored)}:`, e);
193
+ yield this.delete(key);
194
+ this.gc();
195
+ return void 0;
196
+ }
197
+ });
198
+ }
199
+ forEach(callback) {
200
+ return __async(this, null, function* () {
201
+ yield this.transact("readonly", (objectStore, resolve, reject) => {
202
+ const request = withOnError(objectStore.openCursor(), reject);
203
+ request.onsuccess = (event) => __async(this, null, function* () {
204
+ const cursor = event.target.result;
205
+ if (cursor) {
206
+ if (cursor.key) {
207
+ const obj = validateStoredObject(cursor.value);
208
+ if (obj) {
209
+ yield callback(String(cursor.key), obj.value, obj.expireMs);
210
+ } else {
211
+ yield callback(String(cursor.key), void 0, 0);
212
+ }
213
+ }
214
+ cursor.continue();
215
+ } else {
216
+ resolve();
217
+ }
218
+ });
219
+ });
220
+ });
221
+ }
222
+ /** Cannot be a getter because this needs to be async. */
223
+ size() {
224
+ return __async(this, null, function* () {
225
+ let count = 0;
226
+ yield this.forEach(() => {
227
+ count++;
228
+ });
229
+ return count;
230
+ });
231
+ }
232
+ clear() {
233
+ return __async(this, null, function* () {
234
+ const keys = [];
235
+ yield this.forEach((key) => {
236
+ keys.push(key);
237
+ });
238
+ yield this.delete(keys);
239
+ });
240
+ }
241
+ /** Mainly for debugging dumps. */
242
+ asMap() {
243
+ return __async(this, null, function* () {
244
+ const map = /* @__PURE__ */ new Map();
245
+ yield this.forEach((key, value, expireMs) => {
246
+ map.set(key, { value, expireMs });
247
+ });
248
+ return map;
249
+ });
250
+ }
251
+ get lastGcMs() {
252
+ const lastGcMsStr = globalThis.localStorage.getItem(this.gcMsStorageKey);
253
+ if (!lastGcMsStr) return 0;
254
+ const ms = Number(lastGcMsStr);
255
+ return isNaN(ms) ? 0 : ms;
256
+ }
257
+ set lastGcMs(ms) {
258
+ globalThis.localStorage.setItem(this.gcMsStorageKey, String(ms));
259
+ }
260
+ /** Perform garbage-collection if due. */
261
+ gc() {
262
+ return __async(this, null, function* () {
263
+ const lastGcMs = this.lastGcMs;
264
+ if (!lastGcMs) {
265
+ this.lastGcMs = Date.now();
266
+ return;
267
+ }
268
+ if (Date.now() < lastGcMs + GC_INTERVAL_MS) {
269
+ return;
270
+ }
271
+ yield this.gcNow();
272
+ });
273
+ }
274
+ /** Perform garbage-collection immediately without checking. */
275
+ gcNow() {
276
+ return __async(this, null, function* () {
277
+ console.log(`Starting kvStore GC on ${this.dbName} v${this.dbVersion}...`);
278
+ this.lastGcMs = Date.now();
279
+ const keysToDelete = [];
280
+ yield this.forEach(
281
+ (key, value, expireMs) => __async(this, null, function* () {
282
+ if (value === void 0 || Date.now() >= expireMs) {
283
+ keysToDelete.push(key);
284
+ }
285
+ })
286
+ );
287
+ if (keysToDelete.length) {
288
+ yield this.delete(keysToDelete);
289
+ }
290
+ console.log(
291
+ `Finished kvStore GC on ${this.dbName} v${this.dbVersion} - deleted ${keysToDelete.length} keys`
292
+ );
293
+ this.lastGcMs = Date.now();
294
+ });
295
+ }
296
+ /** Get an independent store item with a locked key and value type. */
297
+ field(key) {
298
+ return new KVStoreField(this, key);
299
+ }
300
+ };
301
+ var kvStore = new KVStore(
302
+ DEFAULT_DB_NAME,
303
+ DEFAULT_DB_VERSION,
304
+ DEFAULT_EXPIRY_DELTA_MS
305
+ );
306
+ // Annotate the CommonJS export names for ESM import in node:
307
+ 0 && (module.exports = {
308
+ DEFAULT_EXPIRY_DELTA_MS,
309
+ GC_INTERVAL_MS,
310
+ KVStore,
311
+ KVStoreField,
312
+ MILLIS_PER_DAY,
313
+ kvStore
314
+ });
315
+ //# sourceMappingURL=kvStore.js.map
package/kvStore.js.map ADDED
@@ -0,0 +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":"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","names":[]}
@@ -7,3 +7,12 @@ export declare class LocalStorageCache {
7
7
  static setValue<T>(key: string, value: T, expireDeltaMs: number): void;
8
8
  static getValue<T>(key: string, logError?: boolean): T | undefined;
9
9
  }
10
+ /** Same as above, but saves some config for reuse. */
11
+ export declare class LocalStorageCacheItem<T> {
12
+ readonly key: string;
13
+ readonly expireDeltaMs: number;
14
+ readonly logError: boolean;
15
+ constructor(key: string, expireDeltaMs: number, logError?: boolean, defaultValue?: T);
16
+ set(value: T): void;
17
+ get(): T | undefined;
18
+ }
@@ -20,7 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/localStorageCache.ts
21
21
  var localStorageCache_exports = {};
22
22
  __export(localStorageCache_exports, {
23
- LocalStorageCache: () => LocalStorageCache
23
+ LocalStorageCache: () => LocalStorageCache,
24
+ LocalStorageCacheItem: () => LocalStorageCacheItem
24
25
  });
25
26
  module.exports = __toCommonJS(localStorageCache_exports);
26
27
  var LocalStorageCache = class {
@@ -31,14 +32,12 @@ var LocalStorageCache = class {
31
32
  }
32
33
  static getValue(key, logError = true) {
33
34
  const jsonStr = globalThis.localStorage.getItem(key);
34
- if (!jsonStr) return void 0;
35
+ if (!jsonStr || typeof jsonStr !== "string") {
36
+ return void 0;
37
+ }
35
38
  try {
36
39
  const obj = JSON.parse(jsonStr);
37
- if (!obj || typeof obj !== "object" || !("value" in obj) || !("expireMs" in obj)) {
38
- globalThis.localStorage.removeItem(key);
39
- return void 0;
40
- }
41
- if (Date.now() >= obj.expireMs) {
40
+ if (!obj || typeof obj !== "object" || !("value" in obj) || !("expireMs" in obj) || typeof obj.expireMs !== "number" || Date.now() >= obj.expireMs) {
42
41
  globalThis.localStorage.removeItem(key);
43
42
  return void 0;
44
43
  }
@@ -52,8 +51,27 @@ var LocalStorageCache = class {
52
51
  }
53
52
  }
54
53
  };
54
+ var LocalStorageCacheItem = class {
55
+ constructor(key, expireDeltaMs, logError = true, defaultValue) {
56
+ this.key = key;
57
+ this.expireDeltaMs = expireDeltaMs;
58
+ this.logError = logError;
59
+ if (defaultValue !== void 0) {
60
+ if (this.get() === void 0) {
61
+ this.set(defaultValue);
62
+ }
63
+ }
64
+ }
65
+ set(value) {
66
+ LocalStorageCache.setValue(this.key, value, this.expireDeltaMs);
67
+ }
68
+ get() {
69
+ return LocalStorageCache.getValue(this.key, this.logError);
70
+ }
71
+ };
55
72
  // Annotate the CommonJS export names for ESM import in node:
56
73
  0 && (module.exports = {
57
- LocalStorageCache
74
+ LocalStorageCache,
75
+ LocalStorageCacheItem
58
76
  });
59
77
  //# sourceMappingURL=localStorageCache.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/localStorageCache.ts"],"sourcesContent":["/**\n * Simple local storage cache with support for auto-expiration.\n * Note that this works in the browser context only because nodejs does not\n * have local storage.\n */\nexport class LocalStorageCache {\n public static setValue<T>(key: string, value: T, expireDeltaMs: number) {\n const expireMs = Date.now() + expireDeltaMs;\n const valueStr = JSON.stringify({ value, expireMs });\n\n globalThis.localStorage.setItem(key, valueStr);\n }\n\n public static getValue<T>(key: string, logError = true): T | undefined {\n const jsonStr = globalThis.localStorage.getItem(key);\n if (!jsonStr) return undefined;\n\n try {\n const obj: { value: T; expireMs: number } | undefined =\n JSON.parse(jsonStr);\n if (\n !obj ||\n typeof obj !== \"object\" ||\n !(\"value\" in obj) ||\n !(\"expireMs\" in obj)\n ) {\n globalThis.localStorage.removeItem(key);\n return undefined;\n }\n\n if (Date.now() >= obj.expireMs) {\n globalThis.localStorage.removeItem(key);\n return undefined;\n }\n\n return obj.value;\n } catch (e) {\n if (logError) {\n console.error(`Found invalid storage value: ${key}=${jsonStr}:`, e);\n }\n globalThis.localStorage.removeItem(key);\n return undefined;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAKO,IAAM,oBAAN,MAAwB;AAAA,EAC7B,OAAc,SAAY,KAAa,OAAU,eAAuB;AACtE,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,UAAM,WAAW,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC;AAEnD,eAAW,aAAa,QAAQ,KAAK,QAAQ;AAAA,EAC/C;AAAA,EAEA,OAAc,SAAY,KAAa,WAAW,MAAqB;AACrE,UAAM,UAAU,WAAW,aAAa,QAAQ,GAAG;AACnD,QAAI,CAAC,QAAS,QAAO;AAErB,QAAI;AACF,YAAM,MACJ,KAAK,MAAM,OAAO;AACpB,UACE,CAAC,OACD,OAAO,QAAQ,YACf,EAAE,WAAW,QACb,EAAE,cAAc,MAChB;AACA,mBAAW,aAAa,WAAW,GAAG;AACtC,eAAO;AAAA,MACT;AAEA,UAAI,KAAK,IAAI,KAAK,IAAI,UAAU;AAC9B,mBAAW,aAAa,WAAW,GAAG;AACtC,eAAO;AAAA,MACT;AAEA,aAAO,IAAI;AAAA,IACb,SAAS,GAAG;AACV,UAAI,UAAU;AACZ,gBAAQ,MAAM,gCAAgC,GAAG,IAAI,OAAO,KAAK,CAAC;AAAA,MACpE;AACA,iBAAW,aAAa,WAAW,GAAG;AACtC,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/localStorageCache.ts"],"sourcesContent":["/**\n * Simple local storage cache with support for auto-expiration.\n * Note that this works in the browser context only because nodejs does not\n * have local storage.\n */\nexport class LocalStorageCache {\n public static setValue<T>(key: string, value: T, expireDeltaMs: number) {\n const expireMs = Date.now() + expireDeltaMs;\n const valueStr = JSON.stringify({ value, expireMs });\n\n globalThis.localStorage.setItem(key, valueStr);\n }\n\n public static getValue<T>(key: string, logError = true): T | undefined {\n const jsonStr = globalThis.localStorage.getItem(key);\n\n if (!jsonStr || typeof jsonStr !== \"string\") {\n return undefined;\n }\n\n try {\n const obj: { value: T; expireMs: number } | undefined =\n JSON.parse(jsonStr);\n if (\n !obj ||\n typeof obj !== \"object\" ||\n !(\"value\" in obj) ||\n !(\"expireMs\" in obj) ||\n typeof obj.expireMs !== \"number\" ||\n Date.now() >= obj.expireMs\n ) {\n globalThis.localStorage.removeItem(key);\n return undefined;\n }\n\n return obj.value;\n } catch (e) {\n if (logError) {\n console.error(`Found invalid storage value: ${key}=${jsonStr}:`, e);\n }\n globalThis.localStorage.removeItem(key);\n return undefined;\n }\n }\n}\n\n/** Same as above, but saves some config for reuse. */\nexport class LocalStorageCacheItem<T> {\n public constructor(\n public readonly key: string,\n public readonly expireDeltaMs: number,\n public readonly logError = true,\n defaultValue?: T,\n ) {\n if (defaultValue !== undefined) {\n if (this.get() === undefined) {\n this.set(defaultValue);\n }\n }\n }\n\n public set(value: T) {\n LocalStorageCache.setValue(this.key, value, this.expireDeltaMs);\n }\n\n public get(): T | undefined {\n return LocalStorageCache.getValue(this.key, this.logError);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKO,IAAM,oBAAN,MAAwB;AAAA,EAC7B,OAAc,SAAY,KAAa,OAAU,eAAuB;AACtE,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,UAAM,WAAW,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC;AAEnD,eAAW,aAAa,QAAQ,KAAK,QAAQ;AAAA,EAC/C;AAAA,EAEA,OAAc,SAAY,KAAa,WAAW,MAAqB;AACrE,UAAM,UAAU,WAAW,aAAa,QAAQ,GAAG;AAEnD,QAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,MACJ,KAAK,MAAM,OAAO;AACpB,UACE,CAAC,OACD,OAAO,QAAQ,YACf,EAAE,WAAW,QACb,EAAE,cAAc,QAChB,OAAO,IAAI,aAAa,YACxB,KAAK,IAAI,KAAK,IAAI,UAClB;AACA,mBAAW,aAAa,WAAW,GAAG;AACtC,eAAO;AAAA,MACT;AAEA,aAAO,IAAI;AAAA,IACb,SAAS,GAAG;AACV,UAAI,UAAU;AACZ,gBAAQ,MAAM,gCAAgC,GAAG,IAAI,OAAO,KAAK,CAAC;AAAA,MACpE;AACA,iBAAW,aAAa,WAAW,GAAG;AACtC,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAGO,IAAM,wBAAN,MAA+B;AAAA,EAC7B,YACW,KACA,eACA,WAAW,MAC3B,cACA;AAJgB;AACA;AACA;AAGhB,QAAI,iBAAiB,QAAW;AAC9B,UAAI,KAAK,IAAI,MAAM,QAAW;AAC5B,aAAK,IAAI,YAAY;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAAA,EAEO,IAAI,OAAU;AACnB,sBAAkB,SAAS,KAAK,KAAK,OAAO,KAAK,aAAa;AAAA,EAChE;AAAA,EAEO,MAAqB;AAC1B,WAAO,kBAAkB,SAAS,KAAK,KAAK,KAAK,QAAQ;AAAA,EAC3D;AACF;","names":[]}
package/nonEmpty.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Type asserts that `t` is truthy.
3
+ * Throws an error if `t` is null or undefined.
4
+ */
5
+ export declare function nonEmpty<T>(t: T | null | undefined | "" | 0 | -0 | 0n | false | typeof NaN, varName?: string): T;