diff --git a/npm/.npmignore b/npm/.npmignore new file mode 100644 index 0000000..73c86de --- /dev/null +++ b/npm/.npmignore @@ -0,0 +1,78 @@ +src/ +esm/test/kyber.test.js +esm/test/kyber.test.d.ts +esm/test/utils.js +esm/test/utils.d.ts +esm/deps/deno.land/std@0.213.0/testing/_test_suite.js +esm/deps/deno.land/std@0.213.0/testing/_test_suite.d.ts +esm/deps/deno.land/std@0.213.0/testing/bdd.js +esm/deps/deno.land/std@0.213.0/testing/bdd.d.ts +esm/deps/deno.land/std@0.216.0/assert/_constants.js +esm/deps/deno.land/std@0.216.0/assert/_constants.d.ts +esm/deps/deno.land/std@0.216.0/assert/_diff.js +esm/deps/deno.land/std@0.216.0/assert/_diff.d.ts +esm/deps/deno.land/std@0.216.0/assert/_format.js +esm/deps/deno.land/std@0.216.0/assert/_format.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert.js +esm/deps/deno.land/std@0.216.0/assert/assert.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_almost_equals.js +esm/deps/deno.land/std@0.216.0/assert/assert_almost_equals.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_array_includes.js +esm/deps/deno.land/std@0.216.0/assert/assert_array_includes.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_equals.js +esm/deps/deno.land/std@0.216.0/assert/assert_equals.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_exists.js +esm/deps/deno.land/std@0.216.0/assert/assert_exists.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_false.js +esm/deps/deno.land/std@0.216.0/assert/assert_false.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_greater.js +esm/deps/deno.land/std@0.216.0/assert/assert_greater.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_greater_or_equal.js +esm/deps/deno.land/std@0.216.0/assert/assert_greater_or_equal.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_instance_of.js +esm/deps/deno.land/std@0.216.0/assert/assert_instance_of.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_is_error.js +esm/deps/deno.land/std@0.216.0/assert/assert_is_error.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_less.js +esm/deps/deno.land/std@0.216.0/assert/assert_less.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_less_or_equal.js +esm/deps/deno.land/std@0.216.0/assert/assert_less_or_equal.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_match.js +esm/deps/deno.land/std@0.216.0/assert/assert_match.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_not_equals.js +esm/deps/deno.land/std@0.216.0/assert/assert_not_equals.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_not_instance_of.js +esm/deps/deno.land/std@0.216.0/assert/assert_not_instance_of.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_not_match.js +esm/deps/deno.land/std@0.216.0/assert/assert_not_match.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_not_strict_equals.js +esm/deps/deno.land/std@0.216.0/assert/assert_not_strict_equals.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_object_match.js +esm/deps/deno.land/std@0.216.0/assert/assert_object_match.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_rejects.js +esm/deps/deno.land/std@0.216.0/assert/assert_rejects.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_strict_equals.js +esm/deps/deno.land/std@0.216.0/assert/assert_strict_equals.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_string_includes.js +esm/deps/deno.land/std@0.216.0/assert/assert_string_includes.d.ts +esm/deps/deno.land/std@0.216.0/assert/assert_throws.js +esm/deps/deno.land/std@0.216.0/assert/assert_throws.d.ts +esm/deps/deno.land/std@0.216.0/assert/assertion_error.js +esm/deps/deno.land/std@0.216.0/assert/assertion_error.d.ts +esm/deps/deno.land/std@0.216.0/assert/equal.js +esm/deps/deno.land/std@0.216.0/assert/equal.d.ts +esm/deps/deno.land/std@0.216.0/assert/fail.js +esm/deps/deno.land/std@0.216.0/assert/fail.d.ts +esm/deps/deno.land/std@0.216.0/assert/mod.js +esm/deps/deno.land/std@0.216.0/assert/mod.d.ts +esm/deps/deno.land/std@0.216.0/assert/unimplemented.js +esm/deps/deno.land/std@0.216.0/assert/unimplemented.d.ts +esm/deps/deno.land/std@0.216.0/assert/unreachable.js +esm/deps/deno.land/std@0.216.0/assert/unreachable.d.ts +esm/deps/deno.land/std@0.216.0/fmt/colors.js +esm/deps/deno.land/std@0.216.0/fmt/colors.d.ts +esm/_dnt.test_shims.js +esm/_dnt.test_shims.d.ts +test_runner.js +yarn.lock +pnpm-lock.yaml diff --git a/npm/LICENSE b/npm/LICENSE new file mode 100644 index 0000000..3d8da59 --- /dev/null +++ b/npm/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Ajitomi Daisuke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 0000000..884ec60 --- /dev/null +++ b/npm/README.md @@ -0,0 +1,246 @@ +

crystals-kyber-js

+ +
+ +[![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/crystals_kyber/mod.ts) +![Browser CI](https://github.com/dajiaji/crystals-kyber-js/actions/workflows/ci_browsers.yml/badge.svg) +![Node.js CI](https://github.com/dajiaji/crystals-kyber-js/actions/workflows/ci_node.yml/badge.svg) +![Deno CI](https://github.com/dajiaji/crystals-kyber-js/actions/workflows/ci_deno.yml/badge.svg) +![Cloudflare Workers CI](https://github.com/dajiaji/crystals-kyber-js/actions/workflows/ci_cloudflare.yml/badge.svg) +![Bun CI](https://github.com/dajiaji/crystals-kyber-js/actions/workflows/ci_bun.yml/badge.svg) +[![codecov](https://codecov.io/gh/dajiaji/crystals-kyber-js/branch/main/graph/badge.svg?token=7I7JGKDDJ2)](https://codecov.io/gh/dajiaji/crystals-kyber-js) + +
+ +
+A CRYSTALS-KYBER implementation written in TypeScript for various JavaScript runtimes.
+
+

+ +
+ +[Documentation for main](https://dajiaji.github.io/crystals-kyber-js/docs/main/) + +
+ +This module is based on +ntontutoveanu/crystals-kyber-javascript, +but includes the following improvements:
+ +- ✅ Available on various JavaScript runtimes: Browsers, Node.js, Deno, + Cloudflare Workers, etc.
+- ✅ Written in TypeScript.
+- ✅ Deterministic key generation support.
+- ✅ Constant-time validation for ciphertext.
+- ✅ Better performance: 1.4 to 1.8 times faster than the original + implementation.
+- ✅ Tree-shaking friendly.
+ +For Node.js, you can install `crystals-kyber-js` via npm/yarn: + +```sh +npm install crystals-kyber-js +``` + +Then, you can use it as follows: + +```js +import { MlKem768 } from "crystals-kyber-js"; + +async function doKyber() { + // A recipient generates a key pair. + const recipient = new MlKem768(); // MlKem512 and MlKem1024 are also available. + const [pkR, skR] = await recipient.generateKeyPair(); + //// Deterministic key generation is also supported + // const seed = new Uint8Array(64); + // globalThis.crypto.getRandomValues(seed); // node >= 19 + // const [pkR, skR] = await recipient.deriveKeyPair(seed); + + // A sender generates a ciphertext and a shared secret with pkR. + const sender = new MlKem768(); + const [ct, ssS] = await sender.encap(pkR); + + // The recipient decapsulates the ciphertext and generates the same shared secret with skR. + const ssR = await recipient.decap(ct, skR); + + // ssS === ssR + return; +} + +try { + doKyber(); +} catch (err) { + console.log("failed: ", err.message); +} +``` + +## Index + +- [Installation](#installation) + - [Node.js](#nodejs) + - [Deno](#deno) + - [Web Browsers](#web-browsers) + - [Cloudflare Workers](#cloudflare-workers) +- [Usage](#usage) +- [Contributing](#contributing) + +## Installation + +### Node.js + +Using npm: + +```sh +npm install crystals-kyber-js +``` + +Using yarn: + +```sh +yarn add crystals-kyber-js +``` + +### Deno + +Using deno.land: + +```js +// use a specific version +import { MlKem768 } from "https://deno.land/x/crystals_kyber@1.1.1/mod.ts"; + +// use the latest stable version +import { MlKem768 } from "https://deno.land/x/crystals_kyber/mod.ts"; +``` + +### Web Browsers + +Followings are how to use this module with typical CDNs. Other CDNs can be used +as well. + +Using esm.sh: + +```html + + + + + +``` + +Using unpkg: + +```html + + +``` + +### Cloudflare Workers + +```sh +git clone git@github.com:dajiaji/crystals-kyber-js.git +cd crystals-kyber-js +npm install -g esbuild +deno task dnt +deno task minify > $YOUR_SRC_PATH/crystals-kyber.js +``` + +## Usage + +This section shows some typical usage examples. + +### Node.js + +```js +import { MlKem768 } from "crystals-kyber-js"; +// const { MlKem768 } = require("crystals-kyber-js"); + +async function doKyber() { + const recipient = new MlKem768(); + const [pkR, skR] = await recipient.generateKeyPair(); + + const sender = new MlKem768(); + const [ct, ssS] = await sender.encap(pkR); + + const ssR = await recipient.decap(ct, skR); + + // ssS === ssR + return; +} + +try { + doKyber(); +} catch (err) { + console.log("failed: ", err.message); +} +``` + +### Deno + +```js +import { MlKem512 } from "https://deno.land/x/crystals_kyber@1.1.1/mod.ts"; + +async function doKyber() { + + const recipient = new MlKem512(); + const [pkR, skR] = await recipient.generateKeyPair(); + + const sender = new MlKem512(); + const [ct, ssS] = await sender.encap(pkR); + + const ssR = await recipient.decap(ct, skR); + + // ssS === ssR + return; +} + +try { + doKyber(); +} catch (_err: unknown) { + console.log("failed."); +} +``` + +### Browsers + +```html + + + + + + + +``` + +## Contributing + +We welcome all kind of contributions, filing issues, suggesting new features or +sending PRs. diff --git a/npm/esm/_dnt.shims.d.ts b/npm/esm/_dnt.shims.d.ts new file mode 100644 index 0000000..cefb7f5 --- /dev/null +++ b/npm/esm/_dnt.shims.d.ts @@ -0,0 +1 @@ +export declare const dntGlobalThis: Omit; diff --git a/npm/esm/_dnt.shims.js b/npm/esm/_dnt.shims.js new file mode 100644 index 0000000..bfdccc4 --- /dev/null +++ b/npm/esm/_dnt.shims.js @@ -0,0 +1,57 @@ +const dntGlobals = {}; +export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals); +function createMergeProxy(baseObj, extObj) { + return new Proxy(baseObj, { + get(_target, prop, _receiver) { + if (prop in extObj) { + return extObj[prop]; + } + else { + return baseObj[prop]; + } + }, + set(_target, prop, value) { + if (prop in extObj) { + delete extObj[prop]; + } + baseObj[prop] = value; + return true; + }, + deleteProperty(_target, prop) { + let success = false; + if (prop in extObj) { + delete extObj[prop]; + success = true; + } + if (prop in baseObj) { + delete baseObj[prop]; + success = true; + } + return success; + }, + ownKeys(_target) { + const baseKeys = Reflect.ownKeys(baseObj); + const extKeys = Reflect.ownKeys(extObj); + const extKeysSet = new Set(extKeys); + return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys]; + }, + defineProperty(_target, prop, desc) { + if (prop in extObj) { + delete extObj[prop]; + } + Reflect.defineProperty(baseObj, prop, desc); + return true; + }, + getOwnPropertyDescriptor(_target, prop) { + if (prop in extObj) { + return Reflect.getOwnPropertyDescriptor(extObj, prop); + } + else { + return Reflect.getOwnPropertyDescriptor(baseObj, prop); + } + }, + has(_target, prop) { + return prop in extObj || prop in baseObj; + }, + }); +} diff --git a/npm/esm/_dnt.test_shims.d.ts b/npm/esm/_dnt.test_shims.d.ts new file mode 100644 index 0000000..2b1b022 --- /dev/null +++ b/npm/esm/_dnt.test_shims.d.ts @@ -0,0 +1,5 @@ +import { Deno } from "@deno/shim-deno"; +export { Deno } from "@deno/shim-deno"; +export declare const dntGlobalThis: Omit & { + Deno: typeof Deno; +}; diff --git a/npm/esm/_dnt.test_shims.js b/npm/esm/_dnt.test_shims.js new file mode 100644 index 0000000..aacb619 --- /dev/null +++ b/npm/esm/_dnt.test_shims.js @@ -0,0 +1,61 @@ +import { Deno } from "@deno/shim-deno"; +export { Deno } from "@deno/shim-deno"; +const dntGlobals = { + Deno, +}; +export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals); +function createMergeProxy(baseObj, extObj) { + return new Proxy(baseObj, { + get(_target, prop, _receiver) { + if (prop in extObj) { + return extObj[prop]; + } + else { + return baseObj[prop]; + } + }, + set(_target, prop, value) { + if (prop in extObj) { + delete extObj[prop]; + } + baseObj[prop] = value; + return true; + }, + deleteProperty(_target, prop) { + let success = false; + if (prop in extObj) { + delete extObj[prop]; + success = true; + } + if (prop in baseObj) { + delete baseObj[prop]; + success = true; + } + return success; + }, + ownKeys(_target) { + const baseKeys = Reflect.ownKeys(baseObj); + const extKeys = Reflect.ownKeys(extObj); + const extKeysSet = new Set(extKeys); + return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys]; + }, + defineProperty(_target, prop, desc) { + if (prop in extObj) { + delete extObj[prop]; + } + Reflect.defineProperty(baseObj, prop, desc); + return true; + }, + getOwnPropertyDescriptor(_target, prop) { + if (prop in extObj) { + return Reflect.getOwnPropertyDescriptor(extObj, prop); + } + else { + return Reflect.getOwnPropertyDescriptor(baseObj, prop); + } + }, + has(_target, prop) { + return prop in extObj || prop in baseObj; + }, + }); +} diff --git a/npm/esm/deps/deno.land/std@0.213.0/testing/_test_suite.d.ts b/npm/esm/deps/deno.land/std@0.213.0/testing/_test_suite.d.ts new file mode 100644 index 0000000..98ab128 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.213.0/testing/_test_suite.d.ts @@ -0,0 +1,70 @@ +/** The options for creating a test suite with the describe function. */ +import * as dntShim from "../../../../_dnt.test_shims.js"; +export interface DescribeDefinition extends Omit { + fn?: () => void; + /** + * The `describe` function returns a `TestSuite` representing the group of tests. + * If `describe` is called within another `describe` calls `fn`, the suite will default to that parent `describe` calls returned `TestSuite`. + * If `describe` is not called within another `describe` calls `fn`, the suite will default to the `TestSuite` representing the global group of tests. + */ + suite?: TestSuite; + /** Run some shared setup before all of the tests in the suite. */ + beforeAll?: ((this: T) => void | Promise) | ((this: T) => void | Promise)[]; + /** Run some shared teardown after all of the tests in the suite. */ + afterAll?: ((this: T) => void | Promise) | ((this: T) => void | Promise)[]; + /** Run some shared setup before each test in the suite. */ + beforeEach?: ((this: T) => void | Promise) | ((this: T) => void | Promise)[]; + /** Run some shared teardown after each test in the suite. */ + afterEach?: ((this: T) => void | Promise) | ((this: T) => void | Promise)[]; +} +/** The options for creating an individual test case with the it function. */ +export interface ItDefinition extends Omit { + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise; + /** + * The `describe` function returns a `TestSuite` representing the group of tests. + * If `it` is called within a `describe` calls `fn`, the suite will default to that parent `describe` calls returned `TestSuite`. + * If `it` is not called within a `describe` calls `fn`, the suite will default to the `TestSuite` representing the global group of tests. + */ + suite?: TestSuite; +} +/** The names of all the different types of hooks. */ +export type HookNames = "beforeAll" | "afterAll" | "beforeEach" | "afterEach"; +/** + * A group of tests. + */ +export interface TestSuite { + symbol: symbol; +} +/** + * An internal representation of a group of tests. + */ +export declare class TestSuiteInternal implements TestSuite { + symbol: symbol; + protected describe: DescribeDefinition; + protected steps: (TestSuiteInternal | ItDefinition)[]; + protected hasOnlyStep: boolean; + constructor(describe: DescribeDefinition); + /** Stores how many test suites are executing. */ + static runningCount: number; + /** If a test has been registered yet. Block adding global hooks if a test has been registered. */ + static started: boolean; + /** A map of all test suites by symbol. */ + static suites: Map>; + /** The current test suite being registered. */ + static current: TestSuiteInternal | null; + /** The stack of tests that are actively running. */ + static active: symbol[]; + /** This is used internally for testing this module. */ + static reset(): void; + /** This is used internally to register tests. */ + static registerTest(options: dntShim.Deno.TestDefinition): void; + /** Updates all steps within top level suite to have ignore set to true if only is not set to true on step. */ + static addingOnlyStep(suite: TestSuiteInternal): void; + /** This is used internally to add steps to a test suite. */ + static addStep(suite: TestSuiteInternal, step: TestSuiteInternal | ItDefinition): void; + /** This is used internally to add hooks to a test suite. */ + static setHook(suite: TestSuiteInternal, name: HookNames, fn: (this: T) => void | Promise): void; + /** This is used internally to run all steps for a test suite. */ + static run(suite: TestSuiteInternal, context: T, t: dntShim.Deno.TestContext): Promise; + static runTest(t: dntShim.Deno.TestContext, fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise, context: T, activeIndex?: number): Promise; +} diff --git a/npm/esm/deps/deno.land/std@0.213.0/testing/_test_suite.js b/npm/esm/deps/deno.land/std@0.213.0/testing/_test_suite.js new file mode 100644 index 0000000..ab28466 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.213.0/testing/_test_suite.js @@ -0,0 +1,321 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +/** The options for creating a test suite with the describe function. */ +import * as dntShim from "../../../../_dnt.test_shims.js"; +/** Optional test definition keys. */ +const optionalTestDefinitionKeys = [ + "only", + "permissions", + "ignore", + "sanitizeExit", + "sanitizeOps", + "sanitizeResources", +]; +/** Optional test step definition keys. */ +const optionalTestStepDefinitionKeys = [ + "ignore", + "sanitizeExit", + "sanitizeOps", + "sanitizeResources", +]; +/** + * An internal representation of a group of tests. + */ +export class TestSuiteInternal { + constructor(describe) { + Object.defineProperty(this, "symbol", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "describe", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "steps", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "hasOnlyStep", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + this.describe = describe; + this.steps = []; + this.hasOnlyStep = false; + const { suite } = describe; + if (suite && !TestSuiteInternal.suites.has(suite.symbol)) { + throw new Error("suite does not represent a registered test suite"); + } + const testSuite = suite + ? TestSuiteInternal.suites.get(suite.symbol) + : TestSuiteInternal.current; + this.symbol = Symbol(); + TestSuiteInternal.suites.set(this.symbol, this); + const { fn } = describe; + if (fn) { + const temp = TestSuiteInternal.current; + TestSuiteInternal.current = this; + try { + fn(); + } + finally { + TestSuiteInternal.current = temp; + } + } + if (testSuite) { + TestSuiteInternal.addStep(testSuite, this); + } + else { + const { name, ignore, permissions, sanitizeExit, sanitizeOps, sanitizeResources, } = describe; + let { only } = describe; + if (!ignore && this.hasOnlyStep) { + only = true; + } + TestSuiteInternal.registerTest({ + name, + ignore, + only, + permissions, + sanitizeExit, + sanitizeOps, + sanitizeResources, + fn: async (t) => { + TestSuiteInternal.runningCount++; + try { + const context = {}; + const { beforeAll } = this.describe; + if (typeof beforeAll === "function") { + await beforeAll.call(context); + } + else if (beforeAll) { + for (const hook of beforeAll) { + await hook.call(context); + } + } + try { + TestSuiteInternal.active.push(this.symbol); + await TestSuiteInternal.run(this, context, t); + } + finally { + TestSuiteInternal.active.pop(); + const { afterAll } = this.describe; + if (typeof afterAll === "function") { + await afterAll.call(context); + } + else if (afterAll) { + for (const hook of afterAll) { + await hook.call(context); + } + } + } + } + finally { + TestSuiteInternal.runningCount--; + } + }, + }); + } + } + /** This is used internally for testing this module. */ + static reset() { + TestSuiteInternal.runningCount = 0; + TestSuiteInternal.started = false; + TestSuiteInternal.current = null; + TestSuiteInternal.active = []; + } + /** This is used internally to register tests. */ + static registerTest(options) { + options = { ...options }; + optionalTestDefinitionKeys.forEach((key) => { + if (typeof options[key] === "undefined") + delete options[key]; + }); + dntShim.Deno.test(options); + } + /** Updates all steps within top level suite to have ignore set to true if only is not set to true on step. */ + static addingOnlyStep(suite) { + if (!suite.hasOnlyStep) { + for (let i = 0; i < suite.steps.length; i++) { + const step = suite.steps[i]; + if (!(step instanceof TestSuiteInternal) && !step.only) { + suite.steps.splice(i--, 1); + } + } + suite.hasOnlyStep = true; + } + const parentSuite = suite.describe.suite; + const parentTestSuite = parentSuite && + TestSuiteInternal.suites.get(parentSuite.symbol); + if (parentTestSuite) { + TestSuiteInternal.addingOnlyStep(parentTestSuite); + } + } + /** This is used internally to add steps to a test suite. */ + static addStep(suite, step) { + if (!suite.hasOnlyStep) { + if (step instanceof TestSuiteInternal) { + if (step.hasOnlyStep || step.describe.only) { + TestSuiteInternal.addingOnlyStep(suite); + } + } + else { + if (step.only) + TestSuiteInternal.addingOnlyStep(suite); + } + } + if (!(suite.hasOnlyStep && !(step instanceof TestSuiteInternal) && !step.only)) { + suite.steps.push(step); + } + } + /** This is used internally to add hooks to a test suite. */ + static setHook(suite, name, fn) { + if (suite.describe[name]) { + if (typeof suite.describe[name] === "function") { + suite.describe[name] = [ + suite.describe[name], + ]; + } + suite.describe[name].push(fn); + } + else { + suite.describe[name] = fn; + } + } + /** This is used internally to run all steps for a test suite. */ + static async run(suite, context, t) { + const hasOnly = suite.hasOnlyStep || suite.describe.only || false; + for (const step of suite.steps) { + if (hasOnly && step instanceof TestSuiteInternal && + !(step.hasOnlyStep || step.describe.only || false)) { + continue; + } + const { name, fn, ignore, permissions, sanitizeExit, sanitizeOps, sanitizeResources, } = step instanceof TestSuiteInternal ? step.describe : step; + const options = { + name, + ignore, + sanitizeExit, + sanitizeOps, + sanitizeResources, + fn: async (t) => { + if (permissions) { + throw new Error("permissions option not available for nested tests"); + } + context = { ...context }; + if (step instanceof TestSuiteInternal) { + const { beforeAll } = step.describe; + if (typeof beforeAll === "function") { + await beforeAll.call(context); + } + else if (beforeAll) { + for (const hook of beforeAll) { + await hook.call(context); + } + } + try { + TestSuiteInternal.active.push(step.symbol); + await TestSuiteInternal.run(step, context, t); + } + finally { + TestSuiteInternal.active.pop(); + const { afterAll } = step.describe; + if (typeof afterAll === "function") { + await afterAll.call(context); + } + else if (afterAll) { + for (const hook of afterAll) { + await hook.call(context); + } + } + } + } + else { + await TestSuiteInternal.runTest(t, fn, context); + } + }, + }; + optionalTestStepDefinitionKeys.forEach((key) => { + if (typeof options[key] === "undefined") + delete options[key]; + }); + await t.step(options); + } + } + static async runTest(t, fn, context, activeIndex = 0) { + const suite = TestSuiteInternal.active[activeIndex]; + const testSuite = suite && TestSuiteInternal.suites.get(suite); + if (testSuite) { + if (activeIndex === 0) + context = { ...context }; + const { beforeEach } = testSuite.describe; + if (typeof beforeEach === "function") { + await beforeEach.call(context); + } + else if (beforeEach) { + for (const hook of beforeEach) { + await hook.call(context); + } + } + try { + await TestSuiteInternal.runTest(t, fn, context, activeIndex + 1); + } + finally { + const { afterEach } = testSuite.describe; + if (typeof afterEach === "function") { + await afterEach.call(context); + } + else if (afterEach) { + for (const hook of afterEach) { + await hook.call(context); + } + } + } + } + else { + await fn.call(context, t); + } + } +} +/** Stores how many test suites are executing. */ +Object.defineProperty(TestSuiteInternal, "runningCount", { + enumerable: true, + configurable: true, + writable: true, + value: 0 +}); +/** If a test has been registered yet. Block adding global hooks if a test has been registered. */ +Object.defineProperty(TestSuiteInternal, "started", { + enumerable: true, + configurable: true, + writable: true, + value: false +}); +/** A map of all test suites by symbol. */ +// deno-lint-ignore no-explicit-any +Object.defineProperty(TestSuiteInternal, "suites", { + enumerable: true, + configurable: true, + writable: true, + value: new Map() +}); +/** The current test suite being registered. */ +// deno-lint-ignore no-explicit-any +Object.defineProperty(TestSuiteInternal, "current", { + enumerable: true, + configurable: true, + writable: true, + value: null +}); +/** The stack of tests that are actively running. */ +Object.defineProperty(TestSuiteInternal, "active", { + enumerable: true, + configurable: true, + writable: true, + value: [] +}); diff --git a/npm/esm/deps/deno.land/std@0.213.0/testing/bdd.d.ts b/npm/esm/deps/deno.land/std@0.213.0/testing/bdd.d.ts new file mode 100644 index 0000000..606c79a --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.213.0/testing/bdd.d.ts @@ -0,0 +1,524 @@ +/** A [BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) interface + * to `Deno.test()` API. + * + * With the `bdd.ts` module you can write your tests in a familiar format for + * grouping tests and adding setup/teardown hooks used by other JavaScript testing + * frameworks like Jasmine, Jest, and Mocha. + * + * The `describe` function creates a block that groups together several related + * tests. The `it` function registers an individual test case. + * + * ## Hooks + * + * There are 4 types of hooks available for test suites. A test suite can have + * multiples of each type of hook, they will be called in the order that they are + * registered. The `afterEach` and `afterAll` hooks will be called whether or not + * the test case passes. The *All hooks will be called once for the whole group + * while the *Each hooks will be called for each individual test case. + * + * - `beforeAll`: Runs before all of the tests in the test suite. + * - `afterAll`: Runs after all of the tests in the test suite finish. + * - `beforeEach`: Runs before each of the individual test cases in the test suite. + * - `afterEach`: Runs after each of the individual test cases in the test suite. + * + * If a hook is registered at the top level, a global test suite will be registered + * and all tests will belong to it. Hooks registered at the top level must be + * registered before any individual test cases or test suites. + * + * ## Focusing tests + * + * If you would like to run only specific test cases, you can do so by calling + * `it.only` instead of `it`. If you would like to run only specific test suites, + * you can do so by calling `describe.only` instead of `describe`. + * + * There is one limitation to this when using the flat test grouping style. When + * `describe` is called without being nested, it registers the test with + * `Deno.test`. If a child test case or suite is registered with `it.only` or + * `describe.only`, it will be scoped to the top test suite instead of the file. To + * make them the only tests that run in the file, you would need to register the + * top test suite with `describe.only` too. + * + * ## Ignoring tests + * + * If you would like to not run specific individual test cases, you can do so by + * calling `it.ignore` instead of `it`. If you would like to not run specific test + * suites, you can do so by calling `describe.ignore` instead of `describe`. + * + * ## Sanitization options + * + * Like `Deno.TestDefinition`, the `DescribeDefinition` and `ItDefinition` have + * sanitization options. They work in the same way. + * + * - `sanitizeExit`: Ensure the test case does not prematurely cause the process to + * exit, for example via a call to Deno.exit. Defaults to true. + * - `sanitizeOps`: Check that the number of async completed ops after the test is + * the same as number of dispatched ops. Defaults to true. + * - `sanitizeResources`: Ensure the test case does not "leak" resources - ie. the + * resource table after the test has exactly the same contents as before the + * test. Defaults to true. + * + * ## Permissions option + * + * Like `Deno.TestDefinition`, the `DescribeDefinition` and `ItDefinition` have a + * `permissions` option. They specify the permissions that should be used to run an + * individual test case or test suite. Set this to `"inherit"` to keep the calling + * thread's permissions. Set this to `"none"` to revoke all permissions. + * + * This setting defaults to `"inherit"`. + * + * There is currently one limitation to this, you cannot use the permissions option + * on an individual test case or test suite that belongs to another test suite. + * That's because internally those tests are registered with `t.step` which does + * not support the permissions option. + * + * ## Comparing to Deno\.test + * + * The default way of writing tests is using `Deno.test` and `t.step`. The + * `describe` and `it` functions have similar call signatures to `Deno.test`, + * making it easy to switch between the default style and the behavior-driven + * development style of writing tests. Internally, `describe` and `it` are + * registering tests with `Deno.test` and `t.step`. + * + * Below is an example of a test file using `Deno.test` and `t.step`. In the + * following sections there are examples of how the same test could be written with + * `describe` and `it` using nested test grouping, flat test grouping, or a mix of + * both styles. + * + * ```ts + * import { + * assertEquals, + * assertStrictEquals, + * assertThrows, + * } from "https://deno.land/std@$STD_VERSION/assert/mod.ts"; + * + * class User { + * static users: Map = new Map(); + * age?: number; + * + * constructor(public name: string) { + * if (User.users.has(name)) { + * throw new Deno.errors.AlreadyExists(`User ${name} already exists`); + * } + * User.users.set(name, this); + * } + * + * getAge(): number { + * if (!this.age) { + * throw new Error("Age unknown"); + * } + * return this.age; + * } + * + * setAge(age: number) { + * this.age = age; + * } + * } + * + * Deno.test("User.users initially empty", () => { + * assertEquals(User.users.size, 0); + * }); + * + * Deno.test("User constructor", () => { + * try { + * const user = new User("Kyle"); + * assertEquals(user.name, "Kyle"); + * assertStrictEquals(User.users.get("Kyle"), user); + * } finally { + * User.users.clear(); + * } + * }); + * + * Deno.test("User age", async (t) => { + * const user = new User("Kyle"); + * + * await t.step("getAge", () => { + * assertThrows(() => user.getAge(), Error, "Age unknown"); + * user.age = 18; + * assertEquals(user.getAge(), 18); + * }); + * + * await t.step("setAge", () => { + * user.setAge(18); + * assertEquals(user.getAge(), 18); + * }); + * }); + * ``` + * + * ### Nested test grouping + * + * Tests created within the callback of a `describe` function call will belong to + * the new test suite it creates. The hooks can be created within it or be added to + * the options argument for describe. + * + * ```ts + * import { + * assertEquals, + * assertStrictEquals, + * assertThrows, + * } from "https://deno.land/std@$STD_VERSION/assert/mod.ts"; + * import { + * afterEach, + * beforeEach, + * describe, + * it, + * } from "https://deno.land/std@$STD_VERSION/testing/bdd.ts"; + * + * class User { + * static users: Map = new Map(); + * age?: number; + * + * constructor(public name: string) { + * if (User.users.has(name)) { + * throw new Deno.errors.AlreadyExists(`User ${name} already exists`); + * } + * User.users.set(name, this); + * } + * + * getAge(): number { + * if (!this.age) { + * throw new Error("Age unknown"); + * } + * return this.age; + * } + * + * setAge(age: number) { + * this.age = age; + * } + * } + * + * describe("User", () => { + * it("users initially empty", () => { + * assertEquals(User.users.size, 0); + * }); + * + * it("constructor", () => { + * try { + * const user = new User("Kyle"); + * assertEquals(user.name, "Kyle"); + * assertStrictEquals(User.users.get("Kyle"), user); + * } finally { + * User.users.clear(); + * } + * }); + * + * describe("age", () => { + * let user: User; + * + * beforeEach(() => { + * user = new User("Kyle"); + * }); + * + * afterEach(() => { + * User.users.clear(); + * }); + * + * it("getAge", function () { + * assertThrows(() => user.getAge(), Error, "Age unknown"); + * user.age = 18; + * assertEquals(user.getAge(), 18); + * }); + * + * it("setAge", function () { + * user.setAge(18); + * assertEquals(user.getAge(), 18); + * }); + * }); + * }); + * ``` + * + * ### Flat test grouping + * + * The `describe` function returns a unique symbol that can be used to reference + * the test suite for adding tests to it without having to create them within a + * callback. The gives you the ability to have test grouping without any extra + * indentation in front of the grouped tests. + * + * ```ts + * import { + * assertEquals, + * assertStrictEquals, + * assertThrows, + * } from "https://deno.land/std@$STD_VERSION/assert/mod.ts"; + * import { + * describe, + * it, + * } from "https://deno.land/std@$STD_VERSION/testing/bdd.ts"; + * + * class User { + * static users: Map = new Map(); + * age?: number; + * + * constructor(public name: string) { + * if (User.users.has(name)) { + * throw new Deno.errors.AlreadyExists(`User ${name} already exists`); + * } + * User.users.set(name, this); + * } + * + * getAge(): number { + * if (!this.age) { + * throw new Error("Age unknown"); + * } + * return this.age; + * } + * + * setAge(age: number) { + * this.age = age; + * } + * } + * + * const userTests = describe("User"); + * + * it(userTests, "users initially empty", () => { + * assertEquals(User.users.size, 0); + * }); + * + * it(userTests, "constructor", () => { + * try { + * const user = new User("Kyle"); + * assertEquals(user.name, "Kyle"); + * assertStrictEquals(User.users.get("Kyle"), user); + * } finally { + * User.users.clear(); + * } + * }); + * + * const ageTests = describe({ + * name: "age", + * suite: userTests, + * beforeEach(this: { user: User }) { + * this.user = new User("Kyle"); + * }, + * afterEach() { + * User.users.clear(); + * }, + * }); + * + * it(ageTests, "getAge", function () { + * const { user } = this; + * assertThrows(() => user.getAge(), Error, "Age unknown"); + * user.age = 18; + * assertEquals(user.getAge(), 18); + * }); + * + * it(ageTests, "setAge", function () { + * const { user } = this; + * user.setAge(18); + * assertEquals(user.getAge(), 18); + * }); + * ``` + * + * ### Mixed test grouping + * + * Both nested test grouping and flat test grouping can be used together. This can + * be useful if you'd like to create deep groupings without all the extra + * indentation in front of each line. + * + * ```ts + * import { + * assertEquals, + * assertStrictEquals, + * assertThrows, + * } from "https://deno.land/std@$STD_VERSION/assert/mod.ts"; + * import { + * describe, + * it, + * } from "https://deno.land/std@$STD_VERSION/testing/bdd.ts"; + * + * class User { + * static users: Map = new Map(); + * age?: number; + * + * constructor(public name: string) { + * if (User.users.has(name)) { + * throw new Deno.errors.AlreadyExists(`User ${name} already exists`); + * } + * User.users.set(name, this); + * } + * + * getAge(): number { + * if (!this.age) { + * throw new Error("Age unknown"); + * } + * return this.age; + * } + * + * setAge(age: number) { + * this.age = age; + * } + * } + * + * describe("User", () => { + * it("users initially empty", () => { + * assertEquals(User.users.size, 0); + * }); + * + * it("constructor", () => { + * try { + * const user = new User("Kyle"); + * assertEquals(user.name, "Kyle"); + * assertStrictEquals(User.users.get("Kyle"), user); + * } finally { + * User.users.clear(); + * } + * }); + * + * const ageTests = describe({ + * name: "age", + * beforeEach(this: { user: User }) { + * this.user = new User("Kyle"); + * }, + * afterEach() { + * User.users.clear(); + * }, + * }); + * + * it(ageTests, "getAge", function () { + * const { user } = this; + * assertThrows(() => user.getAge(), Error, "Age unknown"); + * user.age = 18; + * assertEquals(user.getAge(), 18); + * }); + * + * it(ageTests, "setAge", function () { + * const { user } = this; + * user.setAge(18); + * assertEquals(user.getAge(), 18); + * }); + * }); + * ``` + * + * @module + */ +import * as dntShim from "../../../../_dnt.test_shims.js"; +import { DescribeDefinition, ItDefinition, TestSuite } from "./_test_suite.js"; +export type { DescribeDefinition, ItDefinition, TestSuite }; +/** The arguments for an ItFunction. */ +export type ItArgs = [options: ItDefinition] | [ + name: string, + options: Omit, "name"> +] | [ + name: string, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise +] | [fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise] | [ + name: string, + options: Omit, "fn" | "name">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise +] | [ + options: Omit, "fn">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise +] | [ + options: Omit, "fn" | "name">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise +] | [ + suite: TestSuite, + name: string, + options: Omit, "name" | "suite"> +] | [ + suite: TestSuite, + name: string, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise +] | [ + suite: TestSuite, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise +] | [ + suite: TestSuite, + name: string, + options: Omit, "fn" | "name" | "suite">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise +] | [ + suite: TestSuite, + options: Omit, "fn" | "suite">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise +] | [ + suite: TestSuite, + options: Omit, "fn" | "name" | "suite">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise +]; +/** Registers an individual test case. */ +export interface it { + (...args: ItArgs): void; + /** Registers an individual test case with only set to true. */ + only(...args: ItArgs): void; + /** Registers an individual test case with ignore set to true. */ + ignore(...args: ItArgs): void; + /** + * Registers an individual test case with ignore set to true. Alias of + * `.ignore()`. + */ + skip(...args: ItArgs): void; +} +/** Registers an individual test case. */ +export declare function it(...args: ItArgs): void; +export declare namespace it { + var only: (...args: ItArgs) => void; + var ignore: (...args: ItArgs) => void; + var skip: (...args: ItArgs) => void; +} +/** Run some shared setup before all of the tests in the suite. */ +export declare function beforeAll(fn: (this: T) => void | Promise): void; +/** Run some shared teardown after all of the tests in the suite. */ +export declare function afterAll(fn: (this: T) => void | Promise): void; +/** Run some shared setup before each test in the suite. */ +export declare function beforeEach(fn: (this: T) => void | Promise): void; +/** Run some shared teardown after each test in the suite. */ +export declare function afterEach(fn: (this: T) => void | Promise): void; +/** The arguments for a DescribeFunction. */ +export type DescribeArgs = [options: DescribeDefinition] | [name: string] | [ + name: string, + options: Omit, "name"> +] | [name: string, fn: () => void] | [fn: () => void] | [ + name: string, + options: Omit, "fn" | "name">, + fn: () => void +] | [ + options: Omit, "fn">, + fn: () => void +] | [ + options: Omit, "fn" | "name">, + fn: () => void +] | [ + suite: TestSuite, + name: string +] | [ + suite: TestSuite, + name: string, + options: Omit, "name" | "suite"> +] | [ + suite: TestSuite, + name: string, + fn: () => void +] | [ + suite: TestSuite, + fn: () => void +] | [ + suite: TestSuite, + name: string, + options: Omit, "fn" | "name" | "suite">, + fn: () => void +] | [ + suite: TestSuite, + options: Omit, "fn" | "suite">, + fn: () => void +] | [ + suite: TestSuite, + options: Omit, "fn" | "name" | "suite">, + fn: () => void +]; +/** Registers a test suite. */ +export interface describe { + (...args: DescribeArgs): TestSuite; + /** Registers a test suite with only set to true. */ + only(...args: DescribeArgs): TestSuite; + /** Registers a test suite with ignore set to true. */ + ignore(...args: DescribeArgs): TestSuite; + /** Registers a test suite with ignore set to true. Alias of `.ignore()`. */ + skip(...args: ItArgs): void; +} +/** Registers a test suite. */ +export declare function describe(...args: DescribeArgs): TestSuite; +export declare namespace describe { + var only: (...args: DescribeArgs) => TestSuite; + var ignore: (...args: DescribeArgs) => TestSuite; + var skip: (...args: DescribeArgs) => TestSuite; +} diff --git a/npm/esm/deps/deno.land/std@0.213.0/testing/bdd.js b/npm/esm/deps/deno.land/std@0.213.0/testing/bdd.js new file mode 100644 index 0000000..d9c0035 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.213.0/testing/bdd.js @@ -0,0 +1,215 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { TestSuiteInternal, } from "./_test_suite.js"; +/** Generates an ItDefinition from ItArgs. */ +function itDefinition(...args) { + let [suiteOptionsOrNameOrFn, optionsOrNameOrFn, optionsOrFn, fn,] = args; + let suite = undefined; + let name; + let options; + if (typeof suiteOptionsOrNameOrFn === "object" && + typeof suiteOptionsOrNameOrFn.symbol === "symbol") { + suite = suiteOptionsOrNameOrFn; + } + else { + fn = optionsOrFn; + optionsOrFn = optionsOrNameOrFn; + optionsOrNameOrFn = suiteOptionsOrNameOrFn; + } + if (typeof optionsOrNameOrFn === "string") { + name = optionsOrNameOrFn; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + options = {}; + } + else { + options = optionsOrFn; + if (!fn) + fn = options.fn; + } + } + else if (typeof optionsOrNameOrFn === "function") { + fn = optionsOrNameOrFn; + name = fn.name; + options = {}; + } + else { + options = optionsOrNameOrFn; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + } + else { + fn = options.fn; + } + name = options.name ?? fn.name; + } + return { + suite, + ...options, + name, + fn, + }; +} +/** Registers an individual test case. */ +export function it(...args) { + if (TestSuiteInternal.runningCount > 0) { + throw new Error("cannot register new test cases after already registered test cases start running"); + } + const options = itDefinition(...args); + const { suite } = options; + const testSuite = suite + ? TestSuiteInternal.suites.get(suite.symbol) + : TestSuiteInternal.current; + if (!TestSuiteInternal.started) + TestSuiteInternal.started = true; + if (testSuite) { + TestSuiteInternal.addStep(testSuite, options); + } + else { + const { name, fn, ignore, only, permissions, sanitizeExit, sanitizeOps, sanitizeResources, } = options; + TestSuiteInternal.registerTest({ + name, + ignore, + only, + permissions, + sanitizeExit, + sanitizeOps, + sanitizeResources, + async fn(t) { + TestSuiteInternal.runningCount++; + try { + await fn.call({}, t); + } + finally { + TestSuiteInternal.runningCount--; + } + }, + }); + } +} +it.only = function itOnly(...args) { + const options = itDefinition(...args); + return it({ + ...options, + only: true, + }); +}; +it.ignore = function itIgnore(...args) { + const options = itDefinition(...args); + return it({ + ...options, + ignore: true, + }); +}; +it.skip = it.ignore; +function addHook(name, fn) { + if (!TestSuiteInternal.current) { + if (TestSuiteInternal.started) { + throw new Error("cannot add global hooks after a global test is registered"); + } + TestSuiteInternal.current = new TestSuiteInternal({ + name: "global", + [name]: fn, + }); + } + else { + TestSuiteInternal.setHook(TestSuiteInternal.current, name, fn); + } +} +/** Run some shared setup before all of the tests in the suite. */ +export function beforeAll(fn) { + addHook("beforeAll", fn); +} +/** Run some shared teardown after all of the tests in the suite. */ +export function afterAll(fn) { + addHook("afterAll", fn); +} +/** Run some shared setup before each test in the suite. */ +export function beforeEach(fn) { + addHook("beforeEach", fn); +} +/** Run some shared teardown after each test in the suite. */ +export function afterEach(fn) { + addHook("afterEach", fn); +} +/** Generates a DescribeDefinition from DescribeArgs. */ +function describeDefinition(...args) { + let [suiteOptionsOrNameOrFn, optionsOrNameOrFn, optionsOrFn, fn,] = args; + let suite = undefined; + let name; + let options; + if (typeof suiteOptionsOrNameOrFn === "object" && + typeof suiteOptionsOrNameOrFn.symbol === "symbol") { + suite = suiteOptionsOrNameOrFn; + } + else { + fn = optionsOrFn; + optionsOrFn = optionsOrNameOrFn; + optionsOrNameOrFn = suiteOptionsOrNameOrFn; + } + if (typeof optionsOrNameOrFn === "string") { + name = optionsOrNameOrFn; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + options = {}; + } + else { + options = optionsOrFn ?? {}; + if (!fn) + fn = options.fn; + } + } + else if (typeof optionsOrNameOrFn === "function") { + fn = optionsOrNameOrFn; + name = fn.name; + options = {}; + } + else { + options = optionsOrNameOrFn ?? {}; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + } + else { + fn = options.fn; + } + name = options.name ?? fn?.name ?? ""; + } + if (!suite) { + suite = options.suite; + } + if (!suite && TestSuiteInternal.current) { + const { symbol } = TestSuiteInternal.current; + suite = { symbol }; + } + return { + ...options, + suite, + name, + fn, + }; +} +/** Registers a test suite. */ +export function describe(...args) { + if (TestSuiteInternal.runningCount > 0) { + throw new Error("cannot register new test suites after already registered test cases start running"); + } + const options = describeDefinition(...args); + if (!TestSuiteInternal.started) + TestSuiteInternal.started = true; + const { symbol } = new TestSuiteInternal(options); + return { symbol }; +} +describe.only = function describeOnly(...args) { + const options = describeDefinition(...args); + return describe({ + ...options, + only: true, + }); +}; +describe.ignore = function describeIgnore(...args) { + const options = describeDefinition(...args); + return describe({ + ...options, + ignore: true, + }); +}; +describe.skip = describe.ignore; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/_constants.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/_constants.d.ts new file mode 100644 index 0000000..53a0535 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/_constants.d.ts @@ -0,0 +1 @@ +export declare const CAN_NOT_DISPLAY = "[Cannot display]"; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/_constants.js b/npm/esm/deps/deno.land/std@0.216.0/assert/_constants.js new file mode 100644 index 0000000..0cfc1cf --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/_constants.js @@ -0,0 +1,2 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +export const CAN_NOT_DISPLAY = "[Cannot display]"; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/_diff.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/_diff.d.ts new file mode 100644 index 0000000..419fa2e --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/_diff.d.ts @@ -0,0 +1,27 @@ +export declare const DiffType: { + readonly removed: "removed"; + readonly common: "common"; + readonly added: "added"; +}; +export type DiffType = keyof typeof DiffType; +export interface DiffResult { + type: DiffType; + value: T; + details?: Array>; +} +/** + * Renders the differences between the actual and expected values + * @param A Actual value + * @param B Expected value + */ +export declare function diff(A: T[], B: T[]): Array>; +/** + * Renders the differences between the actual and expected strings + * Partially inspired from https://github.com/kpdecker/jsdiff + * @param A Actual string + * @param B Expected string + */ +export declare function diffstr(A: string, B: string): DiffResult[]; +export declare function buildMessage(diffResult: ReadonlyArray>, { stringDiff }?: { + stringDiff?: boolean | undefined; +}): string[]; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/_diff.js b/npm/esm/deps/deno.land/std@0.216.0/assert/_diff.js new file mode 100644 index 0000000..e84ef0a --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/_diff.js @@ -0,0 +1,338 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. +import { bgGreen, bgRed, bold, gray, green, red, white, } from "../fmt/colors.js"; +export const DiffType = { + removed: "removed", + common: "common", + added: "added", +}; +const REMOVED = 1; +const COMMON = 2; +const ADDED = 3; +function createCommon(A, B, reverse) { + const common = []; + if (A.length === 0 || B.length === 0) + return []; + for (let i = 0; i < Math.min(A.length, B.length); i += 1) { + const a = reverse ? A[A.length - i - 1] : A[i]; + const b = reverse ? B[B.length - i - 1] : B[i]; + if (a !== undefined && a === b) { + common.push(a); + } + else { + return common; + } + } + return common; +} +function ensureDefined(item) { + if (item === undefined) { + throw Error("Unexpected missing FarthestPoint"); + } + return item; +} +/** + * Renders the differences between the actual and expected values + * @param A Actual value + * @param B Expected value + */ +export function diff(A, B) { + const prefixCommon = createCommon(A, B); + const suffixCommon = createCommon(A.slice(prefixCommon.length), B.slice(prefixCommon.length), true).reverse(); + A = suffixCommon.length + ? A.slice(prefixCommon.length, -suffixCommon.length) + : A.slice(prefixCommon.length); + B = suffixCommon.length + ? B.slice(prefixCommon.length, -suffixCommon.length) + : B.slice(prefixCommon.length); + const swapped = B.length > A.length; + [A, B] = swapped ? [B, A] : [A, B]; + const M = A.length; + const N = B.length; + if (!M && !N && !suffixCommon.length && !prefixCommon.length) + return []; + if (!N) { + return [ + ...prefixCommon.map((c) => ({ type: DiffType.common, value: c })), + ...A.map((a) => ({ + type: swapped ? DiffType.added : DiffType.removed, + value: a, + })), + ...suffixCommon.map((c) => ({ type: DiffType.common, value: c })), + ]; + } + const offset = N; + const delta = M - N; + const size = M + N + 1; + const fp = Array.from({ length: size }, () => ({ y: -1, id: -1 })); + /** + * INFO: + * This buffer is used to save memory and improve performance. + * The first half is used to save route and last half is used to save diff + * type. + * This is because, when I kept new uint8array area to save type,performance + * worsened. + */ + const routes = new Uint32Array((M * N + size + 1) * 2); + const diffTypesPtrOffset = routes.length / 2; + let ptr = 0; + let p = -1; + function backTrace(A, B, current, swapped) { + const M = A.length; + const N = B.length; + const result = []; + let a = M - 1; + let b = N - 1; + let j = routes[current.id]; + let type = routes[current.id + diffTypesPtrOffset]; + while (true) { + if (!j && !type) + break; + const prev = j; + if (type === REMOVED) { + result.unshift({ + type: swapped ? DiffType.removed : DiffType.added, + value: B[b], + }); + b -= 1; + } + else if (type === ADDED) { + result.unshift({ + type: swapped ? DiffType.added : DiffType.removed, + value: A[a], + }); + a -= 1; + } + else { + result.unshift({ type: DiffType.common, value: A[a] }); + a -= 1; + b -= 1; + } + j = routes[prev]; + type = routes[prev + diffTypesPtrOffset]; + } + return result; + } + function createFP(slide, down, k, M) { + if (slide && slide.y === -1 && down && down.y === -1) { + return { y: 0, id: 0 }; + } + const isAdding = (down?.y === -1) || + k === M || + (slide?.y || 0) > (down?.y || 0) + 1; + if (slide && isAdding) { + const prev = slide.id; + ptr++; + routes[ptr] = prev; + routes[ptr + diffTypesPtrOffset] = ADDED; + return { y: slide.y, id: ptr }; + } + else if (down && !isAdding) { + const prev = down.id; + ptr++; + routes[ptr] = prev; + routes[ptr + diffTypesPtrOffset] = REMOVED; + return { y: down.y + 1, id: ptr }; + } + else { + throw new Error("Unexpected missing FarthestPoint"); + } + } + function snake(k, slide, down, _offset, A, B) { + const M = A.length; + const N = B.length; + if (k < -N || M < k) + return { y: -1, id: -1 }; + const fp = createFP(slide, down, k, M); + while (fp.y + k < M && fp.y < N && A[fp.y + k] === B[fp.y]) { + const prev = fp.id; + ptr++; + fp.id = ptr; + fp.y += 1; + routes[ptr] = prev; + routes[ptr + diffTypesPtrOffset] = COMMON; + } + return fp; + } + let currentFP = ensureDefined(fp[delta + offset]); + while (currentFP && currentFP.y < N) { + p = p + 1; + for (let k = -p; k < delta; ++k) { + fp[k + offset] = snake(k, fp[k - 1 + offset], fp[k + 1 + offset], offset, A, B); + } + for (let k = delta + p; k > delta; --k) { + fp[k + offset] = snake(k, fp[k - 1 + offset], fp[k + 1 + offset], offset, A, B); + } + fp[delta + offset] = snake(delta, fp[delta - 1 + offset], fp[delta + 1 + offset], offset, A, B); + currentFP = ensureDefined(fp[delta + offset]); + } + return [ + ...prefixCommon.map((c) => ({ type: DiffType.common, value: c })), + ...backTrace(A, B, currentFP, swapped), + ...suffixCommon.map((c) => ({ type: DiffType.common, value: c })), + ]; +} +/** + * Renders the differences between the actual and expected strings + * Partially inspired from https://github.com/kpdecker/jsdiff + * @param A Actual string + * @param B Expected string + */ +export function diffstr(A, B) { + function unescape(string) { + // unescape invisible characters. + // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences + return string + .replaceAll("\b", "\\b") + .replaceAll("\f", "\\f") + .replaceAll("\t", "\\t") + .replaceAll("\v", "\\v") + .replaceAll(// does not remove line breaks + /\r\n|\r|\n/g, (str) => str === "\r" ? "\\r" : str === "\n" ? "\\n\n" : "\\r\\n\r\n"); + } + function tokenize(string, { wordDiff = false } = {}) { + if (wordDiff) { + // Split string on whitespace symbols + const tokens = string.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/); + // Extended Latin character set + const words = /^[a-zA-Z\u{C0}-\u{FF}\u{D8}-\u{F6}\u{F8}-\u{2C6}\u{2C8}-\u{2D7}\u{2DE}-\u{2FF}\u{1E00}-\u{1EFF}]+$/u; + // Join boundary splits that we do not consider to be boundaries and merge empty strings surrounded by word chars + for (let i = 0; i < tokens.length - 1; i++) { + const token = tokens[i]; + const tokenPlusTwo = tokens[i + 2]; + if (!tokens[i + 1] && + token && + tokenPlusTwo && + words.test(token) && + words.test(tokenPlusTwo)) { + tokens[i] += tokenPlusTwo; + tokens.splice(i + 1, 2); + i--; + } + } + return tokens.filter((token) => token); + } + else { + // Split string on new lines symbols + const tokens = []; + const lines = string.split(/(\n|\r\n)/); + // Ignore final empty token when text ends with a newline + if (!lines[lines.length - 1]) { + lines.pop(); + } + // Merge the content and line separators into single tokens + for (const [i, line] of lines.entries()) { + if (i % 2) { + tokens[tokens.length - 1] += line; + } + else { + tokens.push(line); + } + } + return tokens; + } + } + // Create details by filtering relevant word-diff for current line + // and merge "space-diff" if surrounded by word-diff for cleaner displays + function createDetails(line, tokens) { + return tokens.filter(({ type }) => type === line.type || type === DiffType.common).map((result, i, t) => { + const token = t[i - 1]; + if ((result.type === DiffType.common) && token && + (token.type === t[i + 1]?.type) && /\s+/.test(result.value)) { + return { + ...result, + type: token.type, + }; + } + return result; + }); + } + // Compute multi-line diff + const diffResult = diff(tokenize(`${unescape(A)}\n`), tokenize(`${unescape(B)}\n`)); + const added = [], removed = []; + for (const result of diffResult) { + if (result.type === DiffType.added) { + added.push(result); + } + if (result.type === DiffType.removed) { + removed.push(result); + } + } + // Compute word-diff + const hasMoreRemovedLines = added.length < removed.length; + const aLines = hasMoreRemovedLines ? added : removed; + const bLines = hasMoreRemovedLines ? removed : added; + for (const a of aLines) { + let tokens = [], b; + // Search another diff line with at least one common token + while (bLines.length) { + b = bLines.shift(); + const tokenized = [ + tokenize(a.value, { wordDiff: true }), + tokenize(b?.value ?? "", { wordDiff: true }), + ]; + if (hasMoreRemovedLines) + tokenized.reverse(); + tokens = diff(tokenized[0], tokenized[1]); + if (tokens.some(({ type, value }) => type === DiffType.common && value.trim().length)) { + break; + } + } + // Register word-diff details + a.details = createDetails(a, tokens); + if (b) { + b.details = createDetails(b, tokens); + } + } + return diffResult; +} +/** + * Colors the output of assertion diffs + * @param diffType Difference type, either added or removed + */ +function createColor(diffType, { background = false } = {}) { + // TODO(@littledivy): Remove this when we can detect + // true color terminals. + // https://github.com/denoland/deno_std/issues/2575 + background = false; + switch (diffType) { + case DiffType.added: + return (s) => background ? bgGreen(white(s)) : green(bold(s)); + case DiffType.removed: + return (s) => background ? bgRed(white(s)) : red(bold(s)); + default: + return white; + } +} +/** + * Prefixes `+` or `-` in diff output + * @param diffType Difference type, either added or removed + */ +function createSign(diffType) { + switch (diffType) { + case DiffType.added: + return "+ "; + case DiffType.removed: + return "- "; + default: + return " "; + } +} +export function buildMessage(diffResult, { stringDiff = false } = {}) { + const messages = [], diffMessages = []; + messages.push(""); + messages.push(""); + messages.push(` ${gray(bold("[Diff]"))} ${red(bold("Actual"))} / ${green(bold("Expected"))}`); + messages.push(""); + messages.push(""); + diffResult.forEach((result) => { + const c = createColor(result.type); + const line = result.details?.map((detail) => detail.type !== DiffType.common + ? createColor(detail.type, { background: true })(detail.value) + : detail.value).join("") ?? result.value; + diffMessages.push(c(`${createSign(result.type)}${line}`)); + }); + messages.push(...(stringDiff ? [diffMessages.join("")] : diffMessages)); + messages.push(""); + return messages; +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/_format.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/_format.d.ts new file mode 100644 index 0000000..47af53c --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/_format.d.ts @@ -0,0 +1 @@ +export declare function format(v: unknown): string; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/_format.js b/npm/esm/deps/deno.land/std@0.216.0/assert/_format.js new file mode 100644 index 0000000..a2ffcae --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/_format.js @@ -0,0 +1,25 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. +// This file been copied to `std/expect`. +/** + * Converts the input into a string. Objects, Sets and Maps are sorted so as to + * make tests less flaky + * @param v Value to be formatted + */ +import * as dntShim from "../../../../_dnt.test_shims.js"; +export function format(v) { + // deno-lint-ignore no-explicit-any + const { Deno } = dntShim.dntGlobalThis; + return typeof Deno?.inspect === "function" + ? Deno.inspect(v, { + depth: Infinity, + sorted: true, + trailingComma: true, + compact: false, + iterableLimit: Infinity, + // getters should be true in assertEquals. + getters: true, + strAbbreviateSize: Infinity, + }) + : `"${String(v).replace(/(?=["\\])/g, "\\")}"`; +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert.d.ts new file mode 100644 index 0000000..260dd5d --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert.d.ts @@ -0,0 +1,12 @@ +/** + * Make an assertion, error will be thrown if `expr` does not have truthy value. + * + * @example + * ```ts + * import { assert } from "https://deno.land/std@$STD_VERSION/assert/assert.ts"; + * + * assert("hello".includes("ello")); // Doesn't throw + * assert("hello".includes("world")); // Throws + * ``` + */ +export declare function assert(expr: unknown, msg?: string): asserts expr; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert.js new file mode 100644 index 0000000..b8dc7bd --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert.js @@ -0,0 +1,18 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion, error will be thrown if `expr` does not have truthy value. + * + * @example + * ```ts + * import { assert } from "https://deno.land/std@$STD_VERSION/assert/assert.ts"; + * + * assert("hello".includes("ello")); // Doesn't throw + * assert("hello".includes("world")); // Throws + * ``` + */ +export function assert(expr, msg = "") { + if (!expr) { + throw new AssertionError(msg); + } +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_almost_equals.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_almost_equals.d.ts new file mode 100644 index 0000000..7481c85 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_almost_equals.d.ts @@ -0,0 +1,17 @@ +/** + * Make an assertion that `actual` and `expected` are almost equal numbers + * through a given tolerance. It can be used to take into account IEEE-754 + * double-precision floating-point representation limitations. If the values + * are not almost equal then throw. + * + * @example + * ```ts + * import { assertAlmostEquals } from "https://deno.land/std@$STD_VERSION/assert/mod.ts"; + * + * assertAlmostEquals(0.01, 0.02, 0.1); // Doesn't throw + * assertAlmostEquals(0.01, 0.02); // Throws + * assertAlmostEquals(0.1 + 0.2, 0.3, 1e-16); // Doesn't throw + * assertAlmostEquals(0.1 + 0.2, 0.3, 1e-17); // Throws + * ``` + */ +export declare function assertAlmostEquals(actual: number, expected: number, tolerance?: number, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_almost_equals.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_almost_equals.js new file mode 100644 index 0000000..58b0dd4 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_almost_equals.js @@ -0,0 +1,31 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that `actual` and `expected` are almost equal numbers + * through a given tolerance. It can be used to take into account IEEE-754 + * double-precision floating-point representation limitations. If the values + * are not almost equal then throw. + * + * @example + * ```ts + * import { assertAlmostEquals } from "https://deno.land/std@$STD_VERSION/assert/mod.ts"; + * + * assertAlmostEquals(0.01, 0.02, 0.1); // Doesn't throw + * assertAlmostEquals(0.01, 0.02); // Throws + * assertAlmostEquals(0.1 + 0.2, 0.3, 1e-16); // Doesn't throw + * assertAlmostEquals(0.1 + 0.2, 0.3, 1e-17); // Throws + * ``` + */ +export function assertAlmostEquals(actual, expected, tolerance = 1e-7, msg) { + if (Object.is(actual, expected)) { + return; + } + const delta = Math.abs(expected - actual); + if (delta <= tolerance) { + return; + } + const msgSuffix = msg ? `: ${msg}` : "."; + const f = (n) => Number.isInteger(n) ? n : n.toExponential(); + throw new AssertionError(`Expected actual: "${f(actual)}" to be close to "${f(expected)}": \ +delta "${f(delta)}" is greater than "${f(tolerance)}"${msgSuffix}`); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_array_includes.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_array_includes.d.ts new file mode 100644 index 0000000..9b79d23 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_array_includes.d.ts @@ -0,0 +1,18 @@ +/** An array-like object (`Array`, `Uint8Array`, `NodeList`, etc.) that is not a string */ +export type ArrayLikeArg = ArrayLike & object; +/** + * Make an assertion that `actual` includes the `expected` values. If not then + * an error will be thrown. + * + * Type parameter can be specified to ensure values under comparison have the + * same type. + * + * @example + * ```ts + * import { assertArrayIncludes } from "https://deno.land/std@$STD_VERSION/assert/assert_array_includes.ts"; + * + * assertArrayIncludes([1, 2], [2]); // Doesn't throw + * assertArrayIncludes([1, 2], [3]); // Throws + * ``` + */ +export declare function assertArrayIncludes(actual: ArrayLikeArg, expected: ArrayLikeArg, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_array_includes.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_array_includes.js new file mode 100644 index 0000000..b0ea5a7 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_array_includes.js @@ -0,0 +1,40 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { equal } from "./equal.js"; +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that `actual` includes the `expected` values. If not then + * an error will be thrown. + * + * Type parameter can be specified to ensure values under comparison have the + * same type. + * + * @example + * ```ts + * import { assertArrayIncludes } from "https://deno.land/std@$STD_VERSION/assert/assert_array_includes.ts"; + * + * assertArrayIncludes([1, 2], [2]); // Doesn't throw + * assertArrayIncludes([1, 2], [3]); // Throws + * ``` + */ +export function assertArrayIncludes(actual, expected, msg) { + const missing = []; + for (let i = 0; i < expected.length; i++) { + let found = false; + for (let j = 0; j < actual.length; j++) { + if (equal(expected[i], actual[j])) { + found = true; + break; + } + } + if (!found) { + missing.push(expected[i]); + } + } + if (missing.length === 0) { + return; + } + const msgSuffix = msg ? `: ${msg}` : "."; + msg = `Expected actual: "${format(actual)}" to include: "${format(expected)}"${msgSuffix}\nmissing: ${format(missing)}`; + throw new AssertionError(msg); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_equals.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_equals.d.ts new file mode 100644 index 0000000..0a733ce --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_equals.d.ts @@ -0,0 +1,20 @@ +/** + * Make an assertion that `actual` and `expected` are equal, deeply. If not + * deeply equal, then throw. + * + * Type parameter can be specified to ensure values under comparison have the + * same type. + * + * @example + * ```ts + * import { assertEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_equals.ts"; + * + * assertEquals("world", "world"); // Doesn't throw + * assertEquals("hello", "world"); // Throws + * ``` + * + * Note: formatter option is experimental and may be removed in the future. + */ +export declare function assertEquals(actual: T, expected: T, msg?: string, options?: { + formatter?: (value: unknown) => string; +}): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_equals.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_equals.js new file mode 100644 index 0000000..5eb718f --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_equals.js @@ -0,0 +1,47 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { equal } from "./equal.js"; +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; +import { red } from "../fmt/colors.js"; +import { buildMessage, diff, diffstr } from "./_diff.js"; +import { CAN_NOT_DISPLAY } from "./_constants.js"; +/** + * Make an assertion that `actual` and `expected` are equal, deeply. If not + * deeply equal, then throw. + * + * Type parameter can be specified to ensure values under comparison have the + * same type. + * + * @example + * ```ts + * import { assertEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_equals.ts"; + * + * assertEquals("world", "world"); // Doesn't throw + * assertEquals("hello", "world"); // Throws + * ``` + * + * Note: formatter option is experimental and may be removed in the future. + */ +export function assertEquals(actual, expected, msg, options = {}) { + if (equal(actual, expected)) { + return; + } + const { formatter = format } = options; + const msgSuffix = msg ? `: ${msg}` : "."; + let message = `Values are not equal${msgSuffix}`; + const actualString = formatter(actual); + const expectedString = formatter(expected); + try { + const stringDiff = (typeof actual === "string") && + (typeof expected === "string"); + const diffResult = stringDiff + ? diffstr(actual, expected) + : diff(actualString.split("\n"), expectedString.split("\n")); + const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n"); + message = `${message}\n${diffMsg}`; + } + catch { + message = `${message}\n${red(CAN_NOT_DISPLAY)} + \n\n`; + } + throw new AssertionError(message); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_exists.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_exists.d.ts new file mode 100644 index 0000000..f643295 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_exists.d.ts @@ -0,0 +1,13 @@ +/** + * Make an assertion that actual is not null or undefined. + * If not then throw. + * + * @example + * ```ts + * import { assertExists } from "https://deno.land/std@$STD_VERSION/assert/assert_exists.ts"; + * + * assertExists("something"); // Doesn't throw + * assertExists(undefined); // Throws + * ``` + */ +export declare function assertExists(actual: T, msg?: string): asserts actual is NonNullable; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_exists.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_exists.js new file mode 100644 index 0000000..2dee36f --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_exists.js @@ -0,0 +1,22 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that actual is not null or undefined. + * If not then throw. + * + * @example + * ```ts + * import { assertExists } from "https://deno.land/std@$STD_VERSION/assert/assert_exists.ts"; + * + * assertExists("something"); // Doesn't throw + * assertExists(undefined); // Throws + * ``` + */ +export function assertExists(actual, msg) { + if (actual === undefined || actual === null) { + const msgSuffix = msg ? `: ${msg}` : "."; + msg = + `Expected actual: "${actual}" to not be null or undefined${msgSuffix}`; + throw new AssertionError(msg); + } +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_false.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_false.d.ts new file mode 100644 index 0000000..d523087 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_false.d.ts @@ -0,0 +1,14 @@ +/** Assertion condition for {@linkcode assertFalse}. */ +export type Falsy = false | 0 | 0n | "" | null | undefined; +/** + * Make an assertion, error will be thrown if `expr` have truthy value. + * + * @example + * ```ts + * import { assertFalse } from "https://deno.land/std@$STD_VERSION/assert/assert_false.ts"; + * + * assertFalse(false); // Doesn't throw + * assertFalse(true); // Throws + * ``` + */ +export declare function assertFalse(expr: unknown, msg?: string): asserts expr is Falsy; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_false.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_false.js new file mode 100644 index 0000000..fc7a90f --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_false.js @@ -0,0 +1,18 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion, error will be thrown if `expr` have truthy value. + * + * @example + * ```ts + * import { assertFalse } from "https://deno.land/std@$STD_VERSION/assert/assert_false.ts"; + * + * assertFalse(false); // Doesn't throw + * assertFalse(true); // Throws + * ``` + */ +export function assertFalse(expr, msg = "") { + if (expr) { + throw new AssertionError(msg); + } +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater.d.ts new file mode 100644 index 0000000..143bd25 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater.d.ts @@ -0,0 +1,14 @@ +/** + * Make an assertion that `actual` is greater than `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertGreater } from "https://deno.land/std@$STD_VERSION/assert/assert_greater.ts"; + * + * assertGreater(2, 1); // Doesn't throw + * assertGreater(1, 1); // Throws + * assertGreater(0, 1); // Throws + * ``` + */ +export declare function assertGreater(actual: T, expected: T, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater.js new file mode 100644 index 0000000..d1ed003 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater.js @@ -0,0 +1,23 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that `actual` is greater than `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertGreater } from "https://deno.land/std@$STD_VERSION/assert/assert_greater.ts"; + * + * assertGreater(2, 1); // Doesn't throw + * assertGreater(1, 1); // Throws + * assertGreater(0, 1); // Throws + * ``` + */ +export function assertGreater(actual, expected, msg) { + if (actual > expected) + return; + const actualString = format(actual); + const expectedString = format(expected); + throw new AssertionError(msg ?? `Expect ${actualString} > ${expectedString}`); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater_or_equal.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater_or_equal.d.ts new file mode 100644 index 0000000..5c1a821 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater_or_equal.d.ts @@ -0,0 +1,14 @@ +/** + * Make an assertion that `actual` is greater than or equal to `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertGreaterOrEqual } from "https://deno.land/std@$STD_VERSION/assert/assert_greater_or_equal.ts"; + * + * assertGreaterOrEqual(2, 1); // Doesn't throw + * assertGreaterOrEqual(1, 1); // Doesn't throw + * assertGreaterOrEqual(0, 1); // Throws + * ``` + */ +export declare function assertGreaterOrEqual(actual: T, expected: T, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater_or_equal.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater_or_equal.js new file mode 100644 index 0000000..5d98971 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_greater_or_equal.js @@ -0,0 +1,23 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that `actual` is greater than or equal to `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertGreaterOrEqual } from "https://deno.land/std@$STD_VERSION/assert/assert_greater_or_equal.ts"; + * + * assertGreaterOrEqual(2, 1); // Doesn't throw + * assertGreaterOrEqual(1, 1); // Doesn't throw + * assertGreaterOrEqual(0, 1); // Throws + * ``` + */ +export function assertGreaterOrEqual(actual, expected, msg) { + if (actual >= expected) + return; + const actualString = format(actual); + const expectedString = format(expected); + throw new AssertionError(msg ?? `Expect ${actualString} >= ${expectedString}`); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_instance_of.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_instance_of.d.ts new file mode 100644 index 0000000..04cebbb --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_instance_of.d.ts @@ -0,0 +1,17 @@ +/** Any constructor */ +export type AnyConstructor = new (...args: any[]) => any; +/** Gets constructor type */ +export type GetConstructorType = T extends new (...args: any) => infer C ? C : never; +/** + * Make an assertion that `obj` is an instance of `type`. + * If not then throw. + * + * @example + * ```ts + * import { assertInstanceOf } from "https://deno.land/std@$STD_VERSION/assert/assert_instance_of.ts"; + * + * assertInstanceOf(new Date(), Date); // Doesn't throw + * assertInstanceOf(new Date(), Number); // Throws + * ``` + */ +export declare function assertInstanceOf(actual: unknown, expectedType: T, msg?: string): asserts actual is GetConstructorType; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_instance_of.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_instance_of.js new file mode 100644 index 0000000..fcb30c5 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_instance_of.js @@ -0,0 +1,46 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that `obj` is an instance of `type`. + * If not then throw. + * + * @example + * ```ts + * import { assertInstanceOf } from "https://deno.land/std@$STD_VERSION/assert/assert_instance_of.ts"; + * + * assertInstanceOf(new Date(), Date); // Doesn't throw + * assertInstanceOf(new Date(), Number); // Throws + * ``` + */ +export function assertInstanceOf(actual, expectedType, msg = "") { + if (actual instanceof expectedType) + return; + const msgSuffix = msg ? `: ${msg}` : "."; + const expectedTypeStr = expectedType.name; + let actualTypeStr = ""; + if (actual === null) { + actualTypeStr = "null"; + } + else if (actual === undefined) { + actualTypeStr = "undefined"; + } + else if (typeof actual === "object") { + actualTypeStr = actual.constructor?.name ?? "Object"; + } + else { + actualTypeStr = typeof actual; + } + if (expectedTypeStr === actualTypeStr) { + msg = + `Expected object to be an instance of "${expectedTypeStr}"${msgSuffix}`; + } + else if (actualTypeStr === "function") { + msg = + `Expected object to be an instance of "${expectedTypeStr}" but was not an instanced object${msgSuffix}`; + } + else { + msg = + `Expected object to be an instance of "${expectedTypeStr}" but was "${actualTypeStr}"${msgSuffix}`; + } + throw new AssertionError(msg); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_is_error.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_is_error.d.ts new file mode 100644 index 0000000..9db664b --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_is_error.d.ts @@ -0,0 +1,18 @@ +/** + * Make an assertion that `error` is an `Error`. + * If not then an error will be thrown. + * An error class and a string that should be included in the + * error message can also be asserted. + * + * @example + * ```ts + * import { assertIsError } from "https://deno.land/std@$STD_VERSION/assert/assert_is_error.ts"; + * + * assertIsError(null); // Throws + * assertIsError(new RangeError("Out of range")); // Doesn't throw + * assertIsError(new RangeError("Out of range"), SyntaxError); // Throws + * assertIsError(new RangeError("Out of range"), SyntaxError, "Out of range"); // Doesn't throw + * assertIsError(new RangeError("Out of range"), SyntaxError, "Within range"); // Throws + * ``` + */ +export declare function assertIsError(error: unknown, ErrorClass?: new (...args: any[]) => E, msgMatches?: string | RegExp, msg?: string): asserts error is E; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_is_error.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_is_error.js new file mode 100644 index 0000000..ad45769 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_is_error.js @@ -0,0 +1,48 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +import { stripAnsiCode } from "../fmt/colors.js"; +/** + * Make an assertion that `error` is an `Error`. + * If not then an error will be thrown. + * An error class and a string that should be included in the + * error message can also be asserted. + * + * @example + * ```ts + * import { assertIsError } from "https://deno.land/std@$STD_VERSION/assert/assert_is_error.ts"; + * + * assertIsError(null); // Throws + * assertIsError(new RangeError("Out of range")); // Doesn't throw + * assertIsError(new RangeError("Out of range"), SyntaxError); // Throws + * assertIsError(new RangeError("Out of range"), SyntaxError, "Out of range"); // Doesn't throw + * assertIsError(new RangeError("Out of range"), SyntaxError, "Within range"); // Throws + * ``` + */ +export function assertIsError(error, +// deno-lint-ignore no-explicit-any +ErrorClass, msgMatches, msg) { + const msgSuffix = msg ? `: ${msg}` : "."; + if (!(error instanceof Error)) { + throw new AssertionError(`Expected "error" to be an Error object${msgSuffix}}`); + } + if (ErrorClass && !(error instanceof ErrorClass)) { + msg = `Expected error to be instance of "${ErrorClass.name}", but was "${typeof error === "object" ? error?.constructor?.name : "[not an object]"}"${msgSuffix}`; + throw new AssertionError(msg); + } + let msgCheck; + if (typeof msgMatches === "string") { + msgCheck = stripAnsiCode(error.message).includes(stripAnsiCode(msgMatches)); + } + if (msgMatches instanceof RegExp) { + msgCheck = msgMatches.test(stripAnsiCode(error.message)); + } + if (msgMatches && !msgCheck) { + msg = `Expected error message to include ${msgMatches instanceof RegExp + ? msgMatches.toString() + : JSON.stringify(msgMatches)}, but got ${error instanceof Error + ? JSON.stringify(error.message) + : '"[not an Error]"' // TODO(kt3k): show more useful information + }${msgSuffix}`; + throw new AssertionError(msg); + } +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less.d.ts new file mode 100644 index 0000000..6fcdfa0 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less.d.ts @@ -0,0 +1,13 @@ +/** + * Make an assertion that `actual` is less than `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertLess } from "https://deno.land/std@$STD_VERSION/assert/assert_less.ts"; + * + * assertLess(1, 2); // Doesn't throw + * assertLess(2, 1); // Throws + * ``` + */ +export declare function assertLess(actual: T, expected: T, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less.js new file mode 100644 index 0000000..01e7ca7 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less.js @@ -0,0 +1,22 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that `actual` is less than `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertLess } from "https://deno.land/std@$STD_VERSION/assert/assert_less.ts"; + * + * assertLess(1, 2); // Doesn't throw + * assertLess(2, 1); // Throws + * ``` + */ +export function assertLess(actual, expected, msg) { + if (actual < expected) + return; + const actualString = format(actual); + const expectedString = format(expected); + throw new AssertionError(msg ?? `Expect ${actualString} < ${expectedString}`); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less_or_equal.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less_or_equal.d.ts new file mode 100644 index 0000000..f715009 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less_or_equal.d.ts @@ -0,0 +1,14 @@ +/** + * Make an assertion that `actual` is less than or equal to `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertLessOrEqual } from "https://deno.land/std@$STD_VERSION/assert/assert_less_or_equal.ts"; + * + * assertLessOrEqual(1, 2); // Doesn't throw + * assertLessOrEqual(1, 1); // Doesn't throw + * assertLessOrEqual(1, 0); // Throws + * ``` + */ +export declare function assertLessOrEqual(actual: T, expected: T, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less_or_equal.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less_or_equal.js new file mode 100644 index 0000000..abeeb68 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_less_or_equal.js @@ -0,0 +1,23 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that `actual` is less than or equal to `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertLessOrEqual } from "https://deno.land/std@$STD_VERSION/assert/assert_less_or_equal.ts"; + * + * assertLessOrEqual(1, 2); // Doesn't throw + * assertLessOrEqual(1, 1); // Doesn't throw + * assertLessOrEqual(1, 0); // Throws + * ``` + */ +export function assertLessOrEqual(actual, expected, msg) { + if (actual <= expected) + return; + const actualString = format(actual); + const expectedString = format(expected); + throw new AssertionError(msg ?? `Expect ${actualString} <= ${expectedString}`); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_match.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_match.d.ts new file mode 100644 index 0000000..fb0af0d --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_match.d.ts @@ -0,0 +1,13 @@ +/** + * Make an assertion that `actual` match RegExp `expected`. If not + * then throw. + * + * @example + * ```ts + * import { assertMatch } from "https://deno.land/std@$STD_VERSION/assert/assert_match.ts"; + * + * assertMatch("Raptor", RegExp(/Raptor/)); // Doesn't throw + * assertMatch("Denosaurus", RegExp(/Raptor/)); // Throws + * ``` + */ +export declare function assertMatch(actual: string, expected: RegExp, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_match.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_match.js new file mode 100644 index 0000000..b9e0f54 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_match.js @@ -0,0 +1,21 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that `actual` match RegExp `expected`. If not + * then throw. + * + * @example + * ```ts + * import { assertMatch } from "https://deno.land/std@$STD_VERSION/assert/assert_match.ts"; + * + * assertMatch("Raptor", RegExp(/Raptor/)); // Doesn't throw + * assertMatch("Denosaurus", RegExp(/Raptor/)); // Throws + * ``` + */ +export function assertMatch(actual, expected, msg) { + if (!expected.test(actual)) { + const msgSuffix = msg ? `: ${msg}` : "."; + msg = `Expected actual: "${actual}" to match: "${expected}"${msgSuffix}`; + throw new AssertionError(msg); + } +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_equals.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_equals.d.ts new file mode 100644 index 0000000..6357f36 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_equals.d.ts @@ -0,0 +1,15 @@ +/** + * Make an assertion that `actual` and `expected` are not equal, deeply. + * If not then throw. + * + * Type parameter can be specified to ensure values under comparison have the same type. + * + * @example + * ```ts + * import { assertNotEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_not_equals.ts"; + * + * assertNotEquals(1, 2); // Doesn't throw + * assertNotEquals(1, 1); // Throws + * ``` + */ +export declare function assertNotEquals(actual: T, expected: T, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_equals.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_equals.js new file mode 100644 index 0000000..b9b693a --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_equals.js @@ -0,0 +1,39 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { CAN_NOT_DISPLAY } from "./_constants.js"; +import { equal } from "./equal.js"; +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that `actual` and `expected` are not equal, deeply. + * If not then throw. + * + * Type parameter can be specified to ensure values under comparison have the same type. + * + * @example + * ```ts + * import { assertNotEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_not_equals.ts"; + * + * assertNotEquals(1, 2); // Doesn't throw + * assertNotEquals(1, 1); // Throws + * ``` + */ +export function assertNotEquals(actual, expected, msg) { + if (!equal(actual, expected)) { + return; + } + let actualString; + let expectedString; + try { + actualString = String(actual); + } + catch { + actualString = CAN_NOT_DISPLAY; + } + try { + expectedString = String(expected); + } + catch { + expectedString = CAN_NOT_DISPLAY; + } + const msgSuffix = msg ? `: ${msg}` : "."; + throw new AssertionError(`Expected actual: ${actualString} not to be: ${expectedString}${msgSuffix}`); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_instance_of.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_instance_of.d.ts new file mode 100644 index 0000000..8fbbc20 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_instance_of.d.ts @@ -0,0 +1,13 @@ +/** + * Make an assertion that `obj` is not an instance of `type`. + * If so, then throw. + * + * @example + * ```ts + * import { assertNotInstanceOf } from "https://deno.land/std@$STD_VERSION/assert/assert_not_instance_of.ts"; + * + * assertNotInstanceOf(new Date(), Number); // Doesn't throw + * assertNotInstanceOf(new Date(), Date); // Throws + * ``` + */ +export declare function assertNotInstanceOf(actual: A, unexpectedType: new (...args: any[]) => T, msg?: string): asserts actual is Exclude; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_instance_of.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_instance_of.js new file mode 100644 index 0000000..cac39a2 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_instance_of.js @@ -0,0 +1,22 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assertFalse } from "./assert_false.js"; +/** + * Make an assertion that `obj` is not an instance of `type`. + * If so, then throw. + * + * @example + * ```ts + * import { assertNotInstanceOf } from "https://deno.land/std@$STD_VERSION/assert/assert_not_instance_of.ts"; + * + * assertNotInstanceOf(new Date(), Number); // Doesn't throw + * assertNotInstanceOf(new Date(), Date); // Throws + * ``` + */ +export function assertNotInstanceOf(actual, +// deno-lint-ignore no-explicit-any +unexpectedType, msg) { + const msgSuffix = msg ? `: ${msg}` : "."; + msg = + `Expected object to not be an instance of "${typeof unexpectedType}"${msgSuffix}`; + assertFalse(actual instanceof unexpectedType, msg); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_match.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_match.d.ts new file mode 100644 index 0000000..f6bfb10 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_match.d.ts @@ -0,0 +1,13 @@ +/** + * Make an assertion that `actual` not match RegExp `expected`. If match + * then throw. + * + * @example + * ```ts + * import { assertNotMatch } from "https://deno.land/std@$STD_VERSION/assert/assert_not_match.ts"; + * + * assertNotMatch("Denosaurus", RegExp(/Raptor/)); // Doesn't throw + * assertNotMatch("Raptor", RegExp(/Raptor/)); // Throws + * ``` + */ +export declare function assertNotMatch(actual: string, expected: RegExp, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_match.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_match.js new file mode 100644 index 0000000..675cf1d --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_match.js @@ -0,0 +1,22 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that `actual` not match RegExp `expected`. If match + * then throw. + * + * @example + * ```ts + * import { assertNotMatch } from "https://deno.land/std@$STD_VERSION/assert/assert_not_match.ts"; + * + * assertNotMatch("Denosaurus", RegExp(/Raptor/)); // Doesn't throw + * assertNotMatch("Raptor", RegExp(/Raptor/)); // Throws + * ``` + */ +export function assertNotMatch(actual, expected, msg) { + if (expected.test(actual)) { + const msgSuffix = msg ? `: ${msg}` : "."; + msg = + `Expected actual: "${actual}" to not match: "${expected}"${msgSuffix}`; + throw new AssertionError(msg); + } +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_strict_equals.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_strict_equals.d.ts new file mode 100644 index 0000000..49e2921 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_strict_equals.d.ts @@ -0,0 +1,13 @@ +/** + * Make an assertion that `actual` and `expected` are not strictly equal. + * If the values are strictly equal then throw. + * + * @example + * ```ts + * import { assertNotStrictEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_not_strict_equals.ts"; + * + * assertNotStrictEquals(1, 1); // Doesn't throw + * assertNotStrictEquals(1, 2); // Throws + * ``` + */ +export declare function assertNotStrictEquals(actual: T, expected: T, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_strict_equals.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_strict_equals.js new file mode 100644 index 0000000..255e1cc --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_not_strict_equals.js @@ -0,0 +1,22 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +import { format } from "./_format.js"; +/** + * Make an assertion that `actual` and `expected` are not strictly equal. + * If the values are strictly equal then throw. + * + * @example + * ```ts + * import { assertNotStrictEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_not_strict_equals.ts"; + * + * assertNotStrictEquals(1, 1); // Doesn't throw + * assertNotStrictEquals(1, 2); // Throws + * ``` + */ +export function assertNotStrictEquals(actual, expected, msg) { + if (!Object.is(actual, expected)) { + return; + } + const msgSuffix = msg ? `: ${msg}` : "."; + throw new AssertionError(`Expected "actual" to not be strictly equal to: ${format(actual)}${msgSuffix}\n`); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_object_match.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_object_match.d.ts new file mode 100644 index 0000000..277bdcb --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_object_match.d.ts @@ -0,0 +1,13 @@ +/** + * Make an assertion that `actual` object is a subset of `expected` object, + * deeply. If not, then throw. + * + * @example + * ```ts + * import { assertObjectMatch } from "https://deno.land/std@$STD_VERSION/assert/assert_object_match.ts"; + * + * assertObjectMatch({ foo: "bar" }, { foo: "bar" }); // Doesn't throw + * assertObjectMatch({ foo: "bar" }, { foo: "baz" }); // Throws + * ``` + */ +export declare function assertObjectMatch(actual: Record, expected: Record, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_object_match.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_object_match.js new file mode 100644 index 0000000..626533d --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_object_match.js @@ -0,0 +1,86 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "./assert_equals.js"; +/** + * Make an assertion that `actual` object is a subset of `expected` object, + * deeply. If not, then throw. + * + * @example + * ```ts + * import { assertObjectMatch } from "https://deno.land/std@$STD_VERSION/assert/assert_object_match.ts"; + * + * assertObjectMatch({ foo: "bar" }, { foo: "bar" }); // Doesn't throw + * assertObjectMatch({ foo: "bar" }, { foo: "baz" }); // Throws + * ``` + */ +export function assertObjectMatch( +// deno-lint-ignore no-explicit-any +actual, expected, msg) { + function filter(a, b) { + const seen = new WeakMap(); + return fn(a, b); + function fn(a, b) { + // Prevent infinite loop with circular references with same filter + if ((seen.has(a)) && (seen.get(a) === b)) { + return a; + } + try { + seen.set(a, b); + } + catch (err) { + if (err instanceof TypeError) { + throw new TypeError(`Cannot assertObjectMatch ${a === null ? null : `type ${typeof a}`}`); + } + else + throw err; + } + // Filter keys and symbols which are present in both actual and expected + const filtered = {}; + const entries = [ + ...Object.getOwnPropertyNames(a), + ...Object.getOwnPropertySymbols(a), + ] + .filter((key) => key in b) + .map((key) => [key, a[key]]); + for (const [key, value] of entries) { + // On array references, build a filtered array and filter nested objects inside + if (Array.isArray(value)) { + const subset = b[key]; + if (Array.isArray(subset)) { + filtered[key] = fn({ ...value }, { ...subset }); + continue; + } + } // On regexp references, keep value as it to avoid loosing pattern and flags + else if (value instanceof RegExp) { + filtered[key] = value; + continue; + } // On nested objects references, build a filtered object recursively + else if (typeof value === "object" && value !== null) { + const subset = b[key]; + if ((typeof subset === "object") && subset) { + // When both operands are maps, build a filtered map with common keys and filter nested objects inside + if ((value instanceof Map) && (subset instanceof Map)) { + filtered[key] = new Map([...value].filter(([k]) => subset.has(k)).map(([k, v]) => [k, typeof v === "object" ? fn(v, subset.get(k)) : v])); + continue; + } + // When both operands are set, build a filtered set with common values + if ((value instanceof Set) && (subset instanceof Set)) { + filtered[key] = new Set([...value].filter((v) => subset.has(v))); + continue; + } + filtered[key] = fn(value, subset); + continue; + } + } + filtered[key] = value; + } + return filtered; + } + } + return assertEquals( + // get the intersection of "actual" and "expected" + // side effect: all the instances' constructor field is "Object" now. + filter(actual, expected), + // set (nested) instances' constructor field to be "Object" without changing expected value. + // see https://github.com/denoland/deno_std/pull/1419 + filter(expected, expected), msg); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_rejects.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_rejects.d.ts new file mode 100644 index 0000000..b746389 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_rejects.d.ts @@ -0,0 +1,26 @@ +/** + * Executes a function which returns a promise, expecting it to reject. + * + * @example + * ```ts + * import { assertRejects } from "https://deno.land/std@$STD_VERSION/assert/assert_rejects.ts"; + * + * await assertRejects(async () => Promise.reject(new Error())); // Doesn't throw + * await assertRejects(async () => console.log("Hello world")); // Throws + * ``` + */ +export declare function assertRejects(fn: () => PromiseLike, msg?: string): Promise; +/** + * Executes a function which returns a promise, expecting it to reject. + * If it does not, then it throws. An error class and a string that should be + * included in the error message can also be asserted. + * + * @example + * ```ts + * import { assertRejects } from "https://deno.land/std@$STD_VERSION/assert/assert_rejects.ts"; + * + * await assertRejects(async () => Promise.reject(new Error()), Error); // Doesn't throw + * await assertRejects(async () => Promise.reject(new Error()), SyntaxError); // Throws + * ``` + */ +export declare function assertRejects(fn: () => PromiseLike, ErrorClass: new (...args: any[]) => E, msgIncludes?: string, msg?: string): Promise; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_rejects.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_rejects.js new file mode 100644 index 0000000..8b59d9e --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_rejects.js @@ -0,0 +1,50 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +import { assertIsError } from "./assert_is_error.js"; +export async function assertRejects(fn, errorClassOrMsg, msgIncludesOrMsg, msg) { + // deno-lint-ignore no-explicit-any + let ErrorClass = undefined; + let msgIncludes = undefined; + let err; + if (typeof errorClassOrMsg !== "string") { + if (errorClassOrMsg === undefined || + errorClassOrMsg.prototype instanceof Error || + errorClassOrMsg.prototype === Error.prototype) { + // deno-lint-ignore no-explicit-any + ErrorClass = errorClassOrMsg; + msgIncludes = msgIncludesOrMsg; + } + } + else { + msg = errorClassOrMsg; + } + let doesThrow = false; + let isPromiseReturned = false; + const msgSuffix = msg ? `: ${msg}` : "."; + try { + const possiblePromise = fn(); + if (possiblePromise && + typeof possiblePromise === "object" && + typeof possiblePromise.then === "function") { + isPromiseReturned = true; + await possiblePromise; + } + } + catch (error) { + if (!isPromiseReturned) { + throw new AssertionError(`Function throws when expected to reject${msgSuffix}`); + } + if (ErrorClass) { + if (error instanceof Error === false) { + throw new AssertionError(`A non-Error object was rejected${msgSuffix}`); + } + assertIsError(error, ErrorClass, msgIncludes, msg); + } + err = error; + doesThrow = true; + } + if (!doesThrow) { + throw new AssertionError(`Expected function to reject${msgSuffix}`); + } + return err; +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_strict_equals.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_strict_equals.d.ts new file mode 100644 index 0000000..5bda868 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_strict_equals.d.ts @@ -0,0 +1,18 @@ +/** + * Make an assertion that `actual` and `expected` are strictly equal. If + * not then throw. + * + * @example + * ```ts + * import { assertStrictEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_strict_equals.ts"; + * + * const a = {}; + * const b = a; + * assertStrictEquals(a, b); // Doesn't throw + * + * const c = {}; + * const d = {}; + * assertStrictEquals(c, d); // Throws + * ``` + */ +export declare function assertStrictEquals(actual: unknown, expected: T, msg?: string): asserts actual is T; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_strict_equals.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_strict_equals.js new file mode 100644 index 0000000..09f1b57 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_strict_equals.js @@ -0,0 +1,55 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; +import { buildMessage, diff, diffstr } from "./_diff.js"; +import { CAN_NOT_DISPLAY } from "./_constants.js"; +import { red } from "../fmt/colors.js"; +/** + * Make an assertion that `actual` and `expected` are strictly equal. If + * not then throw. + * + * @example + * ```ts + * import { assertStrictEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_strict_equals.ts"; + * + * const a = {}; + * const b = a; + * assertStrictEquals(a, b); // Doesn't throw + * + * const c = {}; + * const d = {}; + * assertStrictEquals(c, d); // Throws + * ``` + */ +export function assertStrictEquals(actual, expected, msg) { + if (Object.is(actual, expected)) { + return; + } + const msgSuffix = msg ? `: ${msg}` : "."; + let message; + const actualString = format(actual); + const expectedString = format(expected); + if (actualString === expectedString) { + const withOffset = actualString + .split("\n") + .map((l) => ` ${l}`) + .join("\n"); + message = + `Values have the same structure but are not reference-equal${msgSuffix}\n\n${red(withOffset)}\n`; + } + else { + try { + const stringDiff = (typeof actual === "string") && + (typeof expected === "string"); + const diffResult = stringDiff + ? diffstr(actual, expected) + : diff(actualString.split("\n"), expectedString.split("\n")); + const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n"); + message = `Values are not strictly equal${msgSuffix}\n${diffMsg}`; + } + catch { + message = `\n${red(CAN_NOT_DISPLAY)} + \n\n`; + } + } + throw new AssertionError(message); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_string_includes.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_string_includes.d.ts new file mode 100644 index 0000000..fa80f38 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_string_includes.d.ts @@ -0,0 +1,13 @@ +/** + * Make an assertion that actual includes expected. If not + * then throw. + * + * @example + * ```ts + * import { assertStringIncludes } from "https://deno.land/std@$STD_VERSION/assert/assert_string_includes.ts"; + * + * assertStringIncludes("Hello", "ello"); // Doesn't throw + * assertStringIncludes("Hello", "world"); // Throws + * ``` + */ +export declare function assertStringIncludes(actual: string, expected: string, msg?: string): void; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_string_includes.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_string_includes.js new file mode 100644 index 0000000..434cb58 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_string_includes.js @@ -0,0 +1,21 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +/** + * Make an assertion that actual includes expected. If not + * then throw. + * + * @example + * ```ts + * import { assertStringIncludes } from "https://deno.land/std@$STD_VERSION/assert/assert_string_includes.ts"; + * + * assertStringIncludes("Hello", "ello"); // Doesn't throw + * assertStringIncludes("Hello", "world"); // Throws + * ``` + */ +export function assertStringIncludes(actual, expected, msg) { + if (!actual.includes(expected)) { + const msgSuffix = msg ? `: ${msg}` : "."; + msg = `Expected actual: "${actual}" to contain: "${expected}"${msgSuffix}`; + throw new AssertionError(msg); + } +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_throws.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_throws.d.ts new file mode 100644 index 0000000..aef6774 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_throws.d.ts @@ -0,0 +1,27 @@ +/** + * Executes a function, expecting it to throw. If it does not, then it + * throws. + * + * @example + * ```ts + * import { assertThrows } from "https://deno.land/std@$STD_VERSION/assert/assert_throws.ts"; + * + * assertThrows(() => { throw new TypeError("hello world!"); }); // Doesn't throw + * assertThrows(() => console.log("hello world!")); // Throws + * ``` + */ +export declare function assertThrows(fn: () => unknown, msg?: string): unknown; +/** + * Executes a function, expecting it to throw. If it does not, then it + * throws. An error class and a string that should be included in the + * error message can also be asserted. + * + * @example + * ```ts + * import { assertThrows } from "https://deno.land/std@$STD_VERSION/assert/assert_throws.ts"; + * + * assertThrows(() => { throw new TypeError("hello world!"); }, TypeError); // Doesn't throw + * assertThrows(() => { throw new TypeError("hello world!"); }, RangeError); // Throws + * ``` + */ +export declare function assertThrows(fn: () => unknown, ErrorClass: new (...args: any[]) => E, msgIncludes?: string, msg?: string): E; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assert_throws.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_throws.js new file mode 100644 index 0000000..c084a8f --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assert_throws.js @@ -0,0 +1,44 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assertIsError } from "./assert_is_error.js"; +import { AssertionError } from "./assertion_error.js"; +export function assertThrows(fn, errorClassOrMsg, msgIncludesOrMsg, msg) { + // deno-lint-ignore no-explicit-any + let ErrorClass = undefined; + let msgIncludes = undefined; + let err; + if (typeof errorClassOrMsg !== "string") { + if (errorClassOrMsg === undefined || + errorClassOrMsg.prototype instanceof Error || + errorClassOrMsg.prototype === Error.prototype) { + // deno-lint-ignore no-explicit-any + ErrorClass = errorClassOrMsg; + msgIncludes = msgIncludesOrMsg; + } + else { + msg = msgIncludesOrMsg; + } + } + else { + msg = errorClassOrMsg; + } + let doesThrow = false; + const msgSuffix = msg ? `: ${msg}` : "."; + try { + fn(); + } + catch (error) { + if (ErrorClass) { + if (error instanceof Error === false) { + throw new AssertionError(`A non-Error object was thrown${msgSuffix}`); + } + assertIsError(error, ErrorClass, msgIncludes, msg); + } + err = error; + doesThrow = true; + } + if (!doesThrow) { + msg = `Expected function to throw${msgSuffix}`; + throw new AssertionError(msg); + } + return err; +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assertion_error.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/assertion_error.d.ts new file mode 100644 index 0000000..bca16bf --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assertion_error.d.ts @@ -0,0 +1,14 @@ +/** + * Error thrown when an assertion fails. + * + * @example + * ```ts + * import { AssertionError } from "https://deno.land/std@$STD_VERSION/assert/assertion_error.ts"; + * + * throw new AssertionError("Assertion failed"); + * ``` + */ +export declare class AssertionError extends Error { + /** Constructs a new instance. */ + constructor(message: string); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/assertion_error.js b/npm/esm/deps/deno.land/std@0.216.0/assert/assertion_error.js new file mode 100644 index 0000000..f635660 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/assertion_error.js @@ -0,0 +1,18 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +/** + * Error thrown when an assertion fails. + * + * @example + * ```ts + * import { AssertionError } from "https://deno.land/std@$STD_VERSION/assert/assertion_error.ts"; + * + * throw new AssertionError("Assertion failed"); + * ``` + */ +export class AssertionError extends Error { + /** Constructs a new instance. */ + constructor(message) { + super(message); + this.name = "AssertionError"; + } +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/equal.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/equal.d.ts new file mode 100644 index 0000000..bb0eaf3 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/equal.d.ts @@ -0,0 +1,14 @@ +/** + * Deep equality comparison used in assertions + * @param c actual value + * @param d expected value + * + * @example + * ```ts + * import { equal } from "https://deno.land/std@$STD_VERSION/assert/equal.ts"; + * + * equal({ foo: "bar" }, { foo: "bar" }); // Returns `true` + * equal({ foo: "bar" }, { foo: "baz" }); // Returns `false + * ``` + */ +export declare function equal(c: unknown, d: unknown): boolean; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/equal.js b/npm/esm/deps/deno.land/std@0.216.0/assert/equal.js new file mode 100644 index 0000000..14c8a04 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/equal.js @@ -0,0 +1,110 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +function isKeyedCollection(x) { + return [Symbol.iterator, "size"].every((k) => k in x); +} +function constructorsEqual(a, b) { + return a.constructor === b.constructor || + a.constructor === Object && !b.constructor || + !a.constructor && b.constructor === Object; +} +/** + * Deep equality comparison used in assertions + * @param c actual value + * @param d expected value + * + * @example + * ```ts + * import { equal } from "https://deno.land/std@$STD_VERSION/assert/equal.ts"; + * + * equal({ foo: "bar" }, { foo: "bar" }); // Returns `true` + * equal({ foo: "bar" }, { foo: "baz" }); // Returns `false + * ``` + */ +export function equal(c, d) { + const seen = new Map(); + return (function compare(a, b) { + // Have to render RegExp & Date for string comparison + // unless it's mistreated as object + if (a && + b && + ((a instanceof RegExp && b instanceof RegExp) || + (a instanceof URL && b instanceof URL))) { + return String(a) === String(b); + } + if (a instanceof Date && b instanceof Date) { + const aTime = a.getTime(); + const bTime = b.getTime(); + // Check for NaN equality manually since NaN is not + // equal to itself. + if (Number.isNaN(aTime) && Number.isNaN(bTime)) { + return true; + } + return aTime === bTime; + } + if (typeof a === "number" && typeof b === "number") { + return Number.isNaN(a) && Number.isNaN(b) || a === b; + } + if (Object.is(a, b)) { + return true; + } + if (a && typeof a === "object" && b && typeof b === "object") { + if (a && b && !constructorsEqual(a, b)) { + return false; + } + if (a instanceof WeakMap || b instanceof WeakMap) { + if (!(a instanceof WeakMap && b instanceof WeakMap)) + return false; + throw new TypeError("cannot compare WeakMap instances"); + } + if (a instanceof WeakSet || b instanceof WeakSet) { + if (!(a instanceof WeakSet && b instanceof WeakSet)) + return false; + throw new TypeError("cannot compare WeakSet instances"); + } + if (seen.get(a) === b) { + return true; + } + if (Object.keys(a || {}).length !== Object.keys(b || {}).length) { + return false; + } + seen.set(a, b); + if (isKeyedCollection(a) && isKeyedCollection(b)) { + if (a.size !== b.size) { + return false; + } + let unmatchedEntries = a.size; + for (const [aKey, aValue] of a.entries()) { + for (const [bKey, bValue] of b.entries()) { + /* Given that Map keys can be references, we need + * to ensure that they are also deeply equal */ + if ((aKey === aValue && bKey === bValue && compare(aKey, bKey)) || + (compare(aKey, bKey) && compare(aValue, bValue))) { + unmatchedEntries--; + break; + } + } + } + return unmatchedEntries === 0; + } + const merged = { ...a, ...b }; + for (const key of [ + ...Object.getOwnPropertyNames(merged), + ...Object.getOwnPropertySymbols(merged), + ]) { + if (!compare(a && a[key], b && b[key])) { + return false; + } + if (((key in a) && (!(key in b))) || ((key in b) && (!(key in a)))) { + return false; + } + } + if (a instanceof WeakRef || b instanceof WeakRef) { + if (!(a instanceof WeakRef && b instanceof WeakRef)) + return false; + return compare(a.deref(), b.deref()); + } + return true; + } + return false; + })(c, d); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/fail.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/fail.d.ts new file mode 100644 index 0000000..9e67653 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/fail.d.ts @@ -0,0 +1,11 @@ +/** + * Forcefully throws a failed assertion. + * + * @example + * ```ts + * import { fail } from "https://deno.land/std@$STD_VERSION/assert/fail.ts"; + * + * fail("Deliberately failed!"); // Throws + * ``` + */ +export declare function fail(msg?: string): never; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/fail.js b/npm/esm/deps/deno.land/std@0.216.0/assert/fail.js new file mode 100644 index 0000000..ee9d99b --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/fail.js @@ -0,0 +1,16 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assert } from "./assert.js"; +/** + * Forcefully throws a failed assertion. + * + * @example + * ```ts + * import { fail } from "https://deno.land/std@$STD_VERSION/assert/fail.ts"; + * + * fail("Deliberately failed!"); // Throws + * ``` + */ +export function fail(msg) { + const msgSuffix = msg ? `: ${msg}` : "."; + assert(false, `Failed assertion${msgSuffix}`); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/mod.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/mod.d.ts new file mode 100644 index 0000000..d35a266 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/mod.d.ts @@ -0,0 +1,36 @@ +/** A library of assertion functions. + * If the assertion is false an `AssertionError` will be thrown which will + * result in pretty-printed diff of failing assertion. + * + * This module is browser compatible, but do not rely on good formatting of + * values for AssertionError messages in browsers. + * + * @module + */ +export * from "./assert_almost_equals.js"; +export * from "./assert_array_includes.js"; +export * from "./assert_equals.js"; +export * from "./assert_exists.js"; +export * from "./assert_false.js"; +export * from "./assert_greater_or_equal.js"; +export * from "./assert_greater.js"; +export * from "./assert_instance_of.js"; +export * from "./assert_is_error.js"; +export * from "./assert_less_or_equal.js"; +export * from "./assert_less.js"; +export * from "./assert_match.js"; +export * from "./assert_not_equals.js"; +export * from "./assert_not_instance_of.js"; +export * from "./assert_not_match.js"; +export * from "./assert_not_strict_equals.js"; +export * from "./assert_object_match.js"; +export * from "./assert_rejects.js"; +export * from "./assert_strict_equals.js"; +export * from "./assert_string_includes.js"; +export * from "./assert_throws.js"; +export * from "./assert.js"; +export * from "./assertion_error.js"; +export * from "./equal.js"; +export * from "./fail.js"; +export * from "./unimplemented.js"; +export * from "./unreachable.js"; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/mod.js b/npm/esm/deps/deno.land/std@0.216.0/assert/mod.js new file mode 100644 index 0000000..e2f7b93 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/mod.js @@ -0,0 +1,37 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +/** A library of assertion functions. + * If the assertion is false an `AssertionError` will be thrown which will + * result in pretty-printed diff of failing assertion. + * + * This module is browser compatible, but do not rely on good formatting of + * values for AssertionError messages in browsers. + * + * @module + */ +export * from "./assert_almost_equals.js"; +export * from "./assert_array_includes.js"; +export * from "./assert_equals.js"; +export * from "./assert_exists.js"; +export * from "./assert_false.js"; +export * from "./assert_greater_or_equal.js"; +export * from "./assert_greater.js"; +export * from "./assert_instance_of.js"; +export * from "./assert_is_error.js"; +export * from "./assert_less_or_equal.js"; +export * from "./assert_less.js"; +export * from "./assert_match.js"; +export * from "./assert_not_equals.js"; +export * from "./assert_not_instance_of.js"; +export * from "./assert_not_match.js"; +export * from "./assert_not_strict_equals.js"; +export * from "./assert_object_match.js"; +export * from "./assert_rejects.js"; +export * from "./assert_strict_equals.js"; +export * from "./assert_string_includes.js"; +export * from "./assert_throws.js"; +export * from "./assert.js"; +export * from "./assertion_error.js"; +export * from "./equal.js"; +export * from "./fail.js"; +export * from "./unimplemented.js"; +export * from "./unreachable.js"; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/unimplemented.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/unimplemented.d.ts new file mode 100644 index 0000000..6aa08c6 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/unimplemented.d.ts @@ -0,0 +1,11 @@ +/** + * Use this to stub out methods that will throw when invoked. + * + * @example + * ```ts + * import { unimplemented } from "https://deno.land/std@$STD_VERSION/assert/unimplemented.ts"; + * + * unimplemented(); // Throws + * ``` + */ +export declare function unimplemented(msg?: string): never; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/unimplemented.js b/npm/esm/deps/deno.land/std@0.216.0/assert/unimplemented.js new file mode 100644 index 0000000..799e739 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/unimplemented.js @@ -0,0 +1,16 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +/** + * Use this to stub out methods that will throw when invoked. + * + * @example + * ```ts + * import { unimplemented } from "https://deno.land/std@$STD_VERSION/assert/unimplemented.ts"; + * + * unimplemented(); // Throws + * ``` + */ +export function unimplemented(msg) { + const msgSuffix = msg ? `: ${msg}` : "."; + throw new AssertionError(`Unimplemented${msgSuffix}`); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/unreachable.d.ts b/npm/esm/deps/deno.land/std@0.216.0/assert/unreachable.d.ts new file mode 100644 index 0000000..ce4bbf2 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/unreachable.d.ts @@ -0,0 +1,11 @@ +/** + * Use this to assert unreachable code. + * + * @example + * ```ts + * import { unreachable } from "https://deno.land/std@$STD_VERSION/assert/unreachable.ts"; + * + * unreachable(); // Throws + * ``` + */ +export declare function unreachable(): never; diff --git a/npm/esm/deps/deno.land/std@0.216.0/assert/unreachable.js b/npm/esm/deps/deno.land/std@0.216.0/assert/unreachable.js new file mode 100644 index 0000000..3144c4d --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/assert/unreachable.js @@ -0,0 +1,15 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +/** + * Use this to assert unreachable code. + * + * @example + * ```ts + * import { unreachable } from "https://deno.land/std@$STD_VERSION/assert/unreachable.ts"; + * + * unreachable(); // Throws + * ``` + */ +export function unreachable() { + throw new AssertionError("unreachable"); +} diff --git a/npm/esm/deps/deno.land/std@0.216.0/fmt/colors.d.ts b/npm/esm/deps/deno.land/std@0.216.0/fmt/colors.d.ts new file mode 100644 index 0000000..a9e5fd6 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/fmt/colors.d.ts @@ -0,0 +1,285 @@ +/** RGB 8-bits per channel. Each in range `0->255` or `0x00->0xff` */ +export interface Rgb { + /** Red component value */ + r: number; + /** Green component value */ + g: number; + /** Blue component value */ + b: number; +} +/** + * Set changing text color to enabled or disabled + * @param value + */ +export declare function setColorEnabled(value: boolean): void; +/** Get whether text color change is enabled or disabled. */ +export declare function getColorEnabled(): boolean; +/** + * Reset the text modified. + * @param str text to reset + */ +export declare function reset(str: string): string; +/** + * Make the text bold. + * @param str text to make bold + */ +export declare function bold(str: string): string; +/** + * The text emits only a small amount of light. + * @param str text to dim + * + * Warning: Not all terminal emulators support `dim`. + * For compatibility across all terminals, use {@linkcode gray} or {@linkcode brightBlack} instead. + */ +export declare function dim(str: string): string; +/** + * Make the text italic. + * @param str text to make italic + */ +export declare function italic(str: string): string; +/** + * Make the text underline. + * @param str text to underline + */ +export declare function underline(str: string): string; +/** + * Invert background color and text color. + * @param str text to invert its color + */ +export declare function inverse(str: string): string; +/** + * Make the text hidden. + * @param str text to hide + */ +export declare function hidden(str: string): string; +/** + * Put horizontal line through the center of the text. + * @param str text to strike through + */ +export declare function strikethrough(str: string): string; +/** + * Set text color to black. + * @param str text to make black + */ +export declare function black(str: string): string; +/** + * Set text color to red. + * @param str text to make red + */ +export declare function red(str: string): string; +/** + * Set text color to green. + * @param str text to make green + */ +export declare function green(str: string): string; +/** + * Set text color to yellow. + * @param str text to make yellow + */ +export declare function yellow(str: string): string; +/** + * Set text color to blue. + * @param str text to make blue + */ +export declare function blue(str: string): string; +/** + * Set text color to magenta. + * @param str text to make magenta + */ +export declare function magenta(str: string): string; +/** + * Set text color to cyan. + * @param str text to make cyan + */ +export declare function cyan(str: string): string; +/** + * Set text color to white. + * @param str text to make white + */ +export declare function white(str: string): string; +/** + * Set text color to gray. + * @param str text to make gray + */ +export declare function gray(str: string): string; +/** + * Set text color to bright black. + * @param str text to make bright-black + */ +export declare function brightBlack(str: string): string; +/** + * Set text color to bright red. + * @param str text to make bright-red + */ +export declare function brightRed(str: string): string; +/** + * Set text color to bright green. + * @param str text to make bright-green + */ +export declare function brightGreen(str: string): string; +/** + * Set text color to bright yellow. + * @param str text to make bright-yellow + */ +export declare function brightYellow(str: string): string; +/** + * Set text color to bright blue. + * @param str text to make bright-blue + */ +export declare function brightBlue(str: string): string; +/** + * Set text color to bright magenta. + * @param str text to make bright-magenta + */ +export declare function brightMagenta(str: string): string; +/** + * Set text color to bright cyan. + * @param str text to make bright-cyan + */ +export declare function brightCyan(str: string): string; +/** + * Set text color to bright white. + * @param str text to make bright-white + */ +export declare function brightWhite(str: string): string; +/** + * Set background color to black. + * @param str text to make its background black + */ +export declare function bgBlack(str: string): string; +/** + * Set background color to red. + * @param str text to make its background red + */ +export declare function bgRed(str: string): string; +/** + * Set background color to green. + * @param str text to make its background green + */ +export declare function bgGreen(str: string): string; +/** + * Set background color to yellow. + * @param str text to make its background yellow + */ +export declare function bgYellow(str: string): string; +/** + * Set background color to blue. + * @param str text to make its background blue + */ +export declare function bgBlue(str: string): string; +/** + * Set background color to magenta. + * @param str text to make its background magenta + */ +export declare function bgMagenta(str: string): string; +/** + * Set background color to cyan. + * @param str text to make its background cyan + */ +export declare function bgCyan(str: string): string; +/** + * Set background color to white. + * @param str text to make its background white + */ +export declare function bgWhite(str: string): string; +/** + * Set background color to bright black. + * @param str text to make its background bright-black + */ +export declare function bgBrightBlack(str: string): string; +/** + * Set background color to bright red. + * @param str text to make its background bright-red + */ +export declare function bgBrightRed(str: string): string; +/** + * Set background color to bright green. + * @param str text to make its background bright-green + */ +export declare function bgBrightGreen(str: string): string; +/** + * Set background color to bright yellow. + * @param str text to make its background bright-yellow + */ +export declare function bgBrightYellow(str: string): string; +/** + * Set background color to bright blue. + * @param str text to make its background bright-blue + */ +export declare function bgBrightBlue(str: string): string; +/** + * Set background color to bright magenta. + * @param str text to make its background bright-magenta + */ +export declare function bgBrightMagenta(str: string): string; +/** + * Set background color to bright cyan. + * @param str text to make its background bright-cyan + */ +export declare function bgBrightCyan(str: string): string; +/** + * Set background color to bright white. + * @param str text to make its background bright-white + */ +export declare function bgBrightWhite(str: string): string; +/** + * Set text color using paletted 8bit colors. + * https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @param str text color to apply paletted 8bit colors to + * @param color code + */ +export declare function rgb8(str: string, color: number): string; +/** + * Set background color using paletted 8bit colors. + * https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @param str text color to apply paletted 8bit background colors to + * @param color code + */ +export declare function bgRgb8(str: string, color: number): string; +/** + * Set text color using 24bit rgb. + * `color` can be a number in range `0x000000` to `0xffffff` or + * an `Rgb`. + * + * To produce the color magenta: + * + * ```ts + * import { rgb24 } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts"; + * + * rgb24("foo", 0xff00ff); + * rgb24("foo", {r: 255, g: 0, b: 255}); + * ``` + * @param str text color to apply 24bit rgb to + * @param color code + */ +export declare function rgb24(str: string, color: number | Rgb): string; +/** + * Set background color using 24bit rgb. + * `color` can be a number in range `0x000000` to `0xffffff` or + * an `Rgb`. + * + * To produce the color magenta: + * + * ```ts + * import { bgRgb24 } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts"; + * + * bgRgb24("foo", 0xff00ff); + * bgRgb24("foo", {r: 255, g: 0, b: 255}); + * ``` + * @param str text color to apply 24bit rgb to + * @param color code + */ +export declare function bgRgb24(str: string, color: number | Rgb): string; +/** + * Remove ANSI escape codes from the string. + * @param string to remove ANSI escape codes from + * + * @deprecated (will be removed in 1.0.0) Use {@linkcode stripAnsiCode} instead. + */ +export declare function stripColor(string: string): string; +/** + * Remove ANSI escape codes from the string. + * + * @param string to remove ANSI escape codes from + */ +export declare function stripAnsiCode(string: string): string; diff --git a/npm/esm/deps/deno.land/std@0.216.0/fmt/colors.js b/npm/esm/deps/deno.land/std@0.216.0/fmt/colors.js new file mode 100644 index 0000000..c2cace5 --- /dev/null +++ b/npm/esm/deps/deno.land/std@0.216.0/fmt/colors.js @@ -0,0 +1,489 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. +// A module to print ANSI terminal colors. Inspired by chalk, kleur, and colors +// on npm. +/** + * String formatters and utilities for dealing with ANSI color codes. + * + * This module is browser compatible. + * + * This module supports `NO_COLOR` environmental variable disabling any coloring + * if `NO_COLOR` is set. + * + * @example + * ```ts + * import { + * bgBlue, + * bgRgb24, + * bgRgb8, + * bold, + * italic, + * red, + * rgb24, + * rgb8, + * } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts"; + * + * console.log(bgBlue(italic(red(bold("Hello, World!"))))); + * + * // also supports 8bit colors + * + * console.log(rgb8("Hello, World!", 42)); + * + * console.log(bgRgb8("Hello, World!", 42)); + * + * // and 24bit rgb + * + * console.log(rgb24("Hello, World!", { + * r: 41, + * g: 42, + * b: 43, + * })); + * + * console.log(bgRgb24("Hello, World!", { + * r: 41, + * g: 42, + * b: 43, + * })); + * ``` + * + * @module + */ +// deno-lint-ignore no-explicit-any +import * as dntShim from "../../../../_dnt.test_shims.js"; +const { Deno } = dntShim.dntGlobalThis; +const noColor = typeof Deno?.noColor === "boolean" + ? Deno.noColor + : false; +let enabled = !noColor; +/** + * Set changing text color to enabled or disabled + * @param value + */ +export function setColorEnabled(value) { + if (Deno?.noColor) { + return; + } + enabled = value; +} +/** Get whether text color change is enabled or disabled. */ +export function getColorEnabled() { + return enabled; +} +/** + * Builds color code + * @param open + * @param close + */ +function code(open, close) { + return { + open: `\x1b[${open.join(";")}m`, + close: `\x1b[${close}m`, + regexp: new RegExp(`\\x1b\\[${close}m`, "g"), + }; +} +/** + * Applies color and background based on color code and its associated text + * @param str text to apply color settings to + * @param code color code to apply + */ +function run(str, code) { + return enabled + ? `${code.open}${str.replace(code.regexp, code.open)}${code.close}` + : str; +} +/** + * Reset the text modified. + * @param str text to reset + */ +export function reset(str) { + return run(str, code([0], 0)); +} +/** + * Make the text bold. + * @param str text to make bold + */ +export function bold(str) { + return run(str, code([1], 22)); +} +/** + * The text emits only a small amount of light. + * @param str text to dim + * + * Warning: Not all terminal emulators support `dim`. + * For compatibility across all terminals, use {@linkcode gray} or {@linkcode brightBlack} instead. + */ +export function dim(str) { + return run(str, code([2], 22)); +} +/** + * Make the text italic. + * @param str text to make italic + */ +export function italic(str) { + return run(str, code([3], 23)); +} +/** + * Make the text underline. + * @param str text to underline + */ +export function underline(str) { + return run(str, code([4], 24)); +} +/** + * Invert background color and text color. + * @param str text to invert its color + */ +export function inverse(str) { + return run(str, code([7], 27)); +} +/** + * Make the text hidden. + * @param str text to hide + */ +export function hidden(str) { + return run(str, code([8], 28)); +} +/** + * Put horizontal line through the center of the text. + * @param str text to strike through + */ +export function strikethrough(str) { + return run(str, code([9], 29)); +} +/** + * Set text color to black. + * @param str text to make black + */ +export function black(str) { + return run(str, code([30], 39)); +} +/** + * Set text color to red. + * @param str text to make red + */ +export function red(str) { + return run(str, code([31], 39)); +} +/** + * Set text color to green. + * @param str text to make green + */ +export function green(str) { + return run(str, code([32], 39)); +} +/** + * Set text color to yellow. + * @param str text to make yellow + */ +export function yellow(str) { + return run(str, code([33], 39)); +} +/** + * Set text color to blue. + * @param str text to make blue + */ +export function blue(str) { + return run(str, code([34], 39)); +} +/** + * Set text color to magenta. + * @param str text to make magenta + */ +export function magenta(str) { + return run(str, code([35], 39)); +} +/** + * Set text color to cyan. + * @param str text to make cyan + */ +export function cyan(str) { + return run(str, code([36], 39)); +} +/** + * Set text color to white. + * @param str text to make white + */ +export function white(str) { + return run(str, code([37], 39)); +} +/** + * Set text color to gray. + * @param str text to make gray + */ +export function gray(str) { + return brightBlack(str); +} +/** + * Set text color to bright black. + * @param str text to make bright-black + */ +export function brightBlack(str) { + return run(str, code([90], 39)); +} +/** + * Set text color to bright red. + * @param str text to make bright-red + */ +export function brightRed(str) { + return run(str, code([91], 39)); +} +/** + * Set text color to bright green. + * @param str text to make bright-green + */ +export function brightGreen(str) { + return run(str, code([92], 39)); +} +/** + * Set text color to bright yellow. + * @param str text to make bright-yellow + */ +export function brightYellow(str) { + return run(str, code([93], 39)); +} +/** + * Set text color to bright blue. + * @param str text to make bright-blue + */ +export function brightBlue(str) { + return run(str, code([94], 39)); +} +/** + * Set text color to bright magenta. + * @param str text to make bright-magenta + */ +export function brightMagenta(str) { + return run(str, code([95], 39)); +} +/** + * Set text color to bright cyan. + * @param str text to make bright-cyan + */ +export function brightCyan(str) { + return run(str, code([96], 39)); +} +/** + * Set text color to bright white. + * @param str text to make bright-white + */ +export function brightWhite(str) { + return run(str, code([97], 39)); +} +/** + * Set background color to black. + * @param str text to make its background black + */ +export function bgBlack(str) { + return run(str, code([40], 49)); +} +/** + * Set background color to red. + * @param str text to make its background red + */ +export function bgRed(str) { + return run(str, code([41], 49)); +} +/** + * Set background color to green. + * @param str text to make its background green + */ +export function bgGreen(str) { + return run(str, code([42], 49)); +} +/** + * Set background color to yellow. + * @param str text to make its background yellow + */ +export function bgYellow(str) { + return run(str, code([43], 49)); +} +/** + * Set background color to blue. + * @param str text to make its background blue + */ +export function bgBlue(str) { + return run(str, code([44], 49)); +} +/** + * Set background color to magenta. + * @param str text to make its background magenta + */ +export function bgMagenta(str) { + return run(str, code([45], 49)); +} +/** + * Set background color to cyan. + * @param str text to make its background cyan + */ +export function bgCyan(str) { + return run(str, code([46], 49)); +} +/** + * Set background color to white. + * @param str text to make its background white + */ +export function bgWhite(str) { + return run(str, code([47], 49)); +} +/** + * Set background color to bright black. + * @param str text to make its background bright-black + */ +export function bgBrightBlack(str) { + return run(str, code([100], 49)); +} +/** + * Set background color to bright red. + * @param str text to make its background bright-red + */ +export function bgBrightRed(str) { + return run(str, code([101], 49)); +} +/** + * Set background color to bright green. + * @param str text to make its background bright-green + */ +export function bgBrightGreen(str) { + return run(str, code([102], 49)); +} +/** + * Set background color to bright yellow. + * @param str text to make its background bright-yellow + */ +export function bgBrightYellow(str) { + return run(str, code([103], 49)); +} +/** + * Set background color to bright blue. + * @param str text to make its background bright-blue + */ +export function bgBrightBlue(str) { + return run(str, code([104], 49)); +} +/** + * Set background color to bright magenta. + * @param str text to make its background bright-magenta + */ +export function bgBrightMagenta(str) { + return run(str, code([105], 49)); +} +/** + * Set background color to bright cyan. + * @param str text to make its background bright-cyan + */ +export function bgBrightCyan(str) { + return run(str, code([106], 49)); +} +/** + * Set background color to bright white. + * @param str text to make its background bright-white + */ +export function bgBrightWhite(str) { + return run(str, code([107], 49)); +} +/* Special Color Sequences */ +/** + * Clam and truncate color codes + * @param n + * @param max number to truncate to + * @param min number to truncate from + */ +function clampAndTruncate(n, max = 255, min = 0) { + return Math.trunc(Math.max(Math.min(n, max), min)); +} +/** + * Set text color using paletted 8bit colors. + * https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @param str text color to apply paletted 8bit colors to + * @param color code + */ +export function rgb8(str, color) { + return run(str, code([38, 5, clampAndTruncate(color)], 39)); +} +/** + * Set background color using paletted 8bit colors. + * https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @param str text color to apply paletted 8bit background colors to + * @param color code + */ +export function bgRgb8(str, color) { + return run(str, code([48, 5, clampAndTruncate(color)], 49)); +} +/** + * Set text color using 24bit rgb. + * `color` can be a number in range `0x000000` to `0xffffff` or + * an `Rgb`. + * + * To produce the color magenta: + * + * ```ts + * import { rgb24 } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts"; + * + * rgb24("foo", 0xff00ff); + * rgb24("foo", {r: 255, g: 0, b: 255}); + * ``` + * @param str text color to apply 24bit rgb to + * @param color code + */ +export function rgb24(str, color) { + if (typeof color === "number") { + return run(str, code([38, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff], 39)); + } + return run(str, code([ + 38, + 2, + clampAndTruncate(color.r), + clampAndTruncate(color.g), + clampAndTruncate(color.b), + ], 39)); +} +/** + * Set background color using 24bit rgb. + * `color` can be a number in range `0x000000` to `0xffffff` or + * an `Rgb`. + * + * To produce the color magenta: + * + * ```ts + * import { bgRgb24 } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts"; + * + * bgRgb24("foo", 0xff00ff); + * bgRgb24("foo", {r: 255, g: 0, b: 255}); + * ``` + * @param str text color to apply 24bit rgb to + * @param color code + */ +export function bgRgb24(str, color) { + if (typeof color === "number") { + return run(str, code([48, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff], 49)); + } + return run(str, code([ + 48, + 2, + clampAndTruncate(color.r), + clampAndTruncate(color.g), + clampAndTruncate(color.b), + ], 49)); +} +// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js +const ANSI_PATTERN = new RegExp([ + "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", + "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TXZcf-nq-uy=><~]))", +].join("|"), "g"); +/** + * Remove ANSI escape codes from the string. + * @param string to remove ANSI escape codes from + * + * @deprecated (will be removed in 1.0.0) Use {@linkcode stripAnsiCode} instead. + */ +export function stripColor(string) { + return stripAnsiCode(string); +} +/** + * Remove ANSI escape codes from the string. + * + * @param string to remove ANSI escape codes from + */ +export function stripAnsiCode(string) { + return string.replace(ANSI_PATTERN, ""); +} diff --git a/npm/esm/mod.d.ts b/npm/esm/mod.d.ts new file mode 100644 index 0000000..7434bbb --- /dev/null +++ b/npm/esm/mod.d.ts @@ -0,0 +1,4 @@ +export { MlKemError } from "./src/errors.js"; +export { MlKem512 } from "./src/kyber512.js"; +export { MlKem768 } from "./src/kyber768.js"; +export { MlKem1024 } from "./src/kyber1024.js"; diff --git a/npm/esm/mod.js b/npm/esm/mod.js new file mode 100644 index 0000000..7434bbb --- /dev/null +++ b/npm/esm/mod.js @@ -0,0 +1,4 @@ +export { MlKemError } from "./src/errors.js"; +export { MlKem512 } from "./src/kyber512.js"; +export { MlKem768 } from "./src/kyber768.js"; +export { MlKem1024 } from "./src/kyber1024.js"; diff --git a/npm/esm/package.json b/npm/esm/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/npm/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/npm/esm/src/consts.d.ts b/npm/esm/src/consts.d.ts new file mode 100644 index 0000000..05dbd2a --- /dev/null +++ b/npm/esm/src/consts.d.ts @@ -0,0 +1,10 @@ +/** + * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, + * which was deveploped under the MIT licence below: + * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE + */ +export declare const N = 256; +export declare const Q = 3329; +export declare const Q_INV = 62209; +export declare const NTT_ZETAS: number[]; +export declare const NTT_ZETAS_INV: number[]; diff --git a/npm/esm/src/consts.js b/npm/esm/src/consts.js new file mode 100644 index 0000000..d7a076e --- /dev/null +++ b/npm/esm/src/consts.js @@ -0,0 +1,34 @@ +/** + * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, + * which was deveploped under the MIT licence below: + * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE + */ +export const N = 256; +export const Q = 3329; +export const Q_INV = 62209; +// deno-fmt-ignore +export const NTT_ZETAS = [ + 2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962, + 2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017, + 732, 608, 1787, 411, 3124, 1758, 1223, 652, 2777, 1015, 2036, 1491, 3047, + 1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, 2476, 3239, 3058, 830, + 107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, 2226, + 430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574, + 1653, 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349, + 418, 329, 3173, 3254, 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193, + 1218, 1994, 2455, 220, 2142, 1670, 2144, 1799, 2051, 794, 1819, 2475, 2459, + 478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628, +]; +// deno-fmt-ignore +export const NTT_ZETAS_INV = [ + 1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535, + 1278, 1530, 1185, 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465, + 1285, 2007, 2719, 2726, 2232, 2512, 75, 156, 3000, 2911, 2980, 872, 2685, + 1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, 1676, 1755, 460, 291, 235, + 3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, 1275, 2652, + 1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853, + 1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552, + 2677, 2106, 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871, + 829, 2946, 3065, 1325, 2756, 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171, + 3127, 3042, 1907, 1836, 1517, 359, 758, 1441, +]; diff --git a/npm/esm/src/deps.d.ts b/npm/esm/src/deps.d.ts new file mode 100644 index 0000000..caf7f77 --- /dev/null +++ b/npm/esm/src/deps.d.ts @@ -0,0 +1 @@ +export { sha3_256, sha3_512, shake128, shake256, } from "@openpgp/noble-hashes/sha3"; diff --git a/npm/esm/src/deps.js b/npm/esm/src/deps.js new file mode 100644 index 0000000..caf7f77 --- /dev/null +++ b/npm/esm/src/deps.js @@ -0,0 +1 @@ +export { sha3_256, sha3_512, shake128, shake256, } from "@openpgp/noble-hashes/sha3"; diff --git a/npm/esm/src/errors.d.ts b/npm/esm/src/errors.d.ts new file mode 100644 index 0000000..c0e1484 --- /dev/null +++ b/npm/esm/src/errors.d.ts @@ -0,0 +1,6 @@ +/** + * The base error class of kyber-ts. + */ +export declare class MlKemError extends Error { + constructor(e: unknown); +} diff --git a/npm/esm/src/errors.js b/npm/esm/src/errors.js new file mode 100644 index 0000000..2cc9f4f --- /dev/null +++ b/npm/esm/src/errors.js @@ -0,0 +1,19 @@ +/** + * The base error class of kyber-ts. + */ +export class MlKemError extends Error { + constructor(e) { + let message; + if (e instanceof Error) { + message = e.message; + } + else if (typeof e === "string") { + message = e; + } + else { + message = ""; + } + super(message); + this.name = this.constructor.name; + } +} diff --git a/npm/esm/src/kyber1024.d.ts b/npm/esm/src/kyber1024.d.ts new file mode 100644 index 0000000..95958fa --- /dev/null +++ b/npm/esm/src/kyber1024.d.ts @@ -0,0 +1,68 @@ +import { KyberBase } from "./kyberBase.js"; +/** + * Represents the MlKem1024 class. + * + * MlKem1024 is a subclass of KyberBase and implements specific methods for the Kyber-1024 parameter set. + * + * @example + * + * ```ts + * // import { MlKem1024 } from "crystals-kyber-js"; // Node.js + * import { MlKem1024 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const recipient = new MlKem1024(); + * const [pkR, skR] = await recipient.generateKeyPair(); + + * const sender = new MlKem1024(); + * const [ct, ssS] = await sender.encap(pkR); + + * const ssR = await recipient.decap(ct, skR); + * // ssS === ssR + * ``` + */ +export declare class MlKem1024 extends KyberBase { + protected _k: number; + protected _du: number; + protected _dv: number; + protected _eta1: number; + protected _eta2: number; + /** + * Constructs a new instance of the MlKem1024 class. + */ + constructor(); + /** + * Lossily compresses and serializes a vector of polynomials. + * + * @param u - The vector of polynomials to compress. + * @returns The compressed and serialized data as a Uint8Array. + */ + protected _compressU(r: Uint8Array, u: Array>): Uint8Array; + /** + * Lossily compresses and serializes a polynomial. + * + * @param r - The output buffer to store the compressed data. + * @param v - The polynomial to compress. + * @returns The compressed and serialized data as a Uint8Array. + */ + protected _compressV(r: Uint8Array, v: Array): Uint8Array; + /** + * Deserializes and decompresses a vector of polynomials. + * This is the approximate inverse of the `_compressU` method. + * Since compression is lossy, the decompressed data may not match the original vector of polynomials. + * + * @param a - The compressed and serialized data as a Uint8Array. + * @returns The decompressed vector of polynomials. + */ + protected _decompressU(a: Uint8Array): Array>; + /** + * Decompresses a given polynomial, representing the approximate inverse of + * compress2, in Uint8Array into an array of numbers. + * + * Note that compression is lossy, and thus decompression will not match the + * original input. + * + * @param a - The Uint8Array to decompress. + * @returns An array of numbers obtained from the decompression process. + */ + protected _decompressV(a: Uint8Array): Array; +} diff --git a/npm/esm/src/kyber1024.js b/npm/esm/src/kyber1024.js new file mode 100644 index 0000000..4f37192 --- /dev/null +++ b/npm/esm/src/kyber1024.js @@ -0,0 +1,193 @@ +/** + * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, + * which was deveploped under the MIT licence below: + * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE + */ +import { N, Q } from "./consts.js"; +import { KyberBase } from "./kyberBase.js"; +import { byte, int16, uint16, uint32 } from "./utils.js"; +/** + * Represents the MlKem1024 class. + * + * MlKem1024 is a subclass of KyberBase and implements specific methods for the Kyber-1024 parameter set. + * + * @example + * + * ```ts + * // import { MlKem1024 } from "crystals-kyber-js"; // Node.js + * import { MlKem1024 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const recipient = new MlKem1024(); + * const [pkR, skR] = await recipient.generateKeyPair(); + + * const sender = new MlKem1024(); + * const [ct, ssS] = await sender.encap(pkR); + + * const ssR = await recipient.decap(ct, skR); + * // ssS === ssR + * ``` + */ +export class MlKem1024 extends KyberBase { + /** + * Constructs a new instance of the MlKem1024 class. + */ + constructor() { + super(); + Object.defineProperty(this, "_k", { + enumerable: true, + configurable: true, + writable: true, + value: 4 + }); + Object.defineProperty(this, "_du", { + enumerable: true, + configurable: true, + writable: true, + value: 11 + }); + Object.defineProperty(this, "_dv", { + enumerable: true, + configurable: true, + writable: true, + value: 5 + }); + Object.defineProperty(this, "_eta1", { + enumerable: true, + configurable: true, + writable: true, + value: 2 + }); + Object.defineProperty(this, "_eta2", { + enumerable: true, + configurable: true, + writable: true, + value: 2 + }); + this._skSize = 12 * this._k * N / 8; + this._pkSize = this._skSize + 32; + this._compressedUSize = this._k * this._du * N / 8; + this._compressedVSize = this._dv * N / 8; + } + // compressU lossily compresses and serializes a vector of polynomials. + /** + * Lossily compresses and serializes a vector of polynomials. + * + * @param u - The vector of polynomials to compress. + * @returns The compressed and serialized data as a Uint8Array. + */ + _compressU(r, u) { + const t = new Array(8); + for (let rr = 0, i = 0; i < this._k; i++) { + for (let j = 0; j < N / 8; j++) { + for (let k = 0; k < 8; k++) { + t[k] = uint16((((uint32(u[i][8 * j + k]) << 11) + uint32(Q / 2)) / + uint32(Q)) & 0x7ff); + } + r[rr++] = byte(t[0] >> 0); + r[rr++] = byte((t[0] >> 8) | (t[1] << 3)); + r[rr++] = byte((t[1] >> 5) | (t[2] << 6)); + r[rr++] = byte(t[2] >> 2); + r[rr++] = byte((t[2] >> 10) | (t[3] << 1)); + r[rr++] = byte((t[3] >> 7) | (t[4] << 4)); + r[rr++] = byte((t[4] >> 4) | (t[5] << 7)); + r[rr++] = byte(t[5] >> 1); + r[rr++] = byte((t[5] >> 9) | (t[6] << 2)); + r[rr++] = byte((t[6] >> 6) | (t[7] << 5)); + r[rr++] = byte(t[7] >> 3); + } + } + return r; + } + // compressV lossily compresses and subsequently serializes a polynomial. + /** + * Lossily compresses and serializes a polynomial. + * + * @param r - The output buffer to store the compressed data. + * @param v - The polynomial to compress. + * @returns The compressed and serialized data as a Uint8Array. + */ + _compressV(r, v) { + const t = new Uint8Array(8); + for (let rr = 0, i = 0; i < N / 8; i++) { + for (let j = 0; j < 8; j++) { + t[j] = byte(((uint32(v[8 * i + j]) << 5) + uint32(Q / 2)) / uint32(Q)) & 31; + } + r[rr++] = byte((t[0] >> 0) | (t[1] << 5)); + r[rr++] = byte((t[1] >> 3) | (t[2] << 2) | (t[3] << 7)); + r[rr++] = byte((t[3] >> 1) | (t[4] << 4)); + r[rr++] = byte((t[4] >> 4) | (t[5] << 1) | (t[6] << 6)); + r[rr++] = byte((t[6] >> 2) | (t[7] << 3)); + } + return r; + } + // decompressU de-serializes and decompresses a vector of polynomials and + // represents the approximate inverse of compress1. Since compression is lossy, + // the results of decompression will may not match the original vector of polynomials. + /** + * Deserializes and decompresses a vector of polynomials. + * This is the approximate inverse of the `_compressU` method. + * Since compression is lossy, the decompressed data may not match the original vector of polynomials. + * + * @param a - The compressed and serialized data as a Uint8Array. + * @returns The decompressed vector of polynomials. + */ + _decompressU(a) { + const r = new Array(this._k); + for (let i = 0; i < this._k; i++) { + r[i] = new Array(384); + } + const t = new Array(8); + for (let aa = 0, i = 0; i < this._k; i++) { + for (let j = 0; j < N / 8; j++) { + t[0] = (uint16(a[aa + 0]) >> 0) | (uint16(a[aa + 1]) << 8); + t[1] = (uint16(a[aa + 1]) >> 3) | (uint16(a[aa + 2]) << 5); + t[2] = (uint16(a[aa + 2]) >> 6) | (uint16(a[aa + 3]) << 2) | + (uint16(a[aa + 4]) << 10); + t[3] = (uint16(a[aa + 4]) >> 1) | (uint16(a[aa + 5]) << 7); + t[4] = (uint16(a[aa + 5]) >> 4) | (uint16(a[aa + 6]) << 4); + t[5] = (uint16(a[aa + 6]) >> 7) | (uint16(a[aa + 7]) << 1) | + (uint16(a[aa + 8]) << 9); + t[6] = (uint16(a[aa + 8]) >> 2) | (uint16(a[aa + 9]) << 6); + t[7] = (uint16(a[aa + 9]) >> 5) | (uint16(a[aa + 10]) << 3); + aa = aa + 11; + for (let k = 0; k < 8; k++) { + r[i][8 * j + k] = (uint32(t[k] & 0x7FF) * Q + 1024) >> 11; + } + } + } + return r; + } + // decompressV de-serializes and subsequently decompresses a polynomial, + // representing the approximate inverse of compress2. + // Note that compression is lossy, and thus decompression will not match the + // original input. + /** + * Decompresses a given polynomial, representing the approximate inverse of + * compress2, in Uint8Array into an array of numbers. + * + * Note that compression is lossy, and thus decompression will not match the + * original input. + * + * @param a - The Uint8Array to decompress. + * @returns An array of numbers obtained from the decompression process. + */ + _decompressV(a) { + const r = new Array(384); + const t = new Array(8); + for (let aa = 0, i = 0; i < N / 8; i++) { + t[0] = a[aa + 0] >> 0; + t[1] = (a[aa + 0] >> 5) | (a[aa + 1] << 3); + t[2] = a[aa + 1] >> 2; + t[3] = (a[aa + 1] >> 7) | (a[aa + 2] << 1); + t[4] = (a[aa + 2] >> 4) | (a[aa + 3] << 4); + t[5] = a[aa + 3] >> 1; + t[6] = (a[aa + 3] >> 6) | (a[aa + 4] << 2); + t[7] = a[aa + 4] >> 3; + aa = aa + 5; + for (let j = 0; j < 8; j++) { + r[8 * i + j] = int16(((uint32(t[j] & 31) * uint32(Q)) + 16) >> 5); + } + } + return r; + } +} diff --git a/npm/esm/src/kyber512.d.ts b/npm/esm/src/kyber512.d.ts new file mode 100644 index 0000000..5c4af1a --- /dev/null +++ b/npm/esm/src/kyber512.d.ts @@ -0,0 +1,37 @@ +import { KyberBase } from "./kyberBase.js"; +/** + * Represents the MlKem512 class. + * + * This class extends the KyberBase class and provides specific implementation for MlKem512. + * + * @remarks + * + * MlKem512 is a specific implementation of the Kyber key encapsulation mechanism. + * + * @example + * + * ```ts + * // import { MlKem512 } from "crystals-kyber-js"; // Node.js + * import { MlKem512 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const recipient = new MlKem512(); + * const [pkR, skR] = await recipient.generateKeyPair(); + * + * const sender = new MlKem512(); + * const [ct, ssS] = await sender.encap(pkR); + * + * const ssR = await recipient.decap(ct, skR); + * // ssS === ssR + * ``` + */ +export declare class MlKem512 extends KyberBase { + protected _k: number; + protected _du: number; + protected _dv: number; + protected _eta1: number; + protected _eta2: number; + /** + * Constructs a new instance of the MlKem512 class. + */ + constructor(); +} diff --git a/npm/esm/src/kyber512.js b/npm/esm/src/kyber512.js new file mode 100644 index 0000000..5b5b3b2 --- /dev/null +++ b/npm/esm/src/kyber512.js @@ -0,0 +1,115 @@ +/** + * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, + * which was deveploped under the MIT licence below: + * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE + */ +import { N } from "./consts.js"; +import { KyberBase } from "./kyberBase.js"; +import { byteopsLoad24, int16, prf } from "./utils.js"; +/** + * Represents the MlKem512 class. + * + * This class extends the KyberBase class and provides specific implementation for MlKem512. + * + * @remarks + * + * MlKem512 is a specific implementation of the Kyber key encapsulation mechanism. + * + * @example + * + * ```ts + * // import { MlKem512 } from "crystals-kyber-js"; // Node.js + * import { MlKem512 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const recipient = new MlKem512(); + * const [pkR, skR] = await recipient.generateKeyPair(); + * + * const sender = new MlKem512(); + * const [ct, ssS] = await sender.encap(pkR); + * + * const ssR = await recipient.decap(ct, skR); + * // ssS === ssR + * ``` + */ +export class MlKem512 extends KyberBase { + /** + * Constructs a new instance of the MlKem512 class. + */ + constructor() { + super(); + Object.defineProperty(this, "_k", { + enumerable: true, + configurable: true, + writable: true, + value: 2 + }); + Object.defineProperty(this, "_du", { + enumerable: true, + configurable: true, + writable: true, + value: 10 + }); + Object.defineProperty(this, "_dv", { + enumerable: true, + configurable: true, + writable: true, + value: 4 + }); + Object.defineProperty(this, "_eta1", { + enumerable: true, + configurable: true, + writable: true, + value: 3 + }); + Object.defineProperty(this, "_eta2", { + enumerable: true, + configurable: true, + writable: true, + value: 2 + }); + this._skSize = 12 * this._k * N / 8; + this._pkSize = this._skSize + 32; + this._compressedUSize = this._k * this._du * N / 8; + this._compressedVSize = this._dv * N / 8; + } + /** + * Samples a vector of polynomials from a seed. + * @internal + * @param sigma - The seed. + * @param offset - The offset. + * @param size - The size. + * @returns The sampled vector of polynomials. + */ + _sampleNoise1(sigma, offset, size) { + const r = new Array(size); + for (let i = 0; i < size; i++) { + r[i] = byteopsCbd(prf(this._eta1 * N / 4, sigma, offset), this._eta1); + offset++; + } + return r; + } +} +/** + * Performs the byte operations for the Cbd function. + * + * @param buf - The input buffer. + * @param eta - The value of eta. + * @returns An array of numbers representing the result of the byte operations. + */ +function byteopsCbd(buf, eta) { + let t, d; + let a, b; + const r = new Array(384).fill(0); + for (let i = 0; i < N / 4; i++) { + t = byteopsLoad24(buf.subarray(3 * i, buf.length)); + d = t & 0x00249249; + d = d + ((t >> 1) & 0x00249249); + d = d + ((t >> 2) & 0x00249249); + for (let j = 0; j < 4; j++) { + a = int16((d >> (6 * j + 0)) & 0x7); + b = int16((d >> (6 * j + eta)) & 0x7); + r[4 * i + j] = a - b; + } + } + return r; +} diff --git a/npm/esm/src/kyber768.d.ts b/npm/esm/src/kyber768.d.ts new file mode 100644 index 0000000..5fc8291 --- /dev/null +++ b/npm/esm/src/kyber768.d.ts @@ -0,0 +1,34 @@ +import { KyberBase } from "./kyberBase.js"; +/** + * Represents the MlKem768 class, which extends the KyberBase class. + * + * MlKem768 is a specific implementation of the Kyber key encapsulation mechanism. + * + * @remarks + * + * This class extends the KyberBase class and provides specific implementation for MlKem768. + * + * @example + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const recipient = new MlKem768(); + * const [pkR, skR] = await recipient.generateKeyPair(); + * + * const sender = new MlKem768(); + * const [ct, ssS] = await sender.encap(pkR); + * + * const ssR = await recipient.decap(ct, skR); + * // ssS === ssR + * ``` + */ +export declare class MlKem768 extends KyberBase { + protected _k: number; + protected _du: number; + protected _dv: number; + protected _eta1: number; + protected _eta2: number; + constructor(); +} diff --git a/npm/esm/src/kyber768.js b/npm/esm/src/kyber768.js new file mode 100644 index 0000000..2aa3ceb --- /dev/null +++ b/npm/esm/src/kyber768.js @@ -0,0 +1,71 @@ +/** + * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, + * which was deveploped under the MIT licence below: + * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE + */ +import { N } from "./consts.js"; +import { KyberBase } from "./kyberBase.js"; +/** + * Represents the MlKem768 class, which extends the KyberBase class. + * + * MlKem768 is a specific implementation of the Kyber key encapsulation mechanism. + * + * @remarks + * + * This class extends the KyberBase class and provides specific implementation for MlKem768. + * + * @example + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const recipient = new MlKem768(); + * const [pkR, skR] = await recipient.generateKeyPair(); + * + * const sender = new MlKem768(); + * const [ct, ssS] = await sender.encap(pkR); + * + * const ssR = await recipient.decap(ct, skR); + * // ssS === ssR + * ``` + */ +export class MlKem768 extends KyberBase { + constructor() { + super(); + Object.defineProperty(this, "_k", { + enumerable: true, + configurable: true, + writable: true, + value: 3 + }); + Object.defineProperty(this, "_du", { + enumerable: true, + configurable: true, + writable: true, + value: 10 + }); + Object.defineProperty(this, "_dv", { + enumerable: true, + configurable: true, + writable: true, + value: 4 + }); + Object.defineProperty(this, "_eta1", { + enumerable: true, + configurable: true, + writable: true, + value: 2 + }); + Object.defineProperty(this, "_eta2", { + enumerable: true, + configurable: true, + writable: true, + value: 2 + }); + this._skSize = 12 * this._k * N / 8; + this._pkSize = this._skSize + 32; + this._compressedUSize = this._k * this._du * N / 8; + this._compressedVSize = this._dv * N / 8; + } +} diff --git a/npm/esm/src/kyberBase.d.ts b/npm/esm/src/kyberBase.d.ts new file mode 100644 index 0000000..2de91a1 --- /dev/null +++ b/npm/esm/src/kyberBase.d.ts @@ -0,0 +1,247 @@ +/** + * Represents the base class for the Kyber key encapsulation mechanism. + * + * This class provides the base implementation for the Kyber key encapsulation mechanism. + * + * @remarks + * + * This class is not intended to be used directly. Instead, use one of the subclasses: + * + * @example + * + * ```ts + * // import { KyberBase } from "crystals-kyber-js"; // Node.js + * import { KyberBase } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * class MlKem768 extends KyberBase { + * protected _k = 3; + * protected _du = 10; + * protected _dv = 4; + * protected _eta1 = 2; + * protected _eta2 = 2; + * + * constructor() { + * super(); + * this._skSize = 12 * this._k * N / 8; + * this._pkSize = this._skSize + 32; + * this._compressedUSize = this._k * this._du * N / 8; + * this._compressedVSize = this._dv * N / 8; + * } + * } + * + * const kyber = new MlKem768(); + * ``` + */ +export declare class KyberBase { + private _api; + protected _k: number; + protected _du: number; + protected _dv: number; + protected _eta1: number; + protected _eta2: number; + protected _skSize: number; + protected _pkSize: number; + protected _compressedUSize: number; + protected _compressedVSize: number; + /** + * Creates a new instance of the KyberBase class. + */ + constructor(); + /** + * Generates a keypair [publicKey, privateKey]. + * + * If an error occurred, throws {@link MlKemError}. + * + * @returns A kaypair [publicKey, privateKey]. + * @throws {@link MlKemError} + * + * @example Generates a {@link MlKem768} keypair. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const [pk, sk] = await kyber.generateKeyPair(); + * ``` + */ + generateKeyPair(): Promise<[Uint8Array, Uint8Array]>; + /** + * Derives a keypair [publicKey, privateKey] deterministically from a 64-octet seed. + * + * If an error occurred, throws {@link MlKemError}. + * + * @param seed A 64-octet seed for the deterministic key generation. + * @returns A kaypair [publicKey, privateKey]. + * @throws {@link MlKemError} + * + * @example Derives a {@link MlKem768} keypair deterministically. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const seed = new Uint8Array(64); + * globalThis.crypto.getRandomValues(seed); + * const [pk, sk] = await kyber.deriveKeyPair(seed); + * ``` + */ + deriveKeyPair(seed: Uint8Array): Promise<[Uint8Array, Uint8Array]>; + /** + * Generates a shared secret from the encapsulated ciphertext and the private key. + * + * If an error occurred, throws {@link MlKemError}. + * + * @param pk A public key. + * @param seed An optional 32-octet seed for the deterministic shared secret generation. + * @returns A ciphertext (encapsulated public key) and a shared secret. + * @throws {@link MlKemError} + * + * @example The {@link MlKem768} encapsulation. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const [pk, sk] = await kyber.generateKeyPair(); + * const [ct, ss] = await kyber.encap(pk); + * ``` + */ + encap(pk: Uint8Array, seed?: Uint8Array): Promise<[Uint8Array, Uint8Array]>; + /** + * Generates a ciphertext for the public key and a shared secret. + * + * If an error occurred, throws {@link MlKemError}. + * + * @param ct A ciphertext generated by {@link encap}. + * @param sk A private key. + * @returns A shared secret. + * @throws {@link MlKemError} + * + * @example The {@link MlKem768} decapsulation. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const [pk, sk] = await kyber.generateKeyPair(); + * const [ct, ssS] = await kyber.encap(pk); + * const ssR = await kyber.decap(ct, sk); + * // ssS === ssR + * ``` + */ + decap(ct: Uint8Array, sk: Uint8Array): Promise; + /** + * Sets up the KyberBase instance by loading the necessary crypto library. + * If the crypto library is already loaded, this method does nothing. + * @returns {Promise} A promise that resolves when the setup is complete. + */ + protected _setup(): Promise; + /** + * Returns a Uint8Array seed for cryptographic operations. + * If no seed is provided, a random seed of length 32 bytes is generated. + * If a seed is provided, it must be exactly 32 bytes in length. + * + * @param seed - Optional seed for cryptographic operations. + * @returns A Uint8Array seed. + * @throws Error if the provided seed is not 32 bytes in length. + */ + private _getSeed; + /** + * Derives a key pair from a given seed. + * + * @param seed - The seed used for key derivation. + * @returns An array containing the public key and secret key. + */ + private _deriveKeyPair; + /** + * Derives a CPA key pair using the provided CPA seed. + * + * @param cpaSeed - The CPA seed used for key derivation. + * @returns An array containing the public key and private key. + */ + private _deriveCpaKeyPair; + /** + * K-PKE_Encrypt: Encapsulates a message using the Kyber encryption scheme. + * + * @param pk - The public key. + * @param msg - The message to be encapsulated. + * @param seed - The seed used for generating random values. + * @returns The encapsulated message as a Uint8Array. + */ + private _encap; + /** + * Decapsulates the ciphertext using the provided secret key. + * + * @param ct - The ciphertext to be decapsulated. + * @param sk - The secret key used for decapsulation. + * @returns The decapsulated message as a Uint8Array. + */ + private _decap; + /** + * Generates a sample matrix based on the provided seed and transposition flag. + * + * @param seed - The seed used for generating the matrix. + * @param transposed - A flag indicating whether the matrix should be transposed or not. + * @returns The generated sample matrix. + */ + private _sampleMatrix; + /** + * Generates a 2D array of noise samples. + * + * @param sigma - The noise parameter. + * @param offset - The offset value. + * @param size - The size of the array. + * @returns The generated 2D array of noise samples. + */ + protected _sampleNoise1(sigma: Uint8Array, offset: number, size: number): Array>; + /** + * Generates a 2-dimensional array of noise samples. + * + * @param sigma - The noise parameter. + * @param offset - The offset value. + * @param size - The size of the array. + * @returns The generated 2-dimensional array of noise samples. + */ + protected _sampleNoise2(sigma: Uint8Array, offset: number, size: number): Array>; + /** + * Converts a Uint8Array to a 2D array of numbers representing a polynomial vector. + * Each element in the resulting array represents a polynomial. + * @param a The Uint8Array to convert. + * @returns The 2D array of numbers representing the polynomial vector. + */ + private _polyvecFromBytes; + /** + * Compresses the given array of coefficients into a Uint8Array. + * + * @param r - The output Uint8Array. + * @param u - The array of coefficients. + * @returns The compressed Uint8Array. + */ + protected _compressU(r: Uint8Array, u: Array>): Uint8Array; + /** + * Compresses the given array of numbers into a Uint8Array. + * + * @param r - The Uint8Array to store the compressed values. + * @param v - The array of numbers to compress. + * @returns The compressed Uint8Array. + */ + protected _compressV(r: Uint8Array, v: Array): Uint8Array; + /** + * Decompresses a Uint8Array into a two-dimensional array of numbers. + * + * @param a The Uint8Array to decompress. + * @returns The decompressed two-dimensional array. + */ + protected _decompressU(a: Uint8Array): Array>; + /** + * Decompresses a Uint8Array into an array of numbers. + * + * @param a - The Uint8Array to decompress. + * @returns An array of numbers. + */ + protected _decompressV(a: Uint8Array): Array; +} diff --git a/npm/esm/src/kyberBase.js b/npm/esm/src/kyberBase.js new file mode 100644 index 0000000..6e837e1 --- /dev/null +++ b/npm/esm/src/kyberBase.js @@ -0,0 +1,1036 @@ +/** + * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, + * which was deveploped under the MIT licence below: + * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE + */ +import { sha3_256, sha3_512, shake128, shake256 } from "./deps.js"; +import { N, NTT_ZETAS, NTT_ZETAS_INV, Q, Q_INV } from "./consts.js"; +import { MlKemError } from "./errors.js"; +import { byte, byteopsLoad32, constantTimeCompare, equalUint8Array, int16, int32, loadCrypto, prf, uint16, uint32, } from "./utils.js"; +/** + * Represents the base class for the Kyber key encapsulation mechanism. + * + * This class provides the base implementation for the Kyber key encapsulation mechanism. + * + * @remarks + * + * This class is not intended to be used directly. Instead, use one of the subclasses: + * + * @example + * + * ```ts + * // import { KyberBase } from "crystals-kyber-js"; // Node.js + * import { KyberBase } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * class MlKem768 extends KyberBase { + * protected _k = 3; + * protected _du = 10; + * protected _dv = 4; + * protected _eta1 = 2; + * protected _eta2 = 2; + * + * constructor() { + * super(); + * this._skSize = 12 * this._k * N / 8; + * this._pkSize = this._skSize + 32; + * this._compressedUSize = this._k * this._du * N / 8; + * this._compressedVSize = this._dv * N / 8; + * } + * } + * + * const kyber = new MlKem768(); + * ``` + */ +export class KyberBase { + /** + * Creates a new instance of the KyberBase class. + */ + constructor() { + Object.defineProperty(this, "_api", { + enumerable: true, + configurable: true, + writable: true, + value: undefined + }); + Object.defineProperty(this, "_k", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + Object.defineProperty(this, "_du", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + Object.defineProperty(this, "_dv", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + Object.defineProperty(this, "_eta1", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + Object.defineProperty(this, "_eta2", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + Object.defineProperty(this, "_skSize", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + Object.defineProperty(this, "_pkSize", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + Object.defineProperty(this, "_compressedUSize", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + Object.defineProperty(this, "_compressedVSize", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + } + /** + * Generates a keypair [publicKey, privateKey]. + * + * If an error occurred, throws {@link MlKemError}. + * + * @returns A kaypair [publicKey, privateKey]. + * @throws {@link MlKemError} + * + * @example Generates a {@link MlKem768} keypair. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const [pk, sk] = await kyber.generateKeyPair(); + * ``` + */ + async generateKeyPair() { + await this._setup(); + try { + const rnd = new Uint8Array(64); + this._api.getRandomValues(rnd); + return this._deriveKeyPair(rnd); + } + catch (e) { + throw new MlKemError(e); + } + } + /** + * Derives a keypair [publicKey, privateKey] deterministically from a 64-octet seed. + * + * If an error occurred, throws {@link MlKemError}. + * + * @param seed A 64-octet seed for the deterministic key generation. + * @returns A kaypair [publicKey, privateKey]. + * @throws {@link MlKemError} + * + * @example Derives a {@link MlKem768} keypair deterministically. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const seed = new Uint8Array(64); + * globalThis.crypto.getRandomValues(seed); + * const [pk, sk] = await kyber.deriveKeyPair(seed); + * ``` + */ + async deriveKeyPair(seed) { + await this._setup(); + try { + if (seed.byteLength !== 64) { + throw new Error("seed must be 64 bytes in length"); + } + return this._deriveKeyPair(seed); + } + catch (e) { + throw new MlKemError(e); + } + } + /** + * Generates a shared secret from the encapsulated ciphertext and the private key. + * + * If an error occurred, throws {@link MlKemError}. + * + * @param pk A public key. + * @param seed An optional 32-octet seed for the deterministic shared secret generation. + * @returns A ciphertext (encapsulated public key) and a shared secret. + * @throws {@link MlKemError} + * + * @example The {@link MlKem768} encapsulation. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const [pk, sk] = await kyber.generateKeyPair(); + * const [ct, ss] = await kyber.encap(pk); + * ``` + */ + async encap(pk, seed) { + await this._setup(); + try { + // validate key type; the modulo is checked in `_encap`. + if (pk.length !== 384 * this._k + 32) { + throw new Error("invalid encapsulation key"); + } + const m = this._getSeed(seed); + const [k, r] = g(m, h(pk)); + const ct = this._encap(pk, m, r); + return [ct, k]; + } + catch (e) { + throw new MlKemError(e); + } + } + /** + * Generates a ciphertext for the public key and a shared secret. + * + * If an error occurred, throws {@link MlKemError}. + * + * @param ct A ciphertext generated by {@link encap}. + * @param sk A private key. + * @returns A shared secret. + * @throws {@link MlKemError} + * + * @example The {@link MlKem768} decapsulation. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const [pk, sk] = await kyber.generateKeyPair(); + * const [ct, ssS] = await kyber.encap(pk); + * const ssR = await kyber.decap(ct, sk); + * // ssS === ssR + * ``` + */ + async decap(ct, sk) { + await this._setup(); + try { + // ciphertext type check + if (ct.byteLength !== this._compressedUSize + this._compressedVSize) { + throw new Error("Invalid ct size"); + } + // decapsulation key type check + if (sk.length !== 768 * this._k + 96) { + throw new Error("Invalid decapsulation key"); + } + const sk2 = sk.subarray(0, this._skSize); + const pk = sk.subarray(this._skSize, this._skSize + this._pkSize); + const hpk = sk.subarray(this._skSize + this._pkSize, this._skSize + this._pkSize + 32); + const z = sk.subarray(this._skSize + this._pkSize + 32, this._skSize + this._pkSize + 64); + const m2 = this._decap(ct, sk2); + const [k2, r2] = g(m2, hpk); + const kBar = kdf(z, ct); + const ct2 = this._encap(pk, m2, r2); + return constantTimeCompare(ct, ct2) === 1 ? k2 : kBar; + } + catch (e) { + throw new MlKemError(e); + } + } + /** + * Sets up the KyberBase instance by loading the necessary crypto library. + * If the crypto library is already loaded, this method does nothing. + * @returns {Promise} A promise that resolves when the setup is complete. + */ + async _setup() { + if (this._api !== undefined) { + return; + } + this._api = await loadCrypto(); + } + /** + * Returns a Uint8Array seed for cryptographic operations. + * If no seed is provided, a random seed of length 32 bytes is generated. + * If a seed is provided, it must be exactly 32 bytes in length. + * + * @param seed - Optional seed for cryptographic operations. + * @returns A Uint8Array seed. + * @throws Error if the provided seed is not 32 bytes in length. + */ + _getSeed(seed) { + if (seed == undefined) { + const s = new Uint8Array(32); + this._api.getRandomValues(s); + return s; + } + if (seed.byteLength !== 32) { + throw new Error("seed must be 32 bytes in length"); + } + return seed; + } + /** + * Derives a key pair from a given seed. + * + * @param seed - The seed used for key derivation. + * @returns An array containing the public key and secret key. + */ + _deriveKeyPair(seed) { + const cpaSeed = seed.subarray(0, 32); + const z = seed.subarray(32, 64); + const [pk, skBody] = this._deriveCpaKeyPair(cpaSeed); + const pkh = h(pk); + const sk = new Uint8Array(this._skSize + this._pkSize + 64); + sk.set(skBody, 0); + sk.set(pk, this._skSize); + sk.set(pkh, this._skSize + this._pkSize); + sk.set(z, this._skSize + this._pkSize + 32); + return [pk, sk]; + } + // indcpaKeyGen generates public and private keys for the CPA-secure + // public-key encryption scheme underlying Kyber. + /** + * Derives a CPA key pair using the provided CPA seed. + * + * @param cpaSeed - The CPA seed used for key derivation. + * @returns An array containing the public key and private key. + */ + _deriveCpaKeyPair(cpaSeed) { + const [publicSeed, noiseSeed] = g(cpaSeed); + const a = this._sampleMatrix(publicSeed, false); + const s = this._sampleNoise1(noiseSeed, 0, this._k); + const e = this._sampleNoise1(noiseSeed, this._k, this._k); + // perform number theoretic transform on secret s + for (let i = 0; i < this._k; i++) { + s[i] = ntt(s[i]); + s[i] = reduce(s[i]); + e[i] = ntt(e[i]); + } + // KEY COMPUTATION + // pk = A*s + e + const pk = new Array(this._k); + for (let i = 0; i < this._k; i++) { + pk[i] = polyToMont(multiply(a[i], s)); + pk[i] = add(pk[i], e[i]); + pk[i] = reduce(pk[i]); + } + // PUBLIC KEY + // turn polynomials into byte arrays + const pubKey = new Uint8Array(this._pkSize); + for (let i = 0; i < this._k; i++) { + pubKey.set(polyToBytes(pk[i]), i * 384); + } + // append public seed + pubKey.set(publicSeed, this._skSize); + // PRIVATE KEY + // turn polynomials into byte arrays + const privKey = new Uint8Array(this._skSize); + for (let i = 0; i < this._k; i++) { + privKey.set(polyToBytes(s[i]), i * 384); + } + return [pubKey, privKey]; + } + // _encap is the encapsulation function of the CPA-secure + // public-key encryption scheme underlying Kyber. + /** + * K-PKE_Encrypt: Encapsulates a message using the Kyber encryption scheme. + * + * @param pk - The public key. + * @param msg - The message to be encapsulated. + * @param seed - The seed used for generating random values. + * @returns The encapsulated message as a Uint8Array. + */ + _encap(pk, msg, seed) { + const tHat = new Array(this._k); + const pkCheck = new Uint8Array(384 * this._k); // to validate the pk modulo (see input validation at NIST draft 6.2) + for (let i = 0; i < this._k; i++) { + tHat[i] = polyFromBytes(pk.subarray(i * 384, (i + 1) * 384)); + pkCheck.set(polyToBytes(tHat[i]), i * 384); + } + if (!equalUint8Array(pk.subarray(0, pkCheck.length), pkCheck)) { + throw new Error("invalid encapsulation key"); + } + const rho = pk.subarray(this._skSize); + const a = this._sampleMatrix(rho, true); + const r = this._sampleNoise1(seed, 0, this._k); + const e1 = this._sampleNoise2(seed, this._k, this._k); + const e2 = this._sampleNoise2(seed, this._k * 2, 1)[0]; + // perform number theoretic transform on random vector r + for (let i = 0; i < this._k; i++) { + r[i] = ntt(r[i]); + r[i] = reduce(r[i]); + } + // u = A*r + e1 + const u = new Array(this._k); + for (let i = 0; i < this._k; i++) { + u[i] = multiply(a[i], r); + u[i] = nttInverse(u[i]); + u[i] = add(u[i], e1[i]); + u[i] = reduce(u[i]); + } + // v = tHat*r + e2 + m + const m = polyFromMsg(msg); + let v = multiply(tHat, r); + v = nttInverse(v); + v = add(v, e2); + v = add(v, m); + v = reduce(v); + // compress + const ret = new Uint8Array(this._compressedUSize + this._compressedVSize); + this._compressU(ret.subarray(0, this._compressedUSize), u); + this._compressV(ret.subarray(this._compressedUSize), v); + return ret; + } + // indcpaDecrypt is the decryption function of the CPA-secure + // public-key encryption scheme underlying Kyber. + /** + * Decapsulates the ciphertext using the provided secret key. + * + * @param ct - The ciphertext to be decapsulated. + * @param sk - The secret key used for decapsulation. + * @returns The decapsulated message as a Uint8Array. + */ + _decap(ct, sk) { + // extract ciphertext + const u = this._decompressU(ct.subarray(0, this._compressedUSize)); + const v = this._decompressV(ct.subarray(this._compressedUSize)); + const privateKeyPolyvec = this._polyvecFromBytes(sk); + for (let i = 0; i < this._k; i++) { + u[i] = ntt(u[i]); + } + let mp = multiply(privateKeyPolyvec, u); + mp = nttInverse(mp); + mp = subtract(v, mp); + mp = reduce(mp); + return polyToMsg(mp); + } + // generateMatrixA deterministically generates a matrix `A` (or the transpose of `A`) + // from a seed. Entries of the matrix are polynomials that look uniformly random. + // Performs rejection sampling on the output of an extendable-output function (XOF). + /** + * Generates a sample matrix based on the provided seed and transposition flag. + * + * @param seed - The seed used for generating the matrix. + * @param transposed - A flag indicating whether the matrix should be transposed or not. + * @returns The generated sample matrix. + */ + _sampleMatrix(seed, transposed) { + const a = new Array(this._k); + const transpose = new Uint8Array(2); + for (let ctr = 0, i = 0; i < this._k; i++) { + a[i] = new Array(this._k); + for (let j = 0; j < this._k; j++) { + // set if transposed matrix or not + if (transposed) { + transpose[0] = i; + transpose[1] = j; + } + else { + transpose[0] = j; + transpose[1] = i; + } + const output = xof(seed, transpose); + // run rejection sampling on the output from above + const result = indcpaRejUniform(output.subarray(0, 504), 504, N); + a[i][j] = result[0]; // the result here is an NTT-representation + ctr = result[1]; // keeps track of index of output array from sampling function + while (ctr < N) { // if the polynomial hasnt been filled yet with mod q entries + const outputn = output.subarray(504, 672); // take last 168 bytes of byte array from xof + const result1 = indcpaRejUniform(outputn, 168, N - ctr); // run sampling function again + const missing = result1[0]; // here is additional mod q polynomial coefficients + const ctrn = result1[1]; // how many coefficients were accepted and are in the output + // starting at last position of output array from first sampling function until 256 is reached + for (let k = ctr; k < N; k++) { + a[i][j][k] = missing[k - ctr]; // fill rest of array with the additional coefficients until full + } + ctr = ctr + ctrn; // update index + } + } + } + return a; + } + /** + * Generates a 2D array of noise samples. + * + * @param sigma - The noise parameter. + * @param offset - The offset value. + * @param size - The size of the array. + * @returns The generated 2D array of noise samples. + */ + _sampleNoise1(sigma, offset, size) { + const r = new Array(size); + for (let i = 0; i < size; i++) { + r[i] = byteopsCbd(prf(this._eta1 * N / 4, sigma, offset), this._eta1); + offset++; + } + return r; + } + /** + * Generates a 2-dimensional array of noise samples. + * + * @param sigma - The noise parameter. + * @param offset - The offset value. + * @param size - The size of the array. + * @returns The generated 2-dimensional array of noise samples. + */ + _sampleNoise2(sigma, offset, size) { + const r = new Array(size); + for (let i = 0; i < size; i++) { + r[i] = byteopsCbd(prf(this._eta2 * N / 4, sigma, offset), this._eta2); + offset++; + } + return r; + } + // polyvecFromBytes deserializes a vector of polynomials. + /** + * Converts a Uint8Array to a 2D array of numbers representing a polynomial vector. + * Each element in the resulting array represents a polynomial. + * @param a The Uint8Array to convert. + * @returns The 2D array of numbers representing the polynomial vector. + */ + _polyvecFromBytes(a) { + const r = new Array(this._k); + for (let i = 0; i < this._k; i++) { + r[i] = polyFromBytes(a.subarray(i * 384, (i + 1) * 384)); + } + return r; + } + // compressU lossily compresses and serializes a vector of polynomials. + /** + * Compresses the given array of coefficients into a Uint8Array. + * + * @param r - The output Uint8Array. + * @param u - The array of coefficients. + * @returns The compressed Uint8Array. + */ + _compressU(r, u) { + const t = new Array(4); + for (let rr = 0, i = 0; i < this._k; i++) { + for (let j = 0; j < N / 4; j++) { + for (let k = 0; k < 4; k++) { + // parse {0,...,3328} to {0,...,1023} + t[k] = (((u[i][4 * j + k] << 10) + Q / 2) / Q) & + 0b1111111111; + } + // converts 4 12-bit coefficients {0,...,3328} to 5 8-bit bytes {0,...,255} + // 48 bits down to 40 bits per block + r[rr++] = byte(t[0] >> 0); + r[rr++] = byte((t[0] >> 8) | (t[1] << 2)); + r[rr++] = byte((t[1] >> 6) | (t[2] << 4)); + r[rr++] = byte((t[2] >> 4) | (t[3] << 6)); + r[rr++] = byte(t[3] >> 2); + } + } + return r; + } + // compressV lossily compresses and subsequently serializes a polynomial. + /** + * Compresses the given array of numbers into a Uint8Array. + * + * @param r - The Uint8Array to store the compressed values. + * @param v - The array of numbers to compress. + * @returns The compressed Uint8Array. + */ + _compressV(r, v) { + // const r = new Uint8Array(128); + const t = new Uint8Array(8); + for (let rr = 0, i = 0; i < N / 8; i++) { + for (let j = 0; j < 8; j++) { + t[j] = byte(((v[8 * i + j] << 4) + Q / 2) / Q) & 0b1111; + } + r[rr++] = t[0] | (t[1] << 4); + r[rr++] = t[2] | (t[3] << 4); + r[rr++] = t[4] | (t[5] << 4); + r[rr++] = t[6] | (t[7] << 4); + } + return r; + } + // decompressU de-serializes and decompresses a vector of polynomials and + // represents the approximate inverse of compress1. Since compression is lossy, + // the results of decompression will may not match the original vector of polynomials. + /** + * Decompresses a Uint8Array into a two-dimensional array of numbers. + * + * @param a The Uint8Array to decompress. + * @returns The decompressed two-dimensional array. + */ + _decompressU(a) { + const r = new Array(this._k); + for (let i = 0; i < this._k; i++) { + r[i] = new Array(384); + } + const t = new Array(4); + for (let aa = 0, i = 0; i < this._k; i++) { + for (let j = 0; j < N / 4; j++) { + t[0] = (uint16(a[aa + 0]) >> 0) | (uint16(a[aa + 1]) << 8); + t[1] = (uint16(a[aa + 1]) >> 2) | (uint16(a[aa + 2]) << 6); + t[2] = (uint16(a[aa + 2]) >> 4) | (uint16(a[aa + 3]) << 4); + t[3] = (uint16(a[aa + 3]) >> 6) | (uint16(a[aa + 4]) << 2); + aa = aa + 5; + for (let k = 0; k < 4; k++) { + r[i][4 * j + k] = int16((((uint32(t[k] & 0x3FF)) * (uint32(Q))) + 512) >> 10); + } + } + } + return r; + } + // decompressV de-serializes and subsequently decompresses a polynomial, + // representing the approximate inverse of compress2. + // Note that compression is lossy, and thus decompression will not match the + // original input. + /** + * Decompresses a Uint8Array into an array of numbers. + * + * @param a - The Uint8Array to decompress. + * @returns An array of numbers. + */ + _decompressV(a) { + const r = new Array(384); + for (let aa = 0, i = 0; i < N / 2; i++, aa++) { + r[2 * i + 0] = int16(((uint16(a[aa] & 15) * uint16(Q)) + 8) >> 4); + r[2 * i + 1] = int16(((uint16(a[aa] >> 4) * uint16(Q)) + 8) >> 4); + } + return r; + } +} +/** + * Computes the hash of the input array `a` and an optional input array `b`. + * Returns an array containing two Uint8Arrays, representing the first 32 bytes and the next 32 bytes of the hash digest. + * @param a - The input array to be hashed. + * @param b - An optional input array to be hashed along with `a`. + * @returns An array containing two Uint8Arrays representing the hash digest. + */ +function g(a, b) { + const hash = sha3_512.create().update(a); + if (b !== undefined) { + hash.update(b); + } + const res = hash.digest(); + return [res.subarray(0, 32), res.subarray(32, 64)]; +} +/** + * Computes the SHA3-256 hash of the given message. + * + * @param msg - The input message as a Uint8Array. + * @returns The computed hash as a Uint8Array. + */ +function h(msg) { + return sha3_256.create().update(msg).digest(); +} +/** + * Key Derivation Function (KDF) that takes an input array `a` and an optional input array `b`. + * It uses the SHAKE256 hash function to derive a 32-byte output. + * + * @param a - The input array. + * @param b - The optional input array. + * @returns The derived key as a Uint8Array. + */ +function kdf(a, b) { + const hash = shake256.create({ dkLen: 32 }).update(a); + if (b !== undefined) { + hash.update(b); + } + return hash.digest(); +} +/** + * Computes the extendable-output function (XOF) using the SHAKE128 algorithm. + * + * @param seed - The seed value for the XOF. + * @param transpose - The transpose value for the XOF. + * @returns The computed XOF value as a Uint8Array. + */ +function xof(seed, transpose) { + return shake128.create({ dkLen: 672 }).update(seed).update(transpose) + .digest(); +} +// polyToBytes serializes a polynomial into an array of bytes. +/** + * Converts a polynomial represented by an array of numbers to a Uint8Array. + * Each coefficient of the polynomial is reduced modulo q. + * + * @param a - The array representing the polynomial. + * @returns The Uint8Array representation of the polynomial. + */ +function polyToBytes(a) { + let t0 = 0; + let t1 = 0; + const r = new Uint8Array(384); + const a2 = subtractQ(a); // Returns: a - q if a >= q, else a (each coefficient of the polynomial) + // for 0-127 + for (let i = 0; i < N / 2; i++) { + // get two coefficient entries in the polynomial + t0 = uint16(a2[2 * i]); + t1 = uint16(a2[2 * i + 1]); + // convert the 2 coefficient into 3 bytes + r[3 * i + 0] = byte(t0 >> 0); // byte() does mod 256 of the input (output value 0-255) + r[3 * i + 1] = byte(t0 >> 8) | byte(t1 << 4); + r[3 * i + 2] = byte(t1 >> 4); + } + return r; +} +// polyFromBytes de-serialises an array of bytes into a polynomial, +// and represents the inverse of polyToBytes. +/** + * Converts a Uint8Array to an array of numbers representing a polynomial. + * Each element in the array represents a coefficient of the polynomial. + * The input array `a` should have a length of 384. + * The function performs bitwise operations to extract the coefficients from the input array. + * @param a The Uint8Array to convert to a polynomial. + * @returns An array of numbers representing the polynomial. + */ +function polyFromBytes(a) { + const r = new Array(384).fill(0); + for (let i = 0; i < N / 2; i++) { + r[2 * i] = int16(((uint16(a[3 * i + 0]) >> 0) | (uint16(a[3 * i + 1]) << 8)) & 0xFFF); + r[2 * i + 1] = int16(((uint16(a[3 * i + 1]) >> 4) | (uint16(a[3 * i + 2]) << 4)) & 0xFFF); + } + return r; +} +// polyToMsg converts a polynomial to a 32-byte message +// and represents the inverse of polyFromMsg. +/** + * Converts a polynomial to a message represented as a Uint8Array. + * @param a - The polynomial to convert. + * @returns The message as a Uint8Array. + */ +function polyToMsg(a) { + const msg = new Uint8Array(32); + let t; + const a2 = subtractQ(a); + for (let i = 0; i < N / 8; i++) { + msg[i] = 0; + for (let j = 0; j < 8; j++) { + t = (((uint16(a2[8 * i + j]) << 1) + uint16(Q / 2)) / + uint16(Q)) & 1; + msg[i] |= byte(t << j); + } + } + return msg; +} +// polyFromMsg converts a 32-byte message to a polynomial. +/** + * Converts a Uint8Array message to an array of numbers representing a polynomial. + * Each element in the array is an int16 (0-65535). + * + * @param msg - The Uint8Array message to convert. + * @returns An array of numbers representing the polynomial. + */ +function polyFromMsg(msg) { + const r = new Array(384).fill(0); // each element is int16 (0-65535) + let mask; // int16 + for (let i = 0; i < N / 8; i++) { + for (let j = 0; j < 8; j++) { + mask = -1 * int16((msg[i] >> j) & 1); + r[8 * i + j] = mask & int16((Q + 1) / 2); + } + } + return r; +} +// indcpaRejUniform runs rejection sampling on uniform random bytes +// to generate uniform random integers modulo `Q`. +/** + * Generates an array of random numbers from a given buffer, rejecting values greater than a specified threshold. + * + * @param buf - The input buffer containing random bytes. + * @param bufl - The length of the input buffer. + * @param len - The desired length of the output array. + * @returns An array of random numbers and the actual length of the output array. + */ +function indcpaRejUniform(buf, bufl, len) { + const r = new Array(384).fill(0); + let ctr = 0; + let val0, val1; // d1, d2 in kyber documentation + for (let pos = 0; ctr < len && pos + 3 <= bufl;) { + // compute d1 and d2 + val0 = (uint16((buf[pos]) >> 0) | (uint16(buf[pos + 1]) << 8)) & 0xFFF; + val1 = (uint16((buf[pos + 1]) >> 4) | (uint16(buf[pos + 2]) << 4)) & 0xFFF; + // increment input buffer index by 3 + pos = pos + 3; + // if d1 is less than 3329 + if (val0 < Q) { + // assign to d1 + r[ctr] = val0; + // increment position of output array + ctr = ctr + 1; + } + if (ctr < len && val1 < Q) { + r[ctr] = val1; + ctr = ctr + 1; + } + } + return [r, ctr]; +} +// byteopsCbd computes a polynomial with coefficients distributed +// according to a centered binomial distribution with parameter PARAMS_ETA, +// given an array of uniformly random bytes. +/** + * Converts a Uint8Array buffer to an array of numbers using the CBD operation. + * @param buf - The input Uint8Array buffer. + * @param eta - The value used in the CBD operation. + * @returns An array of numbers obtained from the CBD operation. + */ +function byteopsCbd(buf, eta) { + let t, d; + let a, b; + const r = new Array(384).fill(0); + for (let i = 0; i < N / 8; i++) { + t = byteopsLoad32(buf.subarray(4 * i, buf.length)); + d = t & 0x55555555; + d = d + ((t >> 1) & 0x55555555); + for (let j = 0; j < 8; j++) { + a = int16((d >> (4 * j + 0)) & 0x3); + b = int16((d >> (4 * j + eta)) & 0x3); + r[8 * i + j] = a - b; + } + } + return r; +} +// ntt performs an inplace number-theoretic transform (NTT) in `Rq`. +// The input is in standard order, the output is in bit-reversed order. +/** + * Performs the Number Theoretic Transform (NTT) on an array of numbers. + * + * @param r - The input array of numbers. + * @returns The transformed array of numbers. + */ +function ntt(r) { + // 128, 64, 32, 16, 8, 4, 2 + for (let j = 0, k = 1, l = 128; l >= 2; l >>= 1) { + // 0, + for (let start = 0; start < 256; start = j + l) { + const zeta = NTT_ZETAS[k]; + k = k + 1; + // for each element in the subsections (128, 64, 32, 16, 8, 4, 2) starting at an offset + for (j = start; j < start + l; j++) { + // compute the modular multiplication of the zeta and each element in the subsection + const t = nttFqMul(zeta, r[j + l]); // t is mod q + // overwrite each element in the subsection as the opposite subsection element minus t + r[j + l] = r[j] - t; + // add t back again to the opposite subsection + r[j] = r[j] + t; + } + } + } + return r; +} +// nttFqMul performs multiplication followed by Montgomery reduction +// and returns a 16-bit integer congruent to `a*b*R^{-1} mod Q`. +/** + * Performs an NTT (Number Theoretic Transform) multiplication on two numbers in Fq. + * @param a The first number. + * @param b The second number. + * @returns The result of the NTT multiplication. + */ +function nttFqMul(a, b) { + return byteopsMontgomeryReduce(a * b); +} +// reduce applies Barrett reduction to all coefficients of a polynomial. +/** + * Reduces each element in the given array using the barrett function. + * + * @param r - The array to be reduced. + * @returns The reduced array. + */ +function reduce(r) { + for (let i = 0; i < N; i++) { + r[i] = barrett(r[i]); + } + return r; +} +// barrett computes a Barrett reduction; given +// a integer `a`, returns a integer congruent to +// `a mod Q` in {0,...,Q}. +/** + * Performs the Barrett reduction algorithm on the given number. + * + * @param a - The number to be reduced. + * @returns The result of the reduction. + */ +function barrett(a) { + const v = ((1 << 24) + Q / 2) / Q; + let t = v * a >> 24; + t = t * Q; + return a - t; +} +// byteopsMontgomeryReduce computes a Montgomery reduction; given +// a 32-bit integer `a`, returns `a * R^-1 mod Q` where `R=2^16`. +/** + * Performs Montgomery reduction on a given number. + * @param a - The number to be reduced. + * @returns The reduced number. + */ +function byteopsMontgomeryReduce(a) { + const u = int16(int32(a) * Q_INV); + let t = u * Q; + t = a - t; + t >>= 16; + return int16(t); +} +// polyToMont performs the in-place conversion of all coefficients +// of a polynomial from the normal domain to the Montgomery domain. +/** + * Converts a polynomial to the Montgomery domain. + * + * @param r - The polynomial to be converted. + * @returns The polynomial in the Montgomery domain. + */ +function polyToMont(r) { + // let f = int16(((uint64(1) << 32)) % uint64(Q)); + const f = 1353; // if Q changes then this needs to be updated + for (let i = 0; i < N; i++) { + r[i] = byteopsMontgomeryReduce(int32(r[i]) * int32(f)); + } + return r; +} +// pointwise-multiplies elements of polynomial-vectors +// `a` and `b`, accumulates the results into `r`, and then multiplies by `2^-16`. +/** + * Multiplies two matrices element-wise and returns the result. + * @param a - The first matrix. + * @param b - The second matrix. + * @returns The resulting matrix after element-wise multiplication. + */ +function multiply(a, b) { + let r = polyBaseMulMontgomery(a[0], b[0]); + let t; + for (let i = 1; i < a.length; i++) { + t = polyBaseMulMontgomery(a[i], b[i]); + r = add(r, t); + } + return reduce(r); +} +// polyBaseMulMontgomery performs the multiplication of two polynomials +// in the number-theoretic transform (NTT) domain. +/** + * Performs polynomial base multiplication in Montgomery domain. + * @param a - The first polynomial array. + * @param b - The second polynomial array. + * @returns The result of the polynomial base multiplication. + */ +function polyBaseMulMontgomery(a, b) { + let rx, ry; + for (let i = 0; i < N / 4; i++) { + rx = nttBaseMul(a[4 * i + 0], a[4 * i + 1], b[4 * i + 0], b[4 * i + 1], NTT_ZETAS[64 + i]); + ry = nttBaseMul(a[4 * i + 2], a[4 * i + 3], b[4 * i + 2], b[4 * i + 3], -NTT_ZETAS[64 + i]); + a[4 * i + 0] = rx[0]; + a[4 * i + 1] = rx[1]; + a[4 * i + 2] = ry[0]; + a[4 * i + 3] = ry[1]; + } + return a; +} +// nttBaseMul performs the multiplication of polynomials +// in `Zq[X]/(X^2-zeta)`. Used for multiplication of elements +// in `Rq` in the number-theoretic transformation domain. +/** + * Performs NTT base multiplication. + * + * @param a0 - The first coefficient of the first polynomial. + * @param a1 - The second coefficient of the first polynomial. + * @param b0 - The first coefficient of the second polynomial. + * @param b1 - The second coefficient of the second polynomial. + * @param zeta - The zeta value used in the multiplication. + * @returns An array containing the result of the multiplication. + */ +function nttBaseMul(a0, a1, b0, b1, zeta) { + const r = new Array(2); + r[0] = nttFqMul(a1, b1); + r[0] = nttFqMul(r[0], zeta); + r[0] += nttFqMul(a0, b0); + r[1] = nttFqMul(a0, b1); + r[1] += nttFqMul(a1, b0); + return r; +} +// adds two polynomials. +/** + * Adds two arrays element-wise. + * @param a - The first array. + * @param b - The second array. + * @returns The resulting array after element-wise addition. + */ +function add(a, b) { + const c = new Array(384); + for (let i = 0; i < N; i++) { + c[i] = a[i] + b[i]; + } + return c; +} +// subtracts two polynomials. +/** + * Subtracts the elements of array b from array a. + * + * @param a - The array from which to subtract. + * @param b - The array to subtract. + * @returns The resulting array after subtraction. + */ +function subtract(a, b) { + for (let i = 0; i < N; i++) { + a[i] -= b[i]; + } + return a; +} +// nttInverse performs an inplace inverse number-theoretic transform (NTT) +// in `Rq` and multiplication by Montgomery factor 2^16. +// The input is in bit-reversed order, the output is in standard order. +/** + * Performs the inverse Number Theoretic Transform (NTT) on the given array. + * + * @param r - The input array to perform the inverse NTT on. + * @returns The array after performing the inverse NTT. + */ +function nttInverse(r) { + let j = 0; + for (let k = 0, l = 2; l <= 128; l <<= 1) { + for (let start = 0; start < 256; start = j + l) { + const zeta = NTT_ZETAS_INV[k]; + k = k + 1; + for (j = start; j < start + l; j++) { + const t = r[j]; + r[j] = barrett(t + r[j + l]); + r[j + l] = t - r[j + l]; + r[j + l] = nttFqMul(zeta, r[j + l]); + } + } + } + for (j = 0; j < 256; j++) { + r[j] = nttFqMul(r[j], NTT_ZETAS_INV[127]); + } + return r; +} +// subtractQ applies the conditional subtraction of q to each coefficient of a polynomial. +// if a is 3329 then convert to 0 +// Returns: a - q if a >= q, else a +/** + * Subtracts the value of Q from each element in the given array. + * The result should be a negative integer for each element. + * If the leftmost bit is 0 (positive number), the value of Q is added back. + * + * @param r - The array to subtract Q from. + * @returns The resulting array after the subtraction. + */ +function subtractQ(r) { + for (let i = 0; i < N; i++) { + r[i] -= Q; // should result in a negative integer + // push left most signed bit to right most position + // javascript does bitwise operations in signed 32 bit + // add q back again if left most bit was 0 (positive number) + r[i] += (r[i] >> 31) & Q; + } + return r; +} diff --git a/npm/esm/src/utils.d.ts b/npm/esm/src/utils.d.ts new file mode 100644 index 0000000..7d54737 --- /dev/null +++ b/npm/esm/src/utils.d.ts @@ -0,0 +1,15 @@ +export declare function byte(n: number): number; +export declare function int16(n: number): number; +export declare function uint16(n: number): number; +export declare function int32(n: number): number; +export declare function uint32(n: number): number; +/** + * compares two arrays + * @returns 1 if they are the same or 0 if not + */ +export declare function constantTimeCompare(x: Uint8Array, y: Uint8Array): number; +export declare function equalUint8Array(x: Uint8Array, y: Uint8Array): boolean; +export declare function loadCrypto(): Promise; +export declare function prf(len: number, seed: Uint8Array, nonce: number): Uint8Array; +export declare function byteopsLoad24(x: Uint8Array): number; +export declare function byteopsLoad32(x: Uint8Array): number; diff --git a/npm/esm/src/utils.js b/npm/esm/src/utils.js new file mode 100644 index 0000000..ddb2343 --- /dev/null +++ b/npm/esm/src/utils.js @@ -0,0 +1,113 @@ +import * as dntShim from "../_dnt.shims.js"; +import { shake256 } from "./deps.js"; +export function byte(n) { + return n % 256; +} +export function int16(n) { + const end = -32768; + const start = 32767; + if (n >= end && n <= start) { + return n; + } + if (n < end) { + n = n + 32769; + n = n % 65536; + return start + n; + } + // if (n > start) { + n = n - 32768; + n = n % 65536; + return end + n; +} +export function uint16(n) { + return n % 65536; +} +export function int32(n) { + const end = -2147483648; + const start = 2147483647; + if (n >= end && n <= start) { + return n; + } + if (n < end) { + n = n + 2147483649; + n = n % 4294967296; + return start + n; + } + // if (n > start) { + n = n - 2147483648; + n = n % 4294967296; + return end + n; +} +// any bit operations to be done in uint32 must have >>> 0 +// javascript calculates bitwise in SIGNED 32 bit so you need to convert +export function uint32(n) { + return n % 4294967296; +} +/** + * compares two arrays + * @returns 1 if they are the same or 0 if not + */ +export function constantTimeCompare(x, y) { + // check array lengths + if (x.length != y.length) { + return 0; + } + const v = new Uint8Array([0]); + for (let i = 0; i < x.length; i++) { + v[0] |= x[i] ^ y[i]; + } + // constantTimeByteEq + const z = new Uint8Array([0]); + z[0] = ~(v[0] ^ z[0]); + z[0] &= z[0] >> 4; + z[0] &= z[0] >> 2; + z[0] &= z[0] >> 1; + return z[0]; +} +export function equalUint8Array(x, y) { + if (x.length != y.length) { + return false; + } + for (let i = 0; i < x.length; i++) { + if (x[i] !== y[i]) { + return false; + } + } + return true; +} +export async function loadCrypto() { + if (dntShim.dntGlobalThis !== undefined && globalThis.crypto !== undefined) { + // Browsers, Node.js >= v19, Cloudflare Workers, Bun, etc. + return globalThis.crypto; + } + // Node.js <= v18 + try { + // @ts-ignore: to ignore "crypto" + const { webcrypto } = await import("crypto"); // node:crypto + return webcrypto; + } + catch (_e) { + throw new Error("failed to load Crypto"); + } +} +// prf provides a pseudo-random function (PRF) which returns +// a byte array of length `l`, using the provided key and nonce +// to instantiate the PRF's underlying hash function. +export function prf(len, seed, nonce) { + return shake256.create({ dkLen: len }).update(seed).update(new Uint8Array([nonce])).digest(); +} +// byteopsLoad24 returns a 32-bit unsigned integer loaded from byte x. +export function byteopsLoad24(x) { + let r = uint32(x[0]); + r |= uint32(x[1]) << 8; + r |= uint32(x[2]) << 16; + return r; +} +// byteopsLoad32 returns a 32-bit unsigned integer loaded from byte x. +export function byteopsLoad32(x) { + let r = uint32(x[0]); + r |= uint32(x[1]) << 8; + r |= uint32(x[2]) << 16; + r |= uint32(x[3]) << 24; + return uint32(r); +} diff --git a/npm/esm/test/kyber.test.d.ts b/npm/esm/test/kyber.test.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/npm/esm/test/kyber.test.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/npm/esm/test/kyber.test.js b/npm/esm/test/kyber.test.js new file mode 100644 index 0000000..7188a3e --- /dev/null +++ b/npm/esm/test/kyber.test.js @@ -0,0 +1,61 @@ +import * as dntShim from "../_dnt.test_shims.js"; +import { describe, it } from "../deps/deno.land/std@0.213.0/testing/bdd.js"; +import { assertEquals, assertRejects, } from "../deps/deno.land/std@0.216.0/assert/mod.js"; +import { MlKem1024, MlKem512, MlKem768, MlKemError } from "../mod.js"; +import { loadCrypto } from "../src/utils.js"; +import { parseKAT, testVectorPath } from "./utils.js"; +import { hexToBytes } from "./utils.js"; +[MlKem512, MlKem768, MlKem1024].forEach((KyberClass) => describe(KyberClass.name, () => { + const size = KyberClass.name.substring(5); + describe("KAT vectors", () => { + it("should match expected values", async () => { + const kyber = new KyberClass(); + const katData = await dntShim.Deno.readTextFile(`${testVectorPath()}/kat/kat_MLKEM_${size}.rsp`); + const { ct, sk, ss } = parseKAT(katData); + console.log(`test vector count: ${sk.length}`); + for (let i = 0; i < sk.length; i++) { + const res = await kyber.decap(ct[i], sk[i]); + assertEquals(res, ss[i]); + } + }); + }); + describe("A sample code in README.", () => { + it("should work normally", async () => { + const recipient = new KyberClass(); + const [pkR, skR] = await recipient.generateKeyPair(); + const sender = new KyberClass(); + const [ct, ssS] = await sender.encap(pkR); + const ssR = await recipient.decap(ct, skR); + assertEquals(ssS, ssR); + }); + it("should work normally with deriveKeyPair", async () => { + const recipient = new KyberClass(); + const api = await loadCrypto(); + const seed = new Uint8Array(64); + api.getRandomValues(seed); + const [pkR, skR] = await recipient.deriveKeyPair(seed); + const [pkR2, skR2] = await recipient.deriveKeyPair(seed); + assertEquals(pkR, pkR2); + assertEquals(skR, skR2); + const sender = new KyberClass(); + const [ct, ssS] = await sender.encap(pkR); + const ssR = await recipient.decap(ct, skR); + assertEquals(ssS, ssR); + }); + }); + describe("Advanced testing", () => { + it("Invalid encapsulation keys", async () => { + const sender = new KyberClass(); + const testData = await dntShim.Deno.readTextFile(`${testVectorPath()}/modulus/ML-KEM-${size}.txt`); + const invalidPk = hexToBytes(testData); + await assertRejects(() => sender.encap(invalidPk), MlKemError, "invalid encapsulation key"); + }); + it("'Unlucky' vectors that require an unusually large number of XOF reads", async () => { + const kyber = new KyberClass(); + const testData = await dntShim.Deno.readTextFile(`${testVectorPath()}/unluckysample/ML-KEM-${size}.txt`); + const { c: [ct], dk: [sk], K: [ss] } = parseKAT(testData); + const res = await kyber.decap(ct, sk); + assertEquals(res, ss); + }); + }); +})); diff --git a/npm/esm/test/utils.d.ts b/npm/esm/test/utils.d.ts new file mode 100644 index 0000000..8368240 --- /dev/null +++ b/npm/esm/test/utils.d.ts @@ -0,0 +1,7 @@ +export declare function testVectorPath(): string; +export declare function hexToBytes(v: string): Uint8Array; +export declare function bytesToHex(v: Uint8Array): string; +export declare function hexToDec(hexString: string): number; +export declare function parseKAT(data: string): { + [label: string]: Uint8Array[]; +}; diff --git a/npm/esm/test/utils.js b/npm/esm/test/utils.js new file mode 100644 index 0000000..7a2031d --- /dev/null +++ b/npm/esm/test/utils.js @@ -0,0 +1,42 @@ +const isDeno = () => typeof Deno !== "undefined"; +export function testVectorPath() { + if (isDeno()) { + return "./test/vectors"; + } + return "../../test/vectors"; +} +export function hexToBytes(v) { + if (v.length === 0) { + return new Uint8Array([]); + } + const res = v.match(/[\da-f]{2}/gi); + if (res == null) { + throw new Error("Not hex string."); + } + return new Uint8Array(res.map(function (h) { + return parseInt(h, 16); + })); +} +export function bytesToHex(v) { + return [...v].map((x) => x.toString(16).padStart(2, "0")).join(""); +} +export function hexToDec(hexString) { + return parseInt(hexString, 16); +} +export function parseKAT(data) { + const textByLine = data.trim().split("\n"); + const parsed = {}; + for (let i = 0; i < textByLine.length; i++) { + const [label, hexValue] = textByLine[i].split(" = "); + if (label === "count") + continue; + const value = hexToBytes(hexValue); + if (parsed[label]) { + parsed[label].push(value); + } + else { + parsed[label] = [value]; + } + } + return parsed; +} diff --git a/npm/package-lock.json b/npm/package-lock.json new file mode 100644 index 0000000..0a441cf --- /dev/null +++ b/npm/package-lock.json @@ -0,0 +1,183 @@ +{ + "name": "crystals-kyber-js", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "crystals-kyber-js", + "license": "MIT", + "dependencies": { + "@openpgp/noble-hashes": "1.3.3" + }, + "devDependencies": { + "@deno/shim-deno": "~0.17.0", + "@types/node": "^20.9.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@deno/shim-deno": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@deno/shim-deno/-/shim-deno-0.17.0.tgz", + "integrity": "sha512-+FzsP65eehAgTQdzt1izLEV17ePCZqHxDQqRDbpRc1yJVYtDI2MvbRq5DvOj90uRt6zKn9qtWpEueDqG1QORhQ==", + "dev": true, + "dependencies": { + "@deno/shim-deno-test": "^0.5.0", + "which": "^4.0.0" + } + }, + "node_modules/@deno/shim-deno-test": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@deno/shim-deno-test/-/shim-deno-test-0.5.0.tgz", + "integrity": "sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w==", + "dev": true + }, + "node_modules/@openpgp/noble-hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@openpgp/noble-hashes/-/noble-hashes-1.3.3.tgz", + "integrity": "sha512-CPDNePrQTQrL5Fxd/K3B53Gty4xbNVxnMSUdDmYjW5tTpRqOlme0INYTnnQqoE/XUdIlq5fByOMDuDWR5JpXZw==", + "dependencies": { + "@types/bn.js": "^4.11.6", + "bn.js": "^4.11.8" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.11.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz", + "integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + } + }, + "dependencies": { + "@deno/shim-deno": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@deno/shim-deno/-/shim-deno-0.17.0.tgz", + "integrity": "sha512-+FzsP65eehAgTQdzt1izLEV17ePCZqHxDQqRDbpRc1yJVYtDI2MvbRq5DvOj90uRt6zKn9qtWpEueDqG1QORhQ==", + "dev": true, + "requires": { + "@deno/shim-deno-test": "^0.5.0", + "which": "^4.0.0" + } + }, + "@deno/shim-deno-test": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@deno/shim-deno-test/-/shim-deno-test-0.5.0.tgz", + "integrity": "sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w==", + "dev": true + }, + "@openpgp/noble-hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@openpgp/noble-hashes/-/noble-hashes-1.3.3.tgz", + "integrity": "sha512-CPDNePrQTQrL5Fxd/K3B53Gty4xbNVxnMSUdDmYjW5tTpRqOlme0INYTnnQqoE/XUdIlq5fByOMDuDWR5JpXZw==", + "requires": { + "@types/bn.js": "^4.11.6", + "bn.js": "^4.11.8" + } + }, + "@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "20.11.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz", + "integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==", + "requires": { + "undici-types": "~5.26.4" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "requires": { + "isexe": "^3.1.1" + } + } + } +} diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 0000000..0b29f10 --- /dev/null +++ b/npm/package.json @@ -0,0 +1,47 @@ +{ + "name": "crystals-kyber-js", + "description": "A CRYSTALS-KYBER implementation written in TypeScript for various JavaScript runtimes", + "keywords": [ + "crystals-kyber", + "kyber", + "kem", + "security", + "encryption", + "pqc", + "post-quantum" + ], + "author": "Ajitomi Daisuke", + "homepage": "https://github.com/dajiaji/crystals-kyber-js#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/dajiaji/crystals-kyber-js.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/dajiaji/crystals-kyber-js/issues" + }, + "main": "./script/mod.js", + "module": "./esm/mod.js", + "types": "./esm/mod.d.ts", + "exports": { + ".": { + "import": "./esm/mod.js" + } + }, + "scripts": { + "test": "node test_runner.js" + }, + "sideEffects": false, + "engines": { + "node": ">=16.0.0" + }, + "dependencies": { + "@openpgp/noble-hashes": "1.3.3" + }, + "devDependencies": { + "@types/node": "^20.9.0", + "picocolors": "^1.0.0", + "@deno/shim-deno": "~0.17.0" + }, + "_generatedBy": "dnt@0.39.0" +} \ No newline at end of file diff --git a/npm/src/_dnt.shims.ts b/npm/src/_dnt.shims.ts new file mode 100644 index 0000000..21614f4 --- /dev/null +++ b/npm/src/_dnt.shims.ts @@ -0,0 +1,60 @@ +const dntGlobals = { +}; +export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals); + +function createMergeProxy( + baseObj: T, + extObj: U, +): Omit & U { + return new Proxy(baseObj, { + get(_target, prop, _receiver) { + if (prop in extObj) { + return (extObj as any)[prop]; + } else { + return (baseObj as any)[prop]; + } + }, + set(_target, prop, value) { + if (prop in extObj) { + delete (extObj as any)[prop]; + } + (baseObj as any)[prop] = value; + return true; + }, + deleteProperty(_target, prop) { + let success = false; + if (prop in extObj) { + delete (extObj as any)[prop]; + success = true; + } + if (prop in baseObj) { + delete (baseObj as any)[prop]; + success = true; + } + return success; + }, + ownKeys(_target) { + const baseKeys = Reflect.ownKeys(baseObj); + const extKeys = Reflect.ownKeys(extObj); + const extKeysSet = new Set(extKeys); + return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys]; + }, + defineProperty(_target, prop, desc) { + if (prop in extObj) { + delete (extObj as any)[prop]; + } + Reflect.defineProperty(baseObj, prop, desc); + return true; + }, + getOwnPropertyDescriptor(_target, prop) { + if (prop in extObj) { + return Reflect.getOwnPropertyDescriptor(extObj, prop); + } else { + return Reflect.getOwnPropertyDescriptor(baseObj, prop); + } + }, + has(_target, prop) { + return prop in extObj || prop in baseObj; + }, + }) as any; +} diff --git a/npm/src/_dnt.test_shims.ts b/npm/src/_dnt.test_shims.ts new file mode 100644 index 0000000..4f4afe5 --- /dev/null +++ b/npm/src/_dnt.test_shims.ts @@ -0,0 +1,64 @@ +import { Deno } from "@deno/shim-deno"; +export { Deno } from "@deno/shim-deno"; + +const dntGlobals = { + Deno, +}; +export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals); + +function createMergeProxy( + baseObj: T, + extObj: U, +): Omit & U { + return new Proxy(baseObj, { + get(_target, prop, _receiver) { + if (prop in extObj) { + return (extObj as any)[prop]; + } else { + return (baseObj as any)[prop]; + } + }, + set(_target, prop, value) { + if (prop in extObj) { + delete (extObj as any)[prop]; + } + (baseObj as any)[prop] = value; + return true; + }, + deleteProperty(_target, prop) { + let success = false; + if (prop in extObj) { + delete (extObj as any)[prop]; + success = true; + } + if (prop in baseObj) { + delete (baseObj as any)[prop]; + success = true; + } + return success; + }, + ownKeys(_target) { + const baseKeys = Reflect.ownKeys(baseObj); + const extKeys = Reflect.ownKeys(extObj); + const extKeysSet = new Set(extKeys); + return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys]; + }, + defineProperty(_target, prop, desc) { + if (prop in extObj) { + delete (extObj as any)[prop]; + } + Reflect.defineProperty(baseObj, prop, desc); + return true; + }, + getOwnPropertyDescriptor(_target, prop) { + if (prop in extObj) { + return Reflect.getOwnPropertyDescriptor(extObj, prop); + } else { + return Reflect.getOwnPropertyDescriptor(baseObj, prop); + } + }, + has(_target, prop) { + return prop in extObj || prop in baseObj; + }, + }) as any; +} diff --git a/npm/src/deps/deno.land/std@0.213.0/testing/_test_suite.ts b/npm/src/deps/deno.land/std@0.213.0/testing/_test_suite.ts new file mode 100644 index 0000000..5f177a0 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.213.0/testing/_test_suite.ts @@ -0,0 +1,363 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +/** The options for creating a test suite with the describe function. */ +import * as dntShim from "../../../../_dnt.test_shims.js"; + +export interface DescribeDefinition extends Omit { + fn?: () => void; + /** + * The `describe` function returns a `TestSuite` representing the group of tests. + * If `describe` is called within another `describe` calls `fn`, the suite will default to that parent `describe` calls returned `TestSuite`. + * If `describe` is not called within another `describe` calls `fn`, the suite will default to the `TestSuite` representing the global group of tests. + */ + suite?: TestSuite; + /** Run some shared setup before all of the tests in the suite. */ + beforeAll?: + | ((this: T) => void | Promise) + | ((this: T) => void | Promise)[]; + /** Run some shared teardown after all of the tests in the suite. */ + afterAll?: + | ((this: T) => void | Promise) + | ((this: T) => void | Promise)[]; + /** Run some shared setup before each test in the suite. */ + beforeEach?: + | ((this: T) => void | Promise) + | ((this: T) => void | Promise)[]; + /** Run some shared teardown after each test in the suite. */ + afterEach?: + | ((this: T) => void | Promise) + | ((this: T) => void | Promise)[]; +} + +/** The options for creating an individual test case with the it function. */ +export interface ItDefinition extends Omit { + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise; + /** + * The `describe` function returns a `TestSuite` representing the group of tests. + * If `it` is called within a `describe` calls `fn`, the suite will default to that parent `describe` calls returned `TestSuite`. + * If `it` is not called within a `describe` calls `fn`, the suite will default to the `TestSuite` representing the global group of tests. + */ + suite?: TestSuite; +} + +/** The names of all the different types of hooks. */ +export type HookNames = "beforeAll" | "afterAll" | "beforeEach" | "afterEach"; + +/** Optional test definition keys. */ +const optionalTestDefinitionKeys: (keyof dntShim.Deno.TestDefinition)[] = [ + "only", + "permissions", + "ignore", + "sanitizeExit", + "sanitizeOps", + "sanitizeResources", +]; + +/** Optional test step definition keys. */ +const optionalTestStepDefinitionKeys: (keyof dntShim.Deno.TestStepDefinition)[] = [ + "ignore", + "sanitizeExit", + "sanitizeOps", + "sanitizeResources", +]; + +/** + * A group of tests. + */ +export interface TestSuite { + symbol: symbol; +} + +/** + * An internal representation of a group of tests. + */ +export class TestSuiteInternal implements TestSuite { + symbol: symbol; + protected describe: DescribeDefinition; + protected steps: (TestSuiteInternal | ItDefinition)[]; + protected hasOnlyStep: boolean; + + constructor(describe: DescribeDefinition) { + this.describe = describe; + this.steps = []; + this.hasOnlyStep = false; + + const { suite } = describe; + if (suite && !TestSuiteInternal.suites.has(suite.symbol)) { + throw new Error("suite does not represent a registered test suite"); + } + const testSuite = suite + ? TestSuiteInternal.suites.get(suite.symbol) + : TestSuiteInternal.current; + this.symbol = Symbol(); + TestSuiteInternal.suites.set(this.symbol, this); + + const { fn } = describe; + if (fn) { + const temp = TestSuiteInternal.current; + TestSuiteInternal.current = this; + try { + fn(); + } finally { + TestSuiteInternal.current = temp; + } + } + + if (testSuite) { + TestSuiteInternal.addStep(testSuite, this); + } else { + const { + name, + ignore, + permissions, + sanitizeExit, + sanitizeOps, + sanitizeResources, + } = describe; + let { only } = describe; + if (!ignore && this.hasOnlyStep) { + only = true; + } + TestSuiteInternal.registerTest({ + name, + ignore, + only, + permissions, + sanitizeExit, + sanitizeOps, + sanitizeResources, + fn: async (t) => { + TestSuiteInternal.runningCount++; + try { + const context = {} as T; + const { beforeAll } = this.describe; + if (typeof beforeAll === "function") { + await beforeAll.call(context); + } else if (beforeAll) { + for (const hook of beforeAll) { + await hook.call(context); + } + } + try { + TestSuiteInternal.active.push(this.symbol); + await TestSuiteInternal.run(this, context, t); + } finally { + TestSuiteInternal.active.pop(); + const { afterAll } = this.describe; + if (typeof afterAll === "function") { + await afterAll.call(context); + } else if (afterAll) { + for (const hook of afterAll) { + await hook.call(context); + } + } + } + } finally { + TestSuiteInternal.runningCount--; + } + }, + }); + } + } + + /** Stores how many test suites are executing. */ + static runningCount = 0; + + /** If a test has been registered yet. Block adding global hooks if a test has been registered. */ + static started = false; + + /** A map of all test suites by symbol. */ + // deno-lint-ignore no-explicit-any + static suites = new Map>(); + + /** The current test suite being registered. */ + // deno-lint-ignore no-explicit-any + static current: TestSuiteInternal | null = null; + + /** The stack of tests that are actively running. */ + static active: symbol[] = []; + + /** This is used internally for testing this module. */ + static reset() { + TestSuiteInternal.runningCount = 0; + TestSuiteInternal.started = false; + TestSuiteInternal.current = null; + TestSuiteInternal.active = []; + } + + /** This is used internally to register tests. */ + static registerTest(options: dntShim.Deno.TestDefinition) { + options = { ...options }; + optionalTestDefinitionKeys.forEach((key) => { + if (typeof options[key] === "undefined") delete options[key]; + }); + dntShim.Deno.test(options); + } + + /** Updates all steps within top level suite to have ignore set to true if only is not set to true on step. */ + static addingOnlyStep(suite: TestSuiteInternal) { + if (!suite.hasOnlyStep) { + for (let i = 0; i < suite.steps.length; i++) { + const step = suite.steps[i]!; + if (!(step instanceof TestSuiteInternal) && !step.only) { + suite.steps.splice(i--, 1); + } + } + suite.hasOnlyStep = true; + } + + const parentSuite = suite.describe.suite; + const parentTestSuite = parentSuite && + TestSuiteInternal.suites.get(parentSuite.symbol); + if (parentTestSuite) { + TestSuiteInternal.addingOnlyStep(parentTestSuite); + } + } + + /** This is used internally to add steps to a test suite. */ + static addStep( + suite: TestSuiteInternal, + step: TestSuiteInternal | ItDefinition, + ) { + if (!suite.hasOnlyStep) { + if (step instanceof TestSuiteInternal) { + if (step.hasOnlyStep || step.describe.only) { + TestSuiteInternal.addingOnlyStep(suite); + } + } else { + if (step.only) TestSuiteInternal.addingOnlyStep(suite); + } + } + + if ( + !(suite.hasOnlyStep && !(step instanceof TestSuiteInternal) && !step.only) + ) { + suite.steps.push(step); + } + } + + /** This is used internally to add hooks to a test suite. */ + static setHook( + suite: TestSuiteInternal, + name: HookNames, + fn: (this: T) => void | Promise, + ) { + if (suite.describe[name]) { + if (typeof suite.describe[name] === "function") { + suite.describe[name] = [ + suite.describe[name] as ((this: T) => void | Promise), + ]; + } + (suite.describe[name] as ((this: T) => void | Promise)[]).push(fn); + } else { + suite.describe[name] = fn; + } + } + + /** This is used internally to run all steps for a test suite. */ + static async run( + suite: TestSuiteInternal, + context: T, + t: dntShim.Deno.TestContext, + ) { + const hasOnly = suite.hasOnlyStep || suite.describe.only || false; + for (const step of suite.steps) { + if ( + hasOnly && step instanceof TestSuiteInternal && + !(step.hasOnlyStep || step.describe.only || false) + ) { + continue; + } + + const { + name, + fn, + ignore, + permissions, + sanitizeExit, + sanitizeOps, + sanitizeResources, + } = step instanceof TestSuiteInternal ? step.describe : step; + + const options: dntShim.Deno.TestStepDefinition = { + name, + ignore, + sanitizeExit, + sanitizeOps, + sanitizeResources, + fn: async (t) => { + if (permissions) { + throw new Error( + "permissions option not available for nested tests", + ); + } + context = { ...context }; + if (step instanceof TestSuiteInternal) { + const { beforeAll } = step.describe; + if (typeof beforeAll === "function") { + await beforeAll.call(context); + } else if (beforeAll) { + for (const hook of beforeAll) { + await hook.call(context); + } + } + try { + TestSuiteInternal.active.push(step.symbol); + await TestSuiteInternal.run(step, context, t); + } finally { + TestSuiteInternal.active.pop(); + const { afterAll } = step.describe; + if (typeof afterAll === "function") { + await afterAll.call(context); + } else if (afterAll) { + for (const hook of afterAll) { + await hook.call(context); + } + } + } + } else { + await TestSuiteInternal.runTest(t, fn!, context); + } + }, + }; + optionalTestStepDefinitionKeys.forEach((key) => { + if (typeof options[key] === "undefined") delete options[key]; + }); + await t.step(options); + } + } + + static async runTest( + t: dntShim.Deno.TestContext, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise, + context: T, + activeIndex = 0, + ) { + const suite = TestSuiteInternal.active[activeIndex]; + const testSuite = suite && TestSuiteInternal.suites.get(suite); + if (testSuite) { + if (activeIndex === 0) context = { ...context }; + const { beforeEach } = testSuite.describe; + if (typeof beforeEach === "function") { + await beforeEach.call(context); + } else if (beforeEach) { + for (const hook of beforeEach) { + await hook.call(context); + } + } + try { + await TestSuiteInternal.runTest(t, fn, context, activeIndex + 1); + } finally { + const { afterEach } = testSuite.describe; + if (typeof afterEach === "function") { + await afterEach.call(context); + } else if (afterEach) { + for (const hook of afterEach) { + await hook.call(context); + } + } + } + } else { + await fn.call(context, t); + } + } +} diff --git a/npm/src/deps/deno.land/std@0.213.0/testing/bdd.ts b/npm/src/deps/deno.land/std@0.213.0/testing/bdd.ts new file mode 100644 index 0000000..6635c28 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.213.0/testing/bdd.ts @@ -0,0 +1,821 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +/** A [BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) interface + * to `Deno.test()` API. + * + * With the `bdd.ts` module you can write your tests in a familiar format for + * grouping tests and adding setup/teardown hooks used by other JavaScript testing + * frameworks like Jasmine, Jest, and Mocha. + * + * The `describe` function creates a block that groups together several related + * tests. The `it` function registers an individual test case. + * + * ## Hooks + * + * There are 4 types of hooks available for test suites. A test suite can have + * multiples of each type of hook, they will be called in the order that they are + * registered. The `afterEach` and `afterAll` hooks will be called whether or not + * the test case passes. The *All hooks will be called once for the whole group + * while the *Each hooks will be called for each individual test case. + * + * - `beforeAll`: Runs before all of the tests in the test suite. + * - `afterAll`: Runs after all of the tests in the test suite finish. + * - `beforeEach`: Runs before each of the individual test cases in the test suite. + * - `afterEach`: Runs after each of the individual test cases in the test suite. + * + * If a hook is registered at the top level, a global test suite will be registered + * and all tests will belong to it. Hooks registered at the top level must be + * registered before any individual test cases or test suites. + * + * ## Focusing tests + * + * If you would like to run only specific test cases, you can do so by calling + * `it.only` instead of `it`. If you would like to run only specific test suites, + * you can do so by calling `describe.only` instead of `describe`. + * + * There is one limitation to this when using the flat test grouping style. When + * `describe` is called without being nested, it registers the test with + * `Deno.test`. If a child test case or suite is registered with `it.only` or + * `describe.only`, it will be scoped to the top test suite instead of the file. To + * make them the only tests that run in the file, you would need to register the + * top test suite with `describe.only` too. + * + * ## Ignoring tests + * + * If you would like to not run specific individual test cases, you can do so by + * calling `it.ignore` instead of `it`. If you would like to not run specific test + * suites, you can do so by calling `describe.ignore` instead of `describe`. + * + * ## Sanitization options + * + * Like `Deno.TestDefinition`, the `DescribeDefinition` and `ItDefinition` have + * sanitization options. They work in the same way. + * + * - `sanitizeExit`: Ensure the test case does not prematurely cause the process to + * exit, for example via a call to Deno.exit. Defaults to true. + * - `sanitizeOps`: Check that the number of async completed ops after the test is + * the same as number of dispatched ops. Defaults to true. + * - `sanitizeResources`: Ensure the test case does not "leak" resources - ie. the + * resource table after the test has exactly the same contents as before the + * test. Defaults to true. + * + * ## Permissions option + * + * Like `Deno.TestDefinition`, the `DescribeDefinition` and `ItDefinition` have a + * `permissions` option. They specify the permissions that should be used to run an + * individual test case or test suite. Set this to `"inherit"` to keep the calling + * thread's permissions. Set this to `"none"` to revoke all permissions. + * + * This setting defaults to `"inherit"`. + * + * There is currently one limitation to this, you cannot use the permissions option + * on an individual test case or test suite that belongs to another test suite. + * That's because internally those tests are registered with `t.step` which does + * not support the permissions option. + * + * ## Comparing to Deno\.test + * + * The default way of writing tests is using `Deno.test` and `t.step`. The + * `describe` and `it` functions have similar call signatures to `Deno.test`, + * making it easy to switch between the default style and the behavior-driven + * development style of writing tests. Internally, `describe` and `it` are + * registering tests with `Deno.test` and `t.step`. + * + * Below is an example of a test file using `Deno.test` and `t.step`. In the + * following sections there are examples of how the same test could be written with + * `describe` and `it` using nested test grouping, flat test grouping, or a mix of + * both styles. + * + * ```ts + * import { + * assertEquals, + * assertStrictEquals, + * assertThrows, + * } from "https://deno.land/std@$STD_VERSION/assert/mod.ts"; + * + * class User { + * static users: Map = new Map(); + * age?: number; + * + * constructor(public name: string) { + * if (User.users.has(name)) { + * throw new Deno.errors.AlreadyExists(`User ${name} already exists`); + * } + * User.users.set(name, this); + * } + * + * getAge(): number { + * if (!this.age) { + * throw new Error("Age unknown"); + * } + * return this.age; + * } + * + * setAge(age: number) { + * this.age = age; + * } + * } + * + * Deno.test("User.users initially empty", () => { + * assertEquals(User.users.size, 0); + * }); + * + * Deno.test("User constructor", () => { + * try { + * const user = new User("Kyle"); + * assertEquals(user.name, "Kyle"); + * assertStrictEquals(User.users.get("Kyle"), user); + * } finally { + * User.users.clear(); + * } + * }); + * + * Deno.test("User age", async (t) => { + * const user = new User("Kyle"); + * + * await t.step("getAge", () => { + * assertThrows(() => user.getAge(), Error, "Age unknown"); + * user.age = 18; + * assertEquals(user.getAge(), 18); + * }); + * + * await t.step("setAge", () => { + * user.setAge(18); + * assertEquals(user.getAge(), 18); + * }); + * }); + * ``` + * + * ### Nested test grouping + * + * Tests created within the callback of a `describe` function call will belong to + * the new test suite it creates. The hooks can be created within it or be added to + * the options argument for describe. + * + * ```ts + * import { + * assertEquals, + * assertStrictEquals, + * assertThrows, + * } from "https://deno.land/std@$STD_VERSION/assert/mod.ts"; + * import { + * afterEach, + * beforeEach, + * describe, + * it, + * } from "https://deno.land/std@$STD_VERSION/testing/bdd.ts"; + * + * class User { + * static users: Map = new Map(); + * age?: number; + * + * constructor(public name: string) { + * if (User.users.has(name)) { + * throw new Deno.errors.AlreadyExists(`User ${name} already exists`); + * } + * User.users.set(name, this); + * } + * + * getAge(): number { + * if (!this.age) { + * throw new Error("Age unknown"); + * } + * return this.age; + * } + * + * setAge(age: number) { + * this.age = age; + * } + * } + * + * describe("User", () => { + * it("users initially empty", () => { + * assertEquals(User.users.size, 0); + * }); + * + * it("constructor", () => { + * try { + * const user = new User("Kyle"); + * assertEquals(user.name, "Kyle"); + * assertStrictEquals(User.users.get("Kyle"), user); + * } finally { + * User.users.clear(); + * } + * }); + * + * describe("age", () => { + * let user: User; + * + * beforeEach(() => { + * user = new User("Kyle"); + * }); + * + * afterEach(() => { + * User.users.clear(); + * }); + * + * it("getAge", function () { + * assertThrows(() => user.getAge(), Error, "Age unknown"); + * user.age = 18; + * assertEquals(user.getAge(), 18); + * }); + * + * it("setAge", function () { + * user.setAge(18); + * assertEquals(user.getAge(), 18); + * }); + * }); + * }); + * ``` + * + * ### Flat test grouping + * + * The `describe` function returns a unique symbol that can be used to reference + * the test suite for adding tests to it without having to create them within a + * callback. The gives you the ability to have test grouping without any extra + * indentation in front of the grouped tests. + * + * ```ts + * import { + * assertEquals, + * assertStrictEquals, + * assertThrows, + * } from "https://deno.land/std@$STD_VERSION/assert/mod.ts"; + * import { + * describe, + * it, + * } from "https://deno.land/std@$STD_VERSION/testing/bdd.ts"; + * + * class User { + * static users: Map = new Map(); + * age?: number; + * + * constructor(public name: string) { + * if (User.users.has(name)) { + * throw new Deno.errors.AlreadyExists(`User ${name} already exists`); + * } + * User.users.set(name, this); + * } + * + * getAge(): number { + * if (!this.age) { + * throw new Error("Age unknown"); + * } + * return this.age; + * } + * + * setAge(age: number) { + * this.age = age; + * } + * } + * + * const userTests = describe("User"); + * + * it(userTests, "users initially empty", () => { + * assertEquals(User.users.size, 0); + * }); + * + * it(userTests, "constructor", () => { + * try { + * const user = new User("Kyle"); + * assertEquals(user.name, "Kyle"); + * assertStrictEquals(User.users.get("Kyle"), user); + * } finally { + * User.users.clear(); + * } + * }); + * + * const ageTests = describe({ + * name: "age", + * suite: userTests, + * beforeEach(this: { user: User }) { + * this.user = new User("Kyle"); + * }, + * afterEach() { + * User.users.clear(); + * }, + * }); + * + * it(ageTests, "getAge", function () { + * const { user } = this; + * assertThrows(() => user.getAge(), Error, "Age unknown"); + * user.age = 18; + * assertEquals(user.getAge(), 18); + * }); + * + * it(ageTests, "setAge", function () { + * const { user } = this; + * user.setAge(18); + * assertEquals(user.getAge(), 18); + * }); + * ``` + * + * ### Mixed test grouping + * + * Both nested test grouping and flat test grouping can be used together. This can + * be useful if you'd like to create deep groupings without all the extra + * indentation in front of each line. + * + * ```ts + * import { + * assertEquals, + * assertStrictEquals, + * assertThrows, + * } from "https://deno.land/std@$STD_VERSION/assert/mod.ts"; + * import { + * describe, + * it, + * } from "https://deno.land/std@$STD_VERSION/testing/bdd.ts"; + * + * class User { + * static users: Map = new Map(); + * age?: number; + * + * constructor(public name: string) { + * if (User.users.has(name)) { + * throw new Deno.errors.AlreadyExists(`User ${name} already exists`); + * } + * User.users.set(name, this); + * } + * + * getAge(): number { + * if (!this.age) { + * throw new Error("Age unknown"); + * } + * return this.age; + * } + * + * setAge(age: number) { + * this.age = age; + * } + * } + * + * describe("User", () => { + * it("users initially empty", () => { + * assertEquals(User.users.size, 0); + * }); + * + * it("constructor", () => { + * try { + * const user = new User("Kyle"); + * assertEquals(user.name, "Kyle"); + * assertStrictEquals(User.users.get("Kyle"), user); + * } finally { + * User.users.clear(); + * } + * }); + * + * const ageTests = describe({ + * name: "age", + * beforeEach(this: { user: User }) { + * this.user = new User("Kyle"); + * }, + * afterEach() { + * User.users.clear(); + * }, + * }); + * + * it(ageTests, "getAge", function () { + * const { user } = this; + * assertThrows(() => user.getAge(), Error, "Age unknown"); + * user.age = 18; + * assertEquals(user.getAge(), 18); + * }); + * + * it(ageTests, "setAge", function () { + * const { user } = this; + * user.setAge(18); + * assertEquals(user.getAge(), 18); + * }); + * }); + * ``` + * + * @module + */ +import * as dntShim from "../../../../_dnt.test_shims.js"; + + +import { + DescribeDefinition, + HookNames, + ItDefinition, + TestSuite, + TestSuiteInternal, +} from "./_test_suite.js"; +export type { DescribeDefinition, ItDefinition, TestSuite }; + +/** The arguments for an ItFunction. */ +export type ItArgs = + | [options: ItDefinition] + | [ + name: string, + options: Omit, "name">, + ] + | [ + name: string, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise, + ] + | [fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise] + | [ + name: string, + options: Omit, "fn" | "name">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise, + ] + | [ + options: Omit, "fn">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise, + ] + | [ + options: Omit, "fn" | "name">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise, + ] + | [ + suite: TestSuite, + name: string, + options: Omit, "name" | "suite">, + ] + | [ + suite: TestSuite, + name: string, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise, + ] + | [ + suite: TestSuite, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise, + ] + | [ + suite: TestSuite, + name: string, + options: Omit, "fn" | "name" | "suite">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise, + ] + | [ + suite: TestSuite, + options: Omit, "fn" | "suite">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise, + ] + | [ + suite: TestSuite, + options: Omit, "fn" | "name" | "suite">, + fn: (this: T, t: dntShim.Deno.TestContext) => void | Promise, + ]; + +/** Generates an ItDefinition from ItArgs. */ +function itDefinition(...args: ItArgs): ItDefinition { + let [ + suiteOptionsOrNameOrFn, + optionsOrNameOrFn, + optionsOrFn, + fn, + ] = args; + let suite: TestSuite | undefined = undefined; + let name: string; + let options: + | ItDefinition + | Omit, "fn"> + | Omit, "name"> + | Omit, "fn" | "name">; + if ( + typeof suiteOptionsOrNameOrFn === "object" && + typeof (suiteOptionsOrNameOrFn as TestSuite).symbol === "symbol" + ) { + suite = suiteOptionsOrNameOrFn as TestSuite; + } else { + fn = optionsOrFn as typeof fn; + optionsOrFn = optionsOrNameOrFn as typeof optionsOrFn; + optionsOrNameOrFn = suiteOptionsOrNameOrFn as typeof optionsOrNameOrFn; + } + if (typeof optionsOrNameOrFn === "string") { + name = optionsOrNameOrFn; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + options = {}; + } else { + options = optionsOrFn!; + if (!fn) fn = (options as Omit, "name">).fn; + } + } else if (typeof optionsOrNameOrFn === "function") { + fn = optionsOrNameOrFn; + name = fn.name; + options = {}; + } else { + options = optionsOrNameOrFn!; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + } else { + fn = (options as ItDefinition).fn; + } + name = (options as ItDefinition).name ?? fn.name; + } + + return { + suite, + ...options, + name, + fn, + }; +} + +/** Registers an individual test case. */ +export interface it { + (...args: ItArgs): void; + + /** Registers an individual test case with only set to true. */ + only(...args: ItArgs): void; + + /** Registers an individual test case with ignore set to true. */ + ignore(...args: ItArgs): void; + + /** + * Registers an individual test case with ignore set to true. Alias of + * `.ignore()`. + */ + skip(...args: ItArgs): void; +} + +/** Registers an individual test case. */ +export function it(...args: ItArgs) { + if (TestSuiteInternal.runningCount > 0) { + throw new Error( + "cannot register new test cases after already registered test cases start running", + ); + } + const options = itDefinition(...args); + const { suite } = options; + const testSuite = suite + ? TestSuiteInternal.suites.get(suite.symbol) + : TestSuiteInternal.current; + + if (!TestSuiteInternal.started) TestSuiteInternal.started = true; + if (testSuite) { + TestSuiteInternal.addStep(testSuite, options); + } else { + const { + name, + fn, + ignore, + only, + permissions, + sanitizeExit, + sanitizeOps, + sanitizeResources, + } = options; + TestSuiteInternal.registerTest({ + name, + ignore, + only, + permissions, + sanitizeExit, + sanitizeOps, + sanitizeResources, + async fn(t) { + TestSuiteInternal.runningCount++; + try { + await fn.call({} as T, t); + } finally { + TestSuiteInternal.runningCount--; + } + }, + }); + } +} + +it.only = function itOnly(...args: ItArgs) { + const options = itDefinition(...args); + return it({ + ...options, + only: true, + }); +}; + +it.ignore = function itIgnore(...args: ItArgs) { + const options = itDefinition(...args); + return it({ + ...options, + ignore: true, + }); +}; + +it.skip = it.ignore; + +function addHook( + name: HookNames, + fn: (this: T) => void | Promise, +) { + if (!TestSuiteInternal.current) { + if (TestSuiteInternal.started) { + throw new Error( + "cannot add global hooks after a global test is registered", + ); + } + TestSuiteInternal.current = new TestSuiteInternal({ + name: "global", + [name]: fn, + }); + } else { + TestSuiteInternal.setHook(TestSuiteInternal.current!, name, fn); + } +} + +/** Run some shared setup before all of the tests in the suite. */ +export function beforeAll( + fn: (this: T) => void | Promise, +) { + addHook("beforeAll", fn); +} + +/** Run some shared teardown after all of the tests in the suite. */ +export function afterAll( + fn: (this: T) => void | Promise, +) { + addHook("afterAll", fn); +} + +/** Run some shared setup before each test in the suite. */ +export function beforeEach( + fn: (this: T) => void | Promise, +) { + addHook("beforeEach", fn); +} + +/** Run some shared teardown after each test in the suite. */ +export function afterEach( + fn: (this: T) => void | Promise, +) { + addHook("afterEach", fn); +} + +/** The arguments for a DescribeFunction. */ +export type DescribeArgs = + | [options: DescribeDefinition] + | [name: string] + | [ + name: string, + options: Omit, "name">, + ] + | [name: string, fn: () => void] + | [fn: () => void] + | [ + name: string, + options: Omit, "fn" | "name">, + fn: () => void, + ] + | [ + options: Omit, "fn">, + fn: () => void, + ] + | [ + options: Omit, "fn" | "name">, + fn: () => void, + ] + | [ + suite: TestSuite, + name: string, + ] + | [ + suite: TestSuite, + name: string, + options: Omit, "name" | "suite">, + ] + | [ + suite: TestSuite, + name: string, + fn: () => void, + ] + | [ + suite: TestSuite, + fn: () => void, + ] + | [ + suite: TestSuite, + name: string, + options: Omit, "fn" | "name" | "suite">, + fn: () => void, + ] + | [ + suite: TestSuite, + options: Omit, "fn" | "suite">, + fn: () => void, + ] + | [ + suite: TestSuite, + options: Omit, "fn" | "name" | "suite">, + fn: () => void, + ]; + +/** Generates a DescribeDefinition from DescribeArgs. */ +function describeDefinition( + ...args: DescribeArgs +): DescribeDefinition { + let [ + suiteOptionsOrNameOrFn, + optionsOrNameOrFn, + optionsOrFn, + fn, + ] = args; + let suite: TestSuite | undefined = undefined; + let name: string; + let options: + | DescribeDefinition + | Omit, "fn"> + | Omit, "name"> + | Omit, "fn" | "name">; + if ( + typeof suiteOptionsOrNameOrFn === "object" && + typeof (suiteOptionsOrNameOrFn as TestSuite).symbol === "symbol" + ) { + suite = suiteOptionsOrNameOrFn as TestSuite; + } else { + fn = optionsOrFn as typeof fn; + optionsOrFn = optionsOrNameOrFn as typeof optionsOrFn; + optionsOrNameOrFn = suiteOptionsOrNameOrFn as typeof optionsOrNameOrFn; + } + if (typeof optionsOrNameOrFn === "string") { + name = optionsOrNameOrFn; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + options = {}; + } else { + options = optionsOrFn ?? {}; + if (!fn) fn = (options as Omit, "name">).fn; + } + } else if (typeof optionsOrNameOrFn === "function") { + fn = optionsOrNameOrFn; + name = fn.name; + options = {}; + } else { + options = optionsOrNameOrFn ?? {}; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + } else { + fn = (options as DescribeDefinition).fn; + } + name = (options as DescribeDefinition).name ?? fn?.name ?? ""; + } + + if (!suite) { + suite = options.suite; + } + if (!suite && TestSuiteInternal.current) { + const { symbol } = TestSuiteInternal.current; + suite = { symbol }; + } + + return { + ...options, + suite, + name, + fn, + }; +} + +/** Registers a test suite. */ +export interface describe { + (...args: DescribeArgs): TestSuite; + + /** Registers a test suite with only set to true. */ + only(...args: DescribeArgs): TestSuite; + + /** Registers a test suite with ignore set to true. */ + ignore(...args: DescribeArgs): TestSuite; + + /** Registers a test suite with ignore set to true. Alias of `.ignore()`. */ + skip(...args: ItArgs): void; +} + +/** Registers a test suite. */ +export function describe( + ...args: DescribeArgs +): TestSuite { + if (TestSuiteInternal.runningCount > 0) { + throw new Error( + "cannot register new test suites after already registered test cases start running", + ); + } + const options = describeDefinition(...args); + if (!TestSuiteInternal.started) TestSuiteInternal.started = true; + const { symbol } = new TestSuiteInternal(options); + return { symbol }; +} + +describe.only = function describeOnly( + ...args: DescribeArgs +): TestSuite { + const options = describeDefinition(...args); + return describe({ + ...options, + only: true, + }); +}; + +describe.ignore = function describeIgnore( + ...args: DescribeArgs +): TestSuite { + const options = describeDefinition(...args); + return describe({ + ...options, + ignore: true, + }); +}; + +describe.skip = describe.ignore; diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/_constants.ts b/npm/src/deps/deno.land/std@0.216.0/assert/_constants.ts new file mode 100644 index 0000000..0cfc1cf --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/_constants.ts @@ -0,0 +1,2 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +export const CAN_NOT_DISPLAY = "[Cannot display]"; diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/_diff.ts b/npm/src/deps/deno.land/std@0.216.0/assert/_diff.ts new file mode 100644 index 0000000..8141120 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/_diff.ts @@ -0,0 +1,463 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { + bgGreen, + bgRed, + bold, + gray, + green, + red, + white, +} from "../fmt/colors.js"; + +interface FarthestPoint { + y: number; + id: number; +} + +export const DiffType = { + removed: "removed", + common: "common", + added: "added", +} as const; + +export type DiffType = keyof typeof DiffType; + +export interface DiffResult { + type: DiffType; + value: T; + details?: Array>; +} + +const REMOVED = 1; +const COMMON = 2; +const ADDED = 3; + +function createCommon(A: T[], B: T[], reverse?: boolean): T[] { + const common: T[] = []; + if (A.length === 0 || B.length === 0) return []; + for (let i = 0; i < Math.min(A.length, B.length); i += 1) { + const a = reverse ? A[A.length - i - 1] : A[i]; + const b = reverse ? B[B.length - i - 1] : B[i]; + if (a !== undefined && a === b) { + common.push(a); + } else { + return common; + } + } + return common; +} + +function ensureDefined(item?: T): T { + if (item === undefined) { + throw Error("Unexpected missing FarthestPoint"); + } + return item; +} + +/** + * Renders the differences between the actual and expected values + * @param A Actual value + * @param B Expected value + */ +export function diff(A: T[], B: T[]): Array> { + const prefixCommon = createCommon(A, B); + const suffixCommon = createCommon( + A.slice(prefixCommon.length), + B.slice(prefixCommon.length), + true, + ).reverse(); + A = suffixCommon.length + ? A.slice(prefixCommon.length, -suffixCommon.length) + : A.slice(prefixCommon.length); + B = suffixCommon.length + ? B.slice(prefixCommon.length, -suffixCommon.length) + : B.slice(prefixCommon.length); + const swapped = B.length > A.length; + [A, B] = swapped ? [B, A] : [A, B]; + const M = A.length; + const N = B.length; + if (!M && !N && !suffixCommon.length && !prefixCommon.length) return []; + if (!N) { + return [ + ...prefixCommon.map( + (c): DiffResult => ({ type: DiffType.common, value: c }), + ), + ...A.map( + (a): DiffResult => ({ + type: swapped ? DiffType.added : DiffType.removed, + value: a, + }), + ), + ...suffixCommon.map( + (c): DiffResult => ({ type: DiffType.common, value: c }), + ), + ]; + } + const offset = N; + const delta = M - N; + const size = M + N + 1; + const fp: FarthestPoint[] = Array.from( + { length: size }, + () => ({ y: -1, id: -1 }), + ); + + /** + * INFO: + * This buffer is used to save memory and improve performance. + * The first half is used to save route and last half is used to save diff + * type. + * This is because, when I kept new uint8array area to save type,performance + * worsened. + */ + const routes = new Uint32Array((M * N + size + 1) * 2); + const diffTypesPtrOffset = routes.length / 2; + let ptr = 0; + let p = -1; + + function backTrace( + A: T[], + B: T[], + current: FarthestPoint, + swapped: boolean, + ): Array<{ + type: DiffType; + value: T; + }> { + const M = A.length; + const N = B.length; + const result: { type: DiffType; value: T }[] = []; + let a = M - 1; + let b = N - 1; + let j = routes[current.id]; + let type = routes[current.id + diffTypesPtrOffset]; + while (true) { + if (!j && !type) break; + const prev = j!; + if (type === REMOVED) { + result.unshift({ + type: swapped ? DiffType.removed : DiffType.added, + value: B[b]!, + }); + b -= 1; + } else if (type === ADDED) { + result.unshift({ + type: swapped ? DiffType.added : DiffType.removed, + value: A[a]!, + }); + a -= 1; + } else { + result.unshift({ type: DiffType.common, value: A[a]! }); + a -= 1; + b -= 1; + } + j = routes[prev]; + type = routes[prev + diffTypesPtrOffset]; + } + return result; + } + + function createFP( + slide: FarthestPoint | undefined, + down: FarthestPoint | undefined, + k: number, + M: number, + ): FarthestPoint { + if (slide && slide.y === -1 && down && down.y === -1) { + return { y: 0, id: 0 }; + } + const isAdding = (down?.y === -1) || + k === M || + (slide?.y || 0) > (down?.y || 0) + 1; + if (slide && isAdding) { + const prev = slide.id; + ptr++; + routes[ptr] = prev; + routes[ptr + diffTypesPtrOffset] = ADDED; + return { y: slide.y, id: ptr }; + } else if (down && !isAdding) { + const prev = down.id; + ptr++; + routes[ptr] = prev; + routes[ptr + diffTypesPtrOffset] = REMOVED; + return { y: down.y + 1, id: ptr }; + } else { + throw new Error("Unexpected missing FarthestPoint"); + } + } + + function snake( + k: number, + slide: FarthestPoint | undefined, + down: FarthestPoint | undefined, + _offset: number, + A: T[], + B: T[], + ): FarthestPoint { + const M = A.length; + const N = B.length; + if (k < -N || M < k) return { y: -1, id: -1 }; + const fp = createFP(slide, down, k, M); + while (fp.y + k < M && fp.y < N && A[fp.y + k] === B[fp.y]) { + const prev = fp.id; + ptr++; + fp.id = ptr; + fp.y += 1; + routes[ptr] = prev; + routes[ptr + diffTypesPtrOffset] = COMMON; + } + return fp; + } + + let currentFP = ensureDefined(fp[delta + offset]); + while (currentFP && currentFP.y < N) { + p = p + 1; + for (let k = -p; k < delta; ++k) { + fp[k + offset] = snake( + k, + fp[k - 1 + offset], + fp[k + 1 + offset], + offset, + A, + B, + ); + } + for (let k = delta + p; k > delta; --k) { + fp[k + offset] = snake( + k, + fp[k - 1 + offset], + fp[k + 1 + offset], + offset, + A, + B, + ); + } + fp[delta + offset] = snake( + delta, + fp[delta - 1 + offset], + fp[delta + 1 + offset], + offset, + A, + B, + ); + currentFP = ensureDefined(fp[delta + offset]); + } + return [ + ...prefixCommon.map( + (c): DiffResult => ({ type: DiffType.common, value: c }), + ), + ...backTrace(A, B, currentFP, swapped), + ...suffixCommon.map( + (c): DiffResult => ({ type: DiffType.common, value: c }), + ), + ]; +} + +/** + * Renders the differences between the actual and expected strings + * Partially inspired from https://github.com/kpdecker/jsdiff + * @param A Actual string + * @param B Expected string + */ +export function diffstr(A: string, B: string) { + function unescape(string: string): string { + // unescape invisible characters. + // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences + return string + .replaceAll("\b", "\\b") + .replaceAll("\f", "\\f") + .replaceAll("\t", "\\t") + .replaceAll("\v", "\\v") + .replaceAll( // does not remove line breaks + /\r\n|\r|\n/g, + (str) => str === "\r" ? "\\r" : str === "\n" ? "\\n\n" : "\\r\\n\r\n", + ); + } + + function tokenize(string: string, { wordDiff = false } = {}): string[] { + if (wordDiff) { + // Split string on whitespace symbols + const tokens = string.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/); + // Extended Latin character set + const words = + /^[a-zA-Z\u{C0}-\u{FF}\u{D8}-\u{F6}\u{F8}-\u{2C6}\u{2C8}-\u{2D7}\u{2DE}-\u{2FF}\u{1E00}-\u{1EFF}]+$/u; + + // Join boundary splits that we do not consider to be boundaries and merge empty strings surrounded by word chars + for (let i = 0; i < tokens.length - 1; i++) { + const token = tokens[i]; + const tokenPlusTwo = tokens[i + 2]; + if ( + !tokens[i + 1] && + token && + tokenPlusTwo && + words.test(token) && + words.test(tokenPlusTwo) + ) { + tokens[i] += tokenPlusTwo; + tokens.splice(i + 1, 2); + i--; + } + } + return tokens.filter((token) => token); + } else { + // Split string on new lines symbols + const tokens: string[] = []; + const lines = string.split(/(\n|\r\n)/); + + // Ignore final empty token when text ends with a newline + if (!lines[lines.length - 1]) { + lines.pop(); + } + + // Merge the content and line separators into single tokens + for (const [i, line] of lines.entries()) { + if (i % 2) { + tokens[tokens.length - 1] += line; + } else { + tokens.push(line); + } + } + return tokens; + } + } + + // Create details by filtering relevant word-diff for current line + // and merge "space-diff" if surrounded by word-diff for cleaner displays + function createDetails( + line: DiffResult, + tokens: Array>, + ) { + return tokens.filter(({ type }) => + type === line.type || type === DiffType.common + ).map((result, i, t) => { + const token = t[i - 1]; + if ( + (result.type === DiffType.common) && token && + (token.type === t[i + 1]?.type) && /\s+/.test(result.value) + ) { + return { + ...result, + type: token.type, + }; + } + return result; + }); + } + + // Compute multi-line diff + const diffResult = diff( + tokenize(`${unescape(A)}\n`), + tokenize(`${unescape(B)}\n`), + ); + + const added = [], removed = []; + for (const result of diffResult) { + if (result.type === DiffType.added) { + added.push(result); + } + if (result.type === DiffType.removed) { + removed.push(result); + } + } + + // Compute word-diff + const hasMoreRemovedLines = added.length < removed.length; + const aLines = hasMoreRemovedLines ? added : removed; + const bLines = hasMoreRemovedLines ? removed : added; + for (const a of aLines) { + let tokens = [] as Array>, + b: undefined | DiffResult; + // Search another diff line with at least one common token + while (bLines.length) { + b = bLines.shift(); + const tokenized = [ + tokenize(a.value, { wordDiff: true }), + tokenize(b?.value ?? "", { wordDiff: true }), + ] as [string[], string[]]; + if (hasMoreRemovedLines) tokenized.reverse(); + tokens = diff(tokenized[0], tokenized[1]); + if ( + tokens.some(({ type, value }) => + type === DiffType.common && value.trim().length + ) + ) { + break; + } + } + // Register word-diff details + a.details = createDetails(a, tokens); + if (b) { + b.details = createDetails(b, tokens); + } + } + + return diffResult; +} + +/** + * Colors the output of assertion diffs + * @param diffType Difference type, either added or removed + */ +function createColor( + diffType: DiffType, + { background = false } = {}, +): (s: string) => string { + // TODO(@littledivy): Remove this when we can detect + // true color terminals. + // https://github.com/denoland/deno_std/issues/2575 + background = false; + switch (diffType) { + case DiffType.added: + return (s: string): string => + background ? bgGreen(white(s)) : green(bold(s)); + case DiffType.removed: + return (s: string): string => background ? bgRed(white(s)) : red(bold(s)); + default: + return white; + } +} + +/** + * Prefixes `+` or `-` in diff output + * @param diffType Difference type, either added or removed + */ +function createSign(diffType: DiffType): string { + switch (diffType) { + case DiffType.added: + return "+ "; + case DiffType.removed: + return "- "; + default: + return " "; + } +} + +export function buildMessage( + diffResult: ReadonlyArray>, + { stringDiff = false } = {}, +): string[] { + const messages: string[] = [], diffMessages: string[] = []; + messages.push(""); + messages.push(""); + messages.push( + ` ${gray(bold("[Diff]"))} ${red(bold("Actual"))} / ${ + green(bold("Expected")) + }`, + ); + messages.push(""); + messages.push(""); + diffResult.forEach((result: DiffResult) => { + const c = createColor(result.type); + const line = result.details?.map((detail) => + detail.type !== DiffType.common + ? createColor(detail.type, { background: true })(detail.value) + : detail.value + ).join("") ?? result.value; + diffMessages.push(c(`${createSign(result.type)}${line}`)); + }); + messages.push(...(stringDiff ? [diffMessages.join("")] : diffMessages)); + messages.push(""); + + return messages; +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/_format.ts b/npm/src/deps/deno.land/std@0.216.0/assert/_format.ts new file mode 100644 index 0000000..9ef3713 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/_format.ts @@ -0,0 +1,28 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +// This file been copied to `std/expect`. + +/** + * Converts the input into a string. Objects, Sets and Maps are sorted so as to + * make tests less flaky + * @param v Value to be formatted + */ +import * as dntShim from "../../../../_dnt.test_shims.js"; + +export function format(v: unknown): string { + // deno-lint-ignore no-explicit-any + const { Deno } = dntShim.dntGlobalThis as any; + return typeof Deno?.inspect === "function" + ? Deno.inspect(v, { + depth: Infinity, + sorted: true, + trailingComma: true, + compact: false, + iterableLimit: Infinity, + // getters should be true in assertEquals. + getters: true, + strAbbreviateSize: Infinity, + }) + : `"${String(v).replace(/(?=["\\])/g, "\\")}"`; +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert.ts new file mode 100644 index 0000000..a0db895 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert.ts @@ -0,0 +1,19 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; + +/** + * Make an assertion, error will be thrown if `expr` does not have truthy value. + * + * @example + * ```ts + * import { assert } from "https://deno.land/std@$STD_VERSION/assert/assert.ts"; + * + * assert("hello".includes("ello")); // Doesn't throw + * assert("hello".includes("world")); // Throws + * ``` + */ +export function assert(expr: unknown, msg = ""): asserts expr { + if (!expr) { + throw new AssertionError(msg); + } +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_almost_equals.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_almost_equals.ts new file mode 100644 index 0000000..6f56bed --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_almost_equals.ts @@ -0,0 +1,40 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; + +/** + * Make an assertion that `actual` and `expected` are almost equal numbers + * through a given tolerance. It can be used to take into account IEEE-754 + * double-precision floating-point representation limitations. If the values + * are not almost equal then throw. + * + * @example + * ```ts + * import { assertAlmostEquals } from "https://deno.land/std@$STD_VERSION/assert/mod.ts"; + * + * assertAlmostEquals(0.01, 0.02, 0.1); // Doesn't throw + * assertAlmostEquals(0.01, 0.02); // Throws + * assertAlmostEquals(0.1 + 0.2, 0.3, 1e-16); // Doesn't throw + * assertAlmostEquals(0.1 + 0.2, 0.3, 1e-17); // Throws + * ``` + */ +export function assertAlmostEquals( + actual: number, + expected: number, + tolerance = 1e-7, + msg?: string, +) { + if (Object.is(actual, expected)) { + return; + } + const delta = Math.abs(expected - actual); + if (delta <= tolerance) { + return; + } + + const msgSuffix = msg ? `: ${msg}` : "."; + const f = (n: number) => Number.isInteger(n) ? n : n.toExponential(); + throw new AssertionError( + `Expected actual: "${f(actual)}" to be close to "${f(expected)}": \ +delta "${f(delta)}" is greater than "${f(tolerance)}"${msgSuffix}`, + ); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_array_includes.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_array_includes.ts new file mode 100644 index 0000000..da33e72 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_array_includes.ts @@ -0,0 +1,51 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { equal } from "./equal.js"; +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; + +/** An array-like object (`Array`, `Uint8Array`, `NodeList`, etc.) that is not a string */ +export type ArrayLikeArg = ArrayLike & object; + +/** + * Make an assertion that `actual` includes the `expected` values. If not then + * an error will be thrown. + * + * Type parameter can be specified to ensure values under comparison have the + * same type. + * + * @example + * ```ts + * import { assertArrayIncludes } from "https://deno.land/std@$STD_VERSION/assert/assert_array_includes.ts"; + * + * assertArrayIncludes([1, 2], [2]); // Doesn't throw + * assertArrayIncludes([1, 2], [3]); // Throws + * ``` + */ +export function assertArrayIncludes( + actual: ArrayLikeArg, + expected: ArrayLikeArg, + msg?: string, +) { + const missing: unknown[] = []; + for (let i = 0; i < expected.length; i++) { + let found = false; + for (let j = 0; j < actual.length; j++) { + if (equal(expected[i], actual[j])) { + found = true; + break; + } + } + if (!found) { + missing.push(expected[i]); + } + } + if (missing.length === 0) { + return; + } + + const msgSuffix = msg ? `: ${msg}` : "."; + msg = `Expected actual: "${format(actual)}" to include: "${ + format(expected) + }"${msgSuffix}\nmissing: ${format(missing)}`; + throw new AssertionError(msg); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_equals.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_equals.ts new file mode 100644 index 0000000..1cfeaa7 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_equals.ts @@ -0,0 +1,53 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { equal } from "./equal.js"; +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; +import { red } from "../fmt/colors.js"; +import { buildMessage, diff, diffstr } from "./_diff.js"; +import { CAN_NOT_DISPLAY } from "./_constants.js"; + +/** + * Make an assertion that `actual` and `expected` are equal, deeply. If not + * deeply equal, then throw. + * + * Type parameter can be specified to ensure values under comparison have the + * same type. + * + * @example + * ```ts + * import { assertEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_equals.ts"; + * + * assertEquals("world", "world"); // Doesn't throw + * assertEquals("hello", "world"); // Throws + * ``` + * + * Note: formatter option is experimental and may be removed in the future. + */ +export function assertEquals( + actual: T, + expected: T, + msg?: string, + options: { formatter?: (value: unknown) => string } = {}, +) { + if (equal(actual, expected)) { + return; + } + const { formatter = format } = options; + const msgSuffix = msg ? `: ${msg}` : "."; + let message = `Values are not equal${msgSuffix}`; + + const actualString = formatter(actual); + const expectedString = formatter(expected); + try { + const stringDiff = (typeof actual === "string") && + (typeof expected === "string"); + const diffResult = stringDiff + ? diffstr(actual as string, expected as string) + : diff(actualString.split("\n"), expectedString.split("\n")); + const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n"); + message = `${message}\n${diffMsg}`; + } catch { + message = `${message}\n${red(CAN_NOT_DISPLAY)} + \n\n`; + } + throw new AssertionError(message); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_exists.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_exists.ts new file mode 100644 index 0000000..c467a02 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_exists.ts @@ -0,0 +1,26 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; + +/** + * Make an assertion that actual is not null or undefined. + * If not then throw. + * + * @example + * ```ts + * import { assertExists } from "https://deno.land/std@$STD_VERSION/assert/assert_exists.ts"; + * + * assertExists("something"); // Doesn't throw + * assertExists(undefined); // Throws + * ``` + */ +export function assertExists( + actual: T, + msg?: string, +): asserts actual is NonNullable { + if (actual === undefined || actual === null) { + const msgSuffix = msg ? `: ${msg}` : "."; + msg = + `Expected actual: "${actual}" to not be null or undefined${msgSuffix}`; + throw new AssertionError(msg); + } +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_false.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_false.ts new file mode 100644 index 0000000..604b346 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_false.ts @@ -0,0 +1,22 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; + +/** Assertion condition for {@linkcode assertFalse}. */ +export type Falsy = false | 0 | 0n | "" | null | undefined; + +/** + * Make an assertion, error will be thrown if `expr` have truthy value. + * + * @example + * ```ts + * import { assertFalse } from "https://deno.land/std@$STD_VERSION/assert/assert_false.ts"; + * + * assertFalse(false); // Doesn't throw + * assertFalse(true); // Throws + * ``` + */ +export function assertFalse(expr: unknown, msg = ""): asserts expr is Falsy { + if (expr) { + throw new AssertionError(msg); + } +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_greater.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_greater.ts new file mode 100644 index 0000000..f008caa --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_greater.ts @@ -0,0 +1,24 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; + +/** + * Make an assertion that `actual` is greater than `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertGreater } from "https://deno.land/std@$STD_VERSION/assert/assert_greater.ts"; + * + * assertGreater(2, 1); // Doesn't throw + * assertGreater(1, 1); // Throws + * assertGreater(0, 1); // Throws + * ``` + */ +export function assertGreater(actual: T, expected: T, msg?: string) { + if (actual > expected) return; + + const actualString = format(actual); + const expectedString = format(expected); + throw new AssertionError(msg ?? `Expect ${actualString} > ${expectedString}`); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_greater_or_equal.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_greater_or_equal.ts new file mode 100644 index 0000000..c963fdc --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_greater_or_equal.ts @@ -0,0 +1,30 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; + +/** + * Make an assertion that `actual` is greater than or equal to `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertGreaterOrEqual } from "https://deno.land/std@$STD_VERSION/assert/assert_greater_or_equal.ts"; + * + * assertGreaterOrEqual(2, 1); // Doesn't throw + * assertGreaterOrEqual(1, 1); // Doesn't throw + * assertGreaterOrEqual(0, 1); // Throws + * ``` + */ +export function assertGreaterOrEqual( + actual: T, + expected: T, + msg?: string, +) { + if (actual >= expected) return; + + const actualString = format(actual); + const expectedString = format(expected); + throw new AssertionError( + msg ?? `Expect ${actualString} >= ${expectedString}`, + ); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_instance_of.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_instance_of.ts new file mode 100644 index 0000000..524f4fc --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_instance_of.ts @@ -0,0 +1,57 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; + +/** Any constructor */ +// deno-lint-ignore no-explicit-any +export type AnyConstructor = new (...args: any[]) => any; +/** Gets constructor type */ +export type GetConstructorType = T extends // deno-lint-ignore no-explicit-any +new (...args: any) => infer C ? C + : never; + +/** + * Make an assertion that `obj` is an instance of `type`. + * If not then throw. + * + * @example + * ```ts + * import { assertInstanceOf } from "https://deno.land/std@$STD_VERSION/assert/assert_instance_of.ts"; + * + * assertInstanceOf(new Date(), Date); // Doesn't throw + * assertInstanceOf(new Date(), Number); // Throws + * ``` + */ +export function assertInstanceOf( + actual: unknown, + expectedType: T, + msg = "", +): asserts actual is GetConstructorType { + if (actual instanceof expectedType) return; + + const msgSuffix = msg ? `: ${msg}` : "."; + const expectedTypeStr = expectedType.name; + + let actualTypeStr = ""; + if (actual === null) { + actualTypeStr = "null"; + } else if (actual === undefined) { + actualTypeStr = "undefined"; + } else if (typeof actual === "object") { + actualTypeStr = actual.constructor?.name ?? "Object"; + } else { + actualTypeStr = typeof actual; + } + + if (expectedTypeStr === actualTypeStr) { + msg = + `Expected object to be an instance of "${expectedTypeStr}"${msgSuffix}`; + } else if (actualTypeStr === "function") { + msg = + `Expected object to be an instance of "${expectedTypeStr}" but was not an instanced object${msgSuffix}`; + } else { + msg = + `Expected object to be an instance of "${expectedTypeStr}" but was "${actualTypeStr}"${msgSuffix}`; + } + + throw new AssertionError(msg); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_is_error.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_is_error.ts new file mode 100644 index 0000000..792d2f2 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_is_error.ts @@ -0,0 +1,63 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +import { stripAnsiCode } from "../fmt/colors.js"; + +/** + * Make an assertion that `error` is an `Error`. + * If not then an error will be thrown. + * An error class and a string that should be included in the + * error message can also be asserted. + * + * @example + * ```ts + * import { assertIsError } from "https://deno.land/std@$STD_VERSION/assert/assert_is_error.ts"; + * + * assertIsError(null); // Throws + * assertIsError(new RangeError("Out of range")); // Doesn't throw + * assertIsError(new RangeError("Out of range"), SyntaxError); // Throws + * assertIsError(new RangeError("Out of range"), SyntaxError, "Out of range"); // Doesn't throw + * assertIsError(new RangeError("Out of range"), SyntaxError, "Within range"); // Throws + * ``` + */ +export function assertIsError( + error: unknown, + // deno-lint-ignore no-explicit-any + ErrorClass?: new (...args: any[]) => E, + msgMatches?: string | RegExp, + msg?: string, +): asserts error is E { + const msgSuffix = msg ? `: ${msg}` : "."; + if (!(error instanceof Error)) { + throw new AssertionError( + `Expected "error" to be an Error object${msgSuffix}}`, + ); + } + if (ErrorClass && !(error instanceof ErrorClass)) { + msg = `Expected error to be instance of "${ErrorClass.name}", but was "${ + typeof error === "object" ? error?.constructor?.name : "[not an object]" + }"${msgSuffix}`; + throw new AssertionError(msg); + } + let msgCheck; + if (typeof msgMatches === "string") { + msgCheck = stripAnsiCode(error.message).includes( + stripAnsiCode(msgMatches), + ); + } + if (msgMatches instanceof RegExp) { + msgCheck = msgMatches.test(stripAnsiCode(error.message)); + } + + if (msgMatches && !msgCheck) { + msg = `Expected error message to include ${ + msgMatches instanceof RegExp + ? msgMatches.toString() + : JSON.stringify(msgMatches) + }, but got ${ + error instanceof Error + ? JSON.stringify(error.message) + : '"[not an Error]"' // TODO(kt3k): show more useful information + }${msgSuffix}`; + throw new AssertionError(msg); + } +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_less.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_less.ts new file mode 100644 index 0000000..f5b7569 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_less.ts @@ -0,0 +1,23 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; + +/** + * Make an assertion that `actual` is less than `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertLess } from "https://deno.land/std@$STD_VERSION/assert/assert_less.ts"; + * + * assertLess(1, 2); // Doesn't throw + * assertLess(2, 1); // Throws + * ``` + */ +export function assertLess(actual: T, expected: T, msg?: string) { + if (actual < expected) return; + + const actualString = format(actual); + const expectedString = format(expected); + throw new AssertionError(msg ?? `Expect ${actualString} < ${expectedString}`); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_less_or_equal.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_less_or_equal.ts new file mode 100644 index 0000000..69c4adf --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_less_or_equal.ts @@ -0,0 +1,30 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; + +/** + * Make an assertion that `actual` is less than or equal to `expected`. + * If not then throw. + * + * @example + * ```ts + * import { assertLessOrEqual } from "https://deno.land/std@$STD_VERSION/assert/assert_less_or_equal.ts"; + * + * assertLessOrEqual(1, 2); // Doesn't throw + * assertLessOrEqual(1, 1); // Doesn't throw + * assertLessOrEqual(1, 0); // Throws + * ``` + */ +export function assertLessOrEqual( + actual: T, + expected: T, + msg?: string, +) { + if (actual <= expected) return; + + const actualString = format(actual); + const expectedString = format(expected); + throw new AssertionError( + msg ?? `Expect ${actualString} <= ${expectedString}`, + ); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_match.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_match.ts new file mode 100644 index 0000000..76a883c --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_match.ts @@ -0,0 +1,26 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; + +/** + * Make an assertion that `actual` match RegExp `expected`. If not + * then throw. + * + * @example + * ```ts + * import { assertMatch } from "https://deno.land/std@$STD_VERSION/assert/assert_match.ts"; + * + * assertMatch("Raptor", RegExp(/Raptor/)); // Doesn't throw + * assertMatch("Denosaurus", RegExp(/Raptor/)); // Throws + * ``` + */ +export function assertMatch( + actual: string, + expected: RegExp, + msg?: string, +) { + if (!expected.test(actual)) { + const msgSuffix = msg ? `: ${msg}` : "."; + msg = `Expected actual: "${actual}" to match: "${expected}"${msgSuffix}`; + throw new AssertionError(msg); + } +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_equals.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_equals.ts new file mode 100644 index 0000000..b3dc923 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_equals.ts @@ -0,0 +1,41 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +import { CAN_NOT_DISPLAY } from "./_constants.js"; +import { equal } from "./equal.js"; +import { AssertionError } from "./assertion_error.js"; + +/** + * Make an assertion that `actual` and `expected` are not equal, deeply. + * If not then throw. + * + * Type parameter can be specified to ensure values under comparison have the same type. + * + * @example + * ```ts + * import { assertNotEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_not_equals.ts"; + * + * assertNotEquals(1, 2); // Doesn't throw + * assertNotEquals(1, 1); // Throws + * ``` + */ +export function assertNotEquals(actual: T, expected: T, msg?: string) { + if (!equal(actual, expected)) { + return; + } + let actualString: string; + let expectedString: string; + try { + actualString = String(actual); + } catch { + actualString = CAN_NOT_DISPLAY; + } + try { + expectedString = String(expected); + } catch { + expectedString = CAN_NOT_DISPLAY; + } + const msgSuffix = msg ? `: ${msg}` : "."; + throw new AssertionError( + `Expected actual: ${actualString} not to be: ${expectedString}${msgSuffix}`, + ); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_instance_of.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_instance_of.ts new file mode 100644 index 0000000..3c1e48a --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_instance_of.ts @@ -0,0 +1,26 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assertFalse } from "./assert_false.js"; + +/** + * Make an assertion that `obj` is not an instance of `type`. + * If so, then throw. + * + * @example + * ```ts + * import { assertNotInstanceOf } from "https://deno.land/std@$STD_VERSION/assert/assert_not_instance_of.ts"; + * + * assertNotInstanceOf(new Date(), Number); // Doesn't throw + * assertNotInstanceOf(new Date(), Date); // Throws + * ``` + */ +export function assertNotInstanceOf( + actual: A, + // deno-lint-ignore no-explicit-any + unexpectedType: new (...args: any[]) => T, + msg?: string, +): asserts actual is Exclude { + const msgSuffix = msg ? `: ${msg}` : "."; + msg = + `Expected object to not be an instance of "${typeof unexpectedType}"${msgSuffix}`; + assertFalse(actual instanceof unexpectedType, msg); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_match.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_match.ts new file mode 100644 index 0000000..5c56c0d --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_match.ts @@ -0,0 +1,27 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; + +/** + * Make an assertion that `actual` not match RegExp `expected`. If match + * then throw. + * + * @example + * ```ts + * import { assertNotMatch } from "https://deno.land/std@$STD_VERSION/assert/assert_not_match.ts"; + * + * assertNotMatch("Denosaurus", RegExp(/Raptor/)); // Doesn't throw + * assertNotMatch("Raptor", RegExp(/Raptor/)); // Throws + * ``` + */ +export function assertNotMatch( + actual: string, + expected: RegExp, + msg?: string, +) { + if (expected.test(actual)) { + const msgSuffix = msg ? `: ${msg}` : "."; + msg = + `Expected actual: "${actual}" to not match: "${expected}"${msgSuffix}`; + throw new AssertionError(msg); + } +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_strict_equals.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_strict_equals.ts new file mode 100644 index 0000000..ccf569e --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_not_strict_equals.ts @@ -0,0 +1,32 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +import { format } from "./_format.js"; + +/** + * Make an assertion that `actual` and `expected` are not strictly equal. + * If the values are strictly equal then throw. + * + * @example + * ```ts + * import { assertNotStrictEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_not_strict_equals.ts"; + * + * assertNotStrictEquals(1, 1); // Doesn't throw + * assertNotStrictEquals(1, 2); // Throws + * ``` + */ +export function assertNotStrictEquals( + actual: T, + expected: T, + msg?: string, +) { + if (!Object.is(actual, expected)) { + return; + } + + const msgSuffix = msg ? `: ${msg}` : "."; + throw new AssertionError( + `Expected "actual" to not be strictly equal to: ${ + format(actual) + }${msgSuffix}\n`, + ); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_object_match.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_object_match.ts new file mode 100644 index 0000000..dd2ecef --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_object_match.ts @@ -0,0 +1,100 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "./assert_equals.js"; + +/** + * Make an assertion that `actual` object is a subset of `expected` object, + * deeply. If not, then throw. + * + * @example + * ```ts + * import { assertObjectMatch } from "https://deno.land/std@$STD_VERSION/assert/assert_object_match.ts"; + * + * assertObjectMatch({ foo: "bar" }, { foo: "bar" }); // Doesn't throw + * assertObjectMatch({ foo: "bar" }, { foo: "baz" }); // Throws + * ``` + */ +export function assertObjectMatch( + // deno-lint-ignore no-explicit-any + actual: Record, + expected: Record, + msg?: string, +): void { + type loose = Record; + + function filter(a: loose, b: loose) { + const seen = new WeakMap(); + return fn(a, b); + + function fn(a: loose, b: loose): loose { + // Prevent infinite loop with circular references with same filter + if ((seen.has(a)) && (seen.get(a) === b)) { + return a; + } + try { + seen.set(a, b); + } catch (err) { + if (err instanceof TypeError) { + throw new TypeError( + `Cannot assertObjectMatch ${ + a === null ? null : `type ${typeof a}` + }`, + ); + } else throw err; + } + // Filter keys and symbols which are present in both actual and expected + const filtered = {} as loose; + const entries = [ + ...Object.getOwnPropertyNames(a), + ...Object.getOwnPropertySymbols(a), + ] + .filter((key) => key in b) + .map((key) => [key, a[key as string]]) as Array<[string, unknown]>; + for (const [key, value] of entries) { + // On array references, build a filtered array and filter nested objects inside + if (Array.isArray(value)) { + const subset = (b as loose)[key]; + if (Array.isArray(subset)) { + filtered[key] = fn({ ...value }, { ...subset }); + continue; + } + } // On regexp references, keep value as it to avoid loosing pattern and flags + else if (value instanceof RegExp) { + filtered[key] = value; + continue; + } // On nested objects references, build a filtered object recursively + else if (typeof value === "object" && value !== null) { + const subset = (b as loose)[key]; + if ((typeof subset === "object") && subset) { + // When both operands are maps, build a filtered map with common keys and filter nested objects inside + if ((value instanceof Map) && (subset instanceof Map)) { + filtered[key] = new Map( + [...value].filter(([k]) => subset.has(k)).map(( + [k, v], + ) => [k, typeof v === "object" ? fn(v, subset.get(k)) : v]), + ); + continue; + } + // When both operands are set, build a filtered set with common values + if ((value instanceof Set) && (subset instanceof Set)) { + filtered[key] = new Set([...value].filter((v) => subset.has(v))); + continue; + } + filtered[key] = fn(value as loose, subset as loose); + continue; + } + } + filtered[key] = value; + } + return filtered; + } + } + return assertEquals( + // get the intersection of "actual" and "expected" + // side effect: all the instances' constructor field is "Object" now. + filter(actual, expected), + // set (nested) instances' constructor field to be "Object" without changing expected value. + // see https://github.com/denoland/deno_std/pull/1419 + filter(expected, expected), + msg, + ); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_rejects.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_rejects.ts new file mode 100644 index 0000000..a1cd000 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_rejects.ts @@ -0,0 +1,106 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; +import { assertIsError } from "./assert_is_error.js"; + +/** + * Executes a function which returns a promise, expecting it to reject. + * + * @example + * ```ts + * import { assertRejects } from "https://deno.land/std@$STD_VERSION/assert/assert_rejects.ts"; + * + * await assertRejects(async () => Promise.reject(new Error())); // Doesn't throw + * await assertRejects(async () => console.log("Hello world")); // Throws + * ``` + */ +export function assertRejects( + fn: () => PromiseLike, + msg?: string, +): Promise; +/** + * Executes a function which returns a promise, expecting it to reject. + * If it does not, then it throws. An error class and a string that should be + * included in the error message can also be asserted. + * + * @example + * ```ts + * import { assertRejects } from "https://deno.land/std@$STD_VERSION/assert/assert_rejects.ts"; + * + * await assertRejects(async () => Promise.reject(new Error()), Error); // Doesn't throw + * await assertRejects(async () => Promise.reject(new Error()), SyntaxError); // Throws + * ``` + */ +export function assertRejects( + fn: () => PromiseLike, + // deno-lint-ignore no-explicit-any + ErrorClass: new (...args: any[]) => E, + msgIncludes?: string, + msg?: string, +): Promise; +export async function assertRejects( + fn: () => PromiseLike, + errorClassOrMsg?: + // deno-lint-ignore no-explicit-any + | (new (...args: any[]) => E) + | string, + msgIncludesOrMsg?: string, + msg?: string, +): Promise { + // deno-lint-ignore no-explicit-any + let ErrorClass: (new (...args: any[]) => E) | undefined = undefined; + let msgIncludes: string | undefined = undefined; + let err; + + if (typeof errorClassOrMsg !== "string") { + if ( + errorClassOrMsg === undefined || + errorClassOrMsg.prototype instanceof Error || + errorClassOrMsg.prototype === Error.prototype + ) { + // deno-lint-ignore no-explicit-any + ErrorClass = errorClassOrMsg as new (...args: any[]) => E; + msgIncludes = msgIncludesOrMsg; + } + } else { + msg = errorClassOrMsg; + } + let doesThrow = false; + let isPromiseReturned = false; + const msgSuffix = msg ? `: ${msg}` : "."; + try { + const possiblePromise = fn(); + if ( + possiblePromise && + typeof possiblePromise === "object" && + typeof possiblePromise.then === "function" + ) { + isPromiseReturned = true; + await possiblePromise; + } + } catch (error) { + if (!isPromiseReturned) { + throw new AssertionError( + `Function throws when expected to reject${msgSuffix}`, + ); + } + if (ErrorClass) { + if (error instanceof Error === false) { + throw new AssertionError(`A non-Error object was rejected${msgSuffix}`); + } + assertIsError( + error, + ErrorClass, + msgIncludes, + msg, + ); + } + err = error; + doesThrow = true; + } + if (!doesThrow) { + throw new AssertionError( + `Expected function to reject${msgSuffix}`, + ); + } + return err; +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_strict_equals.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_strict_equals.ts new file mode 100644 index 0000000..0477545 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_strict_equals.ts @@ -0,0 +1,64 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { format } from "./_format.js"; +import { AssertionError } from "./assertion_error.js"; +import { buildMessage, diff, diffstr } from "./_diff.js"; +import { CAN_NOT_DISPLAY } from "./_constants.js"; +import { red } from "../fmt/colors.js"; + +/** + * Make an assertion that `actual` and `expected` are strictly equal. If + * not then throw. + * + * @example + * ```ts + * import { assertStrictEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_strict_equals.ts"; + * + * const a = {}; + * const b = a; + * assertStrictEquals(a, b); // Doesn't throw + * + * const c = {}; + * const d = {}; + * assertStrictEquals(c, d); // Throws + * ``` + */ +export function assertStrictEquals( + actual: unknown, + expected: T, + msg?: string, +): asserts actual is T { + if (Object.is(actual, expected)) { + return; + } + + const msgSuffix = msg ? `: ${msg}` : "."; + let message: string; + + const actualString = format(actual); + const expectedString = format(expected); + + if (actualString === expectedString) { + const withOffset = actualString + .split("\n") + .map((l) => ` ${l}`) + .join("\n"); + message = + `Values have the same structure but are not reference-equal${msgSuffix}\n\n${ + red(withOffset) + }\n`; + } else { + try { + const stringDiff = (typeof actual === "string") && + (typeof expected === "string"); + const diffResult = stringDiff + ? diffstr(actual as string, expected as string) + : diff(actualString.split("\n"), expectedString.split("\n")); + const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n"); + message = `Values are not strictly equal${msgSuffix}\n${diffMsg}`; + } catch { + message = `\n${red(CAN_NOT_DISPLAY)} + \n\n`; + } + } + + throw new AssertionError(message); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_string_includes.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_string_includes.ts new file mode 100644 index 0000000..33d9761 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_string_includes.ts @@ -0,0 +1,26 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; + +/** + * Make an assertion that actual includes expected. If not + * then throw. + * + * @example + * ```ts + * import { assertStringIncludes } from "https://deno.land/std@$STD_VERSION/assert/assert_string_includes.ts"; + * + * assertStringIncludes("Hello", "ello"); // Doesn't throw + * assertStringIncludes("Hello", "world"); // Throws + * ``` + */ +export function assertStringIncludes( + actual: string, + expected: string, + msg?: string, +) { + if (!actual.includes(expected)) { + const msgSuffix = msg ? `: ${msg}` : "."; + msg = `Expected actual: "${actual}" to contain: "${expected}"${msgSuffix}`; + throw new AssertionError(msg); + } +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assert_throws.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assert_throws.ts new file mode 100644 index 0000000..b9287c1 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assert_throws.ts @@ -0,0 +1,94 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assertIsError } from "./assert_is_error.js"; +import { AssertionError } from "./assertion_error.js"; + +/** + * Executes a function, expecting it to throw. If it does not, then it + * throws. + * + * @example + * ```ts + * import { assertThrows } from "https://deno.land/std@$STD_VERSION/assert/assert_throws.ts"; + * + * assertThrows(() => { throw new TypeError("hello world!"); }); // Doesn't throw + * assertThrows(() => console.log("hello world!")); // Throws + * ``` + */ +export function assertThrows( + fn: () => unknown, + msg?: string, +): unknown; +/** + * Executes a function, expecting it to throw. If it does not, then it + * throws. An error class and a string that should be included in the + * error message can also be asserted. + * + * @example + * ```ts + * import { assertThrows } from "https://deno.land/std@$STD_VERSION/assert/assert_throws.ts"; + * + * assertThrows(() => { throw new TypeError("hello world!"); }, TypeError); // Doesn't throw + * assertThrows(() => { throw new TypeError("hello world!"); }, RangeError); // Throws + * ``` + */ +export function assertThrows( + fn: () => unknown, + // deno-lint-ignore no-explicit-any + ErrorClass: new (...args: any[]) => E, + msgIncludes?: string, + msg?: string, +): E; +export function assertThrows( + fn: () => unknown, + errorClassOrMsg?: + // deno-lint-ignore no-explicit-any + | (new (...args: any[]) => E) + | string, + msgIncludesOrMsg?: string, + msg?: string, +): E | Error | unknown { + // deno-lint-ignore no-explicit-any + let ErrorClass: (new (...args: any[]) => E) | undefined = undefined; + let msgIncludes: string | undefined = undefined; + let err; + + if (typeof errorClassOrMsg !== "string") { + if ( + errorClassOrMsg === undefined || + errorClassOrMsg.prototype instanceof Error || + errorClassOrMsg.prototype === Error.prototype + ) { + // deno-lint-ignore no-explicit-any + ErrorClass = errorClassOrMsg as new (...args: any[]) => E; + msgIncludes = msgIncludesOrMsg; + } else { + msg = msgIncludesOrMsg; + } + } else { + msg = errorClassOrMsg; + } + let doesThrow = false; + const msgSuffix = msg ? `: ${msg}` : "."; + try { + fn(); + } catch (error) { + if (ErrorClass) { + if (error instanceof Error === false) { + throw new AssertionError(`A non-Error object was thrown${msgSuffix}`); + } + assertIsError( + error, + ErrorClass, + msgIncludes, + msg, + ); + } + err = error; + doesThrow = true; + } + if (!doesThrow) { + msg = `Expected function to throw${msgSuffix}`; + throw new AssertionError(msg); + } + return err; +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/assertion_error.ts b/npm/src/deps/deno.land/std@0.216.0/assert/assertion_error.ts new file mode 100644 index 0000000..602aef0 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/assertion_error.ts @@ -0,0 +1,19 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +/** + * Error thrown when an assertion fails. + * + * @example + * ```ts + * import { AssertionError } from "https://deno.land/std@$STD_VERSION/assert/assertion_error.ts"; + * + * throw new AssertionError("Assertion failed"); + * ``` + */ +export class AssertionError extends Error { + /** Constructs a new instance. */ + constructor(message: string) { + super(message); + this.name = "AssertionError"; + } +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/equal.ts b/npm/src/deps/deno.land/std@0.216.0/assert/equal.ts new file mode 100644 index 0000000..50f6371 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/equal.ts @@ -0,0 +1,119 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +function isKeyedCollection(x: unknown): x is Set { + return [Symbol.iterator, "size"].every((k) => k in (x as Set)); +} + +function constructorsEqual(a: object, b: object) { + return a.constructor === b.constructor || + a.constructor === Object && !b.constructor || + !a.constructor && b.constructor === Object; +} + +/** + * Deep equality comparison used in assertions + * @param c actual value + * @param d expected value + * + * @example + * ```ts + * import { equal } from "https://deno.land/std@$STD_VERSION/assert/equal.ts"; + * + * equal({ foo: "bar" }, { foo: "bar" }); // Returns `true` + * equal({ foo: "bar" }, { foo: "baz" }); // Returns `false + * ``` + */ +export function equal(c: unknown, d: unknown): boolean { + const seen = new Map(); + return (function compare(a: unknown, b: unknown): boolean { + // Have to render RegExp & Date for string comparison + // unless it's mistreated as object + if ( + a && + b && + ((a instanceof RegExp && b instanceof RegExp) || + (a instanceof URL && b instanceof URL)) + ) { + return String(a) === String(b); + } + if (a instanceof Date && b instanceof Date) { + const aTime = a.getTime(); + const bTime = b.getTime(); + // Check for NaN equality manually since NaN is not + // equal to itself. + if (Number.isNaN(aTime) && Number.isNaN(bTime)) { + return true; + } + return aTime === bTime; + } + if (typeof a === "number" && typeof b === "number") { + return Number.isNaN(a) && Number.isNaN(b) || a === b; + } + if (Object.is(a, b)) { + return true; + } + if (a && typeof a === "object" && b && typeof b === "object") { + if (a && b && !constructorsEqual(a, b)) { + return false; + } + if (a instanceof WeakMap || b instanceof WeakMap) { + if (!(a instanceof WeakMap && b instanceof WeakMap)) return false; + throw new TypeError("cannot compare WeakMap instances"); + } + if (a instanceof WeakSet || b instanceof WeakSet) { + if (!(a instanceof WeakSet && b instanceof WeakSet)) return false; + throw new TypeError("cannot compare WeakSet instances"); + } + if (seen.get(a) === b) { + return true; + } + if (Object.keys(a || {}).length !== Object.keys(b || {}).length) { + return false; + } + seen.set(a, b); + if (isKeyedCollection(a) && isKeyedCollection(b)) { + if (a.size !== b.size) { + return false; + } + + let unmatchedEntries = a.size; + + for (const [aKey, aValue] of a.entries()) { + for (const [bKey, bValue] of b.entries()) { + /* Given that Map keys can be references, we need + * to ensure that they are also deeply equal */ + if ( + (aKey === aValue && bKey === bValue && compare(aKey, bKey)) || + (compare(aKey, bKey) && compare(aValue, bValue)) + ) { + unmatchedEntries--; + break; + } + } + } + + return unmatchedEntries === 0; + } + const merged = { ...a, ...b }; + for ( + const key of [ + ...Object.getOwnPropertyNames(merged), + ...Object.getOwnPropertySymbols(merged), + ] + ) { + type Key = keyof typeof merged; + if (!compare(a && a[key as Key], b && b[key as Key])) { + return false; + } + if (((key in a) && (!(key in b))) || ((key in b) && (!(key in a)))) { + return false; + } + } + if (a instanceof WeakRef || b instanceof WeakRef) { + if (!(a instanceof WeakRef && b instanceof WeakRef)) return false; + return compare(a.deref(), b.deref()); + } + return true; + } + return false; + })(c, d); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/fail.ts b/npm/src/deps/deno.land/std@0.216.0/assert/fail.ts new file mode 100644 index 0000000..9df271b --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/fail.ts @@ -0,0 +1,17 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assert } from "./assert.js"; + +/** + * Forcefully throws a failed assertion. + * + * @example + * ```ts + * import { fail } from "https://deno.land/std@$STD_VERSION/assert/fail.ts"; + * + * fail("Deliberately failed!"); // Throws + * ``` + */ +export function fail(msg?: string): never { + const msgSuffix = msg ? `: ${msg}` : "."; + assert(false, `Failed assertion${msgSuffix}`); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/mod.ts b/npm/src/deps/deno.land/std@0.216.0/assert/mod.ts new file mode 100644 index 0000000..740ea46 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/mod.ts @@ -0,0 +1,39 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +/** A library of assertion functions. + * If the assertion is false an `AssertionError` will be thrown which will + * result in pretty-printed diff of failing assertion. + * + * This module is browser compatible, but do not rely on good formatting of + * values for AssertionError messages in browsers. + * + * @module + */ + +export * from "./assert_almost_equals.js"; +export * from "./assert_array_includes.js"; +export * from "./assert_equals.js"; +export * from "./assert_exists.js"; +export * from "./assert_false.js"; +export * from "./assert_greater_or_equal.js"; +export * from "./assert_greater.js"; +export * from "./assert_instance_of.js"; +export * from "./assert_is_error.js"; +export * from "./assert_less_or_equal.js"; +export * from "./assert_less.js"; +export * from "./assert_match.js"; +export * from "./assert_not_equals.js"; +export * from "./assert_not_instance_of.js"; +export * from "./assert_not_match.js"; +export * from "./assert_not_strict_equals.js"; +export * from "./assert_object_match.js"; +export * from "./assert_rejects.js"; +export * from "./assert_strict_equals.js"; +export * from "./assert_string_includes.js"; +export * from "./assert_throws.js"; +export * from "./assert.js"; +export * from "./assertion_error.js"; +export * from "./equal.js"; +export * from "./fail.js"; +export * from "./unimplemented.js"; +export * from "./unreachable.js"; diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/unimplemented.ts b/npm/src/deps/deno.land/std@0.216.0/assert/unimplemented.ts new file mode 100644 index 0000000..29d7eae --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/unimplemented.ts @@ -0,0 +1,17 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; + +/** + * Use this to stub out methods that will throw when invoked. + * + * @example + * ```ts + * import { unimplemented } from "https://deno.land/std@$STD_VERSION/assert/unimplemented.ts"; + * + * unimplemented(); // Throws + * ``` + */ +export function unimplemented(msg?: string): never { + const msgSuffix = msg ? `: ${msg}` : "."; + throw new AssertionError(`Unimplemented${msgSuffix}`); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/assert/unreachable.ts b/npm/src/deps/deno.land/std@0.216.0/assert/unreachable.ts new file mode 100644 index 0000000..40d2590 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/assert/unreachable.ts @@ -0,0 +1,16 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { AssertionError } from "./assertion_error.js"; + +/** + * Use this to assert unreachable code. + * + * @example + * ```ts + * import { unreachable } from "https://deno.land/std@$STD_VERSION/assert/unreachable.ts"; + * + * unreachable(); // Throws + * ``` + */ +export function unreachable(): never { + throw new AssertionError("unreachable"); +} diff --git a/npm/src/deps/deno.land/std@0.216.0/fmt/colors.ts b/npm/src/deps/deno.land/std@0.216.0/fmt/colors.ts new file mode 100644 index 0000000..3e37d16 --- /dev/null +++ b/npm/src/deps/deno.land/std@0.216.0/fmt/colors.ts @@ -0,0 +1,591 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. +// A module to print ANSI terminal colors. Inspired by chalk, kleur, and colors +// on npm. + +/** + * String formatters and utilities for dealing with ANSI color codes. + * + * This module is browser compatible. + * + * This module supports `NO_COLOR` environmental variable disabling any coloring + * if `NO_COLOR` is set. + * + * @example + * ```ts + * import { + * bgBlue, + * bgRgb24, + * bgRgb8, + * bold, + * italic, + * red, + * rgb24, + * rgb8, + * } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts"; + * + * console.log(bgBlue(italic(red(bold("Hello, World!"))))); + * + * // also supports 8bit colors + * + * console.log(rgb8("Hello, World!", 42)); + * + * console.log(bgRgb8("Hello, World!", 42)); + * + * // and 24bit rgb + * + * console.log(rgb24("Hello, World!", { + * r: 41, + * g: 42, + * b: 43, + * })); + * + * console.log(bgRgb24("Hello, World!", { + * r: 41, + * g: 42, + * b: 43, + * })); + * ``` + * + * @module + */ + +// deno-lint-ignore no-explicit-any +import * as dntShim from "../../../../_dnt.test_shims.js"; + +const { Deno } = dntShim.dntGlobalThis as any; +const noColor = typeof Deno?.noColor === "boolean" + ? Deno.noColor as boolean + : false; + +interface Code { + open: string; + close: string; + regexp: RegExp; +} + +/** RGB 8-bits per channel. Each in range `0->255` or `0x00->0xff` */ +export interface Rgb { + /** Red component value */ + r: number; + /** Green component value */ + g: number; + /** Blue component value */ + b: number; +} + +let enabled = !noColor; + +/** + * Set changing text color to enabled or disabled + * @param value + */ +export function setColorEnabled(value: boolean) { + if (Deno?.noColor) { + return; + } + + enabled = value; +} + +/** Get whether text color change is enabled or disabled. */ +export function getColorEnabled(): boolean { + return enabled; +} + +/** + * Builds color code + * @param open + * @param close + */ +function code(open: number[], close: number): Code { + return { + open: `\x1b[${open.join(";")}m`, + close: `\x1b[${close}m`, + regexp: new RegExp(`\\x1b\\[${close}m`, "g"), + }; +} + +/** + * Applies color and background based on color code and its associated text + * @param str text to apply color settings to + * @param code color code to apply + */ +function run(str: string, code: Code): string { + return enabled + ? `${code.open}${str.replace(code.regexp, code.open)}${code.close}` + : str; +} + +/** + * Reset the text modified. + * @param str text to reset + */ +export function reset(str: string): string { + return run(str, code([0], 0)); +} + +/** + * Make the text bold. + * @param str text to make bold + */ +export function bold(str: string): string { + return run(str, code([1], 22)); +} + +/** + * The text emits only a small amount of light. + * @param str text to dim + * + * Warning: Not all terminal emulators support `dim`. + * For compatibility across all terminals, use {@linkcode gray} or {@linkcode brightBlack} instead. + */ +export function dim(str: string): string { + return run(str, code([2], 22)); +} + +/** + * Make the text italic. + * @param str text to make italic + */ +export function italic(str: string): string { + return run(str, code([3], 23)); +} + +/** + * Make the text underline. + * @param str text to underline + */ +export function underline(str: string): string { + return run(str, code([4], 24)); +} + +/** + * Invert background color and text color. + * @param str text to invert its color + */ +export function inverse(str: string): string { + return run(str, code([7], 27)); +} + +/** + * Make the text hidden. + * @param str text to hide + */ +export function hidden(str: string): string { + return run(str, code([8], 28)); +} + +/** + * Put horizontal line through the center of the text. + * @param str text to strike through + */ +export function strikethrough(str: string): string { + return run(str, code([9], 29)); +} + +/** + * Set text color to black. + * @param str text to make black + */ +export function black(str: string): string { + return run(str, code([30], 39)); +} + +/** + * Set text color to red. + * @param str text to make red + */ +export function red(str: string): string { + return run(str, code([31], 39)); +} + +/** + * Set text color to green. + * @param str text to make green + */ +export function green(str: string): string { + return run(str, code([32], 39)); +} + +/** + * Set text color to yellow. + * @param str text to make yellow + */ +export function yellow(str: string): string { + return run(str, code([33], 39)); +} + +/** + * Set text color to blue. + * @param str text to make blue + */ +export function blue(str: string): string { + return run(str, code([34], 39)); +} + +/** + * Set text color to magenta. + * @param str text to make magenta + */ +export function magenta(str: string): string { + return run(str, code([35], 39)); +} + +/** + * Set text color to cyan. + * @param str text to make cyan + */ +export function cyan(str: string): string { + return run(str, code([36], 39)); +} + +/** + * Set text color to white. + * @param str text to make white + */ +export function white(str: string): string { + return run(str, code([37], 39)); +} + +/** + * Set text color to gray. + * @param str text to make gray + */ +export function gray(str: string): string { + return brightBlack(str); +} + +/** + * Set text color to bright black. + * @param str text to make bright-black + */ +export function brightBlack(str: string): string { + return run(str, code([90], 39)); +} + +/** + * Set text color to bright red. + * @param str text to make bright-red + */ +export function brightRed(str: string): string { + return run(str, code([91], 39)); +} + +/** + * Set text color to bright green. + * @param str text to make bright-green + */ +export function brightGreen(str: string): string { + return run(str, code([92], 39)); +} + +/** + * Set text color to bright yellow. + * @param str text to make bright-yellow + */ +export function brightYellow(str: string): string { + return run(str, code([93], 39)); +} + +/** + * Set text color to bright blue. + * @param str text to make bright-blue + */ +export function brightBlue(str: string): string { + return run(str, code([94], 39)); +} + +/** + * Set text color to bright magenta. + * @param str text to make bright-magenta + */ +export function brightMagenta(str: string): string { + return run(str, code([95], 39)); +} + +/** + * Set text color to bright cyan. + * @param str text to make bright-cyan + */ +export function brightCyan(str: string): string { + return run(str, code([96], 39)); +} + +/** + * Set text color to bright white. + * @param str text to make bright-white + */ +export function brightWhite(str: string): string { + return run(str, code([97], 39)); +} + +/** + * Set background color to black. + * @param str text to make its background black + */ +export function bgBlack(str: string): string { + return run(str, code([40], 49)); +} + +/** + * Set background color to red. + * @param str text to make its background red + */ +export function bgRed(str: string): string { + return run(str, code([41], 49)); +} + +/** + * Set background color to green. + * @param str text to make its background green + */ +export function bgGreen(str: string): string { + return run(str, code([42], 49)); +} + +/** + * Set background color to yellow. + * @param str text to make its background yellow + */ +export function bgYellow(str: string): string { + return run(str, code([43], 49)); +} + +/** + * Set background color to blue. + * @param str text to make its background blue + */ +export function bgBlue(str: string): string { + return run(str, code([44], 49)); +} + +/** + * Set background color to magenta. + * @param str text to make its background magenta + */ +export function bgMagenta(str: string): string { + return run(str, code([45], 49)); +} + +/** + * Set background color to cyan. + * @param str text to make its background cyan + */ +export function bgCyan(str: string): string { + return run(str, code([46], 49)); +} + +/** + * Set background color to white. + * @param str text to make its background white + */ +export function bgWhite(str: string): string { + return run(str, code([47], 49)); +} + +/** + * Set background color to bright black. + * @param str text to make its background bright-black + */ +export function bgBrightBlack(str: string): string { + return run(str, code([100], 49)); +} + +/** + * Set background color to bright red. + * @param str text to make its background bright-red + */ +export function bgBrightRed(str: string): string { + return run(str, code([101], 49)); +} + +/** + * Set background color to bright green. + * @param str text to make its background bright-green + */ +export function bgBrightGreen(str: string): string { + return run(str, code([102], 49)); +} + +/** + * Set background color to bright yellow. + * @param str text to make its background bright-yellow + */ +export function bgBrightYellow(str: string): string { + return run(str, code([103], 49)); +} + +/** + * Set background color to bright blue. + * @param str text to make its background bright-blue + */ +export function bgBrightBlue(str: string): string { + return run(str, code([104], 49)); +} + +/** + * Set background color to bright magenta. + * @param str text to make its background bright-magenta + */ +export function bgBrightMagenta(str: string): string { + return run(str, code([105], 49)); +} + +/** + * Set background color to bright cyan. + * @param str text to make its background bright-cyan + */ +export function bgBrightCyan(str: string): string { + return run(str, code([106], 49)); +} + +/** + * Set background color to bright white. + * @param str text to make its background bright-white + */ +export function bgBrightWhite(str: string): string { + return run(str, code([107], 49)); +} + +/* Special Color Sequences */ + +/** + * Clam and truncate color codes + * @param n + * @param max number to truncate to + * @param min number to truncate from + */ +function clampAndTruncate(n: number, max = 255, min = 0): number { + return Math.trunc(Math.max(Math.min(n, max), min)); +} + +/** + * Set text color using paletted 8bit colors. + * https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @param str text color to apply paletted 8bit colors to + * @param color code + */ +export function rgb8(str: string, color: number): string { + return run(str, code([38, 5, clampAndTruncate(color)], 39)); +} + +/** + * Set background color using paletted 8bit colors. + * https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @param str text color to apply paletted 8bit background colors to + * @param color code + */ +export function bgRgb8(str: string, color: number): string { + return run(str, code([48, 5, clampAndTruncate(color)], 49)); +} + +/** + * Set text color using 24bit rgb. + * `color` can be a number in range `0x000000` to `0xffffff` or + * an `Rgb`. + * + * To produce the color magenta: + * + * ```ts + * import { rgb24 } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts"; + * + * rgb24("foo", 0xff00ff); + * rgb24("foo", {r: 255, g: 0, b: 255}); + * ``` + * @param str text color to apply 24bit rgb to + * @param color code + */ +export function rgb24(str: string, color: number | Rgb): string { + if (typeof color === "number") { + return run( + str, + code( + [38, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff], + 39, + ), + ); + } + return run( + str, + code( + [ + 38, + 2, + clampAndTruncate(color.r), + clampAndTruncate(color.g), + clampAndTruncate(color.b), + ], + 39, + ), + ); +} + +/** + * Set background color using 24bit rgb. + * `color` can be a number in range `0x000000` to `0xffffff` or + * an `Rgb`. + * + * To produce the color magenta: + * + * ```ts + * import { bgRgb24 } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts"; + * + * bgRgb24("foo", 0xff00ff); + * bgRgb24("foo", {r: 255, g: 0, b: 255}); + * ``` + * @param str text color to apply 24bit rgb to + * @param color code + */ +export function bgRgb24(str: string, color: number | Rgb): string { + if (typeof color === "number") { + return run( + str, + code( + [48, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff], + 49, + ), + ); + } + return run( + str, + code( + [ + 48, + 2, + clampAndTruncate(color.r), + clampAndTruncate(color.g), + clampAndTruncate(color.b), + ], + 49, + ), + ); +} + +// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js +const ANSI_PATTERN = new RegExp( + [ + "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", + "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TXZcf-nq-uy=><~]))", + ].join("|"), + "g", +); + +/** + * Remove ANSI escape codes from the string. + * @param string to remove ANSI escape codes from + * + * @deprecated (will be removed in 1.0.0) Use {@linkcode stripAnsiCode} instead. + */ +export function stripColor(string: string): string { + return stripAnsiCode(string); +} + +/** + * Remove ANSI escape codes from the string. + * + * @param string to remove ANSI escape codes from + */ +export function stripAnsiCode(string: string): string { + return string.replace(ANSI_PATTERN, ""); +} diff --git a/npm/src/mod.ts b/npm/src/mod.ts new file mode 100644 index 0000000..7434bbb --- /dev/null +++ b/npm/src/mod.ts @@ -0,0 +1,4 @@ +export { MlKemError } from "./src/errors.js"; +export { MlKem512 } from "./src/kyber512.js"; +export { MlKem768 } from "./src/kyber768.js"; +export { MlKem1024 } from "./src/kyber1024.js"; diff --git a/npm/src/src/consts.ts b/npm/src/src/consts.ts new file mode 100644 index 0000000..55742f8 --- /dev/null +++ b/npm/src/src/consts.ts @@ -0,0 +1,39 @@ +/** + * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, + * which was deveploped under the MIT licence below: + * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE + */ + +export const N = 256; + +export const Q = 3329; + +export const Q_INV = 62209; + +// deno-fmt-ignore +export const NTT_ZETAS = [ + 2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962, + 2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017, + 732, 608, 1787, 411, 3124, 1758, 1223, 652, 2777, 1015, 2036, 1491, 3047, + 1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, 2476, 3239, 3058, 830, + 107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, 2226, + 430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574, + 1653, 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349, + 418, 329, 3173, 3254, 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193, + 1218, 1994, 2455, 220, 2142, 1670, 2144, 1799, 2051, 794, 1819, 2475, 2459, + 478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628, +]; + +// deno-fmt-ignore +export const NTT_ZETAS_INV = [ + 1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535, + 1278, 1530, 1185, 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465, + 1285, 2007, 2719, 2726, 2232, 2512, 75, 156, 3000, 2911, 2980, 872, 2685, + 1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, 1676, 1755, 460, 291, 235, + 3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, 1275, 2652, + 1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853, + 1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552, + 2677, 2106, 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871, + 829, 2946, 3065, 1325, 2756, 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171, + 3127, 3042, 1907, 1836, 1517, 359, 758, 1441, +]; diff --git a/npm/src/src/deps.ts b/npm/src/src/deps.ts new file mode 100644 index 0000000..8c1b46d --- /dev/null +++ b/npm/src/src/deps.ts @@ -0,0 +1,6 @@ +export { + sha3_256, + sha3_512, + shake128, + shake256, +} from "@openpgp/noble-hashes/sha3"; diff --git a/npm/src/src/errors.ts b/npm/src/src/errors.ts new file mode 100644 index 0000000..dccd3b9 --- /dev/null +++ b/npm/src/src/errors.ts @@ -0,0 +1,19 @@ +/** + * The base error class of kyber-ts. + */ +export class MlKemError extends Error { + public constructor(e: unknown) { + let message: string; + + if (e instanceof Error) { + message = e.message; + } else if (typeof e === "string") { + message = e; + } else { + message = ""; + } + super(message); + + this.name = this.constructor.name; + } +} diff --git a/npm/src/src/kyber1024.ts b/npm/src/src/kyber1024.ts new file mode 100644 index 0000000..2605235 --- /dev/null +++ b/npm/src/src/kyber1024.ts @@ -0,0 +1,185 @@ +/** + * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, + * which was deveploped under the MIT licence below: + * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE + */ +import { N, Q } from "./consts.js"; +import { KyberBase } from "./kyberBase.js"; +import { byte, int16, uint16, uint32 } from "./utils.js"; + +/** + * Represents the MlKem1024 class. + * + * MlKem1024 is a subclass of KyberBase and implements specific methods for the Kyber-1024 parameter set. + * + * @example + * + * ```ts + * // import { MlKem1024 } from "crystals-kyber-js"; // Node.js + * import { MlKem1024 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const recipient = new MlKem1024(); + * const [pkR, skR] = await recipient.generateKeyPair(); + + * const sender = new MlKem1024(); + * const [ct, ssS] = await sender.encap(pkR); + + * const ssR = await recipient.decap(ct, skR); + * // ssS === ssR + * ``` + */ +export class MlKem1024 extends KyberBase { + protected _k = 4; + protected _du = 11; + protected _dv = 5; + protected _eta1 = 2; + protected _eta2 = 2; + + /** + * Constructs a new instance of the MlKem1024 class. + */ + constructor() { + super(); + this._skSize = 12 * this._k * N / 8; + this._pkSize = this._skSize + 32; + this._compressedUSize = this._k * this._du * N / 8; + this._compressedVSize = this._dv * N / 8; + } + + // compressU lossily compresses and serializes a vector of polynomials. + + /** + * Lossily compresses and serializes a vector of polynomials. + * + * @param u - The vector of polynomials to compress. + * @returns The compressed and serialized data as a Uint8Array. + */ + protected override _compressU( + r: Uint8Array, + u: Array>, + ): Uint8Array { + const t = new Array(8); + for (let rr = 0, i = 0; i < this._k; i++) { + for (let j = 0; j < N / 8; j++) { + for (let k = 0; k < 8; k++) { + t[k] = uint16( + (((uint32(u[i][8 * j + k]) << 11) + uint32(Q / 2)) / + uint32(Q)) & 0x7ff, + ); + } + r[rr++] = byte(t[0] >> 0); + r[rr++] = byte((t[0] >> 8) | (t[1] << 3)); + r[rr++] = byte((t[1] >> 5) | (t[2] << 6)); + r[rr++] = byte(t[2] >> 2); + r[rr++] = byte((t[2] >> 10) | (t[3] << 1)); + r[rr++] = byte((t[3] >> 7) | (t[4] << 4)); + r[rr++] = byte((t[4] >> 4) | (t[5] << 7)); + r[rr++] = byte(t[5] >> 1); + r[rr++] = byte((t[5] >> 9) | (t[6] << 2)); + r[rr++] = byte((t[6] >> 6) | (t[7] << 5)); + r[rr++] = byte(t[7] >> 3); + } + } + return r; + } + + // compressV lossily compresses and subsequently serializes a polynomial. + + /** + * Lossily compresses and serializes a polynomial. + * + * @param r - The output buffer to store the compressed data. + * @param v - The polynomial to compress. + * @returns The compressed and serialized data as a Uint8Array. + */ + protected override _compressV(r: Uint8Array, v: Array): Uint8Array { + const t = new Uint8Array(8); + for (let rr = 0, i = 0; i < N / 8; i++) { + for (let j = 0; j < 8; j++) { + t[j] = byte( + ((uint32(v[8 * i + j]) << 5) + uint32(Q / 2)) / uint32(Q), + ) & 31; + } + r[rr++] = byte((t[0] >> 0) | (t[1] << 5)); + r[rr++] = byte((t[1] >> 3) | (t[2] << 2) | (t[3] << 7)); + r[rr++] = byte((t[3] >> 1) | (t[4] << 4)); + r[rr++] = byte((t[4] >> 4) | (t[5] << 1) | (t[6] << 6)); + r[rr++] = byte((t[6] >> 2) | (t[7] << 3)); + } + return r; + } + + // decompressU de-serializes and decompresses a vector of polynomials and + // represents the approximate inverse of compress1. Since compression is lossy, + // the results of decompression will may not match the original vector of polynomials. + + /** + * Deserializes and decompresses a vector of polynomials. + * This is the approximate inverse of the `_compressU` method. + * Since compression is lossy, the decompressed data may not match the original vector of polynomials. + * + * @param a - The compressed and serialized data as a Uint8Array. + * @returns The decompressed vector of polynomials. + */ + protected override _decompressU(a: Uint8Array): Array> { + const r = new Array>(this._k); + for (let i = 0; i < this._k; i++) { + r[i] = new Array(384); + } + const t = new Array(8); + for (let aa = 0, i = 0; i < this._k; i++) { + for (let j = 0; j < N / 8; j++) { + t[0] = (uint16(a[aa + 0]) >> 0) | (uint16(a[aa + 1]) << 8); + t[1] = (uint16(a[aa + 1]) >> 3) | (uint16(a[aa + 2]) << 5); + t[2] = (uint16(a[aa + 2]) >> 6) | (uint16(a[aa + 3]) << 2) | + (uint16(a[aa + 4]) << 10); + t[3] = (uint16(a[aa + 4]) >> 1) | (uint16(a[aa + 5]) << 7); + t[4] = (uint16(a[aa + 5]) >> 4) | (uint16(a[aa + 6]) << 4); + t[5] = (uint16(a[aa + 6]) >> 7) | (uint16(a[aa + 7]) << 1) | + (uint16(a[aa + 8]) << 9); + t[6] = (uint16(a[aa + 8]) >> 2) | (uint16(a[aa + 9]) << 6); + t[7] = (uint16(a[aa + 9]) >> 5) | (uint16(a[aa + 10]) << 3); + aa = aa + 11; + for (let k = 0; k < 8; k++) { + r[i][8 * j + k] = (uint32(t[k] & 0x7FF) * Q + 1024) >> 11; + } + } + } + return r; + } + + // decompressV de-serializes and subsequently decompresses a polynomial, + // representing the approximate inverse of compress2. + // Note that compression is lossy, and thus decompression will not match the + // original input. + + /** + * Decompresses a given polynomial, representing the approximate inverse of + * compress2, in Uint8Array into an array of numbers. + * + * Note that compression is lossy, and thus decompression will not match the + * original input. + * + * @param a - The Uint8Array to decompress. + * @returns An array of numbers obtained from the decompression process. + */ + protected override _decompressV(a: Uint8Array): Array { + const r = new Array(384); + const t = new Array(8); + for (let aa = 0, i = 0; i < N / 8; i++) { + t[0] = a[aa + 0] >> 0; + t[1] = (a[aa + 0] >> 5) | (a[aa + 1] << 3); + t[2] = a[aa + 1] >> 2; + t[3] = (a[aa + 1] >> 7) | (a[aa + 2] << 1); + t[4] = (a[aa + 2] >> 4) | (a[aa + 3] << 4); + t[5] = a[aa + 3] >> 1; + t[6] = (a[aa + 3] >> 6) | (a[aa + 4] << 2); + t[7] = a[aa + 4] >> 3; + aa = aa + 5; + for (let j = 0; j < 8; j++) { + r[8 * i + j] = int16(((uint32(t[j] & 31) * uint32(Q)) + 16) >> 5); + } + } + return r; + } +} diff --git a/npm/src/src/kyber512.ts b/npm/src/src/kyber512.ts new file mode 100644 index 0000000..140710e --- /dev/null +++ b/npm/src/src/kyber512.ts @@ -0,0 +1,98 @@ +/** + * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, + * which was deveploped under the MIT licence below: + * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE + */ +import { N } from "./consts.js"; +import { KyberBase } from "./kyberBase.js"; +import { byteopsLoad24, int16, prf } from "./utils.js"; + +/** + * Represents the MlKem512 class. + * + * This class extends the KyberBase class and provides specific implementation for MlKem512. + * + * @remarks + * + * MlKem512 is a specific implementation of the Kyber key encapsulation mechanism. + * + * @example + * + * ```ts + * // import { MlKem512 } from "crystals-kyber-js"; // Node.js + * import { MlKem512 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const recipient = new MlKem512(); + * const [pkR, skR] = await recipient.generateKeyPair(); + * + * const sender = new MlKem512(); + * const [ct, ssS] = await sender.encap(pkR); + * + * const ssR = await recipient.decap(ct, skR); + * // ssS === ssR + * ``` + */ +export class MlKem512 extends KyberBase { + protected _k = 2; + protected _du = 10; + protected _dv = 4; + protected _eta1 = 3; + protected _eta2 = 2; + + /** + * Constructs a new instance of the MlKem512 class. + */ + constructor() { + super(); + this._skSize = 12 * this._k * N / 8; + this._pkSize = this._skSize + 32; + this._compressedUSize = this._k * this._du * N / 8; + this._compressedVSize = this._dv * N / 8; + } + + /** + * Samples a vector of polynomials from a seed. + * @internal + * @param sigma - The seed. + * @param offset - The offset. + * @param size - The size. + * @returns The sampled vector of polynomials. + */ + protected override _sampleNoise1( + sigma: Uint8Array, + offset: number, + size: number, + ): Array> { + const r = new Array>(size); + for (let i = 0; i < size; i++) { + r[i] = byteopsCbd(prf(this._eta1 * N / 4, sigma, offset), this._eta1); + offset++; + } + return r; + } +} + +/** + * Performs the byte operations for the Cbd function. + * + * @param buf - The input buffer. + * @param eta - The value of eta. + * @returns An array of numbers representing the result of the byte operations. + */ +function byteopsCbd(buf: Uint8Array, eta: number): Array { + let t, d; + let a, b; + const r = new Array(384).fill(0); + for (let i = 0; i < N / 4; i++) { + t = byteopsLoad24(buf.subarray(3 * i, buf.length)); + d = t & 0x00249249; + d = d + ((t >> 1) & 0x00249249); + d = d + ((t >> 2) & 0x00249249); + for (let j = 0; j < 4; j++) { + a = int16((d >> (6 * j + 0)) & 0x7); + b = int16((d >> (6 * j + eta)) & 0x7); + r[4 * i + j] = a - b; + } + } + return r; +} diff --git a/npm/src/src/kyber768.ts b/npm/src/src/kyber768.ts new file mode 100644 index 0000000..78d4367 --- /dev/null +++ b/npm/src/src/kyber768.ts @@ -0,0 +1,48 @@ +/** + * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, + * which was deveploped under the MIT licence below: + * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE + */ +import { N } from "./consts.js"; +import { KyberBase } from "./kyberBase.js"; + +/** + * Represents the MlKem768 class, which extends the KyberBase class. + * + * MlKem768 is a specific implementation of the Kyber key encapsulation mechanism. + * + * @remarks + * + * This class extends the KyberBase class and provides specific implementation for MlKem768. + * + * @example + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const recipient = new MlKem768(); + * const [pkR, skR] = await recipient.generateKeyPair(); + * + * const sender = new MlKem768(); + * const [ct, ssS] = await sender.encap(pkR); + * + * const ssR = await recipient.decap(ct, skR); + * // ssS === ssR + * ``` + */ +export class MlKem768 extends KyberBase { + protected _k = 3; + protected _du = 10; + protected _dv = 4; + protected _eta1 = 2; + protected _eta2 = 2; + + constructor() { + super(); + this._skSize = 12 * this._k * N / 8; + this._pkSize = this._skSize + 32; + this._compressedUSize = this._k * this._du * N / 8; + this._compressedVSize = this._dv * N / 8; + } +} diff --git a/npm/src/src/kyberBase.ts b/npm/src/src/kyberBase.ts new file mode 100644 index 0000000..dfc83fe --- /dev/null +++ b/npm/src/src/kyberBase.ts @@ -0,0 +1,1152 @@ +/** + * This implementation is based on https://github.com/antontutoveanu/crystals-kyber-javascript, + * which was deveploped under the MIT licence below: + * https://github.com/antontutoveanu/crystals-kyber-javascript/blob/main/LICENSE + */ +import { sha3_256, sha3_512, shake128, shake256 } from "./deps.js"; + +import { N, NTT_ZETAS, NTT_ZETAS_INV, Q, Q_INV } from "./consts.js"; +import { MlKemError } from "./errors.js"; +import { + byte, + byteopsLoad32, + constantTimeCompare, + equalUint8Array, + int16, + int32, + loadCrypto, + prf, + uint16, + uint32, +} from "./utils.js"; + +/** + * Represents the base class for the Kyber key encapsulation mechanism. + * + * This class provides the base implementation for the Kyber key encapsulation mechanism. + * + * @remarks + * + * This class is not intended to be used directly. Instead, use one of the subclasses: + * + * @example + * + * ```ts + * // import { KyberBase } from "crystals-kyber-js"; // Node.js + * import { KyberBase } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * class MlKem768 extends KyberBase { + * protected _k = 3; + * protected _du = 10; + * protected _dv = 4; + * protected _eta1 = 2; + * protected _eta2 = 2; + * + * constructor() { + * super(); + * this._skSize = 12 * this._k * N / 8; + * this._pkSize = this._skSize + 32; + * this._compressedUSize = this._k * this._du * N / 8; + * this._compressedVSize = this._dv * N / 8; + * } + * } + * + * const kyber = new MlKem768(); + * ``` + */ +export class KyberBase { + private _api: Crypto | undefined = undefined; + protected _k = 0; + protected _du = 0; + protected _dv = 0; + protected _eta1 = 0; + protected _eta2 = 0; + protected _skSize = 0; + protected _pkSize = 0; + protected _compressedUSize = 0; + protected _compressedVSize = 0; + + /** + * Creates a new instance of the KyberBase class. + */ + constructor() {} + + /** + * Generates a keypair [publicKey, privateKey]. + * + * If an error occurred, throws {@link MlKemError}. + * + * @returns A kaypair [publicKey, privateKey]. + * @throws {@link MlKemError} + * + * @example Generates a {@link MlKem768} keypair. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const [pk, sk] = await kyber.generateKeyPair(); + * ``` + */ + public async generateKeyPair(): Promise<[Uint8Array, Uint8Array]> { + await this._setup(); + + try { + const rnd = new Uint8Array(64); + (this._api as Crypto).getRandomValues(rnd); + return this._deriveKeyPair(rnd); + } catch (e: unknown) { + throw new MlKemError(e); + } + } + + /** + * Derives a keypair [publicKey, privateKey] deterministically from a 64-octet seed. + * + * If an error occurred, throws {@link MlKemError}. + * + * @param seed A 64-octet seed for the deterministic key generation. + * @returns A kaypair [publicKey, privateKey]. + * @throws {@link MlKemError} + * + * @example Derives a {@link MlKem768} keypair deterministically. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const seed = new Uint8Array(64); + * globalThis.crypto.getRandomValues(seed); + * const [pk, sk] = await kyber.deriveKeyPair(seed); + * ``` + */ + public async deriveKeyPair( + seed: Uint8Array, + ): Promise<[Uint8Array, Uint8Array]> { + await this._setup(); + + try { + if (seed.byteLength !== 64) { + throw new Error("seed must be 64 bytes in length"); + } + return this._deriveKeyPair(seed); + } catch (e: unknown) { + throw new MlKemError(e); + } + } + + /** + * Generates a shared secret from the encapsulated ciphertext and the private key. + * + * If an error occurred, throws {@link MlKemError}. + * + * @param pk A public key. + * @param seed An optional 32-octet seed for the deterministic shared secret generation. + * @returns A ciphertext (encapsulated public key) and a shared secret. + * @throws {@link MlKemError} + * + * @example The {@link MlKem768} encapsulation. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const [pk, sk] = await kyber.generateKeyPair(); + * const [ct, ss] = await kyber.encap(pk); + * ``` + */ + public async encap( + pk: Uint8Array, + seed?: Uint8Array, + ): Promise<[Uint8Array, Uint8Array]> { + await this._setup(); + + try { + // validate key type; the modulo is checked in `_encap`. + if (pk.length !== 384 * this._k + 32) { + throw new Error("invalid encapsulation key"); + } + const m = this._getSeed(seed); + const [k, r] = g(m, h(pk)); + const ct = this._encap(pk, m, r); + return [ct, k]; + } catch (e: unknown) { + throw new MlKemError(e); + } + } + + /** + * Generates a ciphertext for the public key and a shared secret. + * + * If an error occurred, throws {@link MlKemError}. + * + * @param ct A ciphertext generated by {@link encap}. + * @param sk A private key. + * @returns A shared secret. + * @throws {@link MlKemError} + * + * @example The {@link MlKem768} decapsulation. + * + * ```ts + * // import { MlKem768 } from "crystals-kyber-js"; // Node.js + * import { MlKem768 } from "http://deno.land/x/crystals_kyber/mod.ts"; // Deno + * + * const kyber = new MlKem768(); + * const [pk, sk] = await kyber.generateKeyPair(); + * const [ct, ssS] = await kyber.encap(pk); + * const ssR = await kyber.decap(ct, sk); + * // ssS === ssR + * ``` + */ + public async decap(ct: Uint8Array, sk: Uint8Array): Promise { + await this._setup(); + + try { + // ciphertext type check + if (ct.byteLength !== this._compressedUSize + this._compressedVSize) { + throw new Error("Invalid ct size"); + } + // decapsulation key type check + if (sk.length !== 768 * this._k + 96) { + throw new Error("Invalid decapsulation key"); + } + const sk2 = sk.subarray(0, this._skSize); + const pk = sk.subarray(this._skSize, this._skSize + this._pkSize); + const hpk = sk.subarray( + this._skSize + this._pkSize, + this._skSize + this._pkSize + 32, + ); + const z = sk.subarray( + this._skSize + this._pkSize + 32, + this._skSize + this._pkSize + 64, + ); + + const m2 = this._decap(ct, sk2); + const [k2, r2] = g(m2, hpk); + const kBar = kdf(z, ct); + const ct2 = this._encap(pk, m2, r2); + return constantTimeCompare(ct, ct2) === 1 ? k2 : kBar; + } catch (e: unknown) { + throw new MlKemError(e); + } + } + + /** + * Sets up the KyberBase instance by loading the necessary crypto library. + * If the crypto library is already loaded, this method does nothing. + * @returns {Promise} A promise that resolves when the setup is complete. + */ + protected async _setup() { + if (this._api !== undefined) { + return; + } + this._api = await loadCrypto(); + } + + /** + * Returns a Uint8Array seed for cryptographic operations. + * If no seed is provided, a random seed of length 32 bytes is generated. + * If a seed is provided, it must be exactly 32 bytes in length. + * + * @param seed - Optional seed for cryptographic operations. + * @returns A Uint8Array seed. + * @throws Error if the provided seed is not 32 bytes in length. + */ + private _getSeed(seed?: Uint8Array): Uint8Array { + if (seed == undefined) { + const s = new Uint8Array(32); + (this._api as Crypto).getRandomValues(s); + return s; + } + if (seed.byteLength !== 32) { + throw new Error("seed must be 32 bytes in length"); + } + return seed; + } + + /** + * Derives a key pair from a given seed. + * + * @param seed - The seed used for key derivation. + * @returns An array containing the public key and secret key. + */ + private _deriveKeyPair(seed: Uint8Array): [Uint8Array, Uint8Array] { + const cpaSeed = seed.subarray(0, 32); + const z = seed.subarray(32, 64); + + const [pk, skBody] = this._deriveCpaKeyPair(cpaSeed); + + const pkh = h(pk); + const sk = new Uint8Array(this._skSize + this._pkSize + 64); + sk.set(skBody, 0); + sk.set(pk, this._skSize); + sk.set(pkh, this._skSize + this._pkSize); + sk.set(z, this._skSize + this._pkSize + 32); + return [pk, sk]; + } + + // indcpaKeyGen generates public and private keys for the CPA-secure + // public-key encryption scheme underlying Kyber. + + /** + * Derives a CPA key pair using the provided CPA seed. + * + * @param cpaSeed - The CPA seed used for key derivation. + * @returns An array containing the public key and private key. + */ + private _deriveCpaKeyPair(cpaSeed: Uint8Array): [Uint8Array, Uint8Array] { + const [publicSeed, noiseSeed] = g(cpaSeed); + const a = this._sampleMatrix(publicSeed, false); + const s = this._sampleNoise1(noiseSeed, 0, this._k); + const e = this._sampleNoise1(noiseSeed, this._k, this._k); + + // perform number theoretic transform on secret s + for (let i = 0; i < this._k; i++) { + s[i] = ntt(s[i]); + s[i] = reduce(s[i]); + e[i] = ntt(e[i]); + } + + // KEY COMPUTATION + // pk = A*s + e + const pk = new Array>(this._k); + for (let i = 0; i < this._k; i++) { + pk[i] = polyToMont(multiply(a[i], s)); + pk[i] = add(pk[i], e[i]); + pk[i] = reduce(pk[i]); + } + + // PUBLIC KEY + // turn polynomials into byte arrays + const pubKey = new Uint8Array(this._pkSize); + for (let i = 0; i < this._k; i++) { + pubKey.set(polyToBytes(pk[i]), i * 384); + } + // append public seed + pubKey.set(publicSeed, this._skSize); + + // PRIVATE KEY + // turn polynomials into byte arrays + const privKey = new Uint8Array(this._skSize); + for (let i = 0; i < this._k; i++) { + privKey.set(polyToBytes(s[i]), i * 384); + } + return [pubKey, privKey]; + } + + // _encap is the encapsulation function of the CPA-secure + // public-key encryption scheme underlying Kyber. + + /** + * K-PKE_Encrypt: Encapsulates a message using the Kyber encryption scheme. + * + * @param pk - The public key. + * @param msg - The message to be encapsulated. + * @param seed - The seed used for generating random values. + * @returns The encapsulated message as a Uint8Array. + */ + private _encap( + pk: Uint8Array, + msg: Uint8Array, + seed: Uint8Array, + ): Uint8Array { + const tHat = new Array>(this._k); + const pkCheck = new Uint8Array(384 * this._k); // to validate the pk modulo (see input validation at NIST draft 6.2) + for (let i = 0; i < this._k; i++) { + tHat[i] = polyFromBytes(pk.subarray(i * 384, (i + 1) * 384)); + pkCheck.set(polyToBytes(tHat[i]), i * 384); + } + if (!equalUint8Array(pk.subarray(0, pkCheck.length), pkCheck)) { + throw new Error("invalid encapsulation key"); + } + const rho = pk.subarray(this._skSize); + const a = this._sampleMatrix(rho, true); + const r = this._sampleNoise1(seed, 0, this._k); + const e1 = this._sampleNoise2(seed, this._k, this._k); + const e2 = this._sampleNoise2(seed, this._k * 2, 1)[0]; + + // perform number theoretic transform on random vector r + for (let i = 0; i < this._k; i++) { + r[i] = ntt(r[i]); + r[i] = reduce(r[i]); + } + + // u = A*r + e1 + const u = new Array>(this._k); + for (let i = 0; i < this._k; i++) { + u[i] = multiply(a[i], r); + u[i] = nttInverse(u[i]); + u[i] = add(u[i], e1[i]); + u[i] = reduce(u[i]); + } + + // v = tHat*r + e2 + m + const m = polyFromMsg(msg); + let v = multiply(tHat, r); + v = nttInverse(v); + v = add(v, e2); + v = add(v, m); + v = reduce(v); + + // compress + const ret = new Uint8Array(this._compressedUSize + this._compressedVSize); + this._compressU(ret.subarray(0, this._compressedUSize), u); + this._compressV(ret.subarray(this._compressedUSize), v); + return ret; + } + + // indcpaDecrypt is the decryption function of the CPA-secure + // public-key encryption scheme underlying Kyber. + + /** + * Decapsulates the ciphertext using the provided secret key. + * + * @param ct - The ciphertext to be decapsulated. + * @param sk - The secret key used for decapsulation. + * @returns The decapsulated message as a Uint8Array. + */ + private _decap(ct: Uint8Array, sk: Uint8Array): Uint8Array { + // extract ciphertext + const u = this._decompressU(ct.subarray(0, this._compressedUSize)); + const v = this._decompressV(ct.subarray(this._compressedUSize)); + + const privateKeyPolyvec = this._polyvecFromBytes(sk); + + for (let i = 0; i < this._k; i++) { + u[i] = ntt(u[i]); + } + + let mp = multiply(privateKeyPolyvec, u); + mp = nttInverse(mp); + mp = subtract(v, mp); + mp = reduce(mp); + return polyToMsg(mp); + } + + // generateMatrixA deterministically generates a matrix `A` (or the transpose of `A`) + // from a seed. Entries of the matrix are polynomials that look uniformly random. + // Performs rejection sampling on the output of an extendable-output function (XOF). + + /** + * Generates a sample matrix based on the provided seed and transposition flag. + * + * @param seed - The seed used for generating the matrix. + * @param transposed - A flag indicating whether the matrix should be transposed or not. + * @returns The generated sample matrix. + */ + private _sampleMatrix( + seed: Uint8Array, + transposed: boolean, + ): Array>> { + const a = new Array>>(this._k); + const transpose = new Uint8Array(2); + + for (let ctr = 0, i = 0; i < this._k; i++) { + a[i] = new Array>(this._k); + + for (let j = 0; j < this._k; j++) { + // set if transposed matrix or not + if (transposed) { + transpose[0] = i; + transpose[1] = j; + } else { + transpose[0] = j; + transpose[1] = i; + } + const output = xof(seed, transpose); + + // run rejection sampling on the output from above + const result = indcpaRejUniform(output.subarray(0, 504), 504, N); + a[i][j] = result[0]; // the result here is an NTT-representation + ctr = result[1]; // keeps track of index of output array from sampling function + + while (ctr < N) { // if the polynomial hasnt been filled yet with mod q entries + const outputn = output.subarray(504, 672); // take last 168 bytes of byte array from xof + const result1 = indcpaRejUniform(outputn, 168, N - ctr); // run sampling function again + const missing = result1[0]; // here is additional mod q polynomial coefficients + const ctrn = result1[1]; // how many coefficients were accepted and are in the output + // starting at last position of output array from first sampling function until 256 is reached + for (let k = ctr; k < N; k++) { + a[i][j][k] = missing[k - ctr]; // fill rest of array with the additional coefficients until full + } + ctr = ctr + ctrn; // update index + } + } + } + return a; + } + + /** + * Generates a 2D array of noise samples. + * + * @param sigma - The noise parameter. + * @param offset - The offset value. + * @param size - The size of the array. + * @returns The generated 2D array of noise samples. + */ + protected _sampleNoise1( + sigma: Uint8Array, + offset: number, + size: number, + ): Array> { + const r = new Array>(size); + for (let i = 0; i < size; i++) { + r[i] = byteopsCbd(prf(this._eta1 * N / 4, sigma, offset), this._eta1); + offset++; + } + return r; + } + + /** + * Generates a 2-dimensional array of noise samples. + * + * @param sigma - The noise parameter. + * @param offset - The offset value. + * @param size - The size of the array. + * @returns The generated 2-dimensional array of noise samples. + */ + protected _sampleNoise2( + sigma: Uint8Array, + offset: number, + size: number, + ): Array> { + const r = new Array>(size); + for (let i = 0; i < size; i++) { + r[i] = byteopsCbd(prf(this._eta2 * N / 4, sigma, offset), this._eta2); + offset++; + } + return r; + } + + // polyvecFromBytes deserializes a vector of polynomials. + + /** + * Converts a Uint8Array to a 2D array of numbers representing a polynomial vector. + * Each element in the resulting array represents a polynomial. + * @param a The Uint8Array to convert. + * @returns The 2D array of numbers representing the polynomial vector. + */ + private _polyvecFromBytes(a: Uint8Array): Array> { + const r = new Array>(this._k); + for (let i = 0; i < this._k; i++) { + r[i] = polyFromBytes(a.subarray(i * 384, (i + 1) * 384)); + } + return r; + } + + // compressU lossily compresses and serializes a vector of polynomials. + + /** + * Compresses the given array of coefficients into a Uint8Array. + * + * @param r - The output Uint8Array. + * @param u - The array of coefficients. + * @returns The compressed Uint8Array. + */ + protected _compressU( + r: Uint8Array, + u: Array>, + ): Uint8Array { + const t = new Array(4); + for (let rr = 0, i = 0; i < this._k; i++) { + for (let j = 0; j < N / 4; j++) { + for (let k = 0; k < 4; k++) { + // parse {0,...,3328} to {0,...,1023} + t[k] = (((u[i][4 * j + k] << 10) + Q / 2) / Q) & + 0b1111111111; + } + // converts 4 12-bit coefficients {0,...,3328} to 5 8-bit bytes {0,...,255} + // 48 bits down to 40 bits per block + r[rr++] = byte(t[0] >> 0); + r[rr++] = byte((t[0] >> 8) | (t[1] << 2)); + r[rr++] = byte((t[1] >> 6) | (t[2] << 4)); + r[rr++] = byte((t[2] >> 4) | (t[3] << 6)); + r[rr++] = byte(t[3] >> 2); + } + } + return r; + } + + // compressV lossily compresses and subsequently serializes a polynomial. + + /** + * Compresses the given array of numbers into a Uint8Array. + * + * @param r - The Uint8Array to store the compressed values. + * @param v - The array of numbers to compress. + * @returns The compressed Uint8Array. + */ + protected _compressV(r: Uint8Array, v: Array): Uint8Array { + // const r = new Uint8Array(128); + const t = new Uint8Array(8); + for (let rr = 0, i = 0; i < N / 8; i++) { + for (let j = 0; j < 8; j++) { + t[j] = byte(((v[8 * i + j] << 4) + Q / 2) / Q) & 0b1111; + } + r[rr++] = t[0] | (t[1] << 4); + r[rr++] = t[2] | (t[3] << 4); + r[rr++] = t[4] | (t[5] << 4); + r[rr++] = t[6] | (t[7] << 4); + } + return r; + } + + // decompressU de-serializes and decompresses a vector of polynomials and + // represents the approximate inverse of compress1. Since compression is lossy, + // the results of decompression will may not match the original vector of polynomials. + + /** + * Decompresses a Uint8Array into a two-dimensional array of numbers. + * + * @param a The Uint8Array to decompress. + * @returns The decompressed two-dimensional array. + */ + protected _decompressU(a: Uint8Array): Array> { + const r = new Array>(this._k); + for (let i = 0; i < this._k; i++) { + r[i] = new Array(384); + } + const t = new Array(4); + for (let aa = 0, i = 0; i < this._k; i++) { + for (let j = 0; j < N / 4; j++) { + t[0] = (uint16(a[aa + 0]) >> 0) | (uint16(a[aa + 1]) << 8); + t[1] = (uint16(a[aa + 1]) >> 2) | (uint16(a[aa + 2]) << 6); + t[2] = (uint16(a[aa + 2]) >> 4) | (uint16(a[aa + 3]) << 4); + t[3] = (uint16(a[aa + 3]) >> 6) | (uint16(a[aa + 4]) << 2); + aa = aa + 5; + for (let k = 0; k < 4; k++) { + r[i][4 * j + k] = int16( + (((uint32(t[k] & 0x3FF)) * (uint32(Q))) + 512) >> 10, + ); + } + } + } + return r; + } + + // decompressV de-serializes and subsequently decompresses a polynomial, + // representing the approximate inverse of compress2. + // Note that compression is lossy, and thus decompression will not match the + // original input. + + /** + * Decompresses a Uint8Array into an array of numbers. + * + * @param a - The Uint8Array to decompress. + * @returns An array of numbers. + */ + protected _decompressV(a: Uint8Array): Array { + const r = new Array(384); + for (let aa = 0, i = 0; i < N / 2; i++, aa++) { + r[2 * i + 0] = int16(((uint16(a[aa] & 15) * uint16(Q)) + 8) >> 4); + r[2 * i + 1] = int16(((uint16(a[aa] >> 4) * uint16(Q)) + 8) >> 4); + } + return r; + } +} + +/** + * Computes the hash of the input array `a` and an optional input array `b`. + * Returns an array containing two Uint8Arrays, representing the first 32 bytes and the next 32 bytes of the hash digest. + * @param a - The input array to be hashed. + * @param b - An optional input array to be hashed along with `a`. + * @returns An array containing two Uint8Arrays representing the hash digest. + */ +function g(a: Uint8Array, b?: Uint8Array): [Uint8Array, Uint8Array] { + const hash = sha3_512.create().update(a); + if (b !== undefined) { + hash.update(b); + } + const res = hash.digest(); + return [res.subarray(0, 32), res.subarray(32, 64)]; +} + +/** + * Computes the SHA3-256 hash of the given message. + * + * @param msg - The input message as a Uint8Array. + * @returns The computed hash as a Uint8Array. + */ +function h(msg: Uint8Array): Uint8Array { + return sha3_256.create().update(msg).digest(); +} + +/** + * Key Derivation Function (KDF) that takes an input array `a` and an optional input array `b`. + * It uses the SHAKE256 hash function to derive a 32-byte output. + * + * @param a - The input array. + * @param b - The optional input array. + * @returns The derived key as a Uint8Array. + */ +function kdf(a: Uint8Array, b?: Uint8Array): Uint8Array { + const hash = shake256.create({ dkLen: 32 }).update(a); + if (b !== undefined) { + hash.update(b); + } + return hash.digest(); +} + +/** + * Computes the extendable-output function (XOF) using the SHAKE128 algorithm. + * + * @param seed - The seed value for the XOF. + * @param transpose - The transpose value for the XOF. + * @returns The computed XOF value as a Uint8Array. + */ +function xof(seed: Uint8Array, transpose: Uint8Array): Uint8Array { + return shake128.create({ dkLen: 672 }).update(seed).update(transpose) + .digest(); +} + +// polyToBytes serializes a polynomial into an array of bytes. + +/** + * Converts a polynomial represented by an array of numbers to a Uint8Array. + * Each coefficient of the polynomial is reduced modulo q. + * + * @param a - The array representing the polynomial. + * @returns The Uint8Array representation of the polynomial. + */ +function polyToBytes(a: Array): Uint8Array { + let t0 = 0; + let t1 = 0; + const r = new Uint8Array(384); + const a2 = subtractQ(a); // Returns: a - q if a >= q, else a (each coefficient of the polynomial) + // for 0-127 + for (let i = 0; i < N / 2; i++) { + // get two coefficient entries in the polynomial + t0 = uint16(a2[2 * i]); + t1 = uint16(a2[2 * i + 1]); + + // convert the 2 coefficient into 3 bytes + r[3 * i + 0] = byte(t0 >> 0); // byte() does mod 256 of the input (output value 0-255) + r[3 * i + 1] = byte(t0 >> 8) | byte(t1 << 4); + r[3 * i + 2] = byte(t1 >> 4); + } + return r; +} + +// polyFromBytes de-serialises an array of bytes into a polynomial, +// and represents the inverse of polyToBytes. + +/** + * Converts a Uint8Array to an array of numbers representing a polynomial. + * Each element in the array represents a coefficient of the polynomial. + * The input array `a` should have a length of 384. + * The function performs bitwise operations to extract the coefficients from the input array. + * @param a The Uint8Array to convert to a polynomial. + * @returns An array of numbers representing the polynomial. + */ +function polyFromBytes(a: Uint8Array): Array { + const r = new Array(384).fill(0); + for (let i = 0; i < N / 2; i++) { + r[2 * i] = int16( + ((uint16(a[3 * i + 0]) >> 0) | (uint16(a[3 * i + 1]) << 8)) & 0xFFF, + ); + r[2 * i + 1] = int16( + ((uint16(a[3 * i + 1]) >> 4) | (uint16(a[3 * i + 2]) << 4)) & 0xFFF, + ); + } + return r; +} + +// polyToMsg converts a polynomial to a 32-byte message +// and represents the inverse of polyFromMsg. + +/** + * Converts a polynomial to a message represented as a Uint8Array. + * @param a - The polynomial to convert. + * @returns The message as a Uint8Array. + */ +function polyToMsg(a: Array): Uint8Array { + const msg = new Uint8Array(32); + let t; + const a2 = subtractQ(a); + for (let i = 0; i < N / 8; i++) { + msg[i] = 0; + for (let j = 0; j < 8; j++) { + t = (((uint16(a2[8 * i + j]) << 1) + uint16(Q / 2)) / + uint16(Q)) & 1; + msg[i] |= byte(t << j); + } + } + return msg; +} + +// polyFromMsg converts a 32-byte message to a polynomial. + +/** + * Converts a Uint8Array message to an array of numbers representing a polynomial. + * Each element in the array is an int16 (0-65535). + * + * @param msg - The Uint8Array message to convert. + * @returns An array of numbers representing the polynomial. + */ +function polyFromMsg(msg: Uint8Array): Array { + const r = new Array(384).fill(0); // each element is int16 (0-65535) + let mask; // int16 + for (let i = 0; i < N / 8; i++) { + for (let j = 0; j < 8; j++) { + mask = -1 * int16((msg[i] >> j) & 1); + r[8 * i + j] = mask & int16((Q + 1) / 2); + } + } + return r; +} + +// indcpaRejUniform runs rejection sampling on uniform random bytes +// to generate uniform random integers modulo `Q`. + +/** + * Generates an array of random numbers from a given buffer, rejecting values greater than a specified threshold. + * + * @param buf - The input buffer containing random bytes. + * @param bufl - The length of the input buffer. + * @param len - The desired length of the output array. + * @returns An array of random numbers and the actual length of the output array. + */ +function indcpaRejUniform( + buf: Uint8Array, + bufl: number, + len: number, +): [Array, number] { + const r = new Array(384).fill(0); + let ctr = 0; + let val0, val1; // d1, d2 in kyber documentation + + for (let pos = 0; ctr < len && pos + 3 <= bufl;) { + // compute d1 and d2 + val0 = (uint16((buf[pos]) >> 0) | (uint16(buf[pos + 1]) << 8)) & 0xFFF; + val1 = (uint16((buf[pos + 1]) >> 4) | (uint16(buf[pos + 2]) << 4)) & 0xFFF; + + // increment input buffer index by 3 + pos = pos + 3; + + // if d1 is less than 3329 + if (val0 < Q) { + // assign to d1 + r[ctr] = val0; + // increment position of output array + ctr = ctr + 1; + } + if (ctr < len && val1 < Q) { + r[ctr] = val1; + ctr = ctr + 1; + } + } + return [r, ctr]; +} + +// byteopsCbd computes a polynomial with coefficients distributed +// according to a centered binomial distribution with parameter PARAMS_ETA, +// given an array of uniformly random bytes. + +/** + * Converts a Uint8Array buffer to an array of numbers using the CBD operation. + * @param buf - The input Uint8Array buffer. + * @param eta - The value used in the CBD operation. + * @returns An array of numbers obtained from the CBD operation. + */ +function byteopsCbd(buf: Uint8Array, eta: number): Array { + let t, d; + let a, b; + const r = new Array(384).fill(0); + for (let i = 0; i < N / 8; i++) { + t = byteopsLoad32(buf.subarray(4 * i, buf.length)); + d = t & 0x55555555; + d = d + ((t >> 1) & 0x55555555); + for (let j = 0; j < 8; j++) { + a = int16((d >> (4 * j + 0)) & 0x3); + b = int16((d >> (4 * j + eta)) & 0x3); + r[8 * i + j] = a - b; + } + } + return r; +} + +// ntt performs an inplace number-theoretic transform (NTT) in `Rq`. +// The input is in standard order, the output is in bit-reversed order. + +/** + * Performs the Number Theoretic Transform (NTT) on an array of numbers. + * + * @param r - The input array of numbers. + * @returns The transformed array of numbers. + */ +function ntt(r: Array): Array { + // 128, 64, 32, 16, 8, 4, 2 + for (let j = 0, k = 1, l = 128; l >= 2; l >>= 1) { + // 0, + for (let start = 0; start < 256; start = j + l) { + const zeta = NTT_ZETAS[k]; + k = k + 1; + // for each element in the subsections (128, 64, 32, 16, 8, 4, 2) starting at an offset + for (j = start; j < start + l; j++) { + // compute the modular multiplication of the zeta and each element in the subsection + const t = nttFqMul(zeta, r[j + l]); // t is mod q + // overwrite each element in the subsection as the opposite subsection element minus t + r[j + l] = r[j] - t; + // add t back again to the opposite subsection + r[j] = r[j] + t; + } + } + } + return r; +} + +// nttFqMul performs multiplication followed by Montgomery reduction +// and returns a 16-bit integer congruent to `a*b*R^{-1} mod Q`. + +/** + * Performs an NTT (Number Theoretic Transform) multiplication on two numbers in Fq. + * @param a The first number. + * @param b The second number. + * @returns The result of the NTT multiplication. + */ +function nttFqMul(a: number, b: number): number { + return byteopsMontgomeryReduce(a * b); +} + +// reduce applies Barrett reduction to all coefficients of a polynomial. + +/** + * Reduces each element in the given array using the barrett function. + * + * @param r - The array to be reduced. + * @returns The reduced array. + */ +function reduce(r: Array): Array { + for (let i = 0; i < N; i++) { + r[i] = barrett(r[i]); + } + return r; +} + +// barrett computes a Barrett reduction; given +// a integer `a`, returns a integer congruent to +// `a mod Q` in {0,...,Q}. + +/** + * Performs the Barrett reduction algorithm on the given number. + * + * @param a - The number to be reduced. + * @returns The result of the reduction. + */ +function barrett(a: number): number { + const v = ((1 << 24) + Q / 2) / Q; + let t = v * a >> 24; + t = t * Q; + return a - t; +} + +// byteopsMontgomeryReduce computes a Montgomery reduction; given +// a 32-bit integer `a`, returns `a * R^-1 mod Q` where `R=2^16`. + +/** + * Performs Montgomery reduction on a given number. + * @param a - The number to be reduced. + * @returns The reduced number. + */ +function byteopsMontgomeryReduce(a: number): number { + const u = int16(int32(a) * Q_INV); + let t = u * Q; + t = a - t; + t >>= 16; + return int16(t); +} + +// polyToMont performs the in-place conversion of all coefficients +// of a polynomial from the normal domain to the Montgomery domain. + +/** + * Converts a polynomial to the Montgomery domain. + * + * @param r - The polynomial to be converted. + * @returns The polynomial in the Montgomery domain. + */ +function polyToMont(r: Array): Array { + // let f = int16(((uint64(1) << 32)) % uint64(Q)); + const f = 1353; // if Q changes then this needs to be updated + for (let i = 0; i < N; i++) { + r[i] = byteopsMontgomeryReduce(int32(r[i]) * int32(f)); + } + return r; +} + +// pointwise-multiplies elements of polynomial-vectors +// `a` and `b`, accumulates the results into `r`, and then multiplies by `2^-16`. + +/** + * Multiplies two matrices element-wise and returns the result. + * @param a - The first matrix. + * @param b - The second matrix. + * @returns The resulting matrix after element-wise multiplication. + */ +function multiply( + a: Array>, + b: Array>, +): Array { + let r = polyBaseMulMontgomery(a[0], b[0]); + let t; + for (let i = 1; i < a.length; i++) { + t = polyBaseMulMontgomery(a[i], b[i]); + r = add(r, t); + } + return reduce(r); +} + +// polyBaseMulMontgomery performs the multiplication of two polynomials +// in the number-theoretic transform (NTT) domain. + +/** + * Performs polynomial base multiplication in Montgomery domain. + * @param a - The first polynomial array. + * @param b - The second polynomial array. + * @returns The result of the polynomial base multiplication. + */ +function polyBaseMulMontgomery( + a: Array, + b: Array, +): Array { + let rx, ry; + for (let i = 0; i < N / 4; i++) { + rx = nttBaseMul( + a[4 * i + 0], + a[4 * i + 1], + b[4 * i + 0], + b[4 * i + 1], + NTT_ZETAS[64 + i], + ); + ry = nttBaseMul( + a[4 * i + 2], + a[4 * i + 3], + b[4 * i + 2], + b[4 * i + 3], + -NTT_ZETAS[64 + i], + ); + a[4 * i + 0] = rx[0]; + a[4 * i + 1] = rx[1]; + a[4 * i + 2] = ry[0]; + a[4 * i + 3] = ry[1]; + } + return a; +} + +// nttBaseMul performs the multiplication of polynomials +// in `Zq[X]/(X^2-zeta)`. Used for multiplication of elements +// in `Rq` in the number-theoretic transformation domain. + +/** + * Performs NTT base multiplication. + * + * @param a0 - The first coefficient of the first polynomial. + * @param a1 - The second coefficient of the first polynomial. + * @param b0 - The first coefficient of the second polynomial. + * @param b1 - The second coefficient of the second polynomial. + * @param zeta - The zeta value used in the multiplication. + * @returns An array containing the result of the multiplication. + */ +function nttBaseMul( + a0: number, + a1: number, + b0: number, + b1: number, + zeta: number, +): Array { + const r = new Array(2); + r[0] = nttFqMul(a1, b1); + r[0] = nttFqMul(r[0], zeta); + r[0] += nttFqMul(a0, b0); + r[1] = nttFqMul(a0, b1); + r[1] += nttFqMul(a1, b0); + return r; +} + +// adds two polynomials. + +/** + * Adds two arrays element-wise. + * @param a - The first array. + * @param b - The second array. + * @returns The resulting array after element-wise addition. + */ +function add(a: Array, b: Array): Array { + const c = new Array(384); + for (let i = 0; i < N; i++) { + c[i] = a[i] + b[i]; + } + return c; +} + +// subtracts two polynomials. + +/** + * Subtracts the elements of array b from array a. + * + * @param a - The array from which to subtract. + * @param b - The array to subtract. + * @returns The resulting array after subtraction. + */ +function subtract(a: Array, b: Array): Array { + for (let i = 0; i < N; i++) { + a[i] -= b[i]; + } + return a; +} + +// nttInverse performs an inplace inverse number-theoretic transform (NTT) +// in `Rq` and multiplication by Montgomery factor 2^16. +// The input is in bit-reversed order, the output is in standard order. + +/** + * Performs the inverse Number Theoretic Transform (NTT) on the given array. + * + * @param r - The input array to perform the inverse NTT on. + * @returns The array after performing the inverse NTT. + */ +function nttInverse(r: Array): Array { + let j = 0; + for (let k = 0, l = 2; l <= 128; l <<= 1) { + for (let start = 0; start < 256; start = j + l) { + const zeta = NTT_ZETAS_INV[k]; + k = k + 1; + for (j = start; j < start + l; j++) { + const t = r[j]; + r[j] = barrett(t + r[j + l]); + r[j + l] = t - r[j + l]; + r[j + l] = nttFqMul(zeta, r[j + l]); + } + } + } + for (j = 0; j < 256; j++) { + r[j] = nttFqMul(r[j], NTT_ZETAS_INV[127]); + } + return r; +} + +// subtractQ applies the conditional subtraction of q to each coefficient of a polynomial. +// if a is 3329 then convert to 0 +// Returns: a - q if a >= q, else a + +/** + * Subtracts the value of Q from each element in the given array. + * The result should be a negative integer for each element. + * If the leftmost bit is 0 (positive number), the value of Q is added back. + * + * @param r - The array to subtract Q from. + * @returns The resulting array after the subtraction. + */ +function subtractQ(r: Array): Array { + for (let i = 0; i < N; i++) { + r[i] -= Q; // should result in a negative integer + // push left most signed bit to right most position + // javascript does bitwise operations in signed 32 bit + // add q back again if left most bit was 0 (positive number) + r[i] += (r[i] >> 31) & Q; + } + return r; +} diff --git a/npm/src/src/utils.ts b/npm/src/src/utils.ts new file mode 100644 index 0000000..8c1f9ee --- /dev/null +++ b/npm/src/src/utils.ts @@ -0,0 +1,127 @@ +import * as dntShim from "../_dnt.shims.js"; +import { shake256 } from "./deps.js"; + +export function byte(n: number): number { + return n % 256; +} + +export function int16(n: number): number { + const end = -32768; + const start = 32767; + + if (n >= end && n <= start) { + return n; + } + if (n < end) { + n = n + 32769; + n = n % 65536; + return start + n; + } + // if (n > start) { + n = n - 32768; + n = n % 65536; + return end + n; +} + +export function uint16(n: number): number { + return n % 65536; +} + +export function int32(n: number): number { + const end = -2147483648; + const start = 2147483647; + + if (n >= end && n <= start) { + return n; + } + if (n < end) { + n = n + 2147483649; + n = n % 4294967296; + return start + n; + } + // if (n > start) { + n = n - 2147483648; + n = n % 4294967296; + return end + n; +} + +// any bit operations to be done in uint32 must have >>> 0 +// javascript calculates bitwise in SIGNED 32 bit so you need to convert +export function uint32(n: number): number { + return n % 4294967296; +} + +/** + * compares two arrays + * @returns 1 if they are the same or 0 if not + */ +export function constantTimeCompare(x: Uint8Array, y: Uint8Array): number { + // check array lengths + if (x.length != y.length) { + return 0; + } + const v = new Uint8Array([0]); + for (let i = 0; i < x.length; i++) { + v[0] |= x[i] ^ y[i]; + } + // constantTimeByteEq + const z = new Uint8Array([0]); + z[0] = ~(v[0] ^ z[0]); + z[0] &= z[0] >> 4; + z[0] &= z[0] >> 2; + z[0] &= z[0] >> 1; + return z[0]; +} + +export function equalUint8Array(x: Uint8Array, y: Uint8Array) { + if (x.length != y.length) { + return false; + } + for (let i = 0; i < x.length; i++) { + if (x[i] !== y[i]) { + return false; + } + } + return true; +} + +export async function loadCrypto(): Promise { + if (dntShim.dntGlobalThis !== undefined && globalThis.crypto !== undefined) { + // Browsers, Node.js >= v19, Cloudflare Workers, Bun, etc. + return globalThis.crypto; + } + // Node.js <= v18 + try { + // @ts-ignore: to ignore "crypto" + const { webcrypto } = await import("crypto"); // node:crypto + return (webcrypto as unknown as Crypto); + } catch (_e: unknown) { + throw new Error("failed to load Crypto"); + } +} + +// prf provides a pseudo-random function (PRF) which returns +// a byte array of length `l`, using the provided key and nonce +// to instantiate the PRF's underlying hash function. +export function prf(len: number, seed: Uint8Array, nonce: number): Uint8Array { + return shake256.create({ dkLen: len }).update(seed).update( + new Uint8Array([nonce]), + ).digest(); +} + +// byteopsLoad24 returns a 32-bit unsigned integer loaded from byte x. +export function byteopsLoad24(x: Uint8Array): number { + let r = uint32(x[0]); + r |= uint32(x[1]) << 8; + r |= uint32(x[2]) << 16; + return r; +} + +// byteopsLoad32 returns a 32-bit unsigned integer loaded from byte x. +export function byteopsLoad32(x: Uint8Array): number { + let r = uint32(x[0]); + r |= uint32(x[1]) << 8; + r |= uint32(x[2]) << 16; + r |= uint32(x[3]) << 24; + return uint32(r); +} diff --git a/npm/src/test/kyber.test.ts b/npm/src/test/kyber.test.ts new file mode 100644 index 0000000..1b814bf --- /dev/null +++ b/npm/src/test/kyber.test.ts @@ -0,0 +1,89 @@ +import * as dntShim from "../_dnt.test_shims.js"; +import { describe, it } from "../deps/deno.land/std@0.213.0/testing/bdd.js"; +import { + assertEquals, + assertRejects, +} from "../deps/deno.land/std@0.216.0/assert/mod.js"; + +import { MlKem1024, MlKem512, MlKem768, MlKemError } from "../mod.js"; +import { loadCrypto } from "../src/utils.js"; +import { parseKAT, testVectorPath } from "./utils.js"; +import { hexToBytes } from "./utils.js"; + +[MlKem512, MlKem768, MlKem1024].forEach((KyberClass) => + describe(KyberClass.name, () => { + const size = KyberClass.name.substring(5); + describe("KAT vectors", () => { + it("should match expected values", async () => { + const kyber = new KyberClass(); + const katData = await dntShim.Deno.readTextFile( + `${testVectorPath()}/kat/kat_MLKEM_${size}.rsp`, + ); + const { ct, sk, ss } = parseKAT(katData); + console.log(`test vector count: ${sk.length}`); + + for (let i = 0; i < sk.length; i++) { + const res = await kyber.decap(ct[i], sk[i]); + assertEquals(res, ss[i]); + } + }); + }); + + describe("A sample code in README.", () => { + it("should work normally", async () => { + const recipient = new KyberClass(); + const [pkR, skR] = await recipient.generateKeyPair(); + + const sender = new KyberClass(); + const [ct, ssS] = await sender.encap(pkR); + + const ssR = await recipient.decap(ct, skR); + + assertEquals(ssS, ssR); + }); + + it("should work normally with deriveKeyPair", async () => { + const recipient = new KyberClass(); + const api = await loadCrypto(); + const seed = new Uint8Array(64); + api.getRandomValues(seed); + const [pkR, skR] = await recipient.deriveKeyPair(seed); + const [pkR2, skR2] = await recipient.deriveKeyPair(seed); + assertEquals(pkR, pkR2); + assertEquals(skR, skR2); + + const sender = new KyberClass(); + const [ct, ssS] = await sender.encap(pkR); + + const ssR = await recipient.decap(ct, skR); + + assertEquals(ssS, ssR); + }); + }); + + describe("Advanced testing", () => { + it("Invalid encapsulation keys", async () => { + const sender = new KyberClass(); + const testData = await dntShim.Deno.readTextFile( + `${testVectorPath()}/modulus/ML-KEM-${size}.txt`, + ); + const invalidPk = hexToBytes(testData); + await assertRejects( + () => sender.encap(invalidPk), + MlKemError, + "invalid encapsulation key", + ); + }); + + it("'Unlucky' vectors that require an unusually large number of XOF reads", async () => { + const kyber = new KyberClass(); + const testData = await dntShim.Deno.readTextFile( + `${testVectorPath()}/unluckysample/ML-KEM-${size}.txt`, + ); + const { c: [ct], dk: [sk], K: [ss] } = parseKAT(testData); + const res = await kyber.decap(ct, sk); + assertEquals(res, ss); + }); + }); + }) +); diff --git a/npm/src/test/utils.ts b/npm/src/test/utils.ts new file mode 100644 index 0000000..dffe48b --- /dev/null +++ b/npm/src/test/utils.ts @@ -0,0 +1,48 @@ +declare const Deno: undefined; +const isDeno = () => typeof Deno !== "undefined"; + +export function testVectorPath(): string { + if (isDeno()) { + return "./test/vectors"; + } + return "../../test/vectors"; +} + +export function hexToBytes(v: string): Uint8Array { + if (v.length === 0) { + return new Uint8Array([]); + } + const res = v.match(/[\da-f]{2}/gi); + if (res == null) { + throw new Error("Not hex string."); + } + return new Uint8Array(res.map(function (h) { + return parseInt(h, 16); + })); +} + +export function bytesToHex(v: Uint8Array): string { + return [...v].map((x) => x.toString(16).padStart(2, "0")).join(""); +} + +export function hexToDec(hexString: string): number { + return parseInt(hexString, 16); +} + +export function parseKAT(data: string) { + const textByLine = data.trim().split("\n"); + const parsed: { [label: string]: Uint8Array[] } = {}; + + for (let i = 0; i < textByLine.length; i++) { + const [label, hexValue] = textByLine[i].split(" = "); + if (label === "count") continue; + const value = hexToBytes(hexValue); + if (parsed[label]) { + parsed[label].push(value); + } else { + parsed[label] = [value]; + } + } + + return parsed; +} diff --git a/npm/test_runner.js b/npm/test_runner.js new file mode 100644 index 0000000..3cc6b50 --- /dev/null +++ b/npm/test_runner.js @@ -0,0 +1,183 @@ +const pc = require("picocolors"); +const process = require("process"); +const { pathToFileURL } = require("url"); +const { testDefinitions } = require("@deno/shim-deno/test-internals"); +const filePaths = [ + "test/kyber.test.js", +]; +async function main() { + const testContext = { + process, + pc, + }; + for (const [i, filePath] of filePaths.entries()) { + if (i > 0) { + console.log(""); + } + const esmPath = "./esm/" + filePath; + console.log("\nRunning tests in " + pc.underline(esmPath) + "...\n"); + process.chdir(__dirname + "/esm"); + const esmTestContext = { + origin: pathToFileURL(filePath).toString(), + ...testContext, + }; + await import(esmPath); + await runTestDefinitions(testDefinitions.splice(0, testDefinitions.length), esmTestContext); + } +} +async function runTestDefinitions(testDefinitions, options) { + const testFailures = []; + for (const definition of testDefinitions) { + options.process.stdout.write("test " + definition.name + " ..."); + if (definition.ignore) { + options.process.stdout.write(` ${options.pc.gray("ignored")}\n`); + continue; + } + const context = getTestContext(definition, undefined); + let pass = false; + try { + await definition.fn(context); + if (context.hasFailingChild) { + testFailures.push({ + name: definition.name, + err: new Error("Had failing test step.") + }); + } + else { + pass = true; + } + } + catch (err) { + testFailures.push({ + name: definition.name, + err + }); + } + const testStepOutput = context.getOutput(); + if (testStepOutput.length > 0) { + options.process.stdout.write(testStepOutput); + } + else { + options.process.stdout.write(" "); + } + options.process.stdout.write(getStatusText(pass ? "ok" : "fail")); + options.process.stdout.write("\n"); + } + if (testFailures.length > 0) { + options.process.stdout.write("\nFAILURES"); + for (const failure of testFailures) { + options.process.stdout.write("\n\n"); + options.process.stdout.write(failure.name + "\n"); + options.process.stdout.write(indentText((failure.err?.stack ?? failure.err).toString(), 1)); + } + options.process.exit(1); + } + function getTestContext(definition, parent) { + return { + name: definition.name, + parent, + origin: options.origin, + /** @type {any} */ err: undefined, + status: "ok", + children: [], + get hasFailingChild() { + return this.children.some((c) => c.status === "fail" || c.status === "pending"); + }, + getOutput() { + let output = ""; + if (this.parent) { + output += "test " + this.name + " ..."; + } + if (this.children.length > 0) { + output += "\n" + this.children.map((c) => indentText(c.getOutput(), 1)).join("\n") + "\n"; + } + else if (!this.err) { + output += " "; + } + if (this.parent && this.err) { + output += "\n"; + } + if (this.err) { + output += indentText((this.err.stack ?? this.err).toString(), 1); + if (this.parent) { + output += "\n"; + } + } + if (this.parent) { + output += getStatusText(this.status); + } + return output; + }, + async step(nameOrTestDefinition, fn) { + const definition = getDefinition(); + const context = getTestContext(definition, this); + context.status = "pending"; + this.children.push(context); + if (definition.ignore) { + context.status = "ignored"; + return false; + } + try { + await definition.fn(context); + context.status = "ok"; + if (context.hasFailingChild) { + context.status = "fail"; + return false; + } + return true; + } + catch (err) { + context.status = "fail"; + context.err = err; + return false; + } + /** @returns {TestDefinition} */ function getDefinition() { + if (typeof nameOrTestDefinition === "string") { + if (!(fn instanceof Function)) { + throw new TypeError("Expected function for second argument."); + } + return { + name: nameOrTestDefinition, + fn + }; + } + else if (typeof nameOrTestDefinition === "object") { + return nameOrTestDefinition; + } + else { + throw new TypeError("Expected a test definition or name and function."); + } + } + } + }; + } + function getStatusText(status) { + switch (status) { + case "ok": + return options.pc.green(status); + case "fail": + case "pending": + return options.pc.red(status); + case "ignored": + return options.pc.gray(status); + default: + { + const _assertNever = status; + return status; + } + } + } + function indentText(text, indentLevel) { + if (text === undefined) { + text = "[undefined]"; + } + else if (text === null) { + text = "[null]"; + } + else { + text = text.toString(); + } + return text.split(/\r?\n/).map((line) => " ".repeat(indentLevel) + line).join("\n"); + } +} +main(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..efac484 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "crystals-kyber-js", + "description": "A CRYSTALS-KYBER implementation written in TypeScript for various JavaScript runtimes", + "keywords": [ + "crystals-kyber", + "kyber", + "kem", + "security", + "encryption", + "pqc", + "post-quantum" + ], + "author": "Ajitomi Daisuke", + "homepage": "https://github.com/dajiaji/crystals-kyber-js#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/dajiaji/crystals-kyber-js.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/dajiaji/crystals-kyber-js/issues" + }, + "types": "./npm/esm/mod.d.ts", + "exports": { + ".": { + "import": "./npm/esm/mod.js" + } + }, + "scripts": { + "postinstall": "cd npm/esm && npm install" + } +} \ No newline at end of file