Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into liam/uuid-v7
Browse files Browse the repository at this point in the history
  • Loading branch information
Liam-Tait committed Sep 3, 2024
2 parents 4875b4a + 890958c commit 1a9ca8c
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 129 deletions.
47 changes: 21 additions & 26 deletions async/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,6 @@ export interface RetryOptions {
jitter?: number;
}

const defaultRetryOptions: Required<RetryOptions> = {
multiplier: 2,
maxTimeout: 60000,
maxAttempts: 5,
minTimeout: 1000,
jitter: 1,
};

/**
* Calls the given (possibly asynchronous) function up to `maxAttempts` times.
* Retries as long as the given function throws. If the attempts are exhausted,
Expand Down Expand Up @@ -122,31 +114,34 @@ const defaultRetryOptions: Required<RetryOptions> = {
*
* @typeParam T The return type of the function to retry and returned promise.
* @param fn The function to retry.
* @param opts Additional options.
* @param options Additional options.
* @returns The promise that resolves with the value returned by the function to retry.
*/
export async function retry<T>(
fn: (() => Promise<T>) | (() => T),
opts?: RetryOptions,
options?: RetryOptions,
): Promise<T> {
const options: Required<RetryOptions> = {
...defaultRetryOptions,
...opts,
};
const {
multiplier = 2,
maxTimeout = 60000,
maxAttempts = 5,
minTimeout = 1000,
jitter = 1,
} = options ?? {};

if (options.maxTimeout <= 0) {
if (maxTimeout <= 0) {
throw new TypeError(
`Cannot retry as 'maxTimeout' must be positive: current value is ${options.maxTimeout}`,
`Cannot retry as 'maxTimeout' must be positive: current value is ${maxTimeout}`,
);
}
if (options.minTimeout > options.maxTimeout) {
if (minTimeout > maxTimeout) {
throw new TypeError(
`Cannot retry as 'minTimeout' must be <= 'maxTimeout': current values 'minTimeout=${options.minTimeout}', 'maxTimeout=${options.maxTimeout}'`,
`Cannot retry as 'minTimeout' must be <= 'maxTimeout': current values 'minTimeout=${minTimeout}', 'maxTimeout=${maxTimeout}'`,
);
}
if (options.jitter > 1) {
if (jitter > 1) {
throw new TypeError(
`Cannot retry as 'jitter' must be <= 1: current value is ${options.jitter}`,
`Cannot retry as 'jitter' must be <= 1: current value is ${jitter}`,
);
}

Expand All @@ -155,16 +150,16 @@ export async function retry<T>(
try {
return await fn();
} catch (error) {
if (attempt + 1 >= options.maxAttempts) {
throw new RetryError(error, options.maxAttempts);
if (attempt + 1 >= maxAttempts) {
throw new RetryError(error, maxAttempts);
}

const timeout = exponentialBackoffWithJitter(
options.maxTimeout,
options.minTimeout,
maxTimeout,
minTimeout,
attempt,
options.multiplier,
options.jitter,
multiplier,
jitter,
);
await new Promise((r) => setTimeout(r, timeout));
}
Expand Down
12 changes: 11 additions & 1 deletion async/retry_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Deno.test("retry()", async () => {
assertEquals(result, 3);
});

Deno.test("retry() fails after max errors is passed", async () => {
Deno.test("retry() fails after five errors by default", async () => {
const fiveErrors = generateErroringFunction(5);
await assertRejects(() =>
retry(fiveErrors, {
Expand All @@ -32,6 +32,16 @@ Deno.test("retry() fails after max errors is passed", async () => {
);
});

Deno.test("retry() fails after five errors when undefined is passed", async () => {
const fiveErrors = generateErroringFunction(5);
await assertRejects(() =>
retry(fiveErrors, {
maxAttempts: undefined,
minTimeout: 100,
})
);
});

Deno.test("retry() waits four times by default", async () => {
let callCount = 0;
const onlyErrors = function () {
Expand Down
5 changes: 0 additions & 5 deletions csv/_io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,6 @@ export interface ReadOptions {
fieldsPerRecord?: number;
}

export const defaultReadOptions: ReadOptions = {
separator: ",",
trimLeadingSpace: false,
};

export interface LineReader {
readLine(): Promise<string | null>;
isEOF(): boolean;
Expand Down
4 changes: 2 additions & 2 deletions csv/parse_stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import {
convertRowToObject,
defaultReadOptions,
type LineReader,
parseRecord,
type ParseResult,
Expand Down Expand Up @@ -370,8 +369,9 @@ export class CsvParseStream<
*/
constructor(options?: T) {
this.#options = {
...defaultReadOptions,
...options,
separator: options?.separator ?? ",",
trimLeadingSpace: options?.trimLeadingSpace ?? false,
};

if (
Expand Down
7 changes: 2 additions & 5 deletions csv/parse_stream_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,9 @@ Deno.test({
},
{
name: "Separator is undefined",
input: "a;b;c\n",
input: "a,b,c\n",
output: [["a", "b", "c"]],
separator: undefined,
error: {
klass: TypeError,
msg: "Cannot parse record: separator is required",
},
},
{
name: "MultiLine",
Expand Down
19 changes: 10 additions & 9 deletions fmt/duration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,17 +118,18 @@ export interface FormatOptions {
*/
export function format(
ms: number,
options: FormatOptions = {},
options?: FormatOptions,
): string {
const opt = Object.assign(
{ style: "narrow", ignoreZero: false },
options,
);
const {
style = "narrow",
ignoreZero = false,
} = options ?? {};

const duration = millisecondsToDurationObject(ms);
const durationArr = durationArray(duration);
switch (opt.style) {
switch (style) {
case "narrow": {
if (opt.ignoreZero) {
if (ignoreZero) {
return `${
durationArr.filter((x) => x.value).map((x) =>
`${x.value}${x.type === "us" ? "µs" : x.type}`
Expand All @@ -142,7 +143,7 @@ export function format(
}`;
}
case "full": {
if (opt.ignoreZero) {
if (ignoreZero) {
return `${
durationArr.filter((x) => x.value).map((x) =>
`${x.value} ${keyList[x.type]}`
Expand All @@ -159,7 +160,7 @@ export function format(
? addZero(x.value, 3)
: addZero(x.value, 2)
);
if (opt.ignoreZero) {
if (ignoreZero) {
let cont = true;
while (cont) {
if (!Number(arr[arr.length - 1])) arr.pop();
Expand Down
25 changes: 11 additions & 14 deletions fmt/duration_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assertEquals, assertExists, assertThrows } from "@std/assert";
import { assertEquals, assertExists } from "@std/assert";
import { format } from "./duration.ts";

Deno.test({
Expand All @@ -16,6 +16,16 @@ Deno.test({
},
});

Deno.test({
name: "format() handles default style (narrow)",
fn() {
assertEquals(
format(99674, { style: undefined }),
"0d 0h 1m 39s 674ms 0µs 0ns",
);
},
});

Deno.test({
name: "format() handles full duration",
fn() {
Expand Down Expand Up @@ -79,16 +89,3 @@ Deno.test({
assertEquals(format(16.342, { ignoreZero: true }), "16ms 342µs");
},
});

Deno.test({
name: "format() handles default style error",
fn() {
assertThrows(
() => {
format(16.342, { style: undefined });
},
TypeError,
`style must be "narrow", "full", or "digital"!`,
);
},
});
18 changes: 15 additions & 3 deletions yaml/_type/bool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,20 @@ export const bool: Type<"scalar", boolean> = {
construct: (data: string): boolean => YAML_TRUE_BOOLEANS.includes(data),
resolve: (data: string): boolean => YAML_BOOLEANS.includes(data),
represent: {
lowercase: (object: boolean): string => object ? "true" : "false",
uppercase: (object: boolean): string => object ? "TRUE" : "FALSE",
camelcase: (object: boolean): string => object ? "True" : "False",
// deno-lint-ignore ban-types
lowercase: (object: boolean | Boolean): string => {
const value = object instanceof Boolean ? object.valueOf() : object;
return value ? "true" : "false";
},
// deno-lint-ignore ban-types
uppercase: (object: boolean | Boolean): string => {
const value = object instanceof Boolean ? object.valueOf() : object;
return value ? "TRUE" : "FALSE";
},
// deno-lint-ignore ban-types
camelcase: (object: boolean | Boolean): string => {
const value = object instanceof Boolean ? object.valueOf() : object;
return value ? "True" : "False";
},
},
};
18 changes: 12 additions & 6 deletions yaml/_type/float.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,13 @@ function constructYamlFloat(data: string): number {

const SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/;

function representYamlFloat(object: number, style?: StyleVariant): string {
if (isNaN(object)) {
function representYamlFloat(
// deno-lint-ignore ban-types
object: number | Number,
style?: StyleVariant,
): string {
const value = object instanceof Number ? object.valueOf() : object;
if (isNaN(value)) {
switch (style) {
case "lowercase":
return ".nan";
Expand All @@ -60,7 +65,7 @@ function representYamlFloat(object: number, style?: StyleVariant): string {
case "camelcase":
return ".NaN";
}
} else if (Number.POSITIVE_INFINITY === object) {
} else if (Number.POSITIVE_INFINITY === value) {
switch (style) {
case "lowercase":
return ".inf";
Expand All @@ -69,7 +74,7 @@ function representYamlFloat(object: number, style?: StyleVariant): string {
case "camelcase":
return ".Inf";
}
} else if (Number.NEGATIVE_INFINITY === object) {
} else if (Number.NEGATIVE_INFINITY === value) {
switch (style) {
case "lowercase":
return "-.inf";
Expand All @@ -78,11 +83,11 @@ function representYamlFloat(object: number, style?: StyleVariant): string {
case "camelcase":
return "-.Inf";
}
} else if (isNegativeZero(object)) {
} else if (isNegativeZero(value)) {
return "-0.0";
}

const res = object.toString(10);
const res = value.toString(10);

// JS stringifier can build scientific format without dots: 5e-100,
// while YAML requires dot: 5.e-100. Fix it with simple hack
Expand All @@ -91,6 +96,7 @@ function representYamlFloat(object: number, style?: StyleVariant): string {
}

function isFloat(object: unknown): object is number {
if (object instanceof Number) object = object.valueOf();
return typeof object === "number" &&
(object % 1 !== 0 || isNegativeZero(object));
}
Expand Down
35 changes: 23 additions & 12 deletions yaml/_type/int.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ function constructYamlInteger(data: string): number {
}

function isInteger(object: unknown): object is number {
if (object instanceof Number) object = object.valueOf();
return typeof object === "number" && object % 1 === 0 &&
!isNegativeZero(object);
}
Expand All @@ -142,21 +143,31 @@ export const int: Type<"scalar", number> = {
kind: "scalar",
predicate: isInteger,
represent: {
binary(obj: number): string {
return obj >= 0
? `0b${obj.toString(2)}`
: `-0b${obj.toString(2).slice(1)}`;
// deno-lint-ignore ban-types
binary(object: number | Number): string {
const value = object instanceof Number ? object.valueOf() : object;
return value >= 0
? `0b${value.toString(2)}`
: `-0b${value.toString(2).slice(1)}`;
},
octal(obj: number): string {
return obj >= 0 ? `0${obj.toString(8)}` : `-0${obj.toString(8).slice(1)}`;
// deno-lint-ignore ban-types
octal(object: number | Number): string {
const value = object instanceof Number ? object.valueOf() : object;
return value >= 0
? `0${value.toString(8)}`
: `-0${value.toString(8).slice(1)}`;
},
decimal(obj: number): string {
return obj.toString(10);
// deno-lint-ignore ban-types
decimal(object: number | Number): string {
const value = object instanceof Number ? object.valueOf() : object;
return value.toString(10);
},
hexadecimal(obj: number): string {
return obj >= 0
? `0x${obj.toString(16).toUpperCase()}`
: `-0x${obj.toString(16).toUpperCase().slice(1)}`;
// deno-lint-ignore ban-types
hexadecimal(object: number | Number): string {
const value = object instanceof Number ? object.valueOf() : object;
return value >= 0
? `0x${value.toString(16).toUpperCase()}`
: `-0x${value.toString(16).toUpperCase().slice(1)}`;
},
},
resolve: resolveYamlInteger,
Expand Down
Loading

0 comments on commit 1a9ca8c

Please sign in to comment.