@dr.pogodin/js-utils 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -46,6 +46,12 @@ and used in the same way):
46
46
  — One year expressed in milliseconds.
47
47
 
48
48
  ### Types
49
+ - `Extends<Base, T extends Base>` &mdash; Validates compile-time the type **T**
50
+ extends (is assignable to) the type **Base**; returns **T** if successful.
51
+ - `Implements<Base, T extends Base & ...>` &mdash; Validates compile-time
52
+ the type **T** extends (is assignable to) the type **Base**, and also has
53
+ all (if any) optional fields defined in the **Base**; returns **T** if
54
+ the validation passes.
49
55
  - `ObjectKey` &mdash; **string** | **number** | **symbol** &mdash;
50
56
  The most generic valid type of an object key in TypeScript.
51
57
 
@@ -1,6 +1,9 @@
1
1
  // Babel is used for Jest testing.
2
2
 
3
3
  export default {
4
+ plugins: [
5
+ 'babel-plugin-add-import-extension',
6
+ ],
4
7
  presets: [
5
8
  ['./config/babel/preset', {
6
9
  modules: false,
@@ -12,4 +12,18 @@ exports.assertEmptyObject = assertEmptyObject;
12
12
  function assertEmptyObject(object) {
13
13
  if (Object.keys(object).length) throw Error('The object is not empty');
14
14
  }
15
+
16
+ /**
17
+ * Validates compile-time that the type T extends Base, and returns T.
18
+ *
19
+ * BEWARE: In the case Base has some optional fields missing in T, T still
20
+ * extends Base (as "extends" means T is assignable to Base)! The Implements
21
+ * util below also checks that T has all optional fields defined in Base.
22
+ */
23
+
24
+ /**
25
+ * Validates compile-time that the type T extends (is assignable to) the type
26
+ * Base, and also that it has defined all (if any) optional fields defined in
27
+ * the Base. Returns T if the validation passes.
28
+ */
15
29
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["assertEmptyObject","object","Object","keys","length","Error"],"sources":["../../src/types.ts"],"sourcesContent":["// Misc TypeScript-specific utilities.\n\n/** The most permissive object key type. */\nexport type ObjectKey = string | number | symbol;\n\n/** Asserts given object is empty, both compile- and run-time. */\nexport function assertEmptyObject(object: Record<ObjectKey, never>): void {\n if (Object.keys(object).length) throw Error('The object is not empty');\n}\n"],"mappings":";;;;;;AAAA;;AAEA;;AAGA;AACO,SAASA,iBAAiBA,CAACC,MAAgC,EAAQ;EACxE,IAAIC,MAAM,CAACC,IAAI,CAACF,MAAM,CAAC,CAACG,MAAM,EAAE,MAAMC,KAAK,CAAC,yBAAyB,CAAC;AACxE","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":["assertEmptyObject","object","Object","keys","length","Error"],"sources":["../../src/types.ts"],"sourcesContent":["// Misc TypeScript-specific utilities.\n\n/** The most permissive object key type. */\nexport type ObjectKey = string | number | symbol;\n\n/** Asserts given object is empty, both compile- and run-time. */\nexport function assertEmptyObject(object: Record<ObjectKey, never>): void {\n if (Object.keys(object).length) throw Error('The object is not empty');\n}\n\n/**\n * Validates compile-time that the type T extends Base, and returns T.\n *\n * BEWARE: In the case Base has some optional fields missing in T, T still\n * extends Base (as \"extends\" means T is assignable to Base)! The Implements\n * util below also checks that T has all optional fields defined in Base.\n */\nexport type Extends<Base, T extends Base> = T;\n\n/**\n * Validates compile-time that the type T extends (is assignable to) the type\n * Base, and also that it has defined all (if any) optional fields defined in\n * the Base. Returns T if the validation passes.\n */\nexport type Implements<\n Base,\n T extends Required<T> extends Required<Base> ? Base : Required<Base>,\n> = T;\n"],"mappings":";;;;;;AAAA;;AAEA;;AAGA;AACO,SAASA,iBAAiBA,CAACC,MAAgC,EAAQ;EACxE,IAAIC,MAAM,CAACC,IAAI,CAACF,MAAM,CAAC,CAACG,MAAM,EAAE,MAAMC,KAAK,CAAC,yBAAyB,CAAC;AACxE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA","ignoreList":[]}
@@ -1,5 +1,4 @@
1
- import Barrier from './Barrier';
2
-
1
+ import Barrier from "./Barrier.js";
3
2
  /**
4
3
  * Implements a simple semaphore for async code logic.
5
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"Semaphore.js","names":["Barrier","Semaphore","constructor","ready","pReady","setReady","bool","pDraining","pQueue","length","pDrainQueue","seize","waitReady","barrier","push","pDrainLock","resolve","shift"],"sources":["../../src/Semaphore.ts"],"sourcesContent":["import Barrier from './Barrier';\n\n/**\n * Implements a simple semaphore for async code logic.\n */\nexport default class Semaphore {\n constructor(ready = false) {\n // TODO: Boolean conversion is performed for backward compatibility with\n // plain JS projects. Drop it in future.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion\n this.pReady = !!ready;\n }\n\n get ready(): boolean {\n return this.pReady;\n }\n\n setReady(ready: boolean): void {\n // TODO: Boolean conversion is performed for backward compatibility with\n // plain JS projects. Drop it in future.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion\n const bool = !!ready;\n if (this.pReady !== bool) {\n this.pReady = bool;\n if (bool && !this.pDraining && this.pQueue.length) {\n void this.pDrainQueue();\n }\n }\n }\n\n /**\n * Waits until the semaphore is ready, and marks it as non-ready (seizes it).\n */\n async seize(): Promise<void> {\n return this.waitReady(true);\n }\n\n async waitReady(seize = false): Promise<void> {\n if (!this.pReady || this.pQueue.length) {\n const barrier = new Barrier<void>();\n this.pQueue.push(barrier);\n await barrier;\n if (seize) this.pReady = false;\n void this.pDrainLock!.resolve();\n } else if (seize) this.pReady = false;\n }\n\n // Private members below this point.\n\n /**\n * If semaphore is ready, it releases the next barrier in the queue, if any,\n * and reschedules itself for a call in the next event loop iteration.\n * Otherwise, it breaks the queue draining loop, which will be restarted\n * the next time the semaphore is set ready.\n */\n async pDrainQueue(): Promise<void> {\n this.pDraining = true;\n while (this.pReady && this.pQueue.length) {\n this.pDrainLock = new Barrier();\n void this.pQueue[0]!.resolve();\n await this.pDrainLock;\n void this.pQueue.shift();\n }\n this.pDraining = false;\n this.pDrainLock = null;\n }\n\n // \"true\" when the drain queue process is running (and thus no need to start\n // a new one).\n private pDraining = false;\n\n // Each time a Promise from drain queue is resolved this drainLock is set\n // to block further queue draining until the promise resolution handler\n // (.seize() or .waitReady()) unlocks it, thus confirming it is fine\n // to continue the draining. This is specifically important for .seize(),\n // which should have a chance to switch semaphore state to non-ready prior\n // to next Promise in the queue being unlocked.\n private pDrainLock: Barrier<void> | null = null;\n\n // The array of barriers set for each async code flow awaiting for\n // the Semaphore to become ready.\n private pQueue: Array<Barrier<void>> = [];\n\n private pReady: boolean;\n}\n"],"mappings":"AAAA,OAAOA,OAAO,MAAM,WAAW;;AAE/B;AACA;AACA;AACA,eAAe,MAAMC,SAAS,CAAC;EAC7BC,WAAWA,CAACC,KAAK,GAAG,KAAK,EAAE;IACzB;IACA;IACA;IACA,IAAI,CAACC,MAAM,GAAG,CAAC,CAACD,KAAK;EACvB;EAEA,IAAIA,KAAKA,CAAA,EAAY;IACnB,OAAO,IAAI,CAACC,MAAM;EACpB;EAEAC,QAAQA,CAACF,KAAc,EAAQ;IAC7B;IACA;IACA;IACA,MAAMG,IAAI,GAAG,CAAC,CAACH,KAAK;IACpB,IAAI,IAAI,CAACC,MAAM,KAAKE,IAAI,EAAE;MACxB,IAAI,CAACF,MAAM,GAAGE,IAAI;MAClB,IAAIA,IAAI,IAAI,CAAC,IAAI,CAACC,SAAS,IAAI,IAAI,CAACC,MAAM,CAACC,MAAM,EAAE;QACjD,KAAK,IAAI,CAACC,WAAW,CAAC,CAAC;MACzB;IACF;EACF;;EAEA;AACF;AACA;EACE,MAAMC,KAAKA,CAAA,EAAkB;IAC3B,OAAO,IAAI,CAACC,SAAS,CAAC,IAAI,CAAC;EAC7B;EAEA,MAAMA,SAASA,CAACD,KAAK,GAAG,KAAK,EAAiB;IAC5C,IAAI,CAAC,IAAI,CAACP,MAAM,IAAI,IAAI,CAACI,MAAM,CAACC,MAAM,EAAE;MACtC,MAAMI,OAAO,GAAG,IAAIb,OAAO,CAAO,CAAC;MACnC,IAAI,CAACQ,MAAM,CAACM,IAAI,CAACD,OAAO,CAAC;MACzB,MAAMA,OAAO;MACb,IAAIF,KAAK,EAAE,IAAI,CAACP,MAAM,GAAG,KAAK;MAC9B,KAAK,IAAI,CAACW,UAAU,CAAEC,OAAO,CAAC,CAAC;IACjC,CAAC,MAAM,IAAIL,KAAK,EAAE,IAAI,CAACP,MAAM,GAAG,KAAK;EACvC;;EAEA;;EAEA;AACF;AACA;AACA;AACA;AACA;EACE,MAAMM,WAAWA,CAAA,EAAkB;IACjC,IAAI,CAACH,SAAS,GAAG,IAAI;IACrB,OAAO,IAAI,CAACH,MAAM,IAAI,IAAI,CAACI,MAAM,CAACC,MAAM,EAAE;MACxC,IAAI,CAACM,UAAU,GAAG,IAAIf,OAAO,CAAC,CAAC;MAC/B,KAAK,IAAI,CAACQ,MAAM,CAAC,CAAC,CAAC,CAAEQ,OAAO,CAAC,CAAC;MAC9B,MAAM,IAAI,CAACD,UAAU;MACrB,KAAK,IAAI,CAACP,MAAM,CAACS,KAAK,CAAC,CAAC;IAC1B;IACA,IAAI,CAACV,SAAS,GAAG,KAAK;IACtB,IAAI,CAACQ,UAAU,GAAG,IAAI;EACxB;;EAEA;EACA;EACQR,SAAS,GAAG,KAAK;;EAEzB;EACA;EACA;EACA;EACA;EACA;EACQQ,UAAU,GAAyB,IAAI;;EAE/C;EACA;EACQP,MAAM,GAAyB,EAAE;AAG3C","ignoreList":[]}
1
+ {"version":3,"file":"Semaphore.js","names":["Barrier","Semaphore","constructor","ready","pReady","setReady","bool","pDraining","pQueue","length","pDrainQueue","seize","waitReady","barrier","push","pDrainLock","resolve","shift"],"sources":["../../src/Semaphore.ts"],"sourcesContent":["import Barrier from './Barrier';\n\n/**\n * Implements a simple semaphore for async code logic.\n */\nexport default class Semaphore {\n constructor(ready = false) {\n // TODO: Boolean conversion is performed for backward compatibility with\n // plain JS projects. Drop it in future.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion\n this.pReady = !!ready;\n }\n\n get ready(): boolean {\n return this.pReady;\n }\n\n setReady(ready: boolean): void {\n // TODO: Boolean conversion is performed for backward compatibility with\n // plain JS projects. Drop it in future.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion\n const bool = !!ready;\n if (this.pReady !== bool) {\n this.pReady = bool;\n if (bool && !this.pDraining && this.pQueue.length) {\n void this.pDrainQueue();\n }\n }\n }\n\n /**\n * Waits until the semaphore is ready, and marks it as non-ready (seizes it).\n */\n async seize(): Promise<void> {\n return this.waitReady(true);\n }\n\n async waitReady(seize = false): Promise<void> {\n if (!this.pReady || this.pQueue.length) {\n const barrier = new Barrier<void>();\n this.pQueue.push(barrier);\n await barrier;\n if (seize) this.pReady = false;\n void this.pDrainLock!.resolve();\n } else if (seize) this.pReady = false;\n }\n\n // Private members below this point.\n\n /**\n * If semaphore is ready, it releases the next barrier in the queue, if any,\n * and reschedules itself for a call in the next event loop iteration.\n * Otherwise, it breaks the queue draining loop, which will be restarted\n * the next time the semaphore is set ready.\n */\n async pDrainQueue(): Promise<void> {\n this.pDraining = true;\n while (this.pReady && this.pQueue.length) {\n this.pDrainLock = new Barrier();\n void this.pQueue[0]!.resolve();\n await this.pDrainLock;\n void this.pQueue.shift();\n }\n this.pDraining = false;\n this.pDrainLock = null;\n }\n\n // \"true\" when the drain queue process is running (and thus no need to start\n // a new one).\n private pDraining = false;\n\n // Each time a Promise from drain queue is resolved this drainLock is set\n // to block further queue draining until the promise resolution handler\n // (.seize() or .waitReady()) unlocks it, thus confirming it is fine\n // to continue the draining. This is specifically important for .seize(),\n // which should have a chance to switch semaphore state to non-ready prior\n // to next Promise in the queue being unlocked.\n private pDrainLock: Barrier<void> | null = null;\n\n // The array of barriers set for each async code flow awaiting for\n // the Semaphore to become ready.\n private pQueue: Array<Barrier<void>> = [];\n\n private pReady: boolean;\n}\n"],"mappings":"OAAOA,OAAO;AAEd;AACA;AACA;AACA,eAAe,MAAMC,SAAS,CAAC;EAC7BC,WAAWA,CAACC,KAAK,GAAG,KAAK,EAAE;IACzB;IACA;IACA;IACA,IAAI,CAACC,MAAM,GAAG,CAAC,CAACD,KAAK;EACvB;EAEA,IAAIA,KAAKA,CAAA,EAAY;IACnB,OAAO,IAAI,CAACC,MAAM;EACpB;EAEAC,QAAQA,CAACF,KAAc,EAAQ;IAC7B;IACA;IACA;IACA,MAAMG,IAAI,GAAG,CAAC,CAACH,KAAK;IACpB,IAAI,IAAI,CAACC,MAAM,KAAKE,IAAI,EAAE;MACxB,IAAI,CAACF,MAAM,GAAGE,IAAI;MAClB,IAAIA,IAAI,IAAI,CAAC,IAAI,CAACC,SAAS,IAAI,IAAI,CAACC,MAAM,CAACC,MAAM,EAAE;QACjD,KAAK,IAAI,CAACC,WAAW,CAAC,CAAC;MACzB;IACF;EACF;;EAEA;AACF;AACA;EACE,MAAMC,KAAKA,CAAA,EAAkB;IAC3B,OAAO,IAAI,CAACC,SAAS,CAAC,IAAI,CAAC;EAC7B;EAEA,MAAMA,SAASA,CAACD,KAAK,GAAG,KAAK,EAAiB;IAC5C,IAAI,CAAC,IAAI,CAACP,MAAM,IAAI,IAAI,CAACI,MAAM,CAACC,MAAM,EAAE;MACtC,MAAMI,OAAO,GAAG,IAAIb,OAAO,CAAO,CAAC;MACnC,IAAI,CAACQ,MAAM,CAACM,IAAI,CAACD,OAAO,CAAC;MACzB,MAAMA,OAAO;MACb,IAAIF,KAAK,EAAE,IAAI,CAACP,MAAM,GAAG,KAAK;MAC9B,KAAK,IAAI,CAACW,UAAU,CAAEC,OAAO,CAAC,CAAC;IACjC,CAAC,MAAM,IAAIL,KAAK,EAAE,IAAI,CAACP,MAAM,GAAG,KAAK;EACvC;;EAEA;;EAEA;AACF;AACA;AACA;AACA;AACA;EACE,MAAMM,WAAWA,CAAA,EAAkB;IACjC,IAAI,CAACH,SAAS,GAAG,IAAI;IACrB,OAAO,IAAI,CAACH,MAAM,IAAI,IAAI,CAACI,MAAM,CAACC,MAAM,EAAE;MACxC,IAAI,CAACM,UAAU,GAAG,IAAIf,OAAO,CAAC,CAAC;MAC/B,KAAK,IAAI,CAACQ,MAAM,CAAC,CAAC,CAAC,CAAEQ,OAAO,CAAC,CAAC;MAC9B,MAAM,IAAI,CAACD,UAAU;MACrB,KAAK,IAAI,CAACP,MAAM,CAACS,KAAK,CAAC,CAAC;IAC1B;IACA,IAAI,CAACV,SAAS,GAAG,KAAK;IACtB,IAAI,CAACQ,UAAU,GAAG,IAAI;EACxB;;EAEA;EACA;EACQR,SAAS,GAAG,KAAK;;EAEzB;EACA;EACA;EACA;EACA;EACA;EACQQ,UAAU,GAAyB,IAAI;;EAE/C;EACA;EACQP,MAAM,GAAyB,EAAE;AAG3C","ignoreList":[]}
@@ -1,8 +1,8 @@
1
- export { default as Barrier } from './Barrier';
2
- export * from './Cached';
3
- export * from './Emitter';
4
- export { default as Semaphore } from './Semaphore';
5
- export * from './time';
6
- export * from './types';
7
- export { default as withRetries } from './withRetries';
1
+ export { default as Barrier } from "./Barrier.js";
2
+ export * from "./Cached.js";
3
+ export * from "./Emitter.js";
4
+ export { default as Semaphore } from "./Semaphore.js";
5
+ export * from "./time.js";
6
+ export * from "./types.js";
7
+ export { default as withRetries } from "./withRetries.js";
8
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["default","Barrier","Semaphore","withRetries"],"sources":["../../src/index.ts"],"sourcesContent":["export { default as Barrier } from './Barrier';\nexport * from './Cached';\nexport * from './Emitter';\nexport { default as Semaphore } from './Semaphore';\nexport * from './time';\nexport * from './types';\nexport { default as withRetries } from './withRetries';\n"],"mappings":"AAAA,SAASA,OAAO,IAAIC,OAAO,QAAQ,WAAW;AAC9C,cAAc,UAAU;AACxB,cAAc,WAAW;AACzB,SAASD,OAAO,IAAIE,SAAS,QAAQ,aAAa;AAClD,cAAc,QAAQ;AACtB,cAAc,SAAS;AACvB,SAASF,OAAO,IAAIG,WAAW,QAAQ,eAAe","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["default","Barrier","Semaphore","withRetries"],"sources":["../../src/index.ts"],"sourcesContent":["export { default as Barrier } from './Barrier';\nexport * from './Cached';\nexport * from './Emitter';\nexport { default as Semaphore } from './Semaphore';\nexport * from './time';\nexport * from './types';\nexport { default as withRetries } from './withRetries';\n"],"mappings":"SAASA,OAAO,IAAIC,OAAO;AAAA;AAAA;AAAA,SAGlBD,OAAO,IAAIE,SAAS;AAAA;AAAA;AAAA,SAGpBF,OAAO,IAAIG,WAAW","ignoreList":[]}
@@ -1,6 +1,4 @@
1
- import Barrier from './Barrier';
2
-
3
- // This is not very elegant, but as of now TypeScript does not support type
1
+ import Barrier from "./Barrier.js"; // This is not very elegant, but as of now TypeScript does not support type
4
2
  // arithmetic, thus we can't have constants assigned like `MIN_MS = 60 * SEC_MS`
5
3
  // and have the result type to be 60000 (number literal), it would be just
6
4
  // the generic number type.
@@ -1 +1 @@
1
- {"version":3,"file":"time.js","names":["Barrier","SEC_MS","MIN_MS","HOUR_MS","DAY_MS","YEAR_MS","Timer","abort","pAbort","timeout","pTimeout","constructor","executor","undefined","init","Error","id","setTimeout","resolve","clearTimeout","then","onFulfilled","onRejected","res","timer","t"],"sources":["../../src/time.ts"],"sourcesContent":["import Barrier, { type Executor } from './Barrier';\n\n// This is not very elegant, but as of now TypeScript does not support type\n// arithmetic, thus we can't have constants assigned like `MIN_MS = 60 * SEC_MS`\n// and have the result type to be 60000 (number literal), it would be just\n// the generic number type.\nexport const SEC_MS = 1000;\nexport const MIN_MS = 60000; // 60 * SEC_MS\nexport const HOUR_MS = 3600000; // 60 * MIN_MS\nexport const DAY_MS = 86400000; // 24 * HOUR_MS\nexport const YEAR_MS = 31536000000; // 365 * DAY_MS\n\n// TODO: Ok, as we have ended up with a Timer class, mostly to achieve a good\n// TypeScript typing for timer() function, it makes sense to expose the class\n// from the library as well, and it should be documented later.\nexport class Timer<T> extends Barrier<void, T> {\n private pAbort: () => void;\n\n private pTimeout?: number;\n\n get abort(): () => void {\n return this.pAbort;\n }\n\n get timeout(): number | undefined {\n return this.pTimeout;\n }\n\n /**\n * Creates a new, non-initialized instance of Timer. Call .init() method\n * to actually initialize and launch the timer.\n *\n * NOTE: Although it might be tempting to accept `timeout` value as\n * a constructor's argument, it won't work well, because Timer is an\n * extension of Promise (via Barrier), and the way Promises works (in\n * particular their .then() method, which internally calls constructor()\n * with special executor) does not play along with initalization depending\n * on custom parameters done in constructor().\n *\n * @param executor\n */\n constructor(executor?: Executor<T>) {\n super(executor);\n this.pAbort = () => undefined;\n }\n\n init(timeout: number): this {\n if (this.pTimeout !== undefined) {\n throw Error('This Timer is initialized already');\n }\n this.pTimeout = timeout;\n if (timeout > 0) {\n const id = setTimeout(() => {\n void super.resolve();\n }, timeout);\n this.pAbort = () => {\n clearTimeout(id);\n };\n } else {\n void super.resolve();\n }\n return this;\n }\n\n // TODO: For async functions TS requires the return type to be the global\n // Promise, thus not allowing to return our Timer type extending that via\n // Barrier. Thus, we don't mark this method async for now, disabling the rule,\n // and we should think more about it in future.\n // eslint-disable-next-line @typescript-eslint/promise-function-async\n then<TR1, TR2>(\n onFulfilled?: ((value: T) => TR1 | PromiseLike<TR1>) | null,\n onRejected?: ((reason: unknown) => TR2 | PromiseLike<TR2>) | null,\n ): Timer<TR1 | TR2> {\n const res = super.then(onFulfilled, onRejected) as Timer<TR1 | TR2>;\n if (this.timeout !== undefined) void res.init(this.timeout);\n return res;\n }\n}\n\n/**\n * Creates a Promise, which resolves after the given timeout.\n * @param {number} timeout Timeout [ms].\n * @return {Barrier} Resolves after the timeout. It has additional\n * .abort() method attached, which cancels the pending timer resolution\n * (without resolving or rejecting the barrier).\n */\n// TODO: For async functions TS requires the return type to be the global\n// Promise, thus not allowing to return our Timer type extending that via\n// Barrier. Thus, we don't mark this method async for now, disabling the rule,\n// and we should think more about it in future.\n// eslint-disable-next-line @typescript-eslint/promise-function-async\nexport function timer(timeout: number): Timer<void> {\n const t = new Timer<void>();\n return t.init(timeout);\n}\n"],"mappings":"AAAA,OAAOA,OAAO,MAAyB,WAAW;;AAElD;AACA;AACA;AACA;AACA,OAAO,MAAMC,MAAM,GAAG,IAAI;AAC1B,OAAO,MAAMC,MAAM,GAAG,KAAK,CAAC,CAAC;AAC7B,OAAO,MAAMC,OAAO,GAAG,OAAO,CAAC,CAAC;AAChC,OAAO,MAAMC,MAAM,GAAG,QAAQ,CAAC,CAAC;AAChC,OAAO,MAAMC,OAAO,GAAG,WAAW,CAAC,CAAC;;AAEpC;AACA;AACA;AACA,OAAO,MAAMC,KAAK,SAAYN,OAAO,CAAU;EAK7C,IAAIO,KAAKA,CAAA,EAAe;IACtB,OAAO,IAAI,CAACC,MAAM;EACpB;EAEA,IAAIC,OAAOA,CAAA,EAAuB;IAChC,OAAO,IAAI,CAACC,QAAQ;EACtB;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEC,WAAWA,CAACC,QAAsB,EAAE;IAClC,KAAK,CAACA,QAAQ,CAAC;IACf,IAAI,CAACJ,MAAM,GAAG,MAAMK,SAAS;EAC/B;EAEAC,IAAIA,CAACL,OAAe,EAAQ;IAC1B,IAAI,IAAI,CAACC,QAAQ,KAAKG,SAAS,EAAE;MAC/B,MAAME,KAAK,CAAC,mCAAmC,CAAC;IAClD;IACA,IAAI,CAACL,QAAQ,GAAGD,OAAO;IACvB,IAAIA,OAAO,GAAG,CAAC,EAAE;MACf,MAAMO,EAAE,GAAGC,UAAU,CAAC,MAAM;QAC1B,KAAK,KAAK,CAACC,OAAO,CAAC,CAAC;MACtB,CAAC,EAAET,OAAO,CAAC;MACX,IAAI,CAACD,MAAM,GAAG,MAAM;QAClBW,YAAY,CAACH,EAAE,CAAC;MAClB,CAAC;IACH,CAAC,MAAM;MACL,KAAK,KAAK,CAACE,OAAO,CAAC,CAAC;IACtB;IACA,OAAO,IAAI;EACb;;EAEA;EACA;EACA;EACA;EACA;EACAE,IAAIA,CACFC,WAA2D,EAC3DC,UAAiE,EAC/C;IAClB,MAAMC,GAAG,GAAG,KAAK,CAACH,IAAI,CAACC,WAAW,EAAEC,UAAU,CAAqB;IACnE,IAAI,IAAI,CAACb,OAAO,KAAKI,SAAS,EAAE,KAAKU,GAAG,CAACT,IAAI,CAAC,IAAI,CAACL,OAAO,CAAC;IAC3D,OAAOc,GAAG;EACZ;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,KAAKA,CAACf,OAAe,EAAe;EAClD,MAAMgB,CAAC,GAAG,IAAInB,KAAK,CAAO,CAAC;EAC3B,OAAOmB,CAAC,CAACX,IAAI,CAACL,OAAO,CAAC;AACxB","ignoreList":[]}
1
+ {"version":3,"file":"time.js","names":["Barrier","SEC_MS","MIN_MS","HOUR_MS","DAY_MS","YEAR_MS","Timer","abort","pAbort","timeout","pTimeout","constructor","executor","undefined","init","Error","id","setTimeout","resolve","clearTimeout","then","onFulfilled","onRejected","res","timer","t"],"sources":["../../src/time.ts"],"sourcesContent":["import Barrier, { type Executor } from './Barrier';\n\n// This is not very elegant, but as of now TypeScript does not support type\n// arithmetic, thus we can't have constants assigned like `MIN_MS = 60 * SEC_MS`\n// and have the result type to be 60000 (number literal), it would be just\n// the generic number type.\nexport const SEC_MS = 1000;\nexport const MIN_MS = 60000; // 60 * SEC_MS\nexport const HOUR_MS = 3600000; // 60 * MIN_MS\nexport const DAY_MS = 86400000; // 24 * HOUR_MS\nexport const YEAR_MS = 31536000000; // 365 * DAY_MS\n\n// TODO: Ok, as we have ended up with a Timer class, mostly to achieve a good\n// TypeScript typing for timer() function, it makes sense to expose the class\n// from the library as well, and it should be documented later.\nexport class Timer<T> extends Barrier<void, T> {\n private pAbort: () => void;\n\n private pTimeout?: number;\n\n get abort(): () => void {\n return this.pAbort;\n }\n\n get timeout(): number | undefined {\n return this.pTimeout;\n }\n\n /**\n * Creates a new, non-initialized instance of Timer. Call .init() method\n * to actually initialize and launch the timer.\n *\n * NOTE: Although it might be tempting to accept `timeout` value as\n * a constructor's argument, it won't work well, because Timer is an\n * extension of Promise (via Barrier), and the way Promises works (in\n * particular their .then() method, which internally calls constructor()\n * with special executor) does not play along with initalization depending\n * on custom parameters done in constructor().\n *\n * @param executor\n */\n constructor(executor?: Executor<T>) {\n super(executor);\n this.pAbort = () => undefined;\n }\n\n init(timeout: number): this {\n if (this.pTimeout !== undefined) {\n throw Error('This Timer is initialized already');\n }\n this.pTimeout = timeout;\n if (timeout > 0) {\n const id = setTimeout(() => {\n void super.resolve();\n }, timeout);\n this.pAbort = () => {\n clearTimeout(id);\n };\n } else {\n void super.resolve();\n }\n return this;\n }\n\n // TODO: For async functions TS requires the return type to be the global\n // Promise, thus not allowing to return our Timer type extending that via\n // Barrier. Thus, we don't mark this method async for now, disabling the rule,\n // and we should think more about it in future.\n // eslint-disable-next-line @typescript-eslint/promise-function-async\n then<TR1, TR2>(\n onFulfilled?: ((value: T) => TR1 | PromiseLike<TR1>) | null,\n onRejected?: ((reason: unknown) => TR2 | PromiseLike<TR2>) | null,\n ): Timer<TR1 | TR2> {\n const res = super.then(onFulfilled, onRejected) as Timer<TR1 | TR2>;\n if (this.timeout !== undefined) void res.init(this.timeout);\n return res;\n }\n}\n\n/**\n * Creates a Promise, which resolves after the given timeout.\n * @param {number} timeout Timeout [ms].\n * @return {Barrier} Resolves after the timeout. It has additional\n * .abort() method attached, which cancels the pending timer resolution\n * (without resolving or rejecting the barrier).\n */\n// TODO: For async functions TS requires the return type to be the global\n// Promise, thus not allowing to return our Timer type extending that via\n// Barrier. Thus, we don't mark this method async for now, disabling the rule,\n// and we should think more about it in future.\n// eslint-disable-next-line @typescript-eslint/promise-function-async\nexport function timer(timeout: number): Timer<void> {\n const t = new Timer<void>();\n return t.init(timeout);\n}\n"],"mappings":"OAAOA,OAAO,sBAEd;AACA;AACA;AACA;AACA,OAAO,MAAMC,MAAM,GAAG,IAAI;AAC1B,OAAO,MAAMC,MAAM,GAAG,KAAK,CAAC,CAAC;AAC7B,OAAO,MAAMC,OAAO,GAAG,OAAO,CAAC,CAAC;AAChC,OAAO,MAAMC,MAAM,GAAG,QAAQ,CAAC,CAAC;AAChC,OAAO,MAAMC,OAAO,GAAG,WAAW,CAAC,CAAC;;AAEpC;AACA;AACA;AACA,OAAO,MAAMC,KAAK,SAAYN,OAAO,CAAU;EAK7C,IAAIO,KAAKA,CAAA,EAAe;IACtB,OAAO,IAAI,CAACC,MAAM;EACpB;EAEA,IAAIC,OAAOA,CAAA,EAAuB;IAChC,OAAO,IAAI,CAACC,QAAQ;EACtB;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEC,WAAWA,CAACC,QAAsB,EAAE;IAClC,KAAK,CAACA,QAAQ,CAAC;IACf,IAAI,CAACJ,MAAM,GAAG,MAAMK,SAAS;EAC/B;EAEAC,IAAIA,CAACL,OAAe,EAAQ;IAC1B,IAAI,IAAI,CAACC,QAAQ,KAAKG,SAAS,EAAE;MAC/B,MAAME,KAAK,CAAC,mCAAmC,CAAC;IAClD;IACA,IAAI,CAACL,QAAQ,GAAGD,OAAO;IACvB,IAAIA,OAAO,GAAG,CAAC,EAAE;MACf,MAAMO,EAAE,GAAGC,UAAU,CAAC,MAAM;QAC1B,KAAK,KAAK,CAACC,OAAO,CAAC,CAAC;MACtB,CAAC,EAAET,OAAO,CAAC;MACX,IAAI,CAACD,MAAM,GAAG,MAAM;QAClBW,YAAY,CAACH,EAAE,CAAC;MAClB,CAAC;IACH,CAAC,MAAM;MACL,KAAK,KAAK,CAACE,OAAO,CAAC,CAAC;IACtB;IACA,OAAO,IAAI;EACb;;EAEA;EACA;EACA;EACA;EACA;EACAE,IAAIA,CACFC,WAA2D,EAC3DC,UAAiE,EAC/C;IAClB,MAAMC,GAAG,GAAG,KAAK,CAACH,IAAI,CAACC,WAAW,EAAEC,UAAU,CAAqB;IACnE,IAAI,IAAI,CAACb,OAAO,KAAKI,SAAS,EAAE,KAAKU,GAAG,CAACT,IAAI,CAAC,IAAI,CAACL,OAAO,CAAC;IAC3D,OAAOc,GAAG;EACZ;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,KAAKA,CAACf,OAAe,EAAe;EAClD,MAAMgB,CAAC,GAAG,IAAInB,KAAK,CAAO,CAAC;EAC3B,OAAOmB,CAAC,CAACX,IAAI,CAACL,OAAO,CAAC;AACxB","ignoreList":[]}
@@ -6,4 +6,18 @@
6
6
  export function assertEmptyObject(object) {
7
7
  if (Object.keys(object).length) throw Error('The object is not empty');
8
8
  }
9
+
10
+ /**
11
+ * Validates compile-time that the type T extends Base, and returns T.
12
+ *
13
+ * BEWARE: In the case Base has some optional fields missing in T, T still
14
+ * extends Base (as "extends" means T is assignable to Base)! The Implements
15
+ * util below also checks that T has all optional fields defined in Base.
16
+ */
17
+
18
+ /**
19
+ * Validates compile-time that the type T extends (is assignable to) the type
20
+ * Base, and also that it has defined all (if any) optional fields defined in
21
+ * the Base. Returns T if the validation passes.
22
+ */
9
23
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["assertEmptyObject","object","Object","keys","length","Error"],"sources":["../../src/types.ts"],"sourcesContent":["// Misc TypeScript-specific utilities.\n\n/** The most permissive object key type. */\nexport type ObjectKey = string | number | symbol;\n\n/** Asserts given object is empty, both compile- and run-time. */\nexport function assertEmptyObject(object: Record<ObjectKey, never>): void {\n if (Object.keys(object).length) throw Error('The object is not empty');\n}\n"],"mappings":"AAAA;;AAEA;;AAGA;AACA,OAAO,SAASA,iBAAiBA,CAACC,MAAgC,EAAQ;EACxE,IAAIC,MAAM,CAACC,IAAI,CAACF,MAAM,CAAC,CAACG,MAAM,EAAE,MAAMC,KAAK,CAAC,yBAAyB,CAAC;AACxE","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":["assertEmptyObject","object","Object","keys","length","Error"],"sources":["../../src/types.ts"],"sourcesContent":["// Misc TypeScript-specific utilities.\n\n/** The most permissive object key type. */\nexport type ObjectKey = string | number | symbol;\n\n/** Asserts given object is empty, both compile- and run-time. */\nexport function assertEmptyObject(object: Record<ObjectKey, never>): void {\n if (Object.keys(object).length) throw Error('The object is not empty');\n}\n\n/**\n * Validates compile-time that the type T extends Base, and returns T.\n *\n * BEWARE: In the case Base has some optional fields missing in T, T still\n * extends Base (as \"extends\" means T is assignable to Base)! The Implements\n * util below also checks that T has all optional fields defined in Base.\n */\nexport type Extends<Base, T extends Base> = T;\n\n/**\n * Validates compile-time that the type T extends (is assignable to) the type\n * Base, and also that it has defined all (if any) optional fields defined in\n * the Base. Returns T if the validation passes.\n */\nexport type Implements<\n Base,\n T extends Required<T> extends Required<Base> ? Base : Required<Base>,\n> = T;\n"],"mappings":"AAAA;;AAEA;;AAGA;AACA,OAAO,SAASA,iBAAiBA,CAACC,MAAgC,EAAQ;EACxE,IAAIC,MAAM,CAACC,IAAI,CAACF,MAAM,CAAC,CAACG,MAAM,EAAE,MAAMC,KAAK,CAAC,yBAAyB,CAAC;AACxE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA","ignoreList":[]}
@@ -1,5 +1,4 @@
1
- import { timer } from './time';
2
-
1
+ import { timer } from "./time.js";
3
2
  /**
4
3
  * Attempts to perform the given async `action` up to `maxRetries` times with
5
4
  * the specified `interval`, stopping at the first successful (non-throwing)
@@ -1 +1 @@
1
- {"version":3,"file":"withRetries.js","names":["timer","withRetries","action","maxRetries","interval","n","res","Promise","error"],"sources":["../../src/withRetries.ts"],"sourcesContent":["import { timer } from './time';\n\n/**\n * Attempts to perform the given async `action` up to `maxRetries` times with\n * the specified `interval`, stopping at the first successful (non-throwing)\n * execution.\n * @param action\n * @param maxRetries Optional. The maximum number of re-tries. Defaults 3.\n * @param interval Optional. The interval between re-tries (in milliseconds).\n * Defaults to 300ms.\n * @returns Resolves to the result of the successful `action` execution;\n * or rejects with the error from the last faileda attempt.\n */\nexport default async function withRetries<T>(\n action: () => T,\n maxRetries = 3,\n interval = 300,\n): Promise<Awaited<T>> {\n for (let n = 1; ; ++n) {\n try {\n const res = action();\n return res instanceof Promise ? await res : (res as Awaited<T>);\n } catch (error) {\n if (n < maxRetries) await timer(interval);\n else throw error;\n }\n }\n}\n"],"mappings":"AAAA,SAASA,KAAK,QAAQ,QAAQ;;AAE9B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,eAAeC,WAAWA,CACvCC,MAAe,EACfC,UAAU,GAAG,CAAC,EACdC,QAAQ,GAAG,GAAG,EACO;EACrB,KAAK,IAAIC,CAAC,GAAG,CAAC,GAAI,EAAEA,CAAC,EAAE;IACrB,IAAI;MACF,MAAMC,GAAG,GAAGJ,MAAM,CAAC,CAAC;MACpB,OAAOI,GAAG,YAAYC,OAAO,GAAG,MAAMD,GAAG,GAAIA,GAAkB;IACjE,CAAC,CAAC,OAAOE,KAAK,EAAE;MACd,IAAIH,CAAC,GAAGF,UAAU,EAAE,MAAMH,KAAK,CAACI,QAAQ,CAAC,CAAC,KACrC,MAAMI,KAAK;IAClB;EACF;AACF","ignoreList":[]}
1
+ {"version":3,"file":"withRetries.js","names":["timer","withRetries","action","maxRetries","interval","n","res","Promise","error"],"sources":["../../src/withRetries.ts"],"sourcesContent":["import { timer } from './time';\n\n/**\n * Attempts to perform the given async `action` up to `maxRetries` times with\n * the specified `interval`, stopping at the first successful (non-throwing)\n * execution.\n * @param action\n * @param maxRetries Optional. The maximum number of re-tries. Defaults 3.\n * @param interval Optional. The interval between re-tries (in milliseconds).\n * Defaults to 300ms.\n * @returns Resolves to the result of the successful `action` execution;\n * or rejects with the error from the last faileda attempt.\n */\nexport default async function withRetries<T>(\n action: () => T,\n maxRetries = 3,\n interval = 300,\n): Promise<Awaited<T>> {\n for (let n = 1; ; ++n) {\n try {\n const res = action();\n return res instanceof Promise ? await res : (res as Awaited<T>);\n } catch (error) {\n if (n < maxRetries) await timer(interval);\n else throw error;\n }\n }\n}\n"],"mappings":"SAASA,KAAK;AAEd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,eAAeC,WAAWA,CACvCC,MAAe,EACfC,UAAU,GAAG,CAAC,EACdC,QAAQ,GAAG,GAAG,EACO;EACrB,KAAK,IAAIC,CAAC,GAAG,CAAC,GAAI,EAAEA,CAAC,EAAE;IACrB,IAAI;MACF,MAAMC,GAAG,GAAGJ,MAAM,CAAC,CAAC;MACpB,OAAOI,GAAG,YAAYC,OAAO,GAAG,MAAMD,GAAG,GAAIA,GAAkB;IACjE,CAAC,CAAC,OAAOE,KAAK,EAAE;MACd,IAAIH,CAAC,GAAGF,UAAU,EAAE,MAAMH,KAAK,CAACI,QAAQ,CAAC,CAAC,KACrC,MAAMI,KAAK;IAClB;EACF;AACF","ignoreList":[]}
@@ -2,3 +2,17 @@
2
2
  export type ObjectKey = string | number | symbol;
3
3
  /** Asserts given object is empty, both compile- and run-time. */
4
4
  export declare function assertEmptyObject(object: Record<ObjectKey, never>): void;
5
+ /**
6
+ * Validates compile-time that the type T extends Base, and returns T.
7
+ *
8
+ * BEWARE: In the case Base has some optional fields missing in T, T still
9
+ * extends Base (as "extends" means T is assignable to Base)! The Implements
10
+ * util below also checks that T has all optional fields defined in Base.
11
+ */
12
+ export type Extends<Base, T extends Base> = T;
13
+ /**
14
+ * Validates compile-time that the type T extends (is assignable to) the type
15
+ * Base, and also that it has defined all (if any) optional fields defined in
16
+ * the Base. Returns T if the validation passes.
17
+ */
18
+ export type Implements<Base, T extends Required<T> extends Required<Base> ? Base : Required<Base>> = T;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@dr.pogodin/js-utils",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Collection of JavaScript (TypeScript) utilities.",
5
5
  "main": "./build/module/index.js",
6
6
  "module": "./build/module/index.js",
7
7
  "types": "./build/types/index.d.ts",
8
8
  "exports": {
9
- "require": "./build/common/index.js",
10
9
  "types": "./build/types/index.d.ts",
10
+ "require": "./build/common/index.js",
11
11
  "default": "./build/module/index.js"
12
12
  },
13
13
  "scripts": {
@@ -16,7 +16,7 @@
16
16
  "build:module": "rimraf build/module && babel src -x .ts --out-dir build/module --source-maps --config-file ./babel.module.config.js",
17
17
  "build:types": "rimraf build/types && tsc --project tsconfig.types.json",
18
18
  "jest": "npm run jest:types && npm run jest:logic",
19
- "jest:logic": "jest --config jest.config.js",
19
+ "jest:logic": "jest --config jest.config.js --no-cache",
20
20
  "jest:types": "tstyche",
21
21
  "lint": "eslint",
22
22
  "test": "npm run lint && npm run typecheck && npm run jest",
@@ -38,22 +38,23 @@
38
38
  },
39
39
  "homepage": "https://github.com/birdofpreyru/js-utils#readme",
40
40
  "devDependencies": {
41
- "@babel/cli": "^7.28.0",
42
- "@babel/core": "^7.28.0",
43
- "@babel/plugin-transform-runtime": "^7.28.0",
44
- "@babel/preset-env": "^7.28.0",
45
- "@babel/preset-typescript": "^7.27.1",
46
- "@dr.pogodin/eslint-configs": "^0.0.9",
47
- "@tsconfig/recommended": "^1.0.10",
41
+ "@babel/cli": "^7.28.3",
42
+ "@babel/core": "^7.28.5",
43
+ "@babel/plugin-transform-runtime": "^7.28.5",
44
+ "@babel/preset-env": "^7.28.5",
45
+ "@babel/preset-typescript": "^7.28.5",
46
+ "@dr.pogodin/eslint-configs": "^0.1.2",
47
+ "@tsconfig/recommended": "^1.0.13",
48
48
  "@types/jest": "^30.0.0",
49
- "babel-jest": "^30.0.5",
50
- "jest": "^30.0.5",
49
+ "babel-jest": "^30.2.0",
50
+ "babel-plugin-add-import-extension": "^1.6.0",
51
+ "jest": "^30.2.0",
51
52
  "mockdate": "^3.0.5",
52
- "rimraf": "^6.0.1",
53
- "tstyche": "^4.3.0",
54
- "typescript": "^5.8.3"
53
+ "rimraf": "^6.1.2",
54
+ "tstyche": "^5.0.1",
55
+ "typescript": "^5.9.3"
55
56
  },
56
57
  "dependencies": {
57
- "@babel/runtime": "^7.27.6"
58
+ "@babel/runtime": "^7.28.4"
58
59
  }
59
60
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://tstyche.org/schemas/config.json",
3
+ "checkDeclarationFiles": true,
3
4
  "checkSuppressedErrors": true,
4
- "checkSourceFiles": false,
5
5
  "testFileMatch": [
6
6
  "__tests__/**/*.{ts, tsx}"
7
7
  ]