From 0db91946310c13f5d39f628e4336138ddb2af523 Mon Sep 17 00:00:00 2001 From: gretzkiy Date: Wed, 25 Dec 2024 16:18:47 +0300 Subject: [PATCH 1/2] fix!: remove `Function.protope.once` to `memoize` --- CHANGELOG.md | 10 ++- src/core/async/events/index.ts | 4 +- src/core/async/events/interface.ts | 2 +- src/core/decorators/README.md | 3 - src/core/decorators/index.ts | 61 ------------------- src/core/functools/CHANGELOG.md | 6 ++ src/core/functools/memoize.ts | 8 +-- .../prelude/function/memoize/CHANGELOG.md | 6 ++ src/core/prelude/function/memoize/index.ts | 18 +++--- src/core/prelude/function/memoize/spec.js | 12 ++-- src/core/request/response/index.ts | 28 ++++----- 11 files changed, 57 insertions(+), 101 deletions(-) delete mode 100644 src/core/decorators/README.md delete mode 100644 src/core/decorators/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 23d7f0f4e..142680371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,17 @@ Changelog _Note: Gaps between patch versions are faulty, broken or test releases._ +## v4.0.0-alpha.54 (2024-12-25) + +#### :boom: Breaking Change + +* Renamed `once` method in Function prototype to `memoize` `core/prelude/function/memoize` +* Renamed `once` decorator to `memoize` `core/functools/memoize` +* Removed deprecated module `core/decorators` + ## v4.0.0-alpha.53 (2024-12-16) -#### :bug: Bug Fix +#### :bug: Bug Fix * Added handling the rejection of provider in provider request engine `core/request/engines/provider` diff --git a/src/core/async/events/index.ts b/src/core/async/events/index.ts index e88a4fbad..6fe81a4d4 100644 --- a/src/core/async/events/index.ts +++ b/src/core/async/events/index.ts @@ -140,7 +140,7 @@ export default class Async> extends Super { wrapper(cb: AnyFunction): unknown { if (Object.isFunction(originalEmitter)) { // eslint-disable-next-line func-name-matching - emitter = function wrappedEmitter(this: unknown): CanUndef { + emitter = function wrappedEmitter(this: unknown): CanUndef { // eslint-disable-next-line prefer-rest-params const destructor = originalEmitter.apply(this, arguments); @@ -182,7 +182,7 @@ export default class Async> extends Super { }; function handler(this: unknown, ...handlerArgs: unknown[]): unknown { - if (p.single && (hasMultipleEvent || !emitter.once)) { + if (p.single && (hasMultipleEvent || !('once' in emitter))) { if (hasMultipleEvent) { that.clearEventListener(ids); diff --git a/src/core/async/events/interface.ts b/src/core/async/events/interface.ts index b5268eaf2..e89ebb528 100644 --- a/src/core/async/events/interface.ts +++ b/src/core/async/events/interface.ts @@ -49,7 +49,7 @@ export interface EventEmitterLike { /** * Extended type of event emitter */ -export type EventEmitterLikeP = ((event: string, handler: Function) => CanUndef) | EventEmitterLike; +export type EventEmitterLikeP = ((event: string, handler: AnyFunction) => CanUndef) | EventEmitterLike; export interface AsyncOnOptions extends AsyncCbOptionsSingle { /** diff --git a/src/core/decorators/README.md b/src/core/decorators/README.md deleted file mode 100644 index b6b91d8ee..000000000 --- a/src/core/decorators/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# core/decorators (deprecated) - -This module provides a bunch of helper decorators for functions. Mind that this module is deprecated and will be removed from the next major release of the library. You can use modules from [core/functools](src_core_functools.html) instead of this. diff --git a/src/core/decorators/index.ts b/src/core/decorators/index.ts deleted file mode 100644 index e02051c7f..000000000 --- a/src/core/decorators/index.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-disable prefer-rest-params, @typescript-eslint/no-unused-vars-experimental */ - -/*! - * V4Fire Core - * https://github.com/V4Fire/Core - * - * Released under the MIT license - * https://github.com/V4Fire/Core/blob/master/LICENSE - */ - -/** - * [[include:core/decorators/README.md]] - * @packageDocumentation - */ - -import * as tools from 'core/functools'; - -/** - * @deprecated - * @see core/functools - * @decorator - */ -export const once = tools.deprecate( - { - movedTo: 'core/functools' - }, - - function once(this: unknown, target: object, key: string | symbol, descriptor: PropertyDescriptor): void { - return tools.once.apply(this, arguments); - } -); - -/** - * @deprecated - * @see core/functools - * @decorator - */ -export const debounce = tools.deprecate( - { - movedTo: 'core/functools' - }, - - function debounce(this: unknown, delay?: number): MethodDecorator { - return tools.debounce.apply(this, arguments); - } -); - -/** - * @deprecated - * @see core/functools - * @decorator - */ -export const throttle = tools.deprecate( - { - movedTo: 'core/functools' - }, - - function throttle(this: unknown, delay?: number): MethodDecorator { - return tools.throttle.apply(this, arguments); - } -); diff --git a/src/core/functools/CHANGELOG.md b/src/core/functools/CHANGELOG.md index 5455923af..338fe7bd0 100644 --- a/src/core/functools/CHANGELOG.md +++ b/src/core/functools/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-alpha.54 (2024-12-25) + +#### :boom: Breaking Change + +* Renamed `once` decorator to `memoize` + ## v4.0.0-alpha.29 (2024-04-04) #### :bug: Bug Fix diff --git a/src/core/functools/memoize.ts b/src/core/functools/memoize.ts index 6f6aee71c..b7b17a72b 100644 --- a/src/core/functools/memoize.ts +++ b/src/core/functools/memoize.ts @@ -7,12 +7,12 @@ */ /** - * Decorator for `Function.prototype.once` + * Decorator for `Function.prototype.memoize` * * @decorator - * @see [[Function.once]] + * @see [[Function.memoize]] */ -export function once(target: object, key: string | symbol, descriptor: PropertyDescriptor): void { +export function memoize(target: object, key: string | symbol, descriptor: PropertyDescriptor): void { const method = descriptor.value; @@ -23,7 +23,7 @@ export function once(target: object, key: string | symbol, descriptor: PropertyD descriptor.value = function value(this: object, ...args: unknown[]): unknown { Object.defineProperty(this, key, { configurable: true, - value: method.once() + value: method.memoize() }); return this[key](...args); diff --git a/src/core/prelude/function/memoize/CHANGELOG.md b/src/core/prelude/function/memoize/CHANGELOG.md index 496ec4aee..e9a2c4e18 100644 --- a/src/core/prelude/function/memoize/CHANGELOG.md +++ b/src/core/prelude/function/memoize/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-alpha.54 (2024-12-25) + +#### :boom: Breaking Change + +* Renamed `once` method in Function prototype to `memoize` + ## v3.20.0 (2020-07-05) #### :rocket: New Feature diff --git a/src/core/prelude/function/memoize/index.ts b/src/core/prelude/function/memoize/index.ts index b1918486d..0a0b2a472 100644 --- a/src/core/prelude/function/memoize/index.ts +++ b/src/core/prelude/function/memoize/index.ts @@ -8,8 +8,8 @@ import extend from 'core/prelude/extend'; -/** @see [[Function.once]] */ -extend(Function.prototype, 'once', function once(this: AnyFunction): AnyFunction { +/** @see [[Function.memoize]] */ +extend(Function.prototype, 'memoize', function memoize(this: AnyFunction): AnyFunction { const // eslint-disable-next-line @typescript-eslint/no-this-alias fn = this; @@ -18,7 +18,7 @@ extend(Function.prototype, 'once', function once(this: AnyFunction): AnyFunction called = false, res; - Object.defineProperty(wrapper, 'cancelOnce', { + Object.defineProperty(wrapper, 'cancelmemoize', { configurable: true, enumerable: false, writable: true, @@ -41,11 +41,11 @@ extend(Function.prototype, 'once', function once(this: AnyFunction): AnyFunction } }); -/** @see [[Function.cancelOnce]] */ -extend(Function.prototype, 'cancelOnce', () => undefined); +/** @see [[Function.cancelMemoize]] */ +extend(Function.prototype, 'cancelMemoize', () => undefined); -/** @see [[FunctionConstructor.once]] */ -extend(Function, 'once', (fn: AnyFunction) => fn.once()); +/** @see [[FunctionConstructor.memoize]] */ +extend(Function, 'memoize', (fn: AnyFunction) => fn.memoize()); -/** @see [[FunctionConstructor.cancelOnce]] */ -extend(Function, 'cancelOnce', (fn: AnyFunction) => fn.cancelOnce()); +/** @see [[FunctionConstructor.cancelMemoize]] */ +extend(Function, 'cancelMemoize', (fn: AnyFunction) => fn.cancelMemoize()); diff --git a/src/core/prelude/function/memoize/spec.js b/src/core/prelude/function/memoize/spec.js index da18c5def..8c3f50cfa 100644 --- a/src/core/prelude/function/memoize/spec.js +++ b/src/core/prelude/function/memoize/spec.js @@ -7,9 +7,9 @@ */ describe('core/prelude/function/memoize', () => { - it('`once`', () => { + it('`memoize`', () => { const - rand = Math.random.once(), + rand = Math.random.memoize(), res = rand(); expect(Object.isNumber(res)).toBe(true); @@ -17,15 +17,15 @@ describe('core/prelude/function/memoize', () => { expect(rand()).toBe(res); }); - it('`once` with arguments', () => { - const fn = ((i) => i).once(); + it('`memoize` with arguments', () => { + const fn = ((i) => i).memoize(); expect(fn(1)).toBe(1); expect(fn(2)).toBe(1); }); - it('`Function.once`', () => { + it('`Function.memoize`', () => { const - rand = Function.once(Math.random), + rand = Function.memoize(Math.random), res = rand(); expect(Object.isNumber(res)).toBe(true); diff --git a/src/core/request/response/index.ts b/src/core/request/response/index.ts index e03ee365a..273bfad4c 100644 --- a/src/core/request/response/index.ts +++ b/src/core/request/response/index.ts @@ -12,7 +12,7 @@ */ import { EventEmitter2 as EventEmitter } from 'eventemitter2'; -import { once, deprecated } from 'core/functools'; +import { memoize, deprecated } from 'core/functools'; import { IS_NODE } from 'core/env'; import { convertIfDate } from 'core/json'; @@ -253,7 +253,7 @@ export default class Response< this.headers = Object.freeze(new Headers(p.headers)); if (Object.isFunction(body)) { - this.body = body.once(); + this.body = body.memoize(); this.body[Symbol.asyncIterator] = body[Symbol.asyncIterator].bind(body); } else { @@ -442,7 +442,7 @@ export default class Response< /** * Parses the response body as a JSON object and returns it */ - @once + @memoize json(): AbortablePromise { return this.readBody().then((body) => { if (body == null) { @@ -494,7 +494,7 @@ export default class Response< /** * Parses the response data stream as a JSON tokens and yields them via an asynchronous iterator */ - @once + @memoize jsonStream(): AsyncIterableIterator { const iter = Parser.from(this.textStream()); @@ -511,7 +511,7 @@ export default class Response< /** * Parses the response body as a FormData object and returns it */ - @once + @memoize formData(): AbortablePromise { const that = this; @@ -565,7 +565,7 @@ export default class Response< /** * Parses the response body as a Document instance and returns it */ - @once + @memoize document(): AbortablePromise { return this.readBody().then((body) => { //#if node_js @@ -595,7 +595,7 @@ export default class Response< /** * Parses the response body as a string and returns it */ - @once + @memoize text(): AbortablePromise { return this.readBody().then((body) => this.decodeToString(body)); } @@ -603,7 +603,7 @@ export default class Response< /** * Parses the response data stream as a text chunks and yields them via an asynchronous iterator */ - @once + @memoize textStream(): AsyncIterableIterator { const iter = this.stream(); @@ -628,7 +628,7 @@ export default class Response< /** * Parses the response data stream as an ArrayBuffer chunks and yields them via an asynchronous iterator */ - @once + @memoize stream(): AsyncIterableIterator { const iter = this[Symbol.asyncIterator](); @@ -653,7 +653,7 @@ export default class Response< /** * Parses the response body as a Blob structure and returns it */ - @once + @memoize blob(): AbortablePromise { return this.readBody().then((body) => this.decodeToBlob(body)); } @@ -661,7 +661,7 @@ export default class Response< /** * Parses the response body as an ArrayBuffer and returns it */ - @once + @memoize arrayBuffer(): AbortablePromise { return this.readBody().then((body) => { if (body == null || body === '') { @@ -688,7 +688,7 @@ export default class Response< this.emitter.removeAllListeners(); if (Object.isFunction(this.body)) { - this.body.cancelOnce(); + this.body.cancelMemoize(); Object.defineProperty(this.body, Symbol.asyncIterator, { configurable: true, @@ -707,7 +707,7 @@ export default class Response< Object.delete(this, 'parent'); ['json', 'formData', 'document', 'text', 'blob', 'arrayBuffer', 'decode'].forEach((key) => { - (this[key]).cancelOnce(); + (this[key]).cancelMemoize(); Object.defineProperty(this, key, { configurable: true, @@ -718,7 +718,7 @@ export default class Response< }); ['jsonStream', 'textStream', 'stream', 'decodeStream', Symbol.asyncIterator].forEach((key) => { - (this[key]).cancelOnce(); + (this[key]).cancelMemoize(); Object.defineProperty(this, key, { configurable: true, From cf79bd0e90b2a0929a6aa30a3ce4e63b240aede2 Mon Sep 17 00:00:00 2001 From: gretzkiy Date: Wed, 25 Dec 2024 16:18:59 +0300 Subject: [PATCH 2/2] chore(build): update lib build --- lib/core/async/events/index.js | 2 +- lib/core/async/events/interface.d.ts | 2 +- lib/core/decorators/index.d.ts | 30 --- lib/core/decorators/index.js | 27 --- lib/core/functools/memoize.d.ts | 6 +- lib/core/functools/memoize.js | 6 +- lib/core/log/index.d.ts | 2 +- lib/core/perf/timer/engines/index.d.ts | 2 +- lib/core/prelude/function/memoize/index.js | 10 +- lib/core/prelude/global/index.js | 6 +- lib/core/prelude/i18n/const.d.ts | 9 - lib/core/prelude/i18n/const.js | 11 +- lib/core/prelude/i18n/helpers.d.ts | 53 ++++-- lib/core/prelude/i18n/helpers.js | 66 +++++-- lib/core/prelude/i18n/interface.d.ts | 11 +- lib/core/prelude/i18n/spec.d.ts | 8 + lib/core/prelude/i18n/spec.js | 176 ++++++++++++++++++ lib/core/promise/abortable/index.d.ts | 24 ++- lib/core/request/engines/composition/index.js | 51 ++++- .../engines/composition/interface.d.ts | 13 ++ lib/core/request/engines/fetch/index.js | 5 + lib/core/request/engines/provider/index.js | 12 +- lib/core/request/interface.d.ts | 1 + lib/core/request/response/index.js | 12 +- lib/core/request/response/test/main.spec.d.ts | 1 + lib/core/request/response/test/main.spec.js | 16 ++ lib/lang/interface.d.ts | 9 +- ts-definitions/prelude/function/proto.d.ts | 4 +- ts-definitions/prelude/function/static.d.ts | 4 +- 29 files changed, 429 insertions(+), 150 deletions(-) delete mode 100644 lib/core/decorators/index.d.ts delete mode 100644 lib/core/decorators/index.js create mode 100644 lib/core/prelude/i18n/spec.d.ts create mode 100644 lib/core/prelude/i18n/spec.js create mode 100644 lib/core/request/response/test/main.spec.d.ts create mode 100644 lib/core/request/response/test/main.spec.js diff --git a/lib/core/async/events/index.js b/lib/core/async/events/index.js index e0125b040..e8365b050 100644 --- a/lib/core/async/events/index.js +++ b/lib/core/async/events/index.js @@ -114,7 +114,7 @@ class Async extends _timers.default { args }; function handler(...handlerArgs) { - if (p.single && (hasMultipleEvent || !emitter.once)) { + if (p.single && (hasMultipleEvent || !('once' in emitter))) { if (hasMultipleEvent) { that.clearEventListener(ids); } else { diff --git a/lib/core/async/events/interface.d.ts b/lib/core/async/events/interface.d.ts index 56d035537..2cb55d794 100644 --- a/lib/core/async/events/interface.d.ts +++ b/lib/core/async/events/interface.d.ts @@ -28,7 +28,7 @@ export interface EventEmitterLike { /** * Extended type of event emitter */ -export declare type EventEmitterLikeP = ((event: string, handler: Function) => CanUndef) | EventEmitterLike; +export declare type EventEmitterLikeP = ((event: string, handler: AnyFunction) => CanUndef) | EventEmitterLike; export interface AsyncOnOptions extends AsyncCbOptionsSingle { /** * Additional options for the emitter diff --git a/lib/core/decorators/index.d.ts b/lib/core/decorators/index.d.ts deleted file mode 100644 index 1e85a7301..000000000 --- a/lib/core/decorators/index.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*! - * V4Fire Core - * https://github.com/V4Fire/Core - * - * Released under the MIT license - * https://github.com/V4Fire/Core/blob/master/LICENSE - */ -/** - * [[include:core/decorators/README.md]] - * @packageDocumentation - */ -import * as tools from '../../core/functools'; -/** - * @deprecated - * @see core/functools - * @decorator - */ -export declare const once: tools.WarnedFn<[target: object, key: string | symbol, descriptor: PropertyDescriptor], void>; -/** - * @deprecated - * @see core/functools - * @decorator - */ -export declare const debounce: tools.WarnedFn<[delay?: number | undefined], MethodDecorator>; -/** - * @deprecated - * @see core/functools - * @decorator - */ -export declare const throttle: tools.WarnedFn<[delay?: number | undefined], MethodDecorator>; diff --git a/lib/core/decorators/index.js b/lib/core/decorators/index.js deleted file mode 100644 index 2bb2f4bc2..000000000 --- a/lib/core/decorators/index.js +++ /dev/null @@ -1,27 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.throttle = exports.once = exports.debounce = void 0; -var tools = _interopRequireWildcard(require("../../core/functools")); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -const once = tools.deprecate({ - movedTo: 'core/functools' -}, function once(target, key, descriptor) { - return tools.once.apply(this, arguments); -}); -exports.once = once; -const debounce = tools.deprecate({ - movedTo: 'core/functools' -}, function debounce(delay) { - return tools.debounce.apply(this, arguments); -}); -exports.debounce = debounce; -const throttle = tools.deprecate({ - movedTo: 'core/functools' -}, function throttle(delay) { - return tools.throttle.apply(this, arguments); -}); -exports.throttle = throttle; \ No newline at end of file diff --git a/lib/core/functools/memoize.d.ts b/lib/core/functools/memoize.d.ts index fcabe75b4..673798e66 100644 --- a/lib/core/functools/memoize.d.ts +++ b/lib/core/functools/memoize.d.ts @@ -6,9 +6,9 @@ * https://github.com/V4Fire/Core/blob/master/LICENSE */ /** - * Decorator for `Function.prototype.once` + * Decorator for `Function.prototype.memoize` * * @decorator - * @see [[Function.once]] + * @see [[Function.memoize]] */ -export declare function once(target: object, key: string | symbol, descriptor: PropertyDescriptor): void; +export declare function memoize(target: object, key: string | symbol, descriptor: PropertyDescriptor): void; diff --git a/lib/core/functools/memoize.js b/lib/core/functools/memoize.js index 94c9c67a6..4e2a17319 100644 --- a/lib/core/functools/memoize.js +++ b/lib/core/functools/memoize.js @@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); -exports.once = once; -function once(target, key, descriptor) { +exports.memoize = memoize; +function memoize(target, key, descriptor) { const method = descriptor.value; if (!Object.isFunction(method)) { throw new TypeError(`descriptor.value is not a function: ${method}`); @@ -12,7 +12,7 @@ function once(target, key, descriptor) { descriptor.value = function value(...args) { Object.defineProperty(this, key, { configurable: true, - value: method.once() + value: method.memoize() }); return this[key](...args); }; diff --git a/lib/core/log/index.d.ts b/lib/core/log/index.d.ts index b81e68f1b..d70a16380 100644 --- a/lib/core/log/index.d.ts +++ b/lib/core/log/index.d.ts @@ -11,5 +11,5 @@ export * from '../../core/log/config'; * API for logging * @defaultExport */ -declare const logger: import("./interface").ExtendedLogger; +declare const logger: import("../../core/log/interface").ExtendedLogger; export default logger; diff --git a/lib/core/perf/timer/engines/index.d.ts b/lib/core/perf/timer/engines/index.d.ts index 16d57836d..a5e4a6f06 100644 --- a/lib/core/perf/timer/engines/index.d.ts +++ b/lib/core/perf/timer/engines/index.d.ts @@ -7,6 +7,6 @@ */ export * from '../../../../core/perf/timer/engines/interface'; declare const engines: { - console: import("./interface").PerfTimerEngine; + console: import("../../../../core/perf/timer/engines/interface").PerfTimerEngine; }; export default engines; diff --git a/lib/core/prelude/function/memoize/index.js b/lib/core/prelude/function/memoize/index.js index 453d7f203..cf325d5c4 100644 --- a/lib/core/prelude/function/memoize/index.js +++ b/lib/core/prelude/function/memoize/index.js @@ -2,11 +2,11 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _extend = _interopRequireDefault(require("../../../../core/prelude/extend")); -(0, _extend.default)(Function.prototype, 'once', function once() { +(0, _extend.default)(Function.prototype, 'memoize', function memoize() { const fn = this; let called = false, res; - Object.defineProperty(wrapper, 'cancelOnce', { + Object.defineProperty(wrapper, 'cancelmemoize', { configurable: true, enumerable: false, writable: true, @@ -25,6 +25,6 @@ var _extend = _interopRequireDefault(require("../../../../core/prelude/extend")) return res; } }); -(0, _extend.default)(Function.prototype, 'cancelOnce', () => undefined); -(0, _extend.default)(Function, 'once', fn => fn.once()); -(0, _extend.default)(Function, 'cancelOnce', fn => fn.cancelOnce()); \ No newline at end of file +(0, _extend.default)(Function.prototype, 'cancelMemoize', () => undefined); +(0, _extend.default)(Function, 'memoize', fn => fn.memoize()); +(0, _extend.default)(Function, 'cancelMemoize', fn => fn.cancelMemoize()); \ No newline at end of file diff --git a/lib/core/prelude/global/index.js b/lib/core/prelude/global/index.js index 0ad962986..8973e1d84 100644 --- a/lib/core/prelude/global/index.js +++ b/lib/core/prelude/global/index.js @@ -5,12 +5,12 @@ var _log = _interopRequireDefault(require("../../../core/log")); var _extend = _interopRequireDefault(require("../../../core/prelude/extend")); var _const = require("../../../core/prelude/global/const"); (0, _extend.default)(globalThis, 'Any', obj => obj); -(0, _extend.default)(globalThis, 'stderr', err => { +(0, _extend.default)(globalThis, 'stderr', (err, ...details) => { if (err instanceof Object) { if (_const.errorsToIgnore[err.type] === true) { - _log.default.info('stderr', err); + _log.default.info('stderr', err, ...details); return; } - _log.default.error('stderr', err); + _log.default.error('stderr', err, ...details); } }); \ No newline at end of file diff --git a/lib/core/prelude/i18n/const.d.ts b/lib/core/prelude/i18n/const.d.ts index eab406cdd..946b1beb8 100644 --- a/lib/core/prelude/i18n/const.d.ts +++ b/lib/core/prelude/i18n/const.d.ts @@ -24,12 +24,3 @@ export declare const locale: Locale; * The default application region */ export declare const region: RegionStore; -/** - * A dictionary to map literal pluralization forms to numbers - */ -export declare const pluralizeMap: Pick<{ - none: number; - one: number; - some: number; - many: number; -}, "some" | "none" | "one" | "many">; diff --git a/lib/core/prelude/i18n/const.js b/lib/core/prelude/i18n/const.js index a86e5f5f5..305adc181 100644 --- a/lib/core/prelude/i18n/const.js +++ b/lib/core/prelude/i18n/const.js @@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); -exports.region = exports.pluralizeMap = exports.locale = exports.event = exports.emitter = void 0; +exports.region = exports.locale = exports.event = exports.emitter = void 0; var _eventemitter = require("eventemitter2"); const emitter = new _eventemitter.EventEmitter2({ maxListeners: 100, @@ -21,11 +21,4 @@ const region = { value: undefined, isDefault: false }; -exports.region = region; -const pluralizeMap = Object.createDict({ - none: 0, - one: 1, - some: 2, - many: 5 -}); -exports.pluralizeMap = pluralizeMap; \ No newline at end of file +exports.region = region; \ No newline at end of file diff --git a/lib/core/prelude/i18n/helpers.d.ts b/lib/core/prelude/i18n/helpers.d.ts index ce2cb5f86..ff93bffc6 100644 --- a/lib/core/prelude/i18n/helpers.d.ts +++ b/lib/core/prelude/i18n/helpers.d.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Core/blob/master/LICENSE */ import { Translation, PluralTranslation } from '../../../lang'; -import type { PluralizationCount } from '../../../core/prelude/i18n/interface'; +import type { I18nOpts, PluralizationCount } from '../../../core/prelude/i18n/interface'; /** * Creates a function to internationalize strings in an application based on the given locale and keyset. * Keyset allows you to share the same keys in different contexts. @@ -26,6 +26,7 @@ export declare function i18nFactory(keysetNameOrNames: string | string[], custom * * @param value - a string for the default case, or an array of strings for the plural case * @param params - a dictionary with parameters for internationalization + * @params [opts] - additional options for current translation * * @example * ```typescript @@ -33,33 +34,55 @@ export declare function i18nFactory(keysetNameOrNames: string | string[], custom * * console.log(example); // 'My name is John, I live in Denver' * - * const examplePluralize = resolveTemplate([ - * {count} product, // One - * {count} products, // Some - * {count} products, // Many - * {count} products, // None - * ], {count: 5}); + * const examplePluralize = resolveTemplate({ + * one: {count} product, + * few: {count} products, + * many: {count} products, + * zero: {count} products, + * }, {count: 5}); * * console.log(examplePluralize); // '5 products' * ``` */ -export declare function resolveTemplate(value: Translation, params?: I18nParams): string; +export declare function resolveTemplate(value: Translation, params?: I18nParams, opts?: I18nOpts): string; /** * Returns the correct plural form to translate based on the given count * * @param pluralTranslation - list of translation variants * @param count - the value on the basis of which the form of pluralization will be selected + * @params [opts] - additional options for current translation * * @example * ```typescript - * const result = pluralizeText([ - * {count} product, // One - * {count} products, // Some - * {count} products, // Many - * {count} products, // None - * ], 5); + * const result = pluralizeText({ + * one: {count} product, + * few: {count} products, + * many: {count} products, + * zero: {count} products, + * other: {count} products, + * }, 5, {pluralRules: new Intl.PluralRulse('en')}); * * console.log(result); // '{count} products' * ``` */ -export declare function pluralizeText(pluralTranslation: PluralTranslation, count: CanUndef): string; +export declare function pluralizeText(pluralTranslation: PluralTranslation, count: CanUndef, opts?: I18nOpts): string; +/** + * Returns the plural form name for a given number `n` based on the specified pluralization rules. + * Otherwise will be used default set of rules. + * + * If a `rules` object implementing `Intl.PluralRules` is provided, it will use that to determine the plural form. + * Otherwise, it will fall back to a custom rule set: + * - Returns 'zero' for `n === 0`. + * - Returns 'one' for `n === 1`. + * - Returns 'few' for `n > 1 && n < 5`. + * - Returns 'many' for all other values of `n`. + * + * @param n - The number to evaluate for pluralization. + * @param rules - Plural rules object. If undefined, a default rule set is used. + */ +export declare function getPluralFormName(n: number, rules?: CanUndef): keyof Required; +/** + * Returns an instance of `Intl.PluralRules` for a given locale, if supported. + * @param locale - The locale for which to generate plural rules. + */ +export declare function getPluralRules(locale: Language): CanUndef; diff --git a/lib/core/prelude/i18n/helpers.js b/lib/core/prelude/i18n/helpers.js index d628cbea0..0fa47af62 100644 --- a/lib/core/prelude/i18n/helpers.js +++ b/lib/core/prelude/i18n/helpers.js @@ -4,6 +4,8 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau Object.defineProperty(exports, "__esModule", { value: true }); +exports.getPluralFormName = getPluralFormName; +exports.getPluralRules = getPluralRules; exports.i18nFactory = i18nFactory; exports.pluralizeText = pluralizeText; exports.resolveTemplate = resolveTemplate; @@ -17,22 +19,34 @@ function i18nFactory(keysetNameOrNames, customLocale) { if (resolvedLocale == null) { throw new ReferenceError('The locale for internationalization is not defined'); } + const pluralRules = getPluralRules(resolvedLocale); return function i18n(value, params) { if (Object.isArray(value) && value.length !== 1) { throw new SyntaxError('Using i18n with template literals is allowed only without variables'); } const key = Object.isString(value) ? value : value[0], correctKeyset = keysetNames.find(keysetName => _lang.default[resolvedLocale]?.[keysetName]?.[key]), - translateValue = _lang.default[resolvedLocale]?.[correctKeyset ?? '']?.[key]; + translateValue = _lang.default[resolvedLocale]?.[correctKeyset ?? '']?.[key], + meta = { + language: resolvedLocale, + keyset: correctKeyset, + key + }; if (translateValue != null && translateValue !== '') { - return resolveTemplate(translateValue, params); + return resolveTemplate(translateValue, params, { + pluralRules, + meta + }); } logger.error('Translation for the given key is not found', `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}, available locales: ${Object.keys(_lang.default).join(', ')}`); - return resolveTemplate(key, params); + return resolveTemplate(key, params, { + pluralRules, + meta + }); }; } -function resolveTemplate(value, params) { - const template = Object.isArray(value) ? pluralizeText(value, params?.count) : value; +function resolveTemplate(value, params, opts = {}) { + const template = Object.isPlainObject(value) ? pluralizeText(value, params?.count, opts) : value; return template.replace(/{([^}]+)}/g, (_, key) => { if (params?.[key] == null) { logger.error('Undeclared variable', `Name: "${key}", Template: "${template}"`); @@ -41,28 +55,50 @@ function resolveTemplate(value, params) { return params[key]; }); } -function pluralizeText(pluralTranslation, count) { +function pluralizeText(pluralTranslation, count, opts = {}) { + const { + pluralRules, + meta + } = opts; let normalizedCount; if (Object.isNumber(count)) { normalizedCount = count; } else if (Object.isString(count)) { - if (count in _const.pluralizeMap) { - normalizedCount = _const.pluralizeMap[count]; + const translation = pluralTranslation[count]; + if (translation != null) { + return translation; } } if (normalizedCount == null) { - logger.error('Invalid value of the `count` parameter for string pluralization', `String: ${pluralTranslation[0]}`); + logger.error('Invalid value of the `count` parameter for string pluralization', `Count: ${count}, Key: ${meta?.key}, Language: ${meta?.language}, Keyset: ${meta?.keyset}`); normalizedCount = 1; } - switch (normalizedCount) { + const pluralFormName = getPluralFormName(normalizedCount, pluralRules), + translation = pluralTranslation[pluralFormName]; + if (translation == null) { + logger.error(`Plural form ${pluralFormName} doesn't exist.`, `Key: ${meta?.key}, Language: ${meta?.language}, Keyset: ${meta?.keyset}`); + return pluralTranslation.one; + } + return translation; +} +function getPluralFormName(n, rules) { + if (rules != null) { + return rules.select(n); + } + switch (n) { case 0: - return pluralTranslation[3]; + return 'zero'; case 1: - return pluralTranslation[0]; + return 'one'; default: - if (normalizedCount > 1 && normalizedCount < 5) { - return pluralTranslation[1]; + if (n > 1 && n < 5) { + return 'few'; } - return pluralTranslation[2]; + return 'many'; + } +} +function getPluralRules(locale) { + if ('PluralRules' in globalThis['Intl']) { + return new globalThis['Intl'].PluralRules(locale); } } \ No newline at end of file diff --git a/lib/core/prelude/i18n/interface.d.ts b/lib/core/prelude/i18n/interface.d.ts index 3de348214..4b6be9c69 100644 --- a/lib/core/prelude/i18n/interface.d.ts +++ b/lib/core/prelude/i18n/interface.d.ts @@ -33,4 +33,13 @@ export interface LocaleKVStorage { */ set?: SyncStorage['set']; } -export declare type PluralizationCount = StringPluralizationForms | string | number; +export declare type PluralizationCount = StringPluralizationForms | number; +export interface I18nMeta { + language: string; + key: string; + keyset?: string; +} +export interface I18nOpts { + pluralRules?: Intl.PluralRules; + meta?: I18nMeta; +} diff --git a/lib/core/prelude/i18n/spec.d.ts b/lib/core/prelude/i18n/spec.d.ts new file mode 100644 index 000000000..3c73d6848 --- /dev/null +++ b/lib/core/prelude/i18n/spec.d.ts @@ -0,0 +1,8 @@ +/*! + * V4Fire Core + * https://github.com/V4Fire/Core + * + * Released under the MIT license + * https://github.com/V4Fire/Core/blob/master/LICENSE + */ +export {}; diff --git a/lib/core/prelude/i18n/spec.js b/lib/core/prelude/i18n/spec.js new file mode 100644 index 000000000..ae88a625b --- /dev/null +++ b/lib/core/prelude/i18n/spec.js @@ -0,0 +1,176 @@ +"use strict"; + +var _i18n = require("../../../core/prelude/i18n"); +var _helpers = require("../../../core/prelude/i18n/helpers"); +describe('core/prelude/i18n', () => { + const rules = new Intl.PluralRules('en'); + const forms = { + one: 'first form', + two: 'second form', + few: 'third form', + many: 'fifth form', + zero: 'zeroth form', + other: 'others form' + }; + const formNames = Object.keys(forms); + describe('pluralization forms detection', () => { + it('detecting plural form without Intl rules', () => { + expect((0, _helpers.getPluralFormName)(0)).toBe('zero'); + expect((0, _helpers.getPluralFormName)(1)).toBe('one'); + expect((0, _helpers.getPluralFormName)(2)).toBe('few'); + expect((0, _helpers.getPluralFormName)(5)).toBe('many'); + }); + it('detecting plural form using Intl rules', () => { + expect((0, _helpers.getPluralFormName)(0, rules)).toBe('other'); + expect((0, _helpers.getPluralFormName)(1, rules)).toBe('one'); + expect((0, _helpers.getPluralFormName)(2, rules)).toBe('other'); + expect((0, _helpers.getPluralFormName)(5, rules)).toBe('other'); + }); + }); + describe('text pluralization', () => { + it('using pluralization constants to choose the right form', () => { + formNames.forEach(form => { + expect((0, _i18n.pluralizeText)(forms, form, { + pluralRules: rules + })).toBe(forms[form]); + }); + }); + it('using a number to choose the right form of pluralization', () => { + const input = { + forms, + count: [1, 2, 100, 0] + }; + [forms.one, forms.other, forms.other, forms.other].forEach((form, index) => { + expect((0, _i18n.pluralizeText)(input.forms, input.count[index], { + pluralRules: rules + })).toBe(form); + }); + }); + it('returns "one" form when required plural form is missing', () => { + const input = { + forms, + count: [1, 2, 100, 0] + }; + [forms.one, forms.one, forms.one, forms.one].forEach((form, index) => { + expect((0, _i18n.pluralizeText)({ + one: input.forms.one + }, input.count[index], { + pluralRules: rules + })).toBe(form); + }); + }); + it('returns "one" form when count is invalid', () => { + const input = { + forms + }; + [forms.one, forms.one, forms.one, forms.one].forEach(form => { + expect((0, _i18n.pluralizeText)({ + one: input.forms.one + }, undefined, { + pluralRules: rules + })).toBe(form); + }); + }); + }); + describe('substitution of variables and pluralization forms in a template', () => { + it('template resolving without additional parameters', () => { + expect((0, _i18n.resolveTemplate)('foo bar baz')).toBe('foo bar baz'); + }); + it('passing variables for template resolving', () => { + const tpl = 'foo {macros} {macros2}'; + expect((0, _i18n.resolveTemplate)(tpl, { + macros: 'bar', + macros2: 'baz' + })).toBe('foo bar baz'); + }); + it('if the variable is not set, then it should be displayed as text', () => { + const tpl = 'foo {macros} {macros2}'; + expect((0, _i18n.resolveTemplate)(tpl, { + macros: 'bar' + })).toBe('foo bar macros2'); + }); + it('passing the `count` parameter for template resolving', () => { + const res1 = (0, _i18n.resolveTemplate)({ + one: 'one {count}', + few: 'few {count}', + many: 'many {count}', + other: 'other {count}' + }, { + count: 5 + }, { + pluralRules: rules + }); + const res2 = (0, _i18n.resolveTemplate)({ + one: 'one {count}', + few: 'few {count}', + many: 'many {count}', + other: 'other {count}' + }, { + count: 1 + }, { + pluralRules: rules + }); + expect(res1).toBe('other 5'); + expect(res2).toBe('one 1'); + }); + }); + describe('pluralization for cyrillic language', () => { + it('russian language with Intl', () => { + const cyrillicRules = new Intl.PluralRules('ru'), + forms = { + one: '{count} яблоко', + few: '{count} яблока', + many: '{count} яблок', + zero: '{count} яблок' + }; + expect((0, _i18n.resolveTemplate)(forms, { + count: 1 + }, { + pluralRules: cyrillicRules + })).toBe('1 яблоко'); + expect((0, _i18n.resolveTemplate)(forms, { + count: 2 + }, { + pluralRules: cyrillicRules + })).toBe('2 яблока'); + expect((0, _i18n.resolveTemplate)(forms, { + count: 0 + }, { + pluralRules: cyrillicRules + })).toBe('0 яблок'); + expect((0, _i18n.resolveTemplate)(forms, { + count: 12 + }, { + pluralRules: cyrillicRules + })).toBe('12 яблок'); + expect((0, _i18n.resolveTemplate)(forms, { + count: 22 + }, { + pluralRules: cyrillicRules + })).toBe('22 яблока'); + }); + it('russian language without Intl', () => { + const forms = { + one: '{count} яблоко', + few: '{count} яблока', + many: '{count} яблок', + zero: '{count} яблок' + }; + expect((0, _i18n.resolveTemplate)(forms, { + count: 1 + })).toBe('1 яблоко'); + expect((0, _i18n.resolveTemplate)(forms, { + count: 2 + })).toBe('2 яблока'); + expect((0, _i18n.resolveTemplate)(forms, { + count: 0 + })).toBe('0 яблок'); + expect((0, _i18n.resolveTemplate)(forms, { + count: 12 + })).toBe('12 яблок'); + expect((0, _i18n.resolveTemplate)(forms, { + count: 22 + })).toBe('22 яблок'); + }); + }); +}); \ No newline at end of file diff --git a/lib/core/promise/abortable/index.d.ts b/lib/core/promise/abortable/index.d.ts index 4abce7bf6..4bfb56f6c 100644 --- a/lib/core/promise/abortable/index.d.ts +++ b/lib/core/promise/abortable/index.d.ts @@ -185,8 +185,28 @@ export default class AbortablePromise implements Promise { */ finally(cb?: Nullable): AbortablePromise; /** - * Aborts the current promise (the promise will be rejected) - * @param [reason] - abort reason + * Aborts the current promise. + * The promise will be rejected only if it doesn't have any active consumers. + * You can follow the link to see how to get around this behavior. + * @see https://github.com/V4Fire/Core/blob/a0635b1ed2600409b5c14b5f85f0281a4f48ee8c/src/core/promise/abortable/README.md#tied-promises + * + * @param [reason] + * + * @example + * ```js + * const promise1 = new AbortablePromise(...); + * const promise2 = new AbortablePromise(...); + * + * promise1.then((res) => doSomething(res)); + * promise2.then((res) => doSomething(res)); + * promise2.then((res) => doSomethingElse(res)); + * + * // It will be aborted, because it has only 1 consumer + * promise1.abort(); + * + * // It won't be aborted, because it has 2 consumers + * promise2.abort(); + * ``` */ abort(reason?: unknown): boolean; /** diff --git a/lib/core/request/engines/composition/index.js b/lib/core/request/engines/composition/index.js index fedaf7315..e035b4542 100644 --- a/lib/core/request/engines/composition/index.js +++ b/lib/core/request/engines/composition/index.js @@ -66,21 +66,40 @@ function compositionEngine(compositionRequests, engineOptions) { } const promises = compositionRequests.map(r => _structures.SyncPromise.resolve(r.requestFilter?.(options)).then(filterValue => { if (filterValue === false) { - return; + return {}; } - return r.request(options).then(boundRequest.bind(null, async)).then(request => isRequestResponseObject(request) ? request.data : request).catch(err => { + return r.request(options).then(boundRequest.bind(null, async)).then(async request => { + if (isRequestResponseObject(request)) { + return { + data: await request.data, + headers: request.response.headers, + status: request.response.status + }; + } + return { + data: await request, + headers: {}, + status: _statusCodes.default.OK + }; + }).catch(err => { if (r.failCompositionOnError) { throw err; } + return {}; }); })); - gatherDataFromRequests(promises, options).then(data => { + gatherDataFromRequests(promises, options).then(({ + data, + status, + headers + }) => { resolve(new _request.Response(data, { parent: requestOptions.parent, important: requestOptions.important, responseType: 'object', okStatuses: requestOptions.okStatuses, - status: _statusCodes.default.OK, + status: status ?? _statusCodes.default.OK, + headers: headers ?? {}, decoder: requestOptions.decoders, noContentStatuses: requestOptions.noContentStatuses })); @@ -114,7 +133,9 @@ function boundRequest(async, requestObject) { return requestObject; } async function gatherDataFromRequests(promises, options) { - const accumulator = {}; + const accumulator = { + data: {} + }; if (options.engineOptions?.aggregateErrors) { await Promise.allSettled(promises).then(results => { const errors = []; @@ -139,14 +160,24 @@ async function gatherDataFromRequests(promises, options) { } return accumulator; } -function accumulateData(accumulator, data, compositionRequest) { +function accumulateData(accumulator, newData, compositionRequest) { const { - as - } = compositionRequest; + as + } = compositionRequest, + { + status, + headers, + data + } = newData; + accumulator.data ??= {}; if (as === _const.compositionEngineSpreadResult) { - Object.assign(accumulator, data); + Object.assign(accumulator.data, data); } else { - Object.set(accumulator, as, data); + Object.set(accumulator.data, as, data); + } + if (compositionRequest.propagateStatusAndHeaders === true) { + accumulator.status = status; + accumulator.headers = headers; } return accumulator; } diff --git a/lib/core/request/engines/composition/interface.d.ts b/lib/core/request/engines/composition/interface.d.ts index f3fddcf79..8514b149f 100644 --- a/lib/core/request/engines/composition/interface.d.ts +++ b/lib/core/request/engines/composition/interface.d.ts @@ -8,6 +8,8 @@ import type Provider from '../../../../core/data'; import type { ProviderOptions } from '../../../../core/data'; import type { RequestOptions, RequestResponseObject, MiddlewareParams, RequestPromise, RequestEngine } from '../../../../core/request'; +import type { RawHeaders } from '../../../../core/request/headers'; +import type { StatusCodes } from '../../../../core/status-codes'; export interface CompositionEngineOpts { /** * If true, the engine will change its behavior and will now wait for the completion @@ -124,6 +126,12 @@ export interface CompositionRequest { * If false / undefined, request errors will be ignored. */ failCompositionOnError?: boolean; + /** + * If true, status code and reponse headers will be propagated from this request to the whole + * composition. Note that if there are more than one request with this option set to true, + * only last request's data will be propagated. + */ + propagateStatusAndHeaders?: boolean; } export interface CompositionRequestOptions { /** @@ -152,3 +160,8 @@ export interface CompositionRequestEngine extends RequestEngine { dropCache: NonNullable; destroy: NonNullable; } +export interface GatheredRequestsData { + data?: Dictionary; + headers?: RawHeaders; + status?: StatusCodes; +} diff --git a/lib/core/request/engines/fetch/index.js b/lib/core/request/engines/fetch/index.js index f7306836d..1e338f63f 100644 --- a/lib/core/request/engines/fetch/index.js +++ b/lib/core/request/engines/fetch/index.js @@ -31,10 +31,15 @@ const request = params => { } else if (p.credentials) { credentials = 'include'; } + let redirect = 'follow'; + if (Object.isString(p.redirect)) { + redirect = p.redirect; + } const fetchOpts = { body, headers, credentials, + redirect, method: p.method, signal: abortController.signal }; diff --git a/lib/core/request/engines/provider/index.js b/lib/core/request/engines/provider/index.js index 1790e03a9..478be1bdc 100644 --- a/lib/core/request/engines/provider/index.js +++ b/lib/core/request/engines/provider/index.js @@ -124,9 +124,15 @@ function createProviderEngine(src, methodsMapping = {}) { req.emitter.on(event, e => params.emitter.emit(event, e)); }); params.emitter.emit('drainListeners'); - const providerResObj = await req, - providerResponse = providerResObj.response; - const getResponse = () => providerResObj.data; + let providerResObj; + try { + providerResObj = await req; + } catch (err) { + reject(err); + return; + } + const providerResponse = providerResObj.response, + getResponse = () => providerResObj.data; getResponse[Symbol.asyncIterator] = () => { const type = providerResponse.sourceResponseType; if (!(`${type}Stream` in providerResponse)) { diff --git a/lib/core/request/interface.d.ts b/lib/core/request/interface.d.ts index ff7347f26..78722117b 100644 --- a/lib/core/request/interface.d.ts +++ b/lib/core/request/interface.d.ts @@ -590,6 +590,7 @@ export interface RequestOptions { readonly body?: RequestBody; readonly important?: boolean; readonly credentials?: boolean | RequestCredentials; + readonly redirect?: RequestRedirect; } /** * Request engine diff --git a/lib/core/request/response/index.js b/lib/core/request/response/index.js index 506970ad5..de58d45ac 100644 --- a/lib/core/request/response/index.js +++ b/lib/core/request/response/index.js @@ -120,7 +120,7 @@ let Response = (_dec = (0, _functools.deprecated)({ this.hasNoContent = (0, _helpers.statusesContainStatus)(noContent, this.status); this.headers = Object.freeze(new _headers.default(p.headers)); if (Object.isFunction(body)) { - this.body = body.once(); + this.body = body.memoize(); this.body[Symbol.asyncIterator] = body[Symbol.asyncIterator].bind(body); } else { this.body = body; @@ -369,7 +369,7 @@ let Response = (_dec = (0, _functools.deprecated)({ } arrayBuffer() { return this.readBody().then(body => { - if (body == null) { + if (body == null || body === '') { return new ArrayBuffer(0); } if (body instanceof ArrayBuffer) { @@ -385,7 +385,7 @@ let Response = (_dec = (0, _functools.deprecated)({ this.parent?.abort(); this.emitter.removeAllListeners(); if (Object.isFunction(this.body)) { - this.body.cancelOnce(); + this.body.cancelMemoize(); Object.defineProperty(this.body, Symbol.asyncIterator, { configurable: true, enumerable: false, @@ -403,7 +403,7 @@ let Response = (_dec = (0, _functools.deprecated)({ Object.delete(this, 'jsonReviver'); Object.delete(this, 'parent'); ['json', 'formData', 'document', 'text', 'blob', 'arrayBuffer', 'decode'].forEach(key => { - this[key].cancelOnce(); + this[key].cancelMemoize(); Object.defineProperty(this, key, { configurable: true, enumerable: false, @@ -412,7 +412,7 @@ let Response = (_dec = (0, _functools.deprecated)({ }); }); ['jsonStream', 'textStream', 'stream', 'decodeStream', Symbol.asyncIterator].forEach(key => { - this[key].cancelOnce(); + this[key].cancelMemoize(); Object.defineProperty(this, key, { configurable: true, enumerable: false, @@ -553,7 +553,7 @@ let Response = (_dec = (0, _functools.deprecated)({ }, this.parent); }); } -}, ((0, _applyDecoratedDescriptor2.default)(_class.prototype, "getHeader", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "getHeader"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "json", [_functools.once], Object.getOwnPropertyDescriptor(_class.prototype, "json"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "jsonStream", [_functools.once], Object.getOwnPropertyDescriptor(_class.prototype, "jsonStream"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "formData", [_functools.once], Object.getOwnPropertyDescriptor(_class.prototype, "formData"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "document", [_functools.once], Object.getOwnPropertyDescriptor(_class.prototype, "document"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "text", [_functools.once], Object.getOwnPropertyDescriptor(_class.prototype, "text"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "textStream", [_functools.once], Object.getOwnPropertyDescriptor(_class.prototype, "textStream"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "stream", [_functools.once], Object.getOwnPropertyDescriptor(_class.prototype, "stream"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "blob", [_functools.once], Object.getOwnPropertyDescriptor(_class.prototype, "blob"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "arrayBuffer", [_functools.once], Object.getOwnPropertyDescriptor(_class.prototype, "arrayBuffer"), _class.prototype)), _class)); +}, ((0, _applyDecoratedDescriptor2.default)(_class.prototype, "getHeader", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "getHeader"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "json", [_functools.memoize], Object.getOwnPropertyDescriptor(_class.prototype, "json"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "jsonStream", [_functools.memoize], Object.getOwnPropertyDescriptor(_class.prototype, "jsonStream"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "formData", [_functools.memoize], Object.getOwnPropertyDescriptor(_class.prototype, "formData"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "document", [_functools.memoize], Object.getOwnPropertyDescriptor(_class.prototype, "document"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "text", [_functools.memoize], Object.getOwnPropertyDescriptor(_class.prototype, "text"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "textStream", [_functools.memoize], Object.getOwnPropertyDescriptor(_class.prototype, "textStream"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "stream", [_functools.memoize], Object.getOwnPropertyDescriptor(_class.prototype, "stream"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "blob", [_functools.memoize], Object.getOwnPropertyDescriptor(_class.prototype, "blob"), _class.prototype), (0, _applyDecoratedDescriptor2.default)(_class.prototype, "arrayBuffer", [_functools.memoize], Object.getOwnPropertyDescriptor(_class.prototype, "arrayBuffer"), _class.prototype)), _class)); exports.default = Response; function fastClone(data) { return () => Object.fastClone(data, { diff --git a/lib/core/request/response/test/main.spec.d.ts b/lib/core/request/response/test/main.spec.d.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/lib/core/request/response/test/main.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/lib/core/request/response/test/main.spec.js b/lib/core/request/response/test/main.spec.js new file mode 100644 index 000000000..6faa025b1 --- /dev/null +++ b/lib/core/request/response/test/main.spec.js @@ -0,0 +1,16 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +var _request = require("../../../../core/request"); +var _headers = _interopRequireDefault(require("../../../../core/request/headers")); +describe('core/request/response', () => { + test(['should successfully handle a request with the Content-Type: application/octet-stream header', 'and an empty response body'].join(' '), async () => { + const response = new _request.Response(Promise.resolve(''), { + url: 'url/url', + headers: new _headers.default({ + 'Content-Type': 'application/octet-stream' + }) + }); + await expect(response.decode()).resolves.toBeInstanceOf(ArrayBuffer); + }); +}); \ No newline at end of file diff --git a/lib/lang/interface.d.ts b/lib/lang/interface.d.ts index 421a793d8..1581c846d 100644 --- a/lib/lang/interface.d.ts +++ b/lib/lang/interface.d.ts @@ -9,6 +9,13 @@ export declare type LangPacs = { [key in Language]?: KeysetTranslations; }; export declare type Translation = string | PluralTranslation; -export declare type PluralTranslation = [one: string, some: string, many: string, none: string]; +export interface PluralTranslation { + one: string; + two?: string; + few?: string; + many?: string; + zero?: string; + other?: string; +} export declare type Translations = Dictionary; export declare type KeysetTranslations = Dictionary; diff --git a/ts-definitions/prelude/function/proto.d.ts b/ts-definitions/prelude/function/proto.d.ts index 34c25a6fe..1150e30a1 100644 --- a/ts-definitions/prelude/function/proto.d.ts +++ b/ts-definitions/prelude/function/proto.d.ts @@ -12,12 +12,12 @@ interface Function { /** * Returns a new function that allows to invoke the target function only once */ - once(this: T): T; + memoize(this: T): T; /** * Cancels the memoization of the function result */ - cancelOnce(this: Function): void; + cancelMemoize(this: Function): void; /** * Returns a new function that allows to invoke the target function only with the specified delay. diff --git a/ts-definitions/prelude/function/static.d.ts b/ts-definitions/prelude/function/static.d.ts index 95270afdb..6180f706d 100644 --- a/ts-definitions/prelude/function/static.d.ts +++ b/ts-definitions/prelude/function/static.d.ts @@ -27,12 +27,12 @@ interface FunctionConstructor { * Returns a new function that allows to invoke the specified function only once * @param fn */ - once(fn: T): T; + memoize(fn: T): T; /** * Cancels the memoization of the function result */ - cancelOnce(this: Function): void; + cancelMemoize(this: Function): void; /** * Returns a new function that allows to invoke a function, which it takes, only with the specified delay.