├── LICENSE ├── README.md ├── example ├── README.md ├── automatic-counter-canister │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── build.sh │ ├── can.did │ └── src │ │ └── actor.rs └── e2e-test │ ├── .gitignore │ ├── README.md │ ├── buildscripts │ └── ic-cron-example.sh │ ├── dfx.json │ ├── package.json │ ├── src │ ├── counter.spec.ts │ └── utils.ts │ ├── tsconfig.json │ └── yarn.lock └── ic-cron-rs ├── .gitignore ├── Cargo.toml └── src ├── lib.rs ├── macros.rs ├── task_scheduler.rs └── types.rs /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Alexander Vtyurin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## IC Cron 2 | 3 | Task scheduler rust library for the Internet Computer 4 | 5 | ### Motivation 6 | 7 | The IC provides built-in "heartbeat" functionality which is basically a special function that gets executed each time 8 | consensus ticks. But this is not enough for a comprehensive task scheduling - you still have to implement scheduling 9 | logic by yourself. This rust library does exactly that - provides you with simple APIs for complex background scheduling 10 | scenarios to execute your code at any specific time, as many times as you want. 11 | 12 | ### Installation 13 | 14 | Make sure you're using `dfx 0.8.4` or higher. 15 | 16 | ```toml 17 | # Cargo.toml 18 | 19 | [dependencies] 20 | ic-cron = "0.7" 21 | ``` 22 | 23 | ### Usage 24 | 25 | ```rust 26 | // somewhere in your canister's code 27 | ic_cron::implement_cron!(); 28 | 29 | #[derive(CandidType, Deserialize)] 30 | enum TaskKind { 31 | SendGoodMorning(String), 32 | DoSomethingElse, 33 | } 34 | 35 | // enqueue a task 36 | #[ic_cdk_macros::update] 37 | pub fn enqueue_task_1() { 38 | cron_enqueue( 39 | // set a task payload - any CandidType is supported 40 | TaskKind::SendGoodMorning(String::from("sweetie")), 41 | // set a scheduling interval (how often and how many times to execute) 42 | ic_cron::types::SchedulingOptions { 43 | 1_000_000_000 * 60 * 5, // after waiting for 5 minutes delay once 44 | 1_000_000_000 * 10, // each 10 seconds 45 | iterations: Iterations::Exact(20), // until executed 20 times 46 | }, 47 | ); 48 | } 49 | 50 | // enqueue another task 51 | #[ic_cdk_macros::update] 52 | pub fn enqueue_task_2() { 53 | cron_enqueue( 54 | TaskKind::DoSomethingElse, 55 | ic_cron::types::SchedulingOptions { 56 | 0, // start immediately 57 | 1_000_000_000 * 60 * 5, // each 5 minutes 58 | iterations: Iterations::Infinite, // repeat infinitely 59 | }, 60 | ); 61 | } 62 | 63 | // in a canister heartbeat function get all tasks ready for execution at this exact moment and use it 64 | #[ic_cdk_macros::heartbeat] 65 | fn heartbeat() { 66 | // cron_ready_tasks will only return tasks which should be executed right now 67 | for task in cron_ready_tasks() { 68 | let kind = task.get_payload::().expect("Serialization error"); 69 | 70 | match kind { 71 | TaskKind::SendGoodMorning(name) => { 72 | // will print "Good morning, sweetie!" 73 | println!("Good morning, {}!", name); 74 | }, 75 | TaskKind::DoSomethingElse => { 76 | ... 77 | }, 78 | }; 79 | } 80 | } 81 | ``` 82 | 83 | ### How many cycles does it consume? 84 | 85 | Since this library is just a fancy task queue, there is no significant overhead in terms of cycles. 86 | 87 | ## How does it work? 88 | 89 | This library uses built-in canister heartbeat functionality. Each time you enqueue a task it gets added to the task 90 | queue. Tasks could be scheduled in different ways - they can be executed some exact number of times or infinitely. It is 91 | very similar to how you use `setTimeout()` and `setInterval()` in javascript, but more flexible. Each 92 | time `canister_heartbeat` function is called, you have to call `cron_ready_tasks()` function which efficiently iterates 93 | over the task queue and pops tasks which scheduled execution timestamp is <= current timestamp. Rescheduled tasks get 94 | their next execution timestamp relative to their previous planned execution timestamp - this way the scheduler 95 | compensates an error caused by unstable consensus intervals. 96 | 97 | ## Limitations 98 | 99 | Since `ic-cron` can't pulse faster than the consensus ticks, it has an error of ~2s. 100 | 101 | ## Tutorials 102 | * [Introduction To ic-cron Library](https://dev.to/seniorjoinu/introduction-to-ic-cron-library-17g1) 103 | * [Extending Sonic With Limit Orders Using ic-cron Library](https://hackernoon.com/tutorial-extending-sonic-with-limit-orders-using-ic-cron-library) 104 | * [How to Execute Background Tasks on Particular Weekdays with IC-Cron and Chrono](https://hackernoon.com/how-to-execute-background-tasks-on-particular-weekdays-with-ic-cron-and-chrono) 105 | * [How To Build A Token With Recurrent Payments On The Internet Computer Using ic-cron Library](https://dev.to/seniorjoinu/tutorial-how-to-build-a-token-with-recurrent-payments-on-the-internet-computer-using-ic-cron-library-3l2h) 106 | 107 | ## API 108 | 109 | See the [example](./example) project for better understanding. 110 | 111 | ### implement_cron!() 112 | 113 | This macro will implement all the functions you will use: `get_cron_state()`, `cron_enqueue()`, `cron_dequeue()` 114 | and `cron_ready_tasks()`. 115 | 116 | Basically, this macro implements an inheritance pattern. Just like in a regular object-oriented programming language. 117 | Check the [source code](ic-cron-rs/src/macros.rs) for further info. 118 | 119 | ### cron_enqueue() 120 | 121 | Schedules a new task. Returns task id, which then can be used in `cron_dequeue()` to de-schedule the task. 122 | 123 | Params: 124 | 125 | * `payload: CandidType` - the data you want to provide with the task 126 | * `scheduling_interval: SchedulingInterval` - how often your task should be executed and how many times it should be 127 | rescheduled 128 | 129 | Returns: 130 | 131 | * `ic_cdk::export::candid::Result` - `Ok(task id)` if everything is fine, and `Err` if there is a serialization 132 | issue with your `payload` 133 | 134 | ### cron_dequeue() 135 | 136 | Deschedules the task, removing it from the queue. 137 | 138 | Params: 139 | 140 | * `task_id: u64` - an id of the task you want to delete from the queue 141 | 142 | Returns: 143 | 144 | * `Option` - `Some(task)`, if the operation was a success; `None`, if there was no such task. 145 | 146 | ### cron_ready_tasks() 147 | 148 | Returns a vec of tasks ready to be executed right now. 149 | 150 | Returns: 151 | 152 | * `Vec` - vec of tasks to handle 153 | 154 | ### get_cron_state() 155 | 156 | Returns a static mutable reference to object which can be used to observe scheduler's state and modify it. Mostly 157 | intended for advanced users who want to extend `ic-cron`. See the [source code](ic-cron-rs/src/task_scheduler.rs) for 158 | further info. 159 | 160 | ### _take_cron_state() 161 | 162 | Returns (moved) the cron state. Used to upgrade a canister without state cloning. Make sure you're not using `get_cron_state()` 163 | before `_put_cron_state()` after you call this function. 164 | 165 | ### _put_cron_state() 166 | 167 | Sets the global state of the task scheduler, so this new state is accessible from `get_cron_state()` function. 168 | 169 | Params: 170 | 171 | * `Option` - state object you can get from `get_cron_state()` function 172 | 173 | These two functions could be used to persist scheduled tasks between canister upgrades: 174 | ```rust 175 | #[ic_cdk_macros::pre_upgrade] 176 | fn pre_upgrade_hook() { 177 | let cron_state = _take_cron_state(); 178 | 179 | stable_save((cron_state,)).expect("Unable to save the state to stable memory"); 180 | } 181 | 182 | #[ic_cdk_macros::post_upgrade] 183 | fn post_upgrade_hook() { 184 | let (cron_state,): (Option,) = 185 | stable_restore().expect("Unable to restore the state from stable memory"); 186 | 187 | _put_cron_state(cron_state); 188 | } 189 | ``` 190 | 191 | ## Candid 192 | 193 | You don't need to modify your `.did` file for this library to work. 194 | 195 | ## Contribution 196 | 197 | You can reach me out here on Github opening an issue, or you could start a thread on Dfinity developer forum. 198 | 199 | You're also welcome to suggest new features and open PR's. 200 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ### IC Cron usage example 2 | 3 | The example is an automatic counter canister, which can increment its value without user's trigger. It contains: 4 | 5 | * [canister itself](./automatic-counter-canister) 6 | * [ts-mocha e2e test](./e2e-test) -------------------------------------------------------------------------------- /example/automatic-counter-canister/.gitignore: -------------------------------------------------------------------------------- 1 | .dfx 2 | target 3 | Cargo.lock -------------------------------------------------------------------------------- /example/automatic-counter-canister/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-cron-example" 3 | version = "0.1.0" 4 | authors = ["Александр Втюрин "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | path = "src/actor.rs" 10 | 11 | [dependencies] 12 | ic-cdk = "0.3" 13 | ic-cdk-macros = "0.3" 14 | serde = "1.0" 15 | ic-cron = { path = "../../ic-cron-rs" } -------------------------------------------------------------------------------- /example/automatic-counter-canister/README.md: -------------------------------------------------------------------------------- 1 | ## IC Cron example 2 | 3 | This directory is the example project that demonstrates usage of `ic-cron` library. 4 | 5 | Here we have a simple automatic counting canister, that have two independent counters each of which get incremented by a 6 | cron-scheduled task enqueued by a user. 7 | 8 | Execute the [e2e-test](../e2e-test) locally to see how it works. 9 | -------------------------------------------------------------------------------- /example/automatic-counter-canister/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cargo build --target wasm32-unknown-unknown --release --package ic-cron-example && \ 4 | ic-cdk-optimizer ./target/wasm32-unknown-unknown/release/ic_cron_example.wasm -o ./target/wasm32-unknown-unknown/release/ic-cron-example-opt.wasm -------------------------------------------------------------------------------- /example/automatic-counter-canister/can.did: -------------------------------------------------------------------------------- 1 | service : { 2 | "start_counter_1" : (nat64) -> (nat64); 3 | "get_counter_1" : () -> (nat64); 4 | "start_counter_2" : (nat64, nat64) -> (nat64); 5 | "get_counter_2" : () -> (nat64); 6 | } -------------------------------------------------------------------------------- /example/automatic-counter-canister/src/actor.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::export::candid::{export_service, CandidType, Deserialize}; 2 | use ic_cdk::trap; 3 | use ic_cdk_macros::{heartbeat, init, query, update}; 4 | 5 | use ic_cron::implement_cron; 6 | use ic_cron::types::{Iterations, SchedulingOptions, TaskId}; 7 | 8 | // ------------- MAIN LOGIC ------------------- 9 | 10 | #[derive(Default)] 11 | pub struct AutomaticCounter { 12 | pub counter_1: u64, 13 | pub counter_1_started: bool, 14 | 15 | pub counter_2: u64, 16 | pub counter_2_started: bool, 17 | } 18 | 19 | #[derive(CandidType, Deserialize)] 20 | pub enum CronTaskKind { 21 | One(String), 22 | Two(u64), 23 | } 24 | 25 | #[update] 26 | fn start_counter_1(duration_nano: u64) -> TaskId { 27 | ic_cdk::print("Start counter 1"); 28 | 29 | let state = get_state(); 30 | 31 | if state.counter_1_started { 32 | trap("Counter 1 already started"); 33 | } 34 | 35 | let res = cron_enqueue( 36 | CronTaskKind::One(String::from("Hello from task 1!")), 37 | SchedulingOptions { 38 | delay_nano: duration_nano, 39 | interval_nano: duration_nano, 40 | iterations: Iterations::Infinite, 41 | }, 42 | ); 43 | 44 | state.counter_1_started = true; 45 | 46 | res.unwrap() 47 | } 48 | 49 | #[query] 50 | fn get_counter_1() -> u64 { 51 | get_state().counter_1 52 | } 53 | 54 | #[update] 55 | fn start_counter_2(duration_nano: u64, step: u64) -> TaskId { 56 | ic_cdk::print("Start counter 2"); 57 | 58 | let state = get_state(); 59 | 60 | if state.counter_2_started { 61 | trap("Counter 2 already started"); 62 | } 63 | 64 | let res = cron_enqueue( 65 | CronTaskKind::Two(step), 66 | SchedulingOptions { 67 | delay_nano: duration_nano, 68 | interval_nano: duration_nano, 69 | iterations: Iterations::Infinite, 70 | }, 71 | ); 72 | 73 | state.counter_2_started = true; 74 | 75 | res.unwrap() 76 | } 77 | 78 | #[query] 79 | fn get_counter_2() -> u64 { 80 | get_state().counter_2 81 | } 82 | 83 | // --------------- RECURRENCE ------------------ 84 | 85 | implement_cron!(); 86 | 87 | #[init] 88 | fn init() { 89 | ic_cdk::print("INIT"); 90 | } 91 | 92 | #[heartbeat] 93 | fn tick() { 94 | for task in cron_ready_tasks() { 95 | let kind = task 96 | .get_payload::() 97 | .expect("Unable to deserialize cron task kind"); 98 | 99 | match kind { 100 | CronTaskKind::One(message) => { 101 | ic_cdk::print(format!("Task One executed: {}", message.as_str()).as_str()); 102 | 103 | get_state().counter_1 += 1; 104 | } 105 | CronTaskKind::Two(step) => { 106 | ic_cdk::print("Task Two executed"); 107 | 108 | get_state().counter_2 += step; 109 | } 110 | } 111 | } 112 | } 113 | 114 | // ------------------ STATE ---------------------- 115 | 116 | static mut STATE: Option = None; 117 | 118 | pub fn get_state() -> &'static mut AutomaticCounter { 119 | unsafe { 120 | match STATE.as_mut() { 121 | Some(s) => s, 122 | None => { 123 | STATE = Some(AutomaticCounter::default()); 124 | get_state() 125 | } 126 | } 127 | } 128 | } 129 | 130 | // ---------------- CANDID ----------------------- 131 | 132 | export_service!(); 133 | 134 | #[query(name = "__get_candid_interface_tmp_hack")] 135 | fn export_candid() -> String { 136 | __export_service() 137 | } 138 | -------------------------------------------------------------------------------- /example/e2e-test/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .dfx/ -------------------------------------------------------------------------------- /example/e2e-test/README.md: -------------------------------------------------------------------------------- 1 | ### E2E test for ic-cron 2 | 3 | This directory contains tests for automatic counter canister located at [example directory](..) 4 | 5 | #### Requirements 6 | 7 | * `rust` 8 | * `wasm32-unknown-unknown` target 9 | * `dfx 0.9.0` 10 | * `ic-cdk-optimizer` (`cargo install --locked ic-cdk-optimizer`) 11 | 12 | #### Local development 13 | 14 | * `yarn install` - install dependencies 15 | * `yarn start` - start a replica in a separate terminal 16 | * `yarn build` - build wasm canister code and their ts-bindings 17 | * `yarn test` - start the test 18 | * observe replicas logs 19 | -------------------------------------------------------------------------------- /example/e2e-test/buildscripts/ic-cron-example.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd ../automatic-counter-canister || exit 4 | bash ./build.sh -------------------------------------------------------------------------------- /example/e2e-test/dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "canisters": { 3 | "ic-cron-example": { 4 | "build": "./buildscripts/ic-cron-example.sh", 5 | "candid": "../automatic-counter-canister/can.did", 6 | "wasm": "../automatic-counter-canister/target/wasm32-unknown-unknown/release/ic-cron-example-opt.wasm", 7 | "type": "custom" 8 | } 9 | }, 10 | "dfx": "0.9.0", 11 | "networks": { 12 | "local": { 13 | "bind": "127.0.0.1:8000", 14 | "type": "ephemeral" 15 | } 16 | }, 17 | "version": 1 18 | } -------------------------------------------------------------------------------- /example/e2e-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "e2e-tests", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "dfx start --clean", 8 | "build": "dfx build --check", 9 | "test": "ts-mocha --paths --timeout 1000000 src/**/*.spec.ts" 10 | }, 11 | "devDependencies": { 12 | "@dfinity/agent": "^0.9.2", 13 | "@dfinity/candid": "^0.9.2", 14 | "@dfinity/identity": "^0.9.2", 15 | "@dfinity/principal": "^0.9.2", 16 | "@types/chai": "^4.2.21", 17 | "@types/mocha": "^9.0.0", 18 | "@types/node": "^16.4.3", 19 | "@types/node-fetch": "^2.5.12", 20 | "chai": "^4.3.4", 21 | "mocha": "^9.0.3", 22 | "node-fetch": "^2.6.1", 23 | "ts-mocha": "^8.0.0", 24 | "ts-node": "^10.1.0", 25 | "tsconfig-paths": "^3.10.1", 26 | "typescript": "^4.3.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/e2e-test/src/counter.spec.ts: -------------------------------------------------------------------------------- 1 | import {delay, getSecsNano, ISetup, setup} from "./utils"; 2 | import {Ed25519KeyIdentity} from "@dfinity/identity"; 3 | import {assert} from 'chai'; 4 | 5 | describe('automatic counter', () => { 6 | let counter: ISetup; 7 | 8 | before(async () => { 9 | counter = await setup(Ed25519KeyIdentity.generate()); 10 | }); 11 | 12 | // this test checks for canister 'ic-cron-example' to work nicely 13 | // it may fail sometimes for the first counter, since consensus may take more time, than the counter ticks 14 | it("flow works fine", async () => { 15 | // checking that before any interactions counters are set to zero 16 | let c1Before = await counter.counterClient.get_counter_1(); 17 | let c2Before = await counter.counterClient.get_counter_2(); 18 | 19 | assert.equal(c1Before, 0n); 20 | assert.equal(c2Before, 0n); 21 | 22 | // start a counter 1 (increments by 1 each 3s) and waiting for some ticks to pass 23 | await Promise.all([ 24 | counter.counterClient.start_counter_1(getSecsNano(3)), 25 | 26 | // waiting a little bit more than we need, since consensus may be slow 27 | delay(1000 * 13), 28 | ]); 29 | 30 | // checking that the value of counter 1 was incremented exactly how we expect 31 | const c1After = await counter.counterClient.get_counter_1(); 32 | assert.equal(c1After, 4n); 33 | 34 | // start a counter 2 (increments by 10 each 10s) and waiting for some ticks to pass 35 | await Promise.all([ 36 | counter.counterClient.start_counter_2(getSecsNano(10), 10n), 37 | 38 | // waiting a little bit more than we need, since consensus may be slow 39 | delay(1000 * 21), 40 | ]); 41 | 42 | // checking that the value of counter 2 was incremented exactly how we expect 43 | const c2After = await counter.counterClient.get_counter_2(); 44 | assert.equal(c2After, 20n); 45 | }); 46 | }); -------------------------------------------------------------------------------- /example/e2e-test/src/utils.ts: -------------------------------------------------------------------------------- 1 | import {Actor, CanisterInstallMode, getManagementCanister, HttpAgent, Identity} from "@dfinity/agent"; 2 | import fetch from 'node-fetch'; 3 | import {exec} from 'child_process'; 4 | import {expect} from "chai"; 5 | 6 | import {_SERVICE as ICounterClient} from 'dfx-type/ic-cron-example/ic-cron-example'; 7 | import {idlFactory} from 'dfx-idl/ic-cron-example/ic-cron-example'; 8 | import * as fs from "fs"; 9 | 10 | export interface ISetup { 11 | agent: HttpAgent; 12 | counterClient: ICounterClient; 13 | } 14 | 15 | export async function setup(identity: Identity): Promise { 16 | 17 | const agent = new HttpAgent({ 18 | host: 'http://localhost:8000/', 19 | // @ts-ignore 20 | fetch, 21 | identity 22 | }); 23 | 24 | await agent.fetchRootKey(); 25 | 26 | const managementCanister = getManagementCanister({agent}); 27 | const {canister_id} = await managementCanister.provisional_create_canister_with_cycles({amount: [], settings: []}); 28 | const wasm = fs.readFileSync('.dfx/local/canisters/ic-cron-example/ic-cron-example.wasm'); 29 | 30 | await managementCanister.install_code({ 31 | canister_id, 32 | mode: { [CanisterInstallMode.Install]: null }, 33 | wasm_module: [...wasm], 34 | arg: [] 35 | }); 36 | 37 | const client: ICounterClient = Actor.createActor(idlFactory, { 38 | agent, 39 | canisterId: canister_id 40 | }); 41 | 42 | return { 43 | agent, 44 | counterClient: client 45 | }; 46 | } 47 | 48 | export function getTimeNano(): bigint { 49 | return BigInt(new Date().getTime() * 1000_000) 50 | } 51 | 52 | export function getHoursNano(h: number): bigint { 53 | return BigInt(1000_000_000 * 60 * 60 * h); 54 | } 55 | 56 | export function getSecsNano(s: number): bigint { 57 | return BigInt(1000_000_000 * s); 58 | } 59 | 60 | export function getMinsNano(m: number): bigint { 61 | return BigInt(1000_000_000 * 60 * m); 62 | } 63 | 64 | export async function execAsync(command: string) { 65 | return new Promise((res, rej) => { 66 | exec(command, (err, stderr, stdout) => { 67 | if (err) { 68 | rej(err); 69 | } else if (stderr) { 70 | rej(stderr); 71 | } else if (stdout) { 72 | res(stdout); 73 | } else { 74 | res("No error"); 75 | } 76 | }) 77 | }) 78 | } 79 | 80 | export const expectThrowsAsync = async (method: Promise, errorMessage?: string) => { 81 | let error = null 82 | try { 83 | await method 84 | } catch (err) { 85 | error = err 86 | } 87 | 88 | expect(error).to.be.an('Error', errorMessage); 89 | } 90 | 91 | export async function delay(ms: number) { 92 | return new Promise(resolve => setTimeout(resolve, ms)); 93 | } -------------------------------------------------------------------------------- /example/e2e-test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "ESNext", 8 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ 9 | "module": "commonjs", 10 | /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 11 | // "lib": [], /* Specify library files to be included in the compilation. */ 12 | // "checkJs": true, /* Report errors in .js files. */ 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 14 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 15 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 16 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 17 | // "outFile": "./", /* Concatenate and emit output to single file. */ 18 | "outDir": "./dist", 19 | /* Redirect output structure to the directory. */ 20 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 21 | // "composite": true, /* Enable project compilation */ 22 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 23 | // "removeComments": true, /* Do not emit comments to output. */ 24 | // "noEmit": true, /* Do not emit outputs. */ 25 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 26 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 27 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 28 | 29 | /* Strict Type-Checking Options */ 30 | "strict": true, 31 | /* Enable all strict type-checking options. */ 32 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 33 | // "strictNullChecks": true, /* Enable strict null checks. */ 34 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 35 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 36 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 37 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 38 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 39 | 40 | /* Additional Checks */ 41 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 42 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 43 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 44 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 45 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 46 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 47 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 48 | 49 | /* Module Resolution Options */ 50 | "moduleResolution": "node", 51 | /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 52 | "baseUrl": "./", 53 | /* Base directory to resolve non-absolute module names. */ 54 | "paths": { 55 | "dfx-idl/*": [ 56 | ".dfx/local/canisters/*.did.js" 57 | ], 58 | "dfx-type/*": [ 59 | ".dfx/local/canisters/*.did.d.ts" 60 | ], 61 | }, 62 | /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 63 | //"rootDirs": ["src", ".dfx/local/canisters"], /* List of root folders whose combined content represents the structure of the project at runtime. */ 64 | // "typeRoots": [], /* List of folders to include type definitions from. */ 65 | // "types": [], /* Type declaration files to be included in compilation. */ 66 | "allowJs": true, 67 | "allowSyntheticDefaultImports": true, 68 | /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 69 | "esModuleInterop": true, 70 | /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 71 | "preserveSymlinks": true, 72 | /* Do not resolve the real path of symlinks. */ 73 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 74 | 75 | /* Source Map Options */ 76 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 77 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 78 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 79 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 80 | 81 | /* Experimental Options */ 82 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 83 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 84 | 85 | /* Advanced Options */ 86 | "skipLibCheck": true, 87 | /* Skip type checking of declaration files. */ 88 | "forceConsistentCasingInFileNames": true 89 | /* Disallow inconsistently-cased references to the same file. */ 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /example/e2e-test/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@dfinity/agent@^0.9.2": 6 | version "0.9.3" 7 | resolved "https://registry.yarnpkg.com/@dfinity/agent/-/agent-0.9.3.tgz#e49f07d6749e534c4c30aa2df2bba59e3fa9570f" 8 | integrity sha512-vR0t5Uf+srbUHTdGunJxek3a4jCmBKU+B2yYEX0/aSGQfJmEf5X5snuNcnrIDOCfHeK7uxmOqs65BdI8Lhn4fg== 9 | dependencies: 10 | base64-arraybuffer "^0.2.0" 11 | bignumber.js "^9.0.0" 12 | borc "^2.1.1" 13 | buffer "^6.0.3" 14 | buffer-pipe "^0.0.4" 15 | js-sha256 "0.9.0" 16 | simple-cbor "^0.4.1" 17 | 18 | "@dfinity/candid@^0.9.2": 19 | version "0.9.3" 20 | resolved "https://registry.yarnpkg.com/@dfinity/candid/-/candid-0.9.3.tgz#96b2a5339da634ccc5546a0560f48f5ceef70dfe" 21 | integrity sha512-VMovKFrExqN0mwcn1/aut4Ou4cA9IA2+QrBjV+Ro4eVCpVvGpVRqn4jizlV9QnJjcuEBG1yImIG5RKMdZ2ejQQ== 22 | dependencies: 23 | buffer "^6.0.3" 24 | buffer-pipe "^0.0.4" 25 | 26 | "@dfinity/identity@^0.9.2": 27 | version "0.9.3" 28 | resolved "https://registry.yarnpkg.com/@dfinity/identity/-/identity-0.9.3.tgz#7d65d1ee7655d9028034bf22a92c21eea8b24028" 29 | integrity sha512-8mEpkLqGW74QMsVJzuewwN0tO1fdShAT+vyfQM08VOX/u4IHckH8zZ+qGTeXKaGzuwiJRa5WZT2U7L32LDfJJg== 30 | dependencies: 31 | borc "^2.1.1" 32 | buffer "^6.0.3" 33 | buffer-pipe "0.0.4" 34 | tweetnacl "^1.0.1" 35 | 36 | "@dfinity/principal@^0.9.2": 37 | version "0.9.3" 38 | resolved "https://registry.yarnpkg.com/@dfinity/principal/-/principal-0.9.3.tgz#52e4980bac8dd35e7a6bb2564bea89df97383e06" 39 | integrity sha512-DSL3b/gGm+f57+3XkFjlK4DigtKXe9MVO8UYXDQWkVIhy0UF1KYjRNGs6awc1GTQoOTxeQ8UTFwXuvnEvJ1hpQ== 40 | 41 | "@tsconfig/node10@^1.0.7": 42 | version "1.0.8" 43 | resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" 44 | integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== 45 | 46 | "@tsconfig/node12@^1.0.7": 47 | version "1.0.9" 48 | resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" 49 | integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== 50 | 51 | "@tsconfig/node14@^1.0.0": 52 | version "1.0.1" 53 | resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" 54 | integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== 55 | 56 | "@tsconfig/node16@^1.0.1": 57 | version "1.0.2" 58 | resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" 59 | integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== 60 | 61 | "@types/chai@^4.2.21": 62 | version "4.2.21" 63 | resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.21.tgz#9f35a5643129df132cf3b5c1ec64046ea1af0650" 64 | integrity sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg== 65 | 66 | "@types/mocha@^9.0.0": 67 | version "9.0.0" 68 | resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.0.0.tgz#3205bcd15ada9bc681ac20bef64e9e6df88fd297" 69 | integrity sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA== 70 | 71 | "@types/node-fetch@^2.5.12": 72 | version "2.5.12" 73 | resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" 74 | integrity sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw== 75 | dependencies: 76 | "@types/node" "*" 77 | form-data "^3.0.0" 78 | 79 | "@types/node@*", "@types/node@^16.4.3": 80 | version "16.4.13" 81 | resolved "https://registry.yarnpkg.com/@types/node/-/node-16.4.13.tgz#7dfd9c14661edc65cccd43a29eb454174642370d" 82 | integrity sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg== 83 | 84 | "@ungap/promise-all-settled@1.1.2": 85 | version "1.1.2" 86 | resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" 87 | integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== 88 | 89 | ansi-colors@4.1.1: 90 | version "4.1.1" 91 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" 92 | integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== 93 | 94 | ansi-regex@^3.0.0: 95 | version "3.0.0" 96 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 97 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 98 | 99 | ansi-regex@^5.0.0: 100 | version "5.0.0" 101 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" 102 | integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== 103 | 104 | ansi-styles@^4.0.0, ansi-styles@^4.1.0: 105 | version "4.3.0" 106 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 107 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 108 | dependencies: 109 | color-convert "^2.0.1" 110 | 111 | anymatch@~3.1.2: 112 | version "3.1.2" 113 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" 114 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== 115 | dependencies: 116 | normalize-path "^3.0.0" 117 | picomatch "^2.0.4" 118 | 119 | arg@^4.1.0: 120 | version "4.1.3" 121 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 122 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 123 | 124 | argparse@^2.0.1: 125 | version "2.0.1" 126 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" 127 | integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 128 | 129 | arrify@^1.0.0: 130 | version "1.0.1" 131 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" 132 | integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= 133 | 134 | assertion-error@^1.1.0: 135 | version "1.1.0" 136 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" 137 | integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== 138 | 139 | asynckit@^0.4.0: 140 | version "0.4.0" 141 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 142 | integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= 143 | 144 | balanced-match@^1.0.0: 145 | version "1.0.2" 146 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 147 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 148 | 149 | base64-arraybuffer@^0.2.0: 150 | version "0.2.0" 151 | resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45" 152 | integrity sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ== 153 | 154 | base64-js@^1.3.1: 155 | version "1.5.1" 156 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 157 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 158 | 159 | bignumber.js@^9.0.0: 160 | version "9.0.1" 161 | resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" 162 | integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== 163 | 164 | binary-extensions@^2.0.0: 165 | version "2.2.0" 166 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 167 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 168 | 169 | borc@^2.1.1: 170 | version "2.1.2" 171 | resolved "https://registry.yarnpkg.com/borc/-/borc-2.1.2.tgz#6ce75e7da5ce711b963755117dd1b187f6f8cf19" 172 | integrity sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w== 173 | dependencies: 174 | bignumber.js "^9.0.0" 175 | buffer "^5.5.0" 176 | commander "^2.15.0" 177 | ieee754 "^1.1.13" 178 | iso-url "~0.4.7" 179 | json-text-sequence "~0.1.0" 180 | readable-stream "^3.6.0" 181 | 182 | brace-expansion@^1.1.7: 183 | version "1.1.11" 184 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 185 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 186 | dependencies: 187 | balanced-match "^1.0.0" 188 | concat-map "0.0.1" 189 | 190 | braces@~3.0.2: 191 | version "3.0.2" 192 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 193 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 194 | dependencies: 195 | fill-range "^7.0.1" 196 | 197 | browser-stdout@1.3.1: 198 | version "1.3.1" 199 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 200 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== 201 | 202 | buffer-from@^1.0.0, buffer-from@^1.1.0: 203 | version "1.1.2" 204 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 205 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 206 | 207 | buffer-pipe@0.0.4, buffer-pipe@^0.0.4: 208 | version "0.0.4" 209 | resolved "https://registry.yarnpkg.com/buffer-pipe/-/buffer-pipe-0.0.4.tgz#e7f2ea95beb60fb692fc1dd1c878e894397a974d" 210 | integrity sha512-8cHio1V6wgX+LIX6+af4tCn0+Ljl2vQd9JZdZ8vDJZdDf8x5p2DneKaq1dWxSswJG+sK4Inok9aqoqILG5kQVQ== 211 | dependencies: 212 | safe-buffer "^5.1.2" 213 | 214 | buffer@^5.5.0: 215 | version "5.7.1" 216 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" 217 | integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== 218 | dependencies: 219 | base64-js "^1.3.1" 220 | ieee754 "^1.1.13" 221 | 222 | buffer@^6.0.3: 223 | version "6.0.3" 224 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" 225 | integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== 226 | dependencies: 227 | base64-js "^1.3.1" 228 | ieee754 "^1.2.1" 229 | 230 | camelcase@^6.0.0: 231 | version "6.2.0" 232 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" 233 | integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== 234 | 235 | chai@^4.3.4: 236 | version "4.3.4" 237 | resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49" 238 | integrity sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA== 239 | dependencies: 240 | assertion-error "^1.1.0" 241 | check-error "^1.0.2" 242 | deep-eql "^3.0.1" 243 | get-func-name "^2.0.0" 244 | pathval "^1.1.1" 245 | type-detect "^4.0.5" 246 | 247 | chalk@^4.1.0: 248 | version "4.1.2" 249 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 250 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 251 | dependencies: 252 | ansi-styles "^4.1.0" 253 | supports-color "^7.1.0" 254 | 255 | check-error@^1.0.2: 256 | version "1.0.2" 257 | resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 258 | integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= 259 | 260 | chokidar@3.5.2: 261 | version "3.5.2" 262 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" 263 | integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== 264 | dependencies: 265 | anymatch "~3.1.2" 266 | braces "~3.0.2" 267 | glob-parent "~5.1.2" 268 | is-binary-path "~2.1.0" 269 | is-glob "~4.0.1" 270 | normalize-path "~3.0.0" 271 | readdirp "~3.6.0" 272 | optionalDependencies: 273 | fsevents "~2.3.2" 274 | 275 | cliui@^7.0.2: 276 | version "7.0.4" 277 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" 278 | integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== 279 | dependencies: 280 | string-width "^4.2.0" 281 | strip-ansi "^6.0.0" 282 | wrap-ansi "^7.0.0" 283 | 284 | color-convert@^2.0.1: 285 | version "2.0.1" 286 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 287 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 288 | dependencies: 289 | color-name "~1.1.4" 290 | 291 | color-name@~1.1.4: 292 | version "1.1.4" 293 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 294 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 295 | 296 | combined-stream@^1.0.8: 297 | version "1.0.8" 298 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 299 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 300 | dependencies: 301 | delayed-stream "~1.0.0" 302 | 303 | commander@^2.15.0: 304 | version "2.20.3" 305 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 306 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== 307 | 308 | concat-map@0.0.1: 309 | version "0.0.1" 310 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 311 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 312 | 313 | create-require@^1.1.0: 314 | version "1.1.1" 315 | resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" 316 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== 317 | 318 | debug@4.3.1: 319 | version "4.3.1" 320 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" 321 | integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== 322 | dependencies: 323 | ms "2.1.2" 324 | 325 | decamelize@^4.0.0: 326 | version "4.0.0" 327 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" 328 | integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== 329 | 330 | deep-eql@^3.0.1: 331 | version "3.0.1" 332 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" 333 | integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== 334 | dependencies: 335 | type-detect "^4.0.0" 336 | 337 | delayed-stream@~1.0.0: 338 | version "1.0.0" 339 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 340 | integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= 341 | 342 | delimit-stream@0.1.0: 343 | version "0.1.0" 344 | resolved "https://registry.yarnpkg.com/delimit-stream/-/delimit-stream-0.1.0.tgz#9b8319477c0e5f8aeb3ce357ae305fc25ea1cd2b" 345 | integrity sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs= 346 | 347 | diff@5.0.0: 348 | version "5.0.0" 349 | resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" 350 | integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== 351 | 352 | diff@^3.1.0: 353 | version "3.5.0" 354 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" 355 | integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== 356 | 357 | diff@^4.0.1: 358 | version "4.0.2" 359 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 360 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 361 | 362 | emoji-regex@^8.0.0: 363 | version "8.0.0" 364 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 365 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 366 | 367 | escalade@^3.1.1: 368 | version "3.1.1" 369 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 370 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 371 | 372 | escape-string-regexp@4.0.0: 373 | version "4.0.0" 374 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 375 | integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 376 | 377 | fill-range@^7.0.1: 378 | version "7.0.1" 379 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 380 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 381 | dependencies: 382 | to-regex-range "^5.0.1" 383 | 384 | find-up@5.0.0: 385 | version "5.0.0" 386 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" 387 | integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== 388 | dependencies: 389 | locate-path "^6.0.0" 390 | path-exists "^4.0.0" 391 | 392 | flat@^5.0.2: 393 | version "5.0.2" 394 | resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" 395 | integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== 396 | 397 | form-data@^3.0.0: 398 | version "3.0.1" 399 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" 400 | integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== 401 | dependencies: 402 | asynckit "^0.4.0" 403 | combined-stream "^1.0.8" 404 | mime-types "^2.1.12" 405 | 406 | fs.realpath@^1.0.0: 407 | version "1.0.0" 408 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 409 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 410 | 411 | fsevents@~2.3.2: 412 | version "2.3.2" 413 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 414 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 415 | 416 | get-caller-file@^2.0.5: 417 | version "2.0.5" 418 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 419 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 420 | 421 | get-func-name@^2.0.0: 422 | version "2.0.0" 423 | resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" 424 | integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= 425 | 426 | glob-parent@~5.1.2: 427 | version "5.1.2" 428 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 429 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 430 | dependencies: 431 | is-glob "^4.0.1" 432 | 433 | glob@7.1.7: 434 | version "7.1.7" 435 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" 436 | integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== 437 | dependencies: 438 | fs.realpath "^1.0.0" 439 | inflight "^1.0.4" 440 | inherits "2" 441 | minimatch "^3.0.4" 442 | once "^1.3.0" 443 | path-is-absolute "^1.0.0" 444 | 445 | growl@1.10.5: 446 | version "1.10.5" 447 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" 448 | integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== 449 | 450 | has-flag@^4.0.0: 451 | version "4.0.0" 452 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 453 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 454 | 455 | he@1.2.0: 456 | version "1.2.0" 457 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" 458 | integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 459 | 460 | ieee754@^1.1.13, ieee754@^1.2.1: 461 | version "1.2.1" 462 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 463 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 464 | 465 | inflight@^1.0.4: 466 | version "1.0.6" 467 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 468 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 469 | dependencies: 470 | once "^1.3.0" 471 | wrappy "1" 472 | 473 | inherits@2, inherits@^2.0.3: 474 | version "2.0.4" 475 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 476 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 477 | 478 | is-binary-path@~2.1.0: 479 | version "2.1.0" 480 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 481 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 482 | dependencies: 483 | binary-extensions "^2.0.0" 484 | 485 | is-extglob@^2.1.1: 486 | version "2.1.1" 487 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 488 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 489 | 490 | is-fullwidth-code-point@^2.0.0: 491 | version "2.0.0" 492 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 493 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 494 | 495 | is-fullwidth-code-point@^3.0.0: 496 | version "3.0.0" 497 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 498 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 499 | 500 | is-glob@^4.0.1, is-glob@~4.0.1: 501 | version "4.0.1" 502 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 503 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== 504 | dependencies: 505 | is-extglob "^2.1.1" 506 | 507 | is-number@^7.0.0: 508 | version "7.0.0" 509 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 510 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 511 | 512 | is-plain-obj@^2.1.0: 513 | version "2.1.0" 514 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" 515 | integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== 516 | 517 | is-unicode-supported@^0.1.0: 518 | version "0.1.0" 519 | resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" 520 | integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== 521 | 522 | isexe@^2.0.0: 523 | version "2.0.0" 524 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 525 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 526 | 527 | iso-url@~0.4.7: 528 | version "0.4.7" 529 | resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-0.4.7.tgz#de7e48120dae46921079fe78f325ac9e9217a385" 530 | integrity sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog== 531 | 532 | js-sha256@0.9.0: 533 | version "0.9.0" 534 | resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" 535 | integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== 536 | 537 | js-yaml@4.1.0: 538 | version "4.1.0" 539 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" 540 | integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== 541 | dependencies: 542 | argparse "^2.0.1" 543 | 544 | json-text-sequence@~0.1.0: 545 | version "0.1.1" 546 | resolved "https://registry.yarnpkg.com/json-text-sequence/-/json-text-sequence-0.1.1.tgz#a72f217dc4afc4629fff5feb304dc1bd51a2f3d2" 547 | integrity sha1-py8hfcSvxGKf/1/rME3BvVGi89I= 548 | dependencies: 549 | delimit-stream "0.1.0" 550 | 551 | json5@^2.2.0: 552 | version "2.2.0" 553 | resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" 554 | integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== 555 | dependencies: 556 | minimist "^1.2.5" 557 | 558 | locate-path@^6.0.0: 559 | version "6.0.0" 560 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" 561 | integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== 562 | dependencies: 563 | p-locate "^5.0.0" 564 | 565 | log-symbols@4.1.0: 566 | version "4.1.0" 567 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" 568 | integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== 569 | dependencies: 570 | chalk "^4.1.0" 571 | is-unicode-supported "^0.1.0" 572 | 573 | make-error@^1.1.1: 574 | version "1.3.6" 575 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 576 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 577 | 578 | mime-db@1.49.0: 579 | version "1.49.0" 580 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" 581 | integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== 582 | 583 | mime-types@^2.1.12: 584 | version "2.1.32" 585 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" 586 | integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== 587 | dependencies: 588 | mime-db "1.49.0" 589 | 590 | minimatch@3.0.4, minimatch@^3.0.4: 591 | version "3.0.4" 592 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 593 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 594 | dependencies: 595 | brace-expansion "^1.1.7" 596 | 597 | minimist@^1.2.0, minimist@^1.2.5: 598 | version "1.2.5" 599 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 600 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 601 | 602 | mkdirp@^0.5.1: 603 | version "0.5.5" 604 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 605 | integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== 606 | dependencies: 607 | minimist "^1.2.5" 608 | 609 | mocha@^9.0.3: 610 | version "9.0.3" 611 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.0.3.tgz#128cd6bbd3ee0adcdaef715f357f76ec1e6227c7" 612 | integrity sha512-hnYFrSefHxYS2XFGtN01x8un0EwNu2bzKvhpRFhgoybIvMaOkkL60IVPmkb5h6XDmUl4IMSB+rT5cIO4/4bJgg== 613 | dependencies: 614 | "@ungap/promise-all-settled" "1.1.2" 615 | ansi-colors "4.1.1" 616 | browser-stdout "1.3.1" 617 | chokidar "3.5.2" 618 | debug "4.3.1" 619 | diff "5.0.0" 620 | escape-string-regexp "4.0.0" 621 | find-up "5.0.0" 622 | glob "7.1.7" 623 | growl "1.10.5" 624 | he "1.2.0" 625 | js-yaml "4.1.0" 626 | log-symbols "4.1.0" 627 | minimatch "3.0.4" 628 | ms "2.1.3" 629 | nanoid "3.1.23" 630 | serialize-javascript "6.0.0" 631 | strip-json-comments "3.1.1" 632 | supports-color "8.1.1" 633 | which "2.0.2" 634 | wide-align "1.1.3" 635 | workerpool "6.1.5" 636 | yargs "16.2.0" 637 | yargs-parser "20.2.4" 638 | yargs-unparser "2.0.0" 639 | 640 | ms@2.1.2: 641 | version "2.1.2" 642 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 643 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 644 | 645 | ms@2.1.3: 646 | version "2.1.3" 647 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 648 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 649 | 650 | nanoid@3.1.23: 651 | version "3.1.23" 652 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" 653 | integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== 654 | 655 | node-fetch@^2.6.1: 656 | version "2.6.1" 657 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" 658 | integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== 659 | 660 | normalize-path@^3.0.0, normalize-path@~3.0.0: 661 | version "3.0.0" 662 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 663 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 664 | 665 | once@^1.3.0: 666 | version "1.4.0" 667 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 668 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 669 | dependencies: 670 | wrappy "1" 671 | 672 | p-limit@^3.0.2: 673 | version "3.1.0" 674 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" 675 | integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== 676 | dependencies: 677 | yocto-queue "^0.1.0" 678 | 679 | p-locate@^5.0.0: 680 | version "5.0.0" 681 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" 682 | integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== 683 | dependencies: 684 | p-limit "^3.0.2" 685 | 686 | path-exists@^4.0.0: 687 | version "4.0.0" 688 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 689 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 690 | 691 | path-is-absolute@^1.0.0: 692 | version "1.0.1" 693 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 694 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 695 | 696 | pathval@^1.1.1: 697 | version "1.1.1" 698 | resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" 699 | integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== 700 | 701 | picomatch@^2.0.4, picomatch@^2.2.1: 702 | version "2.3.0" 703 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" 704 | integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== 705 | 706 | randombytes@^2.1.0: 707 | version "2.1.0" 708 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" 709 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 710 | dependencies: 711 | safe-buffer "^5.1.0" 712 | 713 | readable-stream@^3.6.0: 714 | version "3.6.0" 715 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 716 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 717 | dependencies: 718 | inherits "^2.0.3" 719 | string_decoder "^1.1.1" 720 | util-deprecate "^1.0.1" 721 | 722 | readdirp@~3.6.0: 723 | version "3.6.0" 724 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 725 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 726 | dependencies: 727 | picomatch "^2.2.1" 728 | 729 | require-directory@^2.1.1: 730 | version "2.1.1" 731 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 732 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 733 | 734 | safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: 735 | version "5.2.1" 736 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 737 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 738 | 739 | serialize-javascript@6.0.0: 740 | version "6.0.0" 741 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" 742 | integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== 743 | dependencies: 744 | randombytes "^2.1.0" 745 | 746 | simple-cbor@^0.4.1: 747 | version "0.4.1" 748 | resolved "https://registry.yarnpkg.com/simple-cbor/-/simple-cbor-0.4.1.tgz#0c88312e87db52b94e0e92f6bd1cf634e86f8a22" 749 | integrity sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w== 750 | 751 | source-map-support@^0.5.17, source-map-support@^0.5.6: 752 | version "0.5.19" 753 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" 754 | integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== 755 | dependencies: 756 | buffer-from "^1.0.0" 757 | source-map "^0.6.0" 758 | 759 | source-map@^0.6.0: 760 | version "0.6.1" 761 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 762 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 763 | 764 | "string-width@^1.0.2 || 2": 765 | version "2.1.1" 766 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 767 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 768 | dependencies: 769 | is-fullwidth-code-point "^2.0.0" 770 | strip-ansi "^4.0.0" 771 | 772 | string-width@^4.1.0, string-width@^4.2.0: 773 | version "4.2.2" 774 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" 775 | integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== 776 | dependencies: 777 | emoji-regex "^8.0.0" 778 | is-fullwidth-code-point "^3.0.0" 779 | strip-ansi "^6.0.0" 780 | 781 | string_decoder@^1.1.1: 782 | version "1.3.0" 783 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 784 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 785 | dependencies: 786 | safe-buffer "~5.2.0" 787 | 788 | strip-ansi@^4.0.0: 789 | version "4.0.0" 790 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 791 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 792 | dependencies: 793 | ansi-regex "^3.0.0" 794 | 795 | strip-ansi@^6.0.0: 796 | version "6.0.0" 797 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" 798 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== 799 | dependencies: 800 | ansi-regex "^5.0.0" 801 | 802 | strip-bom@^3.0.0: 803 | version "3.0.0" 804 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 805 | integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= 806 | 807 | strip-json-comments@3.1.1: 808 | version "3.1.1" 809 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" 810 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 811 | 812 | supports-color@8.1.1: 813 | version "8.1.1" 814 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" 815 | integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== 816 | dependencies: 817 | has-flag "^4.0.0" 818 | 819 | supports-color@^7.1.0: 820 | version "7.2.0" 821 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 822 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 823 | dependencies: 824 | has-flag "^4.0.0" 825 | 826 | to-regex-range@^5.0.1: 827 | version "5.0.1" 828 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 829 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 830 | dependencies: 831 | is-number "^7.0.0" 832 | 833 | ts-mocha@^8.0.0: 834 | version "8.0.0" 835 | resolved "https://registry.yarnpkg.com/ts-mocha/-/ts-mocha-8.0.0.tgz#962d0fa12eeb6468aa1a6b594bb3bbc818da3ef0" 836 | integrity sha512-Kou1yxTlubLnD5C3unlCVO7nh0HERTezjoVhVw/M5S1SqoUec0WgllQvPk3vzPMc6by8m6xD1uR1yRf8lnVUbA== 837 | dependencies: 838 | ts-node "7.0.1" 839 | optionalDependencies: 840 | tsconfig-paths "^3.5.0" 841 | 842 | ts-node@7.0.1: 843 | version "7.0.1" 844 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.1.tgz#9562dc2d1e6d248d24bc55f773e3f614337d9baf" 845 | integrity sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw== 846 | dependencies: 847 | arrify "^1.0.0" 848 | buffer-from "^1.1.0" 849 | diff "^3.1.0" 850 | make-error "^1.1.1" 851 | minimist "^1.2.0" 852 | mkdirp "^0.5.1" 853 | source-map-support "^0.5.6" 854 | yn "^2.0.0" 855 | 856 | ts-node@^10.1.0: 857 | version "10.1.0" 858 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.1.0.tgz#e656d8ad3b61106938a867f69c39a8ba6efc966e" 859 | integrity sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA== 860 | dependencies: 861 | "@tsconfig/node10" "^1.0.7" 862 | "@tsconfig/node12" "^1.0.7" 863 | "@tsconfig/node14" "^1.0.0" 864 | "@tsconfig/node16" "^1.0.1" 865 | arg "^4.1.0" 866 | create-require "^1.1.0" 867 | diff "^4.0.1" 868 | make-error "^1.1.1" 869 | source-map-support "^0.5.17" 870 | yn "3.1.1" 871 | 872 | tsconfig-paths@^3.10.1, tsconfig-paths@^3.5.0: 873 | version "3.10.1" 874 | resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz#79ae67a68c15289fdf5c51cb74f397522d795ed7" 875 | integrity sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q== 876 | dependencies: 877 | json5 "^2.2.0" 878 | minimist "^1.2.0" 879 | strip-bom "^3.0.0" 880 | 881 | tweetnacl@^1.0.1: 882 | version "1.0.3" 883 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" 884 | integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== 885 | 886 | type-detect@^4.0.0, type-detect@^4.0.5: 887 | version "4.0.8" 888 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 889 | integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 890 | 891 | typescript@^4.3.5: 892 | version "4.3.5" 893 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" 894 | integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== 895 | 896 | util-deprecate@^1.0.1: 897 | version "1.0.2" 898 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 899 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 900 | 901 | which@2.0.2: 902 | version "2.0.2" 903 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 904 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 905 | dependencies: 906 | isexe "^2.0.0" 907 | 908 | wide-align@1.1.3: 909 | version "1.1.3" 910 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" 911 | integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== 912 | dependencies: 913 | string-width "^1.0.2 || 2" 914 | 915 | workerpool@6.1.5: 916 | version "6.1.5" 917 | resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581" 918 | integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw== 919 | 920 | wrap-ansi@^7.0.0: 921 | version "7.0.0" 922 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 923 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 924 | dependencies: 925 | ansi-styles "^4.0.0" 926 | string-width "^4.1.0" 927 | strip-ansi "^6.0.0" 928 | 929 | wrappy@1: 930 | version "1.0.2" 931 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 932 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 933 | 934 | y18n@^5.0.5: 935 | version "5.0.8" 936 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" 937 | integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== 938 | 939 | yargs-parser@20.2.4: 940 | version "20.2.4" 941 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" 942 | integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== 943 | 944 | yargs-parser@^20.2.2: 945 | version "20.2.9" 946 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" 947 | integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== 948 | 949 | yargs-unparser@2.0.0: 950 | version "2.0.0" 951 | resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" 952 | integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== 953 | dependencies: 954 | camelcase "^6.0.0" 955 | decamelize "^4.0.0" 956 | flat "^5.0.2" 957 | is-plain-obj "^2.1.0" 958 | 959 | yargs@16.2.0: 960 | version "16.2.0" 961 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" 962 | integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== 963 | dependencies: 964 | cliui "^7.0.2" 965 | escalade "^3.1.1" 966 | get-caller-file "^2.0.5" 967 | require-directory "^2.1.1" 968 | string-width "^4.2.0" 969 | y18n "^5.0.5" 970 | yargs-parser "^20.2.2" 971 | 972 | yn@3.1.1: 973 | version "3.1.1" 974 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 975 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 976 | 977 | yn@^2.0.0: 978 | version "2.0.0" 979 | resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" 980 | integrity sha1-5a2ryKz0CPY4X8dklWhMiOavaJo= 981 | 982 | yocto-queue@^0.1.0: 983 | version "0.1.0" 984 | resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" 985 | integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== 986 | -------------------------------------------------------------------------------- /ic-cron-rs/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | *.iml 4 | .idea/ -------------------------------------------------------------------------------- /ic-cron-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-cron" 3 | version = "0.7.1" 4 | authors = ["Александр Втюрин "] 5 | edition = "2018" 6 | description = "Task scheduler rust library for the Internet Computer" 7 | license = "MIT" 8 | readme = "../README.md" 9 | keywords = ["dfinity", "internet-computer", "ic", "cron", "cronjob"] 10 | 11 | [dependencies] 12 | ic-cdk = "0.6.6" 13 | ic-cdk-macros = "0.6.6" 14 | serde = "1.0.147" 15 | candid = "0.8.3" -------------------------------------------------------------------------------- /ic-cron-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod macros; 2 | pub mod task_scheduler; 3 | pub mod types; 4 | -------------------------------------------------------------------------------- /ic-cron-rs/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! implement_cron { 3 | () => { 4 | pub static mut _CRON_STATE: Option = None; 5 | 6 | pub fn get_cron_state() -> &'static mut ic_cron::task_scheduler::TaskScheduler { 7 | unsafe { 8 | match _CRON_STATE.as_mut() { 9 | Some(cron) => cron, 10 | None => { 11 | _put_cron_state(Some(ic_cron::task_scheduler::TaskScheduler::default())); 12 | get_cron_state() 13 | } 14 | } 15 | } 16 | } 17 | 18 | pub fn _take_cron_state() -> Option { 19 | unsafe { _CRON_STATE.take() } 20 | } 21 | 22 | pub fn _put_cron_state(state: Option) { 23 | unsafe { 24 | _CRON_STATE = state; 25 | } 26 | } 27 | 28 | pub fn cron_enqueue( 29 | payload: Payload, 30 | scheduling_options: ic_cron::types::SchedulingOptions, 31 | ) -> ic_cdk::export::candid::Result { 32 | let cron = get_cron_state(); 33 | 34 | let id = cron.enqueue(payload, scheduling_options, ic_cdk::api::time())?; 35 | 36 | Ok(id) 37 | } 38 | 39 | pub fn cron_dequeue( 40 | task_id: ic_cron::types::TaskId, 41 | ) -> Option { 42 | get_cron_state().dequeue(task_id) 43 | } 44 | 45 | pub fn cron_ready_tasks() -> Vec { 46 | get_cron_state().iterate(ic_cdk::api::time()) 47 | } 48 | }; 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use crate as ic_cron; 54 | use crate::implement_cron; 55 | use crate::task_scheduler::TaskScheduler; 56 | use ic_cdk::storage::{stable_restore, stable_save}; 57 | use ic_cdk_macros::{heartbeat, post_upgrade, pre_upgrade}; 58 | 59 | implement_cron!(); 60 | 61 | #[pre_upgrade] 62 | fn pre_upgrade_hook() { 63 | let cron_state = _take_cron_state(); 64 | 65 | stable_save((cron_state,)).expect("Unable to save the state to stable memory"); 66 | } 67 | 68 | #[post_upgrade] 69 | fn post_upgrade_hook() { 70 | let (cron_state,): (Option,) = 71 | stable_restore().expect("Unable to restore the state from stable memory"); 72 | 73 | _put_cron_state(cron_state); 74 | } 75 | 76 | #[heartbeat] 77 | fn tick() { 78 | let tasks = cron_ready_tasks(); 79 | } 80 | 81 | #[test] 82 | fn no_op() { 83 | assert!(true); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ic-cron-rs/src/task_scheduler.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::collections::HashMap; 3 | 4 | use ic_cdk::export::candid::{CandidType, Deserialize, Result as CandidResult}; 5 | 6 | use crate::types::{ 7 | Iterations, ScheduledTask, SchedulingOptions, TaskExecutionQueue, TaskId, TaskTimestamp, 8 | }; 9 | 10 | #[derive(Default, CandidType, Deserialize, Clone)] 11 | pub struct TaskScheduler { 12 | pub tasks: HashMap, 13 | pub task_id_counter: TaskId, 14 | 15 | pub queue: TaskExecutionQueue, 16 | } 17 | 18 | impl TaskScheduler { 19 | pub fn enqueue( 20 | &mut self, 21 | payload: TaskPayload, 22 | scheduling_interval: SchedulingOptions, 23 | timestamp: u64, 24 | ) -> CandidResult { 25 | let id = self.generate_task_id(); 26 | let task = ScheduledTask::new(id, payload, timestamp, None, scheduling_interval)?; 27 | 28 | match task.scheduling_options.iterations { 29 | Iterations::Exact(times) => { 30 | if times > 0 { 31 | self.queue.push(TaskTimestamp { 32 | task_id: id, 33 | timestamp: timestamp + task.scheduling_options.delay_nano, 34 | }) 35 | } 36 | } 37 | Iterations::Infinite => self.queue.push(TaskTimestamp { 38 | task_id: id, 39 | timestamp: timestamp + task.scheduling_options.delay_nano, 40 | }), 41 | }; 42 | 43 | self.tasks.insert(id, task); 44 | 45 | Ok(id) 46 | } 47 | 48 | pub fn iterate(&mut self, timestamp: u64) -> Vec { 49 | let mut tasks = vec![]; 50 | 51 | for task_id in self 52 | .queue 53 | .pop_ready(timestamp) 54 | .into_iter() 55 | .map(|it| it.task_id) 56 | { 57 | let mut should_remove = false; 58 | 59 | match self.tasks.entry(task_id) { 60 | Entry::Occupied(mut entry) => { 61 | let task = entry.get_mut(); 62 | 63 | match task.scheduling_options.iterations { 64 | Iterations::Infinite => { 65 | let new_rescheduled_at = if task.delay_passed { 66 | if let Some(rescheduled_at) = task.rescheduled_at { 67 | rescheduled_at + task.scheduling_options.interval_nano 68 | } else { 69 | task.scheduled_at + task.scheduling_options.interval_nano 70 | } 71 | } else { 72 | task.delay_passed = true; 73 | 74 | if let Some(rescheduled_at) = task.rescheduled_at { 75 | rescheduled_at + task.scheduling_options.delay_nano 76 | } else { 77 | task.scheduled_at + task.scheduling_options.delay_nano 78 | } 79 | }; 80 | 81 | task.rescheduled_at = Some(new_rescheduled_at); 82 | 83 | self.queue.push(TaskTimestamp { 84 | task_id, 85 | timestamp: new_rescheduled_at 86 | + task.scheduling_options.interval_nano, 87 | }); 88 | } 89 | Iterations::Exact(times_left) => { 90 | if times_left > 1 { 91 | let new_rescheduled_at = if task.delay_passed { 92 | if let Some(rescheduled_at) = task.rescheduled_at { 93 | rescheduled_at + task.scheduling_options.interval_nano 94 | } else { 95 | task.scheduled_at + task.scheduling_options.interval_nano 96 | } 97 | } else { 98 | task.delay_passed = true; 99 | 100 | if let Some(rescheduled_at) = task.rescheduled_at { 101 | rescheduled_at + task.scheduling_options.delay_nano 102 | } else { 103 | task.scheduled_at + task.scheduling_options.delay_nano 104 | } 105 | }; 106 | 107 | task.rescheduled_at = Some(new_rescheduled_at); 108 | 109 | self.queue.push(TaskTimestamp { 110 | task_id, 111 | timestamp: new_rescheduled_at 112 | + task.scheduling_options.interval_nano, 113 | }); 114 | 115 | task.scheduling_options.iterations = 116 | Iterations::Exact(times_left - 1); 117 | } else { 118 | should_remove = true; 119 | } 120 | } 121 | }; 122 | 123 | tasks.push(task.clone()); 124 | } 125 | Entry::Vacant(_) => {} 126 | } 127 | 128 | if should_remove { 129 | self.tasks.remove(&task_id); 130 | } 131 | } 132 | 133 | tasks 134 | } 135 | 136 | pub fn dequeue(&mut self, task_id: TaskId) -> Option { 137 | self.tasks.remove(&task_id) 138 | } 139 | 140 | pub fn is_empty(&self) -> bool { 141 | self.queue.is_empty() 142 | } 143 | 144 | pub fn get_task(&self, task_id: &TaskId) -> Option<&ScheduledTask> { 145 | self.tasks.get(task_id) 146 | } 147 | 148 | pub fn get_task_mut(&mut self, task_id: &TaskId) -> Option<&mut ScheduledTask> { 149 | self.tasks.get_mut(task_id) 150 | } 151 | 152 | pub fn get_task_by_id_cloned(&self, task_id: &TaskId) -> Option { 153 | self.get_task(task_id).cloned() 154 | } 155 | 156 | pub fn get_tasks_cloned(&self) -> Vec { 157 | self.tasks.values().cloned().collect() 158 | } 159 | 160 | fn generate_task_id(&mut self) -> TaskId { 161 | let res = self.task_id_counter; 162 | self.task_id_counter += 1; 163 | 164 | res 165 | } 166 | } 167 | 168 | #[cfg(test)] 169 | mod tests { 170 | use ic_cdk::export::candid::{decode_one, encode_one}; 171 | use ic_cdk::export::candid::{CandidType, Deserialize}; 172 | 173 | use crate::task_scheduler::TaskScheduler; 174 | use crate::types::{Iterations, SchedulingOptions}; 175 | 176 | #[derive(CandidType, Deserialize)] 177 | pub struct TestPayload { 178 | pub a: bool, 179 | } 180 | 181 | #[test] 182 | fn main_flow_works_fine() { 183 | let mut scheduler = TaskScheduler::default(); 184 | 185 | let task_id_1 = scheduler 186 | .enqueue( 187 | TestPayload { a: true }, 188 | SchedulingOptions { 189 | delay_nano: 10, 190 | interval_nano: 10, 191 | iterations: Iterations::Exact(1), 192 | }, 193 | 0, 194 | ) 195 | .ok() 196 | .unwrap(); 197 | 198 | let task_id_2 = scheduler 199 | .enqueue( 200 | TestPayload { a: true }, 201 | SchedulingOptions { 202 | delay_nano: 10, 203 | interval_nano: 10, 204 | iterations: Iterations::Infinite, 205 | }, 206 | 0, 207 | ) 208 | .ok() 209 | .unwrap(); 210 | 211 | let task_id_3 = scheduler 212 | .enqueue( 213 | TestPayload { a: false }, 214 | SchedulingOptions { 215 | delay_nano: 20, 216 | interval_nano: 20, 217 | iterations: Iterations::Exact(2), 218 | }, 219 | 0, 220 | ) 221 | .ok() 222 | .unwrap(); 223 | 224 | assert!(!scheduler.is_empty(), "Scheduler is not empty"); 225 | 226 | let tasks_emp = scheduler.iterate(5); 227 | assert!( 228 | tasks_emp.is_empty(), 229 | "There should not be any tasks at timestamp 5" 230 | ); 231 | 232 | let tasks_1_2 = scheduler.iterate(10); 233 | assert_eq!( 234 | tasks_1_2.len(), 235 | 2, 236 | "At timestamp 10 there should be 2 tasks" 237 | ); 238 | assert!( 239 | tasks_1_2.iter().any(|t| t.id == task_id_1), 240 | "Should contain task 1" 241 | ); 242 | assert!( 243 | tasks_1_2.iter().any(|t| t.id == task_id_2), 244 | "Should contain task 2" 245 | ); 246 | 247 | let tasks_emp = scheduler.iterate(15); 248 | assert!( 249 | tasks_emp.is_empty(), 250 | "There should not be any tasks at timestamp 15" 251 | ); 252 | 253 | let tasks_2_3 = scheduler.iterate(20); 254 | assert_eq!( 255 | tasks_2_3.len(), 256 | 2, 257 | "At timestamp 20 there should be 2 tasks" 258 | ); 259 | assert!( 260 | tasks_2_3.iter().any(|t| t.id == task_id_2), 261 | "Should contain task 2" 262 | ); 263 | assert!( 264 | tasks_2_3.iter().any(|t| t.id == task_id_3), 265 | "Should contain task 3" 266 | ); 267 | 268 | let tasks_2 = scheduler.iterate(30); 269 | assert_eq!( 270 | tasks_2.len(), 271 | 1, 272 | "There should be a single task at timestamp 30" 273 | ); 274 | assert_eq!(tasks_2[0].id, task_id_2, "Should contain task 2"); 275 | 276 | let tasks_2_3 = scheduler.iterate(42); 277 | assert_eq!( 278 | tasks_2_3.len(), 279 | 2, 280 | "At timestamp 40 there should be 2 tasks" 281 | ); 282 | assert!( 283 | tasks_2_3.iter().any(|t| t.id == task_id_2), 284 | "Should contain task 2" 285 | ); 286 | assert!( 287 | tasks_2_3.iter().any(|t| t.id == task_id_3), 288 | "Should contain task 3" 289 | ); 290 | 291 | let tasks_2 = scheduler.iterate(55); 292 | assert_eq!( 293 | tasks_2.len(), 294 | 1, 295 | "There should be a single task at timestamp 60" 296 | ); 297 | assert_eq!(tasks_2[0].id, task_id_2, "Should contain task 2"); 298 | 299 | let tasks_2 = scheduler.iterate(60); 300 | assert_eq!( 301 | tasks_2.len(), 302 | 1, 303 | "There should be a single task at timestamp 60" 304 | ); 305 | assert_eq!(tasks_2[0].id, task_id_2, "Should contain task 2"); 306 | 307 | scheduler.dequeue(task_id_2).unwrap(); 308 | 309 | scheduler 310 | .enqueue( 311 | TestPayload { a: true }, 312 | SchedulingOptions { 313 | delay_nano: 10, 314 | interval_nano: 10, 315 | iterations: Iterations::Exact(1), 316 | }, 317 | 0, 318 | ) 319 | .ok() 320 | .unwrap(); 321 | } 322 | 323 | #[test] 324 | fn delay_works_fine() { 325 | let mut scheduler = TaskScheduler::default(); 326 | 327 | let task_id_1 = scheduler 328 | .enqueue( 329 | TestPayload { a: true }, 330 | SchedulingOptions { 331 | delay_nano: 10, 332 | interval_nano: 20, 333 | iterations: Iterations::Infinite, 334 | }, 335 | 0, 336 | ) 337 | .ok() 338 | .unwrap(); 339 | 340 | let tasks = scheduler.iterate(5); 341 | 342 | assert!( 343 | tasks.is_empty(), 344 | "There shouldn't be any task at this timestamp (5)" 345 | ); 346 | 347 | let tasks = scheduler.iterate(10); 348 | assert_eq!( 349 | tasks.len(), 350 | 1, 351 | "There should be a task that was triggered by a delay at this timestamp (10)" 352 | ); 353 | 354 | let tasks = scheduler.iterate(20); 355 | assert!( 356 | tasks.is_empty(), 357 | "There shouldn't be any task at this timestamp (20)" 358 | ); 359 | 360 | let tasks = scheduler.iterate(30); 361 | assert_eq!( 362 | tasks.len(), 363 | 1, 364 | "There should be a task that was triggered by an interval at this timestamp (30)" 365 | ); 366 | 367 | let tasks = scheduler.iterate(50); 368 | assert_eq!( 369 | tasks.len(), 370 | 1, 371 | "There should be a task that was triggered by an interval at this timestamp (50)" 372 | ); 373 | } 374 | 375 | #[test] 376 | fn ser_de_works_fine() { 377 | let mut scheduler = TaskScheduler::default(); 378 | 379 | scheduler 380 | .enqueue( 381 | TestPayload { a: true }, 382 | SchedulingOptions { 383 | delay_nano: 10, 384 | interval_nano: 20, 385 | iterations: Iterations::Infinite, 386 | }, 387 | 0, 388 | ) 389 | .ok() 390 | .unwrap(); 391 | 392 | let bytes = encode_one(scheduler).expect("Should be able to encode task scheduler"); 393 | let mut scheduler: TaskScheduler = 394 | decode_one(&bytes).expect("Should be able to decode task scheduler"); 395 | 396 | let tasks = scheduler.iterate(10); 397 | 398 | assert_eq!( 399 | tasks.len(), 400 | 1, 401 | "There should be a task that was triggered by a delay at this timestamp (10)" 402 | ); 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /ic-cron-rs/src/types.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::{max, min, Ordering}; 2 | use std::collections::BinaryHeap; 3 | 4 | use ic_cdk::export::candid::types::{Serializer, Type}; 5 | use ic_cdk::export::candid::{ 6 | decode_one, encode_one, CandidType, Deserialize, Result as CandidResult, 7 | }; 8 | 9 | pub type TaskId = u64; 10 | 11 | #[derive(Clone, CandidType, Deserialize)] 12 | pub struct Task { 13 | pub data: Vec, 14 | } 15 | 16 | #[derive(Clone, Copy, CandidType, Deserialize)] 17 | pub enum Iterations { 18 | Infinite, 19 | Exact(u64), 20 | } 21 | 22 | #[derive(Clone, Copy, CandidType, Deserialize)] 23 | pub struct SchedulingOptions { 24 | pub delay_nano: u64, 25 | pub interval_nano: u64, 26 | pub iterations: Iterations, 27 | } 28 | 29 | #[derive(Clone, CandidType, Deserialize)] 30 | pub struct ScheduledTask { 31 | pub id: TaskId, 32 | pub payload: Task, 33 | pub scheduled_at: u64, 34 | pub rescheduled_at: Option, 35 | pub scheduling_options: SchedulingOptions, 36 | pub delay_passed: bool, 37 | } 38 | 39 | impl ScheduledTask { 40 | pub fn new( 41 | id: TaskId, 42 | payload: TaskPayload, 43 | scheduled_at: u64, 44 | rescheduled_at: Option, 45 | scheduling_interval: SchedulingOptions, 46 | ) -> CandidResult { 47 | let task = Task { 48 | data: encode_one(payload).unwrap(), 49 | }; 50 | 51 | Ok(Self { 52 | id, 53 | payload: task, 54 | scheduled_at, 55 | rescheduled_at, 56 | scheduling_options: scheduling_interval, 57 | delay_passed: false, 58 | }) 59 | } 60 | 61 | pub fn get_payload<'a, T>(&'a self) -> CandidResult 62 | where 63 | T: Deserialize<'a> + CandidType, 64 | { 65 | decode_one(&self.payload.data) 66 | } 67 | 68 | pub fn set_payload(&mut self, payload: T) { 69 | self.payload.data = encode_one(payload).unwrap() 70 | } 71 | } 72 | 73 | #[derive(CandidType, Deserialize, Clone, Copy)] 74 | pub struct TaskTimestamp { 75 | pub task_id: TaskId, 76 | pub timestamp: u64, 77 | } 78 | 79 | impl PartialEq for TaskTimestamp { 80 | fn eq(&self, other: &Self) -> bool { 81 | self.timestamp.eq(&other.timestamp) && self.task_id.eq(&other.task_id) 82 | } 83 | } 84 | 85 | impl Eq for TaskTimestamp {} 86 | 87 | impl PartialOrd for TaskTimestamp { 88 | fn partial_cmp(&self, other: &Self) -> Option { 89 | self.timestamp 90 | .partial_cmp(&other.timestamp) 91 | .map(|it| it.reverse()) 92 | } 93 | 94 | fn lt(&self, other: &Self) -> bool { 95 | self.timestamp.gt(&other.timestamp) 96 | } 97 | 98 | fn le(&self, other: &Self) -> bool { 99 | self.timestamp.ge(&other.timestamp) 100 | } 101 | 102 | fn gt(&self, other: &Self) -> bool { 103 | self.timestamp.lt(&other.timestamp) 104 | } 105 | 106 | fn ge(&self, other: &Self) -> bool { 107 | self.timestamp.le(&other.timestamp) 108 | } 109 | } 110 | 111 | impl Ord for TaskTimestamp { 112 | fn cmp(&self, other: &Self) -> Ordering { 113 | self.timestamp.cmp(&other.timestamp).reverse() 114 | } 115 | 116 | fn max(self, other: Self) -> Self 117 | where 118 | Self: Sized, 119 | { 120 | max(self, other) 121 | } 122 | 123 | fn min(self, other: Self) -> Self 124 | where 125 | Self: Sized, 126 | { 127 | min(self, other) 128 | } 129 | 130 | fn clamp(self, min: Self, max: Self) -> Self 131 | where 132 | Self: Sized, 133 | { 134 | if self.timestamp < max.timestamp { 135 | max 136 | } else if self.timestamp > min.timestamp { 137 | min 138 | } else { 139 | self 140 | } 141 | } 142 | } 143 | 144 | #[derive(Default, Deserialize, Clone)] 145 | pub struct TaskExecutionQueue(BinaryHeap); 146 | 147 | impl TaskExecutionQueue { 148 | #[inline(always)] 149 | pub fn push(&mut self, task: TaskTimestamp) { 150 | self.0.push(task); 151 | } 152 | 153 | pub fn pop_ready(&mut self, timestamp: u64) -> Vec { 154 | let mut cur = self.0.peek(); 155 | if cur.is_none() { 156 | return Vec::new(); 157 | } 158 | 159 | let mut result = vec![]; 160 | 161 | while cur.unwrap().timestamp <= timestamp { 162 | result.push(self.0.pop().unwrap()); 163 | 164 | cur = self.0.peek(); 165 | if cur.is_none() { 166 | break; 167 | } 168 | } 169 | 170 | result 171 | } 172 | 173 | #[inline(always)] 174 | pub fn is_empty(&self) -> bool { 175 | self.0.is_empty() 176 | } 177 | 178 | #[inline(always)] 179 | pub fn len(&self) -> usize { 180 | self.0.len() 181 | } 182 | } 183 | 184 | impl CandidType for TaskExecutionQueue { 185 | fn _ty() -> Type { 186 | Type::Vec(Box::new(TaskTimestamp::_ty())) 187 | } 188 | 189 | fn ty() -> Type { 190 | Self::_ty() 191 | } 192 | 193 | fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> 194 | where 195 | S: Serializer, 196 | { 197 | self.clone().0.into_sorted_vec().idl_serialize(serializer) 198 | } 199 | } 200 | --------------------------------------------------------------------------------