commit
cce04c558f
38 changed files with 1531 additions and 10 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,4 +1,6 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/.idea
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
/build
|
/build
|
||||||
/.svelte-kit
|
/.svelte-kit
|
||||||
|
|
|
||||||
5
.idea/.gitignore
vendored
Normal file
5
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
11
.idea/aws.xml
Normal file
11
.idea/aws.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="accountSettings">
|
||||||
|
<option name="activeRegion" value="us-east-1" />
|
||||||
|
<option name="recentlyUsedRegions">
|
||||||
|
<list>
|
||||||
|
<option value="us-east-1" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
65
.idea/codeStyles/Project.xml
Normal file
65
.idea/codeStyles/Project.xml
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<HTMLCodeStyleSettings>
|
||||||
|
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||||
|
<option name="HTML_QUOTE_STYLE" value="Single" />
|
||||||
|
<option name="HTML_ENFORCE_QUOTES" value="true" />
|
||||||
|
</HTMLCodeStyleSettings>
|
||||||
|
<JSCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</JSCodeStyleSettings>
|
||||||
|
<TypeScriptCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</TypeScriptCodeStyleSettings>
|
||||||
|
<VueCodeStyleSettings>
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
||||||
|
</VueCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="HTML">
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JavaScript">
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="TypeScript">
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Vue">
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/Project_Default.xml
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/thomaswilson-sveltekit.iml" filepath="$PROJECT_DIR$/.idea/thomaswilson-sveltekit.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
12
.idea/thomaswilson-sveltekit.iml
Normal file
12
.idea/thomaswilson-sveltekit.iml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
69
.idea/workspace.xml
Normal file
69
.idea/workspace.xml
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="74a06e37-01d9-4509-8ef0-5f4ca3c12565" name="Changes" comment="" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="GitSEFilterConfiguration">
|
||||||
|
<file-type-list>
|
||||||
|
<filtered-out-file-type name="LOCAL_BRANCH" />
|
||||||
|
<filtered-out-file-type name="REMOTE_BRANCH" />
|
||||||
|
<filtered-out-file-type name="TAG" />
|
||||||
|
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
|
||||||
|
</file-type-list>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectId" id="2DcptjnYJZ3WE1cAFfqixbjIsUq" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="autoscrollFromSource" value="true" />
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||||
|
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||||
|
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||||
|
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||||
|
<property name="node.js.detected.package.eslint" value="true" />
|
||||||
|
<property name="node.js.detected.package.tslint" value="true" />
|
||||||
|
<property name="node.js.selected.package.eslint" value="(autodetect)" />
|
||||||
|
<property name="node.js.selected.package.tslint" value="(autodetect)" />
|
||||||
|
<property name="nodejs_package_manager_path" value="yarn" />
|
||||||
|
<property name="ts.external.directory.path" value="$APPLICATION_HOME_DIR$/plugins/JavaScriptLanguage/jsLanguageServicesImpl/external" />
|
||||||
|
<property name="vue.rearranger.settings.migration" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="74a06e37-01d9-4509-8ef0-5f4ca3c12565" name="Changes" comment="" />
|
||||||
|
<created>1661008443366</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1661008443366</updated>
|
||||||
|
<workItem from="1661008446925" duration="7444000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
<component name="Vcs.Log.Tabs.Properties">
|
||||||
|
<option name="TAB_STATES">
|
||||||
|
<map>
|
||||||
|
<entry key="MAIN">
|
||||||
|
<value>
|
||||||
|
<State />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
12
package.json
12
package.json
|
|
@ -9,7 +9,8 @@
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
|
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
|
||||||
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
|
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. .",
|
||||||
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^1.0.0-next.65",
|
"@sveltejs/adapter-auto": "^1.0.0-next.65",
|
||||||
|
|
@ -28,12 +29,17 @@
|
||||||
"svelte-preprocess": "^4.10.1",
|
"svelte-preprocess": "^4.10.1",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^4.7.4",
|
||||||
"vite": "^3.0.4"
|
"vite": "^3.0.4",
|
||||||
|
"vitest": "^0.21.0"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"mdsvex": "^0.10.5",
|
"mdsvex": "^0.10.5",
|
||||||
"sanitize-html": "^2.7.0"
|
"mongodb": "^4.8.1",
|
||||||
|
"nanoid": "3.3.4",
|
||||||
|
"node-fetch": "^3.2.10",
|
||||||
|
"sanitize-html": "^2.7.0",
|
||||||
|
"zod": "^3.18.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
src/components/games/ApiPasswordForm.svelte
Normal file
49
src/components/games/ApiPasswordForm.svelte
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{ change: string }>();
|
||||||
|
|
||||||
|
let apiPassword = '';
|
||||||
|
let state: 'edit' | 'view' = 'edit';
|
||||||
|
let unsubscribe: () => void;
|
||||||
|
|
||||||
|
function onSubmit() {
|
||||||
|
state = 'view';
|
||||||
|
if (localStorage) {
|
||||||
|
localStorage.setItem('apiPassword', apiPassword);
|
||||||
|
}
|
||||||
|
dispatch('change', apiPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEdit() {
|
||||||
|
state = 'edit';
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (localStorage !== undefined) {
|
||||||
|
apiPassword = localStorage.getItem('apiPassword') || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiPassword.length > 0) {
|
||||||
|
dispatch('change', apiPassword);
|
||||||
|
state = 'view';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
{#if apiPassword.length === 0}
|
||||||
|
<p>
|
||||||
|
To save things to the ledger you need to enter the password. Right now you haven't set one.
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{#if state === 'view'}
|
||||||
|
<button on:click={onEdit}>Edit Password</button>
|
||||||
|
{:else}
|
||||||
|
<form on:submit|preventDefault={onSubmit}>
|
||||||
|
<input type="text" bind:value={apiPassword} />
|
||||||
|
<input type="submit" value="Set Password" />
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
63
src/components/games/FloriferousPlayerForm.svelte
Normal file
63
src/components/games/FloriferousPlayerForm.svelte
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { FloriferousPlayer } from '$lib/floriferous';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
let nameInput: HTMLInputElement;
|
||||||
|
let name = '';
|
||||||
|
let score = 0;
|
||||||
|
let rowAtEndOfGame = 1;
|
||||||
|
|
||||||
|
function handleFormSubmit() {
|
||||||
|
const player = new FloriferousPlayer({
|
||||||
|
name,
|
||||||
|
score,
|
||||||
|
rowAtEndOfGame: rowAtEndOfGame
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch('submit', player);
|
||||||
|
|
||||||
|
name = '';
|
||||||
|
score = 0;
|
||||||
|
rowAtEndOfGame = 1;
|
||||||
|
nameInput.focus();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={() => handleFormSubmit()}>
|
||||||
|
<div class="field">
|
||||||
|
<label for="player-name">Name</label>
|
||||||
|
<input bind:this={nameInput} bind:value={name} type="text" id="player-name" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="player-score">Score</label>
|
||||||
|
<input bind:value={score} type="number" step="1" min="0" id="player-score" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="player-score">Finishing Row</label>
|
||||||
|
<input bind:value={rowAtEndOfGame} type="number" step="1" min="0" id="player-score" />
|
||||||
|
<p class="example-text">"1" for the highest row, "2" for the second highest, etc.</p>
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="add" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
border: 1px solid var(--gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
padding: var(--spacing-sm) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-text {
|
||||||
|
color: var(--grey800);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
3
src/components/games/index.ts
Normal file
3
src/components/games/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import FloriferousPlayerForm from './FloriferousPlayerForm.svelte';
|
||||||
|
|
||||||
|
export { FloriferousPlayerForm };
|
||||||
3
src/lib/Authenticator.ts
Normal file
3
src/lib/Authenticator.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface Authenticator {
|
||||||
|
authenticate(password: string): boolean;
|
||||||
|
}
|
||||||
8
src/lib/floriferous/FloriferousGameRepository.ts
Normal file
8
src/lib/floriferous/FloriferousGameRepository.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import type { FloriferousGame } from './floriferous-game';
|
||||||
|
|
||||||
|
export interface FloriferousGameRepository {
|
||||||
|
save(game: FloriferousGame): Promise<FloriferousGame>;
|
||||||
|
getById(id: string): Promise<FloriferousGame | null>;
|
||||||
|
getRecent(count: number): Promise<FloriferousGame[]>;
|
||||||
|
|
||||||
|
}
|
||||||
117
src/lib/floriferous/floriferous-api-controller.spec.ts
Normal file
117
src/lib/floriferous/floriferous-api-controller.spec.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import type { ApiGamesFloriferousPostRequest } from './floriferous-api-controller';
|
||||||
|
import { FloriferousApiController } from './floriferous-api-controller';
|
||||||
|
import { StubFloriferousGameRepository } from './stub-floriferous-game-repository';
|
||||||
|
import { FloriferousGame } from './floriferous-game';
|
||||||
|
import { SimplePasswordAuthenticator } from '../simple-password-authenticator';
|
||||||
|
import { Headers } from 'node-fetch';
|
||||||
|
|
||||||
|
const isDate = (value = 'invalid'): boolean => {
|
||||||
|
return value !== 'invalid' && !isNaN(Date.parse(value));
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('FloriferousApiController', () => {
|
||||||
|
const stubGameRepository = new StubFloriferousGameRepository();
|
||||||
|
const authenticator = new SimplePasswordAuthenticator('expected-password');
|
||||||
|
const controller = new FloriferousApiController(stubGameRepository, authenticator);
|
||||||
|
|
||||||
|
it('should validate a request with a proper password', async () => {
|
||||||
|
// GIVEN
|
||||||
|
const headers: Headers = new Headers();
|
||||||
|
headers.set('x-api-password', 'expected-password');
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const result = controller.isRequestAuthenticated({ headers: headers });
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not validate a request with an invalid password', async () => {
|
||||||
|
// GIVEN
|
||||||
|
const headers = new Headers();
|
||||||
|
headers.set('x-api-password', 'invalid-password');
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const result = controller.isRequestAuthenticated({ headers });
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not validate a request without a password', async () => {
|
||||||
|
// GIVEN
|
||||||
|
const request = {
|
||||||
|
headers: new Headers()
|
||||||
|
};
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const result = controller.isRequestAuthenticated(request);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a list of recent games', async () => {
|
||||||
|
// GIVEN
|
||||||
|
const gameOne = new FloriferousGame({
|
||||||
|
id: 'game-one',
|
||||||
|
players: [{ name: 'Alice', score: 10, rowAtEndOfGame: 1 }],
|
||||||
|
playedTs: new Date('2022-07-01T07:00Z')
|
||||||
|
});
|
||||||
|
const gameTwo = new FloriferousGame({
|
||||||
|
id: 'game-two',
|
||||||
|
players: [],
|
||||||
|
playedTs: new Date('2022-08-20T13:25Z')
|
||||||
|
});
|
||||||
|
|
||||||
|
stubGameRepository.setAllGames([gameOne, gameTwo]);
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const result = await controller.getRecentGames(10);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(result).toStrictEqual([
|
||||||
|
{
|
||||||
|
id: 'game-one',
|
||||||
|
playedTs: '2022-07-01T07:00:00.000Z',
|
||||||
|
players: [{ name: 'Alice', score: 10, rowAtEndOfGame: 1 }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'game-two',
|
||||||
|
playedTs: '2022-08-20T13:25:00.000Z',
|
||||||
|
players: []
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save a new game', async () => {
|
||||||
|
// GIVEN
|
||||||
|
const requestBody: ApiGamesFloriferousPostRequest = {
|
||||||
|
players: [
|
||||||
|
{
|
||||||
|
name: 'Alice',
|
||||||
|
rowAtEndOfGame: 1,
|
||||||
|
score: 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const response = await controller.createNewGame(requestBody);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(isDate(response.playedTs)).toBe(true);
|
||||||
|
expect(response).toStrictEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
playedTs: expect.any(String),
|
||||||
|
players: [
|
||||||
|
{
|
||||||
|
name: 'Alice',
|
||||||
|
score: 10,
|
||||||
|
rowAtEndOfGame: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
88
src/lib/floriferous/floriferous-api-controller.ts
Normal file
88
src/lib/floriferous/floriferous-api-controller.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
import type { ZodError } from 'zod';
|
||||||
|
import type { FloriferousGameJson } from './floriferous-game-api-port';
|
||||||
|
import type { FloriferousGameRepository } from './FloriferousGameRepository';
|
||||||
|
import { FloriferousGameApiPort } from './floriferous-game-api-port';
|
||||||
|
import { FloriferousGame } from './floriferous-game';
|
||||||
|
import { FloriferousPlayer } from './floriferous-player';
|
||||||
|
import type { Authenticator } from '../Authenticator';
|
||||||
|
|
||||||
|
export interface ApiGamesFloriferousPostRequest {
|
||||||
|
players: {
|
||||||
|
name: string;
|
||||||
|
score: number;
|
||||||
|
rowAtEndOfGame: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiGamesFloriferousPostRequestSchema = z.object({
|
||||||
|
players: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
name: z.string().min(1, { message: 'Player names must be at least 1 character long' }),
|
||||||
|
score: z
|
||||||
|
.number({ invalid_type_error: 'Player Score must be a number.' })
|
||||||
|
.int({ message: 'Player Score must be a whole number.' })
|
||||||
|
.min(0, { message: 'Player score cannot be less than 0.' }),
|
||||||
|
rowAtEndOfGame: z.number().int().min(0)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.min(1)
|
||||||
|
});
|
||||||
|
|
||||||
|
export class FloriferousApiController {
|
||||||
|
constructor(
|
||||||
|
private readonly repository: FloriferousGameRepository,
|
||||||
|
private readonly validator: Authenticator
|
||||||
|
) {}
|
||||||
|
|
||||||
|
isRequestAuthenticated(request: any): boolean {
|
||||||
|
const password = request?.headers?.get('x-api-password') ?? '';
|
||||||
|
|
||||||
|
if (password === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.validator.authenticate(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRecentGames(count = 10): Promise<FloriferousGameJson[]> {
|
||||||
|
const games = await this.repository.getRecent(count);
|
||||||
|
return games.map((game) => FloriferousGameApiPort.gameToJson(game));
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNewGame(data: ApiGamesFloriferousPostRequest): Promise<FloriferousGameJson> {
|
||||||
|
try {
|
||||||
|
apiGamesFloriferousPostRequestSchema.parse(data);
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.issues !== undefined) {
|
||||||
|
const zodError: ZodError = e;
|
||||||
|
throw new Error(zodError.issues[0].message);
|
||||||
|
}
|
||||||
|
console.error({
|
||||||
|
message: `Caught error validating body data in createNewGame`,
|
||||||
|
error: e,
|
||||||
|
data: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(e?.message ?? e);
|
||||||
|
}
|
||||||
|
|
||||||
|
const validatedData = data;
|
||||||
|
|
||||||
|
const players: FloriferousPlayer[] = validatedData.players.map(
|
||||||
|
({ name, rowAtEndOfGame, score }) => {
|
||||||
|
return new FloriferousPlayer({ name, rowAtEndOfGame, score });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const game = new FloriferousGame({
|
||||||
|
players,
|
||||||
|
playedTs: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
const savedGame = await this.repository.save(game);
|
||||||
|
|
||||||
|
return FloriferousGameApiPort.gameToJson(game);
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/lib/floriferous/floriferous-game-api-port.spec.ts
Normal file
57
src/lib/floriferous/floriferous-game-api-port.spec.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { FloriferousGame } from './floriferous-game';
|
||||||
|
import { FloriferousPlayer } from './floriferous-player';
|
||||||
|
import { FloriferousGameApiPort } from './floriferous-game-api-port';
|
||||||
|
|
||||||
|
import { it, expect } from 'vitest';
|
||||||
|
|
||||||
|
it('should stringify a FloriferousGame into JSON', () => {
|
||||||
|
// GIVEN
|
||||||
|
const game = new FloriferousGame({
|
||||||
|
id: 'the-id',
|
||||||
|
playedTs: new Date('2020-01-01T00:00Z'),
|
||||||
|
players: [
|
||||||
|
new FloriferousPlayer({ name: 'first player', rowAtEndOfGame: 1, score: 2 }),
|
||||||
|
new FloriferousPlayer({ name: 'second player', rowAtEndOfGame: 3, score: 4 })
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const gameAsJson = FloriferousGameApiPort.gameToJson(game);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(gameAsJson).toStrictEqual({
|
||||||
|
id: 'the-id',
|
||||||
|
playedTs: '2020-01-01T00:00:00.000Z',
|
||||||
|
players: [
|
||||||
|
{ name: 'first player', rowAtEndOfGame: 1, score: 2 },
|
||||||
|
{ name: 'second player', rowAtEndOfGame: 3, score: 4 }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse JSON into a floriferous game', () => {
|
||||||
|
// GIVEN
|
||||||
|
const gameAsJson = {
|
||||||
|
id: 'the-id',
|
||||||
|
playedTs: '2020-01-01T00:00:00.000Z',
|
||||||
|
players: [
|
||||||
|
{ name: 'first player', rowAtEndOfGame: 1, score: 2 },
|
||||||
|
{ name: 'second player', rowAtEndOfGame: 3, score: 4 }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const game = FloriferousGameApiPort.jsonToGame(gameAsJson);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(game).toStrictEqual(
|
||||||
|
new FloriferousGame({
|
||||||
|
id: 'the-id',
|
||||||
|
playedTs: new Date('2020-01-01T00:00Z'),
|
||||||
|
players: [
|
||||||
|
{ name: 'first player', rowAtEndOfGame: 1, score: 2 },
|
||||||
|
{ name: 'second player', rowAtEndOfGame: 3, score: 4 }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
39
src/lib/floriferous/floriferous-game-api-port.ts
Normal file
39
src/lib/floriferous/floriferous-game-api-port.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { FloriferousGame } from './floriferous-game';
|
||||||
|
|
||||||
|
export interface FloriferousGameJson {
|
||||||
|
id: string;
|
||||||
|
playedTs: string;
|
||||||
|
players: {
|
||||||
|
name: string;
|
||||||
|
score: number;
|
||||||
|
rowAtEndOfGame: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FloriferousGameApiPort {
|
||||||
|
static jsonToGame(json: FloriferousGameJson): FloriferousGame {
|
||||||
|
const players = json.players.map((player) => ({
|
||||||
|
name: player.name,
|
||||||
|
score: player.score,
|
||||||
|
rowAtEndOfGame: player.rowAtEndOfGame
|
||||||
|
}));
|
||||||
|
|
||||||
|
return new FloriferousGame({
|
||||||
|
id: json.id,
|
||||||
|
playedTs: new Date(json.playedTs),
|
||||||
|
players
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static gameToJson(game: FloriferousGame): FloriferousGameJson {
|
||||||
|
return {
|
||||||
|
id: game.id,
|
||||||
|
playedTs: game.playedTs.toISOString(),
|
||||||
|
players: game.players.map((player) => ({
|
||||||
|
name: player.name,
|
||||||
|
score: player.score,
|
||||||
|
rowAtEndOfGame: player.rowAtEndOfGame
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/lib/floriferous/floriferous-game.spec.ts
Normal file
46
src/lib/floriferous/floriferous-game.spec.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { FloriferousGame } from './floriferous-game';
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { FloriferousPlayer } from './floriferous-player';
|
||||||
|
|
||||||
|
describe('FloriferousGame', () => {
|
||||||
|
const alice = new FloriferousPlayer({
|
||||||
|
name: 'Alice',
|
||||||
|
score: 2,
|
||||||
|
rowAtEndOfGame: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const bob = new FloriferousPlayer({
|
||||||
|
name: 'Bob',
|
||||||
|
score: 1,
|
||||||
|
rowAtEndOfGame: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Determines a winner', () => {
|
||||||
|
const game = new FloriferousGame();
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
game.addPlayer(alice);
|
||||||
|
game.addPlayer(bob);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(game.winner).toBe('Alice');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Breaks a tie using the player closest to the top of the board', () => {
|
||||||
|
// GIVEN
|
||||||
|
const bobWithTwoPoints = new FloriferousPlayer({
|
||||||
|
name: 'Bob',
|
||||||
|
score: 2,
|
||||||
|
rowAtEndOfGame: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const game = new FloriferousGame();
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
game.addPlayer(alice);
|
||||||
|
game.addPlayer(bobWithTwoPoints);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(game.winner).toBe('Alice');
|
||||||
|
});
|
||||||
|
});
|
||||||
53
src/lib/floriferous/floriferous-game.ts
Normal file
53
src/lib/floriferous/floriferous-game.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import type { FloriferousPlayer } from './floriferous-player';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
|
export interface FloriferousGameParams {
|
||||||
|
playedTs?: Date;
|
||||||
|
id?: string;
|
||||||
|
players?: FloriferousPlayer[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FloriferousGame {
|
||||||
|
readonly id: string;
|
||||||
|
readonly playedTs: Date;
|
||||||
|
private _players: FloriferousPlayer[] = [];
|
||||||
|
|
||||||
|
constructor({ id = nanoid(), playedTs = new Date(), players = [] }: FloriferousGameParams = {}) {
|
||||||
|
this.id = id;
|
||||||
|
this.playedTs = playedTs;
|
||||||
|
this._players = players;
|
||||||
|
}
|
||||||
|
|
||||||
|
addPlayer(player: FloriferousPlayer): void {
|
||||||
|
this._players.push(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
addPlayers(players: FloriferousPlayer[]): void {
|
||||||
|
players.forEach((player) => {
|
||||||
|
this.addPlayer(player);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get players(): FloriferousPlayer[] {
|
||||||
|
return this._players;
|
||||||
|
}
|
||||||
|
|
||||||
|
get winner(): string | undefined {
|
||||||
|
if (this._players.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const playersSortedByScore = this._players.sort((a, b) => {
|
||||||
|
return b.score - a.score;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (playersSortedByScore[0].score === playersSortedByScore[1].score) {
|
||||||
|
const playersSortedByRowAtEndOfGame = this._players.sort((a, b) => {
|
||||||
|
return a.rowAtEndOfGame - b.rowAtEndOfGame;
|
||||||
|
});
|
||||||
|
|
||||||
|
return playersSortedByRowAtEndOfGame[0].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return playersSortedByScore[0].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/lib/floriferous/floriferous-player.spec.ts
Normal file
18
src/lib/floriferous/floriferous-player.spec.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { FloriferousPlayer } from './floriferous-player';
|
||||||
|
|
||||||
|
describe('FloriferousPlayer', () => {
|
||||||
|
it('should construct with properties', () => {
|
||||||
|
// GIVEN
|
||||||
|
const player = new FloriferousPlayer({
|
||||||
|
name: 'Alice',
|
||||||
|
score: 2,
|
||||||
|
rowAtEndOfGame: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(player.name).toBe('Alice');
|
||||||
|
expect(player.score).toBe(2);
|
||||||
|
expect(player.rowAtEndOfGame).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
17
src/lib/floriferous/floriferous-player.ts
Normal file
17
src/lib/floriferous/floriferous-player.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
export interface FloriferousPlayerParams {
|
||||||
|
name: string;
|
||||||
|
score: number;
|
||||||
|
rowAtEndOfGame: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FloriferousPlayer {
|
||||||
|
readonly name: string;
|
||||||
|
readonly score: number;
|
||||||
|
readonly rowAtEndOfGame: number;
|
||||||
|
|
||||||
|
constructor(params: FloriferousPlayerParams) {
|
||||||
|
this.name = params.name;
|
||||||
|
this.score = params.score;
|
||||||
|
this.rowAtEndOfGame = params.rowAtEndOfGame;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/lib/floriferous/index.ts
Normal file
2
src/lib/floriferous/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { FloriferousGame } from './floriferous-game';
|
||||||
|
export { FloriferousPlayer } from './floriferous-player';
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
import { describe, expect, it, beforeEach, afterAll } from 'vitest';
|
||||||
|
|
||||||
|
import { FloriferousGame, type FloriferousGameParams } from './floriferous-game';
|
||||||
|
import { FloriferousPlayer, type FloriferousPlayerParams } from './floriferous-player';
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
import { MongodbFloriferousGameRepository } from './mongodb-floriferous-game-repository';
|
||||||
|
import { MongoClient } from 'mongodb';
|
||||||
|
|
||||||
|
describe('MongoDB FloriferousGame Repository', () => {
|
||||||
|
const mongoDbUrl = import.meta.env.VITE_MONGO_URL;
|
||||||
|
const mongoDbName = import.meta.env.VITE_MONGO_DB_NAME;
|
||||||
|
const janitor = new MongodbJanitor(mongoDbUrl, mongoDbName);
|
||||||
|
|
||||||
|
let playerOne: FloriferousPlayer;
|
||||||
|
let playerTwo: FloriferousPlayer;
|
||||||
|
let game: FloriferousGame;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
playerOne = floriferousPlayerFactory({ name: 'Player 1' });
|
||||||
|
playerTwo = floriferousPlayerFactory({ name: 'Player 2' });
|
||||||
|
game = floriferousGameFactory();
|
||||||
|
game.addPlayer(playerOne);
|
||||||
|
game.addPlayer(playerTwo);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await janitor.deleteAllDocumentsInConnection('floriferous-games');
|
||||||
|
});
|
||||||
|
|
||||||
|
const repository = new MongodbFloriferousGameRepository(mongoDbUrl, mongoDbName);
|
||||||
|
|
||||||
|
it('should save', async () => {
|
||||||
|
// WHEN
|
||||||
|
const savedGame = await repository.save(game);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(savedGame).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get by ID when it exists', async () => {
|
||||||
|
// GIVEN
|
||||||
|
const savedGame = await repository.save(game);
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const foundGame = await repository.getById(game.id);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(foundGame).toStrictEqual(game);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when fetching by ID for a game which did not happen', async () => {
|
||||||
|
// WHEN
|
||||||
|
const foundGame = await repository.getById('any-random-id');
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(foundGame).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the last 10 games, sorted by playedTs', async () => {
|
||||||
|
// GIVEN
|
||||||
|
await janitor.deleteAllDocumentsInConnection('floriferous-games');
|
||||||
|
const gameOne = floriferousGameFactory({
|
||||||
|
playedTs: new Date('2022-08-07T19:00Z')
|
||||||
|
});
|
||||||
|
|
||||||
|
const gameTwo = floriferousGameFactory({
|
||||||
|
playedTs: new Date('2022-08-07T06:00Z')
|
||||||
|
});
|
||||||
|
|
||||||
|
await repository.save(gameOne);
|
||||||
|
await repository.save(gameTwo);
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const recentGames = await repository.getRecent(10);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(recentGames).toStrictEqual([gameTwo, gameOne]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty array when no games have taken place', async () => {
|
||||||
|
// GIVEN
|
||||||
|
await janitor.deleteAllDocumentsInConnection('floriferous-games');
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const recentGames = await repository.getRecent(5);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(recentGames).toStrictEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class MongodbJanitor {
|
||||||
|
private client: MongoClient;
|
||||||
|
|
||||||
|
constructor(private readonly url: string, private readonly dbName: string) {
|
||||||
|
this.client = new MongoClient(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteAllDocumentsInConnection(collectionName: string): Promise<void> {
|
||||||
|
console.info(`Deleting all documents in ${collectionName}`);
|
||||||
|
|
||||||
|
const connection = await this.client.connect();
|
||||||
|
|
||||||
|
const deltedDocuments = await connection
|
||||||
|
.db(this.dbName)
|
||||||
|
.collection(collectionName)
|
||||||
|
.deleteMany({});
|
||||||
|
|
||||||
|
await connection.close();
|
||||||
|
|
||||||
|
console.info(`Deleted ${deltedDocuments.deletedCount} documents`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function floriferousPlayerFactory(
|
||||||
|
overrides: Partial<FloriferousPlayerParams> = {}
|
||||||
|
): FloriferousPlayer {
|
||||||
|
return new FloriferousPlayer({
|
||||||
|
name: `name-${customAlphabet('abcdefghijklmnopqrstuvwxyz', 8)}`,
|
||||||
|
score: Math.floor(Math.random() * 100),
|
||||||
|
rowAtEndOfGame: Math.floor(Math.random() * 10),
|
||||||
|
...overrides
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function floriferousGameFactory(overrides: Partial<FloriferousGameParams> = {}): FloriferousGame {
|
||||||
|
return new FloriferousGame(overrides);
|
||||||
|
}
|
||||||
123
src/lib/floriferous/mongodb-floriferous-game-repository.ts
Normal file
123
src/lib/floriferous/mongodb-floriferous-game-repository.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
import { FloriferousGame } from './floriferous-game';
|
||||||
|
import { MongoClient } from 'mongodb';
|
||||||
|
import { FloriferousPlayer } from './floriferous-player';
|
||||||
|
import type { FloriferousGameRepository } from './FloriferousGameRepository';
|
||||||
|
|
||||||
|
interface FloriferousGameMongoDocument {
|
||||||
|
id: string;
|
||||||
|
playedTs: Date;
|
||||||
|
players: {
|
||||||
|
name: string;
|
||||||
|
score: number;
|
||||||
|
rowAtEndOfGame: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MongodbFloriferousGameRepository implements FloriferousGameRepository {
|
||||||
|
private client: MongoClient;
|
||||||
|
private connection: MongoClient | null = null;
|
||||||
|
private collectionName = 'floriferous-games';
|
||||||
|
|
||||||
|
constructor(private readonly mongodbUrl: string, private readonly dbName: string) {
|
||||||
|
this.client = new MongoClient(mongodbUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async connect(): Promise<void> {
|
||||||
|
this.connection = await this.client.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async disconnect(): Promise<void> {
|
||||||
|
if (this.connection === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(game: FloriferousGame): Promise<FloriferousGame> {
|
||||||
|
await this.connect();
|
||||||
|
|
||||||
|
const data = MongodbFloriferousGameRepository.gameToMongoDocument(game);
|
||||||
|
|
||||||
|
const document = await this.connection
|
||||||
|
.db(this.dbName)
|
||||||
|
.collection<FloriferousGameMongoDocument>(this.collectionName)
|
||||||
|
.insertOne(data);
|
||||||
|
|
||||||
|
await this.disconnect();
|
||||||
|
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: string): Promise<FloriferousGame | null> {
|
||||||
|
await this.connect();
|
||||||
|
|
||||||
|
const document = await this.connection
|
||||||
|
.db(this.dbName)
|
||||||
|
.collection<FloriferousGameMongoDocument>(this.collectionName)
|
||||||
|
.findOne({ id });
|
||||||
|
|
||||||
|
await this.disconnect();
|
||||||
|
|
||||||
|
if (document === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MongodbFloriferousGameRepository.documentToGame(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRecent(count = 10): Promise<FloriferousGame[]> {
|
||||||
|
await this.connect();
|
||||||
|
|
||||||
|
const documents = await this.connection
|
||||||
|
.db(this.dbName)
|
||||||
|
.collection<FloriferousGameMongoDocument>(this.collectionName)
|
||||||
|
.find({})
|
||||||
|
.sort({ playedTs: 1 })
|
||||||
|
.limit(count)
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
await this.disconnect();
|
||||||
|
|
||||||
|
if (!documents) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatted = documents.map((document) =>
|
||||||
|
MongodbFloriferousGameRepository.documentToGame(document)
|
||||||
|
);
|
||||||
|
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static documentToGame(document: FloriferousGameMongoDocument): FloriferousGame {
|
||||||
|
const players: FloriferousPlayer[] = document.players.map(
|
||||||
|
({ name, rowAtEndOfGame, score }) =>
|
||||||
|
new FloriferousPlayer({
|
||||||
|
name,
|
||||||
|
rowAtEndOfGame: rowAtEndOfGame,
|
||||||
|
score
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const game = new FloriferousGame({
|
||||||
|
id: document.id,
|
||||||
|
playedTs: document.playedTs,
|
||||||
|
players
|
||||||
|
});
|
||||||
|
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static gameToMongoDocument(game: FloriferousGame): FloriferousGameMongoDocument {
|
||||||
|
return {
|
||||||
|
id: game.id,
|
||||||
|
playedTs: game.playedTs,
|
||||||
|
players: game.players.map((player) => ({
|
||||||
|
rowAtEndOfGame: player.rowAtEndOfGame,
|
||||||
|
name: player.name,
|
||||||
|
score: player.score
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/lib/floriferous/stub-floriferous-game-repository.ts
Normal file
30
src/lib/floriferous/stub-floriferous-game-repository.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import type { FloriferousGameRepository } from './FloriferousGameRepository';
|
||||||
|
import type { FloriferousGame } from './floriferous-game';
|
||||||
|
|
||||||
|
export class StubFloriferousGameRepository implements FloriferousGameRepository{
|
||||||
|
private games: FloriferousGame[] = [];
|
||||||
|
|
||||||
|
setAllGames(games: FloriferousGame[]): void {
|
||||||
|
this.games = games;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getById(id: string): Promise<FloriferousGame | null> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecent(count: number): Promise<FloriferousGame[]> {
|
||||||
|
return Promise.resolve(this.games.map((game, index) => {
|
||||||
|
if (index < count -1) {
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}).filter((game) => game !== undefined));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
save(game: FloriferousGame): Promise<FloriferousGame> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/lib/simple-password-authenticator.spec.ts
Normal file
26
src/lib/simple-password-authenticator.spec.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { SimplePasswordAuthenticator } from './simple-password-authenticator';
|
||||||
|
|
||||||
|
import { it, expect} from 'vitest'
|
||||||
|
|
||||||
|
it('should do nothing when things are valid', () => {
|
||||||
|
// GIVEN
|
||||||
|
const authenticator = new SimplePasswordAuthenticator('expected-password');
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const result = authenticator.authenticate('expected-password');
|
||||||
|
|
||||||
|
//
|
||||||
|
expect(result).toBeTruthy();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not authenticate when the password is invalid', () => {
|
||||||
|
// GIVEN
|
||||||
|
const authenticator = new SimplePasswordAuthenticator('expected-password');
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const result = authenticator.authenticate('invalid-password');
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(result).toBeFalsy();
|
||||||
|
|
||||||
|
})
|
||||||
13
src/lib/simple-password-authenticator.ts
Normal file
13
src/lib/simple-password-authenticator.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import type { Authenticator } from './Authenticator';
|
||||||
|
|
||||||
|
export class SimplePasswordAuthenticator implements Authenticator{
|
||||||
|
constructor(private readonly password: string) {
|
||||||
|
if (this.password === undefined) {
|
||||||
|
throw new Error('Password must be defined');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate(password: string): boolean {
|
||||||
|
return this.password === password;
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/routes/api/games/floriferous.json/+server.ts
Normal file
51
src/routes/api/games/floriferous.json/+server.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { MONGO_URL, MONGO_DB_NAME, API_PASSWORD } from '$env/static/private';
|
||||||
|
import type { error } from '@sveltejs/kit';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
|
import { MongodbFloriferousGameRepository } from '$lib/floriferous/mongodb-floriferous-game-repository';
|
||||||
|
import { FloriferousApiController } from '$lib/floriferous/floriferous-api-controller';
|
||||||
|
import { SimplePasswordAuthenticator } from '$lib/simple-password-authenticator';
|
||||||
|
import {
|
||||||
|
FloriferousGameApiPort,
|
||||||
|
type FloriferousGameJson
|
||||||
|
} from '$lib/floriferous/floriferous-game-api-port';
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async () => {
|
||||||
|
const controller = new FloriferousApiController(
|
||||||
|
new MongodbFloriferousGameRepository(MONGO_URL, MONGO_DB_NAME),
|
||||||
|
new SimplePasswordAuthenticator(API_PASSWORD)
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await controller.getRecentGames(10);
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(response), { status: 200 });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POST = async ({ request }) => {
|
||||||
|
const controller = new FloriferousApiController(
|
||||||
|
new MongodbFloriferousGameRepository(MONGO_URL, MONGO_DB_NAME),
|
||||||
|
new SimplePasswordAuthenticator(API_PASSWORD)
|
||||||
|
);
|
||||||
|
|
||||||
|
const isAuthenticated = controller.isRequestAuthenticated(request);
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
return {
|
||||||
|
status: 401,
|
||||||
|
body: 'Unauthorized'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestBody = await request.json();
|
||||||
|
const response = await controller.createNewGame(requestBody);
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(response), {
|
||||||
|
status: 200
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return new Response(JSON.stringify(e), {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
151
src/routes/games/floriferous/+page.svelte
Normal file
151
src/routes/games/floriferous/+page.svelte
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { slide } from 'svelte/transition';
|
||||||
|
import type { PageData } from './$types.ts';
|
||||||
|
|
||||||
|
import { FloriferousGame } from '../../../lib/floriferous';
|
||||||
|
import type { FloriferousPlayer } from '../../../lib/floriferous';
|
||||||
|
import { FloriferousPlayerForm } from '../../../components/games';
|
||||||
|
import {
|
||||||
|
FloriferousGameApiPort,
|
||||||
|
type FloriferousGameJson
|
||||||
|
} from '$lib/floriferous/floriferous-game-api-port';
|
||||||
|
import ApiPasswordFrom from '../../../components/games/ApiPasswordForm.svelte';
|
||||||
|
import type { ApiGamesFloriferousPostRequest } from '$lib/floriferous/floriferous-api-controller';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
let previousGames: FloriferousGame[] = data.previousGames;
|
||||||
|
let apiPassword = '';
|
||||||
|
let players: FloriferousPlayer[] = [];
|
||||||
|
let isWinnerVisible = false;
|
||||||
|
let isSaveSubmitting = false;
|
||||||
|
let isGameSaved = false;
|
||||||
|
|
||||||
|
function handleShowWinner() {
|
||||||
|
isWinnerVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAddPlayer(event: CustomEvent<FloriferousPlayer>) {
|
||||||
|
players = [...players, event.detail];
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRemovePlayer(playerToRemove: FloriferousPlayer) {
|
||||||
|
players = players.filter((player) => {
|
||||||
|
return playerToRemove.name !== player.name;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearGameData() {
|
||||||
|
players = [];
|
||||||
|
isWinnerVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSaveGame() {
|
||||||
|
isSaveSubmitting = true;
|
||||||
|
|
||||||
|
if (players.length < 2) {
|
||||||
|
console.warn(`Not enough players to save game`);
|
||||||
|
isSaveSubmitting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body: { players: FloriferousPlayer[] } = { players };
|
||||||
|
|
||||||
|
fetch('/api/games/floriferous.json', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-api-password': apiPassword
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((gameAsJson: FloriferousGameJson) => {
|
||||||
|
const game = FloriferousGameApiPort.jsonToGame(gameAsJson);
|
||||||
|
previousGames = [...previousGames, game];
|
||||||
|
clearGameData();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error.status === 401) {
|
||||||
|
console.warn(`Invalid API password`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error(error);
|
||||||
|
isSaveSubmitting = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$: game = new FloriferousGame({ playedTs: new Date(), players });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>Floriferous Scoring</h1>
|
||||||
|
{#if previousGames.length > 0}
|
||||||
|
<section class="previous-games">
|
||||||
|
<h2>Previous Games</h2>
|
||||||
|
<ul>
|
||||||
|
{#each previousGames as game}
|
||||||
|
<li transition:slide>
|
||||||
|
{Intl.DateTimeFormat('en-GB', {
|
||||||
|
weekday: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
}).format(game.playedTs)}:
|
||||||
|
{game.winner} Won
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<section class="players">
|
||||||
|
<h2>Players</h2>
|
||||||
|
|
||||||
|
{#if players.length > 0}
|
||||||
|
<ul>
|
||||||
|
{#each players as player}
|
||||||
|
<li>
|
||||||
|
{player.name} ({player.score} points, finished on row {player.rowAtEndOfGame}) (<button
|
||||||
|
on:click={() => onRemovePlayer(player)}>Remove</button
|
||||||
|
>)
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{#if players.length > 1}
|
||||||
|
{#if isWinnerVisible}
|
||||||
|
<p transition:slide>And the winner is:<strong>{game.winner}</strong></p>
|
||||||
|
<h3>Add to Ledger</h3>
|
||||||
|
|
||||||
|
<ApiPasswordFrom on:change={(event) => (apiPassword = event.detail)} />
|
||||||
|
|
||||||
|
{#if apiPassword.length > 0}
|
||||||
|
<p>You can save this game in the Ledger</p>
|
||||||
|
<button on:click={onSaveGame}>Save Game</button>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<button on:click={handleShowWinner}>Show me the winner</button>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<p>Add at least one player to get started</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !isWinnerVisible}
|
||||||
|
<h3>Add a New Player</h3>
|
||||||
|
<FloriferousPlayerForm on:submit={onAddPlayer} />
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
12
src/routes/games/floriferous/+page.ts
Normal file
12
src/routes/games/floriferous/+page.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import {
|
||||||
|
FloriferousGameApiPort,
|
||||||
|
type FloriferousGameJson
|
||||||
|
} from '$lib/floriferous/floriferous-game-api-port';
|
||||||
|
import type { Load } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const load: Load = async ({ fetch }): Promise<{ previousGames: FloriferousGameJson[] }> => {
|
||||||
|
const previousGames = await fetch('/api/games/floriferous.json').then((res) => res.json());
|
||||||
|
return {
|
||||||
|
previousGames: previousGames.map(FloriferousGameApiPort.jsonToGame)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -32,6 +32,10 @@
|
||||||
--spacing-lg: 1rem;
|
--spacing-lg: 1rem;
|
||||||
--spacing-xl: 1.5rem;
|
--spacing-xl: 1.5rem;
|
||||||
--navbar-height: 75px;
|
--navbar-height: 75px;
|
||||||
|
|
||||||
|
--font-size-sm: 0.875rem;
|
||||||
|
--font-size-md: 1.25rem;
|
||||||
|
--font-size-lg: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@ const config = {
|
||||||
})],
|
})],
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
adapter: adapter({ split: false })
|
adapter: adapter({ split: false }),
|
||||||
|
env: {
|
||||||
|
publicPrefix: 'PUBLIC_'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
{
|
{
|
||||||
"extends": "./.svelte-kit/tsconfig.json"
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "NodeNext"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
/** @type {import('vite').UserConfig} */
|
/** @type {import('vite').UserConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
plugins: [sveltekit()]
|
plugins: [sveltekit()],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
227
yarn.lock
227
yarn.lock
|
|
@ -200,6 +200,18 @@
|
||||||
magic-string "^0.26.2"
|
magic-string "^0.26.2"
|
||||||
svelte-hmr "^0.14.12"
|
svelte-hmr "^0.14.12"
|
||||||
|
|
||||||
|
"@types/chai-subset@^1.3.3":
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
|
||||||
|
integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==
|
||||||
|
dependencies:
|
||||||
|
"@types/chai" "*"
|
||||||
|
|
||||||
|
"@types/chai@*", "@types/chai@^4.3.3":
|
||||||
|
version "4.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07"
|
||||||
|
integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==
|
||||||
|
|
||||||
"@types/json-schema@^7.0.9":
|
"@types/json-schema@^7.0.9":
|
||||||
version "7.0.11"
|
version "7.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||||
|
|
@ -234,6 +246,19 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
||||||
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
||||||
|
|
||||||
|
"@types/webidl-conversions@*":
|
||||||
|
version "6.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz#e33bc8ea812a01f63f90481c666334844b12a09e"
|
||||||
|
integrity sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==
|
||||||
|
|
||||||
|
"@types/whatwg-url@^8.2.1":
|
||||||
|
version "8.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz#749d5b3873e845897ada99be4448041d4cc39e63"
|
||||||
|
integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
"@types/webidl-conversions" "*"
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@^5.32.0":
|
"@typescript-eslint/eslint-plugin@^5.32.0":
|
||||||
version "5.33.1"
|
version "5.33.1"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.1.tgz#c0a480d05211660221eda963cc844732fe9b1714"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.1.tgz#c0a480d05211660221eda963cc844732fe9b1714"
|
||||||
|
|
@ -435,6 +460,11 @@ array-union@^2.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
|
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
|
||||||
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
|
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
|
||||||
|
|
||||||
|
assertion-error@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
|
||||||
|
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
|
||||||
|
|
||||||
astral-regex@^2.0.0:
|
astral-regex@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
|
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
|
||||||
|
|
@ -450,6 +480,11 @@ balanced-match@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||||
|
|
||||||
|
base64-js@^1.3.1:
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||||
|
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||||
|
|
||||||
binary-extensions@^2.0.0:
|
binary-extensions@^2.0.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||||
|
|
@ -477,16 +512,44 @@ braces@^3.0.2, braces@~3.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
fill-range "^7.0.1"
|
fill-range "^7.0.1"
|
||||||
|
|
||||||
|
bson@^4.7.0:
|
||||||
|
version "4.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bson/-/bson-4.7.0.tgz#7874a60091ffc7a45c5dd2973b5cad7cded9718a"
|
||||||
|
integrity sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA==
|
||||||
|
dependencies:
|
||||||
|
buffer "^5.6.0"
|
||||||
|
|
||||||
buffer-crc32@^0.2.5:
|
buffer-crc32@^0.2.5:
|
||||||
version "0.2.13"
|
version "0.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||||
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
||||||
|
|
||||||
|
buffer@^5.6.0:
|
||||||
|
version "5.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
|
||||||
|
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
|
||||||
|
dependencies:
|
||||||
|
base64-js "^1.3.1"
|
||||||
|
ieee754 "^1.1.13"
|
||||||
|
|
||||||
callsites@^3.0.0:
|
callsites@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||||
|
|
||||||
|
chai@^4.3.6:
|
||||||
|
version "4.3.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c"
|
||||||
|
integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==
|
||||||
|
dependencies:
|
||||||
|
assertion-error "^1.1.0"
|
||||||
|
check-error "^1.0.2"
|
||||||
|
deep-eql "^3.0.1"
|
||||||
|
get-func-name "^2.0.0"
|
||||||
|
loupe "^2.3.1"
|
||||||
|
pathval "^1.1.1"
|
||||||
|
type-detect "^4.0.5"
|
||||||
|
|
||||||
chalk@^2.0.0:
|
chalk@^2.0.0:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||||
|
|
@ -504,6 +567,11 @@ chalk@^4.0.0:
|
||||||
ansi-styles "^4.1.0"
|
ansi-styles "^4.1.0"
|
||||||
supports-color "^7.1.0"
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
|
check-error@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||||
|
integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==
|
||||||
|
|
||||||
chokidar@^3.4.1:
|
chokidar@^3.4.1:
|
||||||
version "3.5.3"
|
version "3.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
||||||
|
|
@ -594,6 +662,13 @@ debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "2.1.2"
|
||||||
|
|
||||||
|
deep-eql@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
|
||||||
|
integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==
|
||||||
|
dependencies:
|
||||||
|
type-detect "^4.0.0"
|
||||||
|
|
||||||
deep-is@^0.1.3:
|
deep-is@^0.1.3:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||||
|
|
@ -609,6 +684,11 @@ delegates@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||||
integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
|
integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
|
||||||
|
|
||||||
|
denque@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
|
||||||
|
integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
|
||||||
|
|
||||||
detect-indent@^6.0.0:
|
detect-indent@^6.0.0:
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
|
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
|
||||||
|
|
@ -1095,6 +1175,11 @@ gauge@^3.0.0:
|
||||||
strip-ansi "^6.0.1"
|
strip-ansi "^6.0.1"
|
||||||
wide-align "^1.1.2"
|
wide-align "^1.1.2"
|
||||||
|
|
||||||
|
get-func-name@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
|
||||||
|
integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==
|
||||||
|
|
||||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||||
|
|
@ -1188,6 +1273,11 @@ https-proxy-agent@^5.0.0:
|
||||||
agent-base "6"
|
agent-base "6"
|
||||||
debug "4"
|
debug "4"
|
||||||
|
|
||||||
|
ieee754@^1.1.13:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||||
|
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||||
|
|
||||||
ignore@^4.0.6:
|
ignore@^4.0.6:
|
||||||
version "4.0.6"
|
version "4.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
||||||
|
|
@ -1224,6 +1314,11 @@ inherits@2, inherits@^2.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
|
|
||||||
|
ip@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da"
|
||||||
|
integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==
|
||||||
|
|
||||||
is-binary-path@~2.1.0:
|
is-binary-path@~2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||||
|
|
@ -1311,6 +1406,11 @@ levn@^0.4.1:
|
||||||
prelude-ls "^1.2.1"
|
prelude-ls "^1.2.1"
|
||||||
type-check "~0.4.0"
|
type-check "~0.4.0"
|
||||||
|
|
||||||
|
local-pkg@^0.4.2:
|
||||||
|
version "0.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.2.tgz#13107310b77e74a0e513147a131a2ba288176c2f"
|
||||||
|
integrity sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==
|
||||||
|
|
||||||
lodash.merge@^4.6.2:
|
lodash.merge@^4.6.2:
|
||||||
version "4.6.2"
|
version "4.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||||
|
|
@ -1321,6 +1421,13 @@ lodash.truncate@^4.4.2:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
|
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
|
||||||
integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==
|
integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==
|
||||||
|
|
||||||
|
loupe@^2.3.1:
|
||||||
|
version "2.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3"
|
||||||
|
integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==
|
||||||
|
dependencies:
|
||||||
|
get-func-name "^2.0.0"
|
||||||
|
|
||||||
lru-cache@^6.0.0:
|
lru-cache@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||||
|
|
@ -1359,6 +1466,11 @@ mdsvex@^0.10.5:
|
||||||
prismjs "^1.17.1"
|
prismjs "^1.17.1"
|
||||||
vfile-message "^2.0.4"
|
vfile-message "^2.0.4"
|
||||||
|
|
||||||
|
memory-pager@^1.0.2:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
|
||||||
|
integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==
|
||||||
|
|
||||||
merge2@^1.3.0, merge2@^1.4.1:
|
merge2@^1.3.0, merge2@^1.4.1:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
|
|
@ -1421,6 +1533,26 @@ mkdirp@^1.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||||
|
|
||||||
|
mongodb-connection-string-url@^2.5.3:
|
||||||
|
version "2.5.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz#c0c572b71570e58be2bd52b33dffd1330cfb6990"
|
||||||
|
integrity sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/whatwg-url" "^8.2.1"
|
||||||
|
whatwg-url "^11.0.0"
|
||||||
|
|
||||||
|
mongodb@^4.8.1:
|
||||||
|
version "4.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.9.0.tgz#58618439b721f2d6f7d38bb10a4612e29d7f1c8a"
|
||||||
|
integrity sha512-tJJEFJz7OQTQPZeVHZJIeSOjMRqc5eSyXTt86vSQENEErpkiG7279tM/GT5AVZ7TgXNh9HQxoa2ZkbrANz5GQw==
|
||||||
|
dependencies:
|
||||||
|
bson "^4.7.0"
|
||||||
|
denque "^2.1.0"
|
||||||
|
mongodb-connection-string-url "^2.5.3"
|
||||||
|
socks "^2.7.0"
|
||||||
|
optionalDependencies:
|
||||||
|
saslprep "^1.0.3"
|
||||||
|
|
||||||
mri@^1.1.0:
|
mri@^1.1.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
|
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
|
||||||
|
|
@ -1436,7 +1568,7 @@ ms@2.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||||
|
|
||||||
nanoid@^3.3.4:
|
nanoid@3.3.4, nanoid@^3.3.4:
|
||||||
version "3.3.4"
|
version "3.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
||||||
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
|
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
|
||||||
|
|
@ -1458,7 +1590,7 @@ node-fetch@^2.6.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
whatwg-url "^5.0.0"
|
whatwg-url "^5.0.0"
|
||||||
|
|
||||||
node-fetch@^3.2.4:
|
node-fetch@^3.2.10, node-fetch@^3.2.4:
|
||||||
version "3.2.10"
|
version "3.2.10"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8"
|
||||||
integrity sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==
|
integrity sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==
|
||||||
|
|
@ -1550,6 +1682,11 @@ path-type@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||||
|
|
||||||
|
pathval@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
|
||||||
|
integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
|
||||||
|
|
||||||
picocolors@^1.0.0:
|
picocolors@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||||
|
|
@ -1599,7 +1736,7 @@ progress@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||||
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
||||||
|
|
||||||
punycode@^2.1.0:
|
punycode@^2.1.0, punycode@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||||
|
|
@ -1733,6 +1870,13 @@ sanitize-html@^2.7.0:
|
||||||
parse-srcset "^1.0.2"
|
parse-srcset "^1.0.2"
|
||||||
postcss "^8.3.11"
|
postcss "^8.3.11"
|
||||||
|
|
||||||
|
saslprep@^1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226"
|
||||||
|
integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==
|
||||||
|
dependencies:
|
||||||
|
sparse-bitfield "^3.0.3"
|
||||||
|
|
||||||
semver@^6.0.0:
|
semver@^6.0.0:
|
||||||
version "6.3.0"
|
version "6.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||||
|
|
@ -1795,6 +1939,19 @@ slice-ansi@^4.0.0:
|
||||||
astral-regex "^2.0.0"
|
astral-regex "^2.0.0"
|
||||||
is-fullwidth-code-point "^3.0.0"
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
|
||||||
|
smart-buffer@^4.2.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
|
||||||
|
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
|
||||||
|
|
||||||
|
socks@^2.7.0:
|
||||||
|
version "2.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.0.tgz#f9225acdb841e874dca25f870e9130990f3913d0"
|
||||||
|
integrity sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==
|
||||||
|
dependencies:
|
||||||
|
ip "^2.0.0"
|
||||||
|
smart-buffer "^4.2.0"
|
||||||
|
|
||||||
sorcery@^0.10.0:
|
sorcery@^0.10.0:
|
||||||
version "0.10.0"
|
version "0.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/sorcery/-/sorcery-0.10.0.tgz#8ae90ad7d7cb05fc59f1ab0c637845d5c15a52b7"
|
resolved "https://registry.yarnpkg.com/sorcery/-/sorcery-0.10.0.tgz#8ae90ad7d7cb05fc59f1ab0c637845d5c15a52b7"
|
||||||
|
|
@ -1815,6 +1972,13 @@ sourcemap-codec@^1.3.0, sourcemap-codec@^1.4.8:
|
||||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||||
|
|
||||||
|
sparse-bitfield@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11"
|
||||||
|
integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==
|
||||||
|
dependencies:
|
||||||
|
memory-pager "^1.0.2"
|
||||||
|
|
||||||
sprintf-js@~1.0.2:
|
sprintf-js@~1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
|
|
@ -1946,6 +2110,16 @@ tiny-glob@^0.2.9:
|
||||||
globalyzer "0.1.0"
|
globalyzer "0.1.0"
|
||||||
globrex "^0.1.2"
|
globrex "^0.1.2"
|
||||||
|
|
||||||
|
tinypool@^0.2.4:
|
||||||
|
version "0.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.2.4.tgz#4d2598c4689d1a2ce267ddf3360a9c6b3925a20c"
|
||||||
|
integrity sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==
|
||||||
|
|
||||||
|
tinyspy@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-1.0.2.tgz#6da0b3918bfd56170fb3cd3a2b5ef832ee1dff0d"
|
||||||
|
integrity sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==
|
||||||
|
|
||||||
to-regex-range@^5.0.1:
|
to-regex-range@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||||
|
|
@ -1958,6 +2132,13 @@ totalist@^3.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd"
|
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd"
|
||||||
integrity sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==
|
integrity sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==
|
||||||
|
|
||||||
|
tr46@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9"
|
||||||
|
integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==
|
||||||
|
dependencies:
|
||||||
|
punycode "^2.1.1"
|
||||||
|
|
||||||
tr46@~0.0.3:
|
tr46@~0.0.3:
|
||||||
version "0.0.3"
|
version "0.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||||
|
|
@ -1987,6 +2168,11 @@ type-check@^0.4.0, type-check@~0.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls "^1.2.1"
|
prelude-ls "^1.2.1"
|
||||||
|
|
||||||
|
type-detect@^4.0.0, type-detect@^4.0.5:
|
||||||
|
version "4.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||||
|
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||||
|
|
||||||
type-fest@^0.20.2:
|
type-fest@^0.20.2:
|
||||||
version "0.20.2"
|
version "0.20.2"
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
||||||
|
|
@ -2034,7 +2220,7 @@ vfile-message@^2.0.4:
|
||||||
"@types/unist" "^2.0.0"
|
"@types/unist" "^2.0.0"
|
||||||
unist-util-stringify-position "^2.0.0"
|
unist-util-stringify-position "^2.0.0"
|
||||||
|
|
||||||
vite@^3.0.4:
|
"vite@^2.9.12 || ^3.0.0-0", vite@^3.0.4:
|
||||||
version "3.0.9"
|
version "3.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.9.tgz#45fac22c2a5290a970f23d66c1aef56a04be8a30"
|
resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.9.tgz#45fac22c2a5290a970f23d66c1aef56a04be8a30"
|
||||||
integrity sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==
|
integrity sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==
|
||||||
|
|
@ -2046,6 +2232,21 @@ vite@^3.0.4:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
vitest@^0.21.0:
|
||||||
|
version "0.21.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.21.1.tgz#b4f5b901c9a23a3aaec76d3404f3072821d93d00"
|
||||||
|
integrity sha512-WBIxuFmIDPuK47GO6Lu9eNeRMqHj/FWL3dk73OHH3eyPPWPiu+UB3QHLkLK2PEggCqJW4FaWoWg8R68S7p9+9Q==
|
||||||
|
dependencies:
|
||||||
|
"@types/chai" "^4.3.3"
|
||||||
|
"@types/chai-subset" "^1.3.3"
|
||||||
|
"@types/node" "*"
|
||||||
|
chai "^4.3.6"
|
||||||
|
debug "^4.3.4"
|
||||||
|
local-pkg "^0.4.2"
|
||||||
|
tinypool "^0.2.4"
|
||||||
|
tinyspy "^1.0.0"
|
||||||
|
vite "^2.9.12 || ^3.0.0-0"
|
||||||
|
|
||||||
web-streams-polyfill@^3.0.3:
|
web-streams-polyfill@^3.0.3:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
|
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
|
||||||
|
|
@ -2056,6 +2257,19 @@ webidl-conversions@^3.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||||
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
||||||
|
|
||||||
|
webidl-conversions@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
|
||||||
|
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
|
||||||
|
|
||||||
|
whatwg-url@^11.0.0:
|
||||||
|
version "11.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018"
|
||||||
|
integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==
|
||||||
|
dependencies:
|
||||||
|
tr46 "^3.0.0"
|
||||||
|
webidl-conversions "^7.0.0"
|
||||||
|
|
||||||
whatwg-url@^5.0.0:
|
whatwg-url@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
||||||
|
|
@ -2100,3 +2314,8 @@ yallist@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||||
|
|
||||||
|
zod@^3.18.0:
|
||||||
|
version "3.18.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.18.0.tgz#2eed58b3cafb8d9a67aa2fee69279702f584f3bc"
|
||||||
|
integrity sha512-gwTm8RfUCe8l9rDwN5r2A17DkAa8Ez4Yl4yXqc5VqeGaXaJahzYYXbTwvhroZi0SNBqTwh/bKm2N0mpCzuw4bA==
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue