refactor: ♻️ rename /(R|r)essource/gi to (R|r)esource (french to english)

This commit is contained in:
Julien Oculi 2024-07-16 13:44:14 +02:00
parent 9bb122f813
commit 3c4c2c517d
12 changed files with 171 additions and 115 deletions

View file

@ -1,3 +1,3 @@
# Coh@bit ressources # KM - Coh@bit resources manager
Server de gestion des ressources de cohabit. Système de gestion des ressources de cohabit.

View file

@ -1,5 +1,5 @@
{ {
"name": "@cohabit/ressources", "name": "@cohabit/resources-manager",
"version": "0.1.2", "version": "0.1.2",
"exports": { "exports": {
".": "./mod.ts", ".": "./mod.ts",

55
src/api/routes/users.ts Normal file
View file

@ -0,0 +1,55 @@
import type { Db, Resource } from '../../../mod.ts'
import type { UUID } from '../../../types.ts'
import type { ResourceBuilder, ResourceType } from '../types.ts'
import { respondJson } from '../utils.ts'
export async function resourceHandler<T extends Resource>(
type: ResourceType<T>,
Builder: ResourceBuilder<T>,
db: Db,
req: Request,
id?: string,
): Promise<Response> {
if (req.method === 'POST') {
try {
const json = await req.json() as T
try {
//@ts-expect-error Extends from Resource
const resource = Builder.create(json)
//@ts-expect-error not a generic
const result = await db.resource[type].set(resource)
if (result.ok) {
//@ts-expect-error generic to fix
return respondJson(resource)
} else {
return respondJson(new Error(`can't insert ${resource} in db`))
}
} catch (error) {
return respondJson(error)
}
} catch (error) {
return respondJson(error)
}
}
if (req.method === 'GET') {
if (id === undefined) {
return respondJson(new Error('missing "id" for "GET"'))
}
try {
const resource = await db.resource[type].get({ uuid: id as UUID })
//@ts-expect-error generic to fix
return respondJson(resource)
} catch {
return respondJson(
new Error(`can't find any ${type} with the current uuid ${id}`),
)
}
}
if (req.method === 'PATCH') {
throw new Error('not implemented')
}
if (req.method === 'DELETE') {
throw new Error('not implemented')
}
return respondJson(new Error(`method "${req.method}" is not allowed`))
}

View file

@ -3,14 +3,14 @@ import {
Group, Group,
Machine, Machine,
type Ref, type Ref,
type Ressource, type Resource,
Service, Service,
User, User,
} from '../models/mod.ts' } from '../models/mod.ts'
import type { CredentialCategory } from '../models/src/credential.ts' import type { CredentialCategory } from '../models/src/credential.ts'
//!TODO link ressources (get, list) //!TODO link resources (get, list)
//!TODO Purge unused ressources (delete) //!TODO Purge unused resources (delete)
export class Db { export class Db {
#kv: Deno.Kv #kv: Deno.Kv
@ -27,112 +27,112 @@ export class Db {
return this.#kv return this.#kv
} }
get prefix(): { ressource: 'ressource' } { get prefix(): { resource: 'resource' } {
return { return {
ressource: 'ressource', resource: 'resource',
} }
} }
get ressource() { get resource() {
return { return {
credential: this.#ressourceStorage<Credential<CredentialCategory>>( credential: this.#resourceStorage<Credential<CredentialCategory>>(
'credential', 'credential',
Credential, Credential,
), ),
group: this.#ressourceStorage<Group>('group', Group), group: this.#resourceStorage<Group>('group', Group),
machine: this.#ressourceStorage<Machine>('machine', Machine), machine: this.#resourceStorage<Machine>('machine', Machine),
service: this.#ressourceStorage<Service>('service', Service), service: this.#resourceStorage<Service>('service', Service),
user: this.#ressourceStorage<User>('user', User), user: this.#resourceStorage<User>('user', User),
} }
} }
#ressourceStorage<T extends Ressource>( #resourceStorage<T extends Resource>(
type: RessourceType<T>, type: ResourceType<T>,
Builder: RessourceBuilder<T>, Builder: ResourceBuilder<T>,
) { ) {
return { return {
get: (ressource: Pick<T, 'uuid'>) => get: (resource: Pick<T, 'uuid'>) =>
this.#get<T>(type, Builder, ressource), this.#get<T>(type, Builder, resource),
set: (ressources: T[]) => this.#set<T>(type, ressources), set: (resources: T[]) => this.#set<T>(type, resources),
delete: (ressources: Pick<T, 'uuid'>[]) => delete: (resources: Pick<T, 'uuid'>[]) =>
this.#delete<T>(type, ressources), this.#delete<T>(type, resources),
list: ( list: (
filter: (ressource: T) => boolean | Promise<boolean> = () => true, filter: (resource: T) => boolean | Promise<boolean> = () => true,
) => this.#list<T>(type, Builder, filter), ) => this.#list<T>(type, Builder, filter),
listRef: async ( listRef: async (
filter: (ressource: T) => boolean | Promise<boolean> = () => true, filter: (resource: T) => boolean | Promise<boolean> = () => true,
) => { ) => {
const ressources: Ref<T>[] = [] const resources: Ref<T>[] = []
for await (const ressource of this.#list<T>(type, Builder, filter)) { for await (const resource of this.#list<T>(type, Builder, filter)) {
ressources.push(ressource.toRef() as Ref<T>) resources.push(resource.toRef() as Ref<T>)
} }
return ressources return resources
}, },
} }
} }
async #get<T extends Ressource>( async #get<T extends Resource>(
type: RessourceType<T>, type: ResourceType<T>,
Builder: RessourceBuilder<T>, Builder: ResourceBuilder<T>,
entry: Pick<T, 'uuid'>, entry: Pick<T, 'uuid'>,
): Promise<T> { ): Promise<T> {
const json = await this.#kv.get<RessourceJson<T>>([ const json = await this.#kv.get<ResourceJson<T>>([
this.prefix.ressource, this.prefix.resource,
type, type,
entry.uuid, entry.uuid,
]) ])
if (json.value) { if (json.value) {
//@ts-expect-error Type union of Ressource types for Builder //@ts-expect-error Type union of Resource types for Builder
return Builder.fromJSON(json.value) return Builder.fromJSON(json.value)
} }
throw new ReferenceError( throw new ReferenceError(
`no ressource.${type} was found with uuid: "${entry.uuid}"`, `no resource.${type} was found with uuid: "${entry.uuid}"`,
) )
} }
#set<T extends Ressource>(type: RessourceType<T>, entries: T[]) { #set<T extends Resource>(type: ResourceType<T>, entries: T[]) {
const atomic = this.#kv.atomic() const atomic = this.#kv.atomic()
for (const entry of entries) { for (const entry of entries) {
//! TODO check if `refs` exists //! TODO check if `refs` exists
const key = [this.prefix.ressource, type, entry.uuid] const key = [this.prefix.resource, type, entry.uuid]
atomic.set(key, entry.toJSON()) atomic.set(key, entry.toJSON())
} }
return atomic.commit() return atomic.commit()
} }
#delete<T extends Ressource>( #delete<T extends Resource>(
type: RessourceType<T>, type: ResourceType<T>,
entries: Pick<T, 'uuid'>[], entries: Pick<T, 'uuid'>[],
): Promise<Deno.KvCommitResult | Deno.KvCommitError> { ): Promise<Deno.KvCommitResult | Deno.KvCommitError> {
const atomic = this.#kv.atomic() const atomic = this.#kv.atomic()
for (const entry of entries) { for (const entry of entries) {
//! TODO check if `refs` exists //! TODO check if `refs` exists
atomic.delete([this.prefix.ressource, type, entry.uuid]) atomic.delete([this.prefix.resource, type, entry.uuid])
} }
return atomic.commit() return atomic.commit()
} }
async *#list<T extends Ressource>( async *#list<T extends Resource>(
type: RessourceType<T>, type: ResourceType<T>,
Builder: RessourceBuilder<T>, Builder: ResourceBuilder<T>,
filter: (entry: T) => boolean | Promise<boolean>, filter: (entry: T) => boolean | Promise<boolean>,
): AsyncGenerator<T, void, void> { ): AsyncGenerator<T, void, void> {
const list = this.#kv.list<RessourceJson<T>>({ const list = this.#kv.list<ResourceJson<T>>({
prefix: [this.prefix.ressource, type], prefix: [this.prefix.resource, type],
}) })
for await (const entry of list) { for await (const entry of list) {
const value = entry.value const value = entry.value
//@ts-expect-error Type union of Ressource types for Builder //@ts-expect-error Type union of Resource types for Builder
const ressource = Builder.fromJSON(value) as T const resource = Builder.fromJSON(value) as T
if (await filter(ressource)) { if (await filter(resource)) {
yield ressource yield resource
} }
} }
} }
} }
type RessourceType<T extends Ressource> = T extends type ResourceType<T extends Resource> = T extends
Credential<CredentialCategory> ? 'credential' Credential<CredentialCategory> ? 'credential'
: T extends Group ? 'group' : T extends Group ? 'group'
: T extends Machine ? 'machine' : T extends Machine ? 'machine'
@ -140,7 +140,7 @@ type RessourceType<T extends Ressource> = T extends
: T extends User ? 'user' : T extends User ? 'user'
: never : never
type RessourceBuilder<T extends Ressource> = T extends type ResourceBuilder<T extends Resource> = T extends
Credential<CredentialCategory> ? typeof Credential Credential<CredentialCategory> ? typeof Credential
: T extends Group ? typeof Group : T extends Group ? typeof Group
: T extends Machine ? typeof Machine : T extends Machine ? typeof Machine
@ -148,4 +148,4 @@ type RessourceBuilder<T extends Ressource> = T extends
: T extends User ? typeof User : T extends User ? typeof User
: never : never
type RessourceJson<T extends Ressource> = ReturnType<T['toJSON']> type ResourceJson<T extends Resource> = ReturnType<T['toJSON']>

View file

@ -1,7 +1,8 @@
export { Ref, type RefResolver, type RefString } from './utils/ref.ts'
export { Credential } from './src/credential.ts' export { Credential } from './src/credential.ts'
export { Group } from './src/group.ts' export { Group } from './src/group.ts'
export { Machine } from './src/machine.ts' export { Machine } from './src/machine.ts'
export type { Ressource } from './src/ressource.ts' export type { Resource } from './src/resource.ts'
export { Service } from './src/service.ts' export { Service } from './src/service.ts'
export { User } from './src/user.ts' export { User } from './src/user.ts'
export { Ref, type RefResolver, type RefString } from './utils/ref.ts'

View file

@ -1,9 +1,9 @@
import type { Base64String, ToJson, UUID } from '../../../types.ts' import type { Base64String, ToJson, UUID } from '../../../types.ts'
import { Ressource } from './ressource.ts'
import type { Ref } from '../utils/ref.ts'
import { Avatar } from '../utils/avatar.ts' import { Avatar } from '../utils/avatar.ts'
import type { Ref } from '../utils/ref.ts'
import { Resource } from './resource.ts'
export class Credential<T extends CredentialCategory> extends Ressource { export class Credential<T extends CredentialCategory> extends Resource {
static fromJSON<T extends CredentialCategory>( static fromJSON<T extends CredentialCategory>(
json: ToJson<Credential<T>>, json: ToJson<Credential<T>>,
): Credential<T> { ): Credential<T> {
@ -48,7 +48,7 @@ export class Credential<T extends CredentialCategory> extends Ressource {
private constructor( private constructor(
{ category, store, ...props }: { category, store, ...props }:
& Pick<Credential<T>, 'category' | 'store'> & Pick<Credential<T>, 'category' | 'store'>
& Pick<Ressource, 'name' | 'uuid' | 'avatar' | 'createdAt' | 'updatedAt'>, & Pick<Resource, 'name' | 'uuid' | 'avatar' | 'createdAt' | 'updatedAt'>,
) { ) {
super(props) super(props)
@ -107,7 +107,7 @@ export class Credential<T extends CredentialCategory> extends Ressource {
} }
} }
export interface Credential<T extends CredentialCategory> extends Ressource { export interface Credential<T extends CredentialCategory> extends Resource {
type: 'credential' type: 'credential'
category: T category: T
store: Readonly<CredentialStore<T>['store']> store: Readonly<CredentialStore<T>['store']>

View file

@ -1,9 +1,9 @@
import type { Posix, ToJson, UUID } from '../../../types.ts' import type { Posix, ToJson, UUID } from '../../../types.ts'
import { Ressource } from './ressource.ts'
import { Ref } from '../utils/ref.ts'
import { Avatar } from '../utils/avatar.ts' import { Avatar } from '../utils/avatar.ts'
import { Ref } from '../utils/ref.ts'
import { Resource } from './resource.ts'
export class Group extends Ressource { export class Group extends Resource {
static fromJSON( static fromJSON(
{ posix, groups, ...json }: ToJson<Group>, { posix, groups, ...json }: ToJson<Group>,
): Group { ): Group {
@ -56,7 +56,7 @@ export class Group extends Ressource {
private constructor( private constructor(
{ posix, permissions, groups, ...props }: { posix, permissions, groups, ...props }:
& Pick<Group, 'posix' | 'permissions' | 'groups'> & Pick<Group, 'posix' | 'permissions' | 'groups'>
& Pick<Ressource, 'name' | 'avatar' | 'uuid' | 'createdAt' | 'updatedAt'>, & Pick<Resource, 'name' | 'avatar' | 'uuid' | 'createdAt' | 'updatedAt'>,
) { ) {
super(props) super(props)
@ -133,7 +133,7 @@ export class Group extends Ressource {
} }
} }
export interface Group extends Ressource { export interface Group extends Resource {
type: 'group' type: 'group'
posix: Posix | undefined posix: Posix | undefined
groups: readonly Ref<Group>[] groups: readonly Ref<Group>[]

View file

@ -1,10 +1,10 @@
import { Ref } from '../utils/ref.ts'
import type { ToJson, UrlString } from '../../../types.ts' import type { ToJson, UrlString } from '../../../types.ts'
import type { Group } from './group.ts'
import { Ressource } from './ressource.ts'
import { Avatar } from '../utils/avatar.ts' import { Avatar } from '../utils/avatar.ts'
import { Ref } from '../utils/ref.ts'
import type { Group } from './group.ts'
import { Resource } from './resource.ts'
export class Machine extends Ressource { export class Machine extends Resource {
static fromJSON( static fromJSON(
{ url, ...json }: ToJson<Machine>, { url, ...json }: ToJson<Machine>,
): Machine { ): Machine {
@ -59,7 +59,7 @@ export class Machine extends Ressource {
private constructor( private constructor(
{ tags, url, status, groups, ...props }: { tags, url, status, groups, ...props }:
& Pick<Machine, 'tags' | 'url' | 'status' | 'avatar' | 'groups'> & Pick<Machine, 'tags' | 'url' | 'status' | 'avatar' | 'groups'>
& Pick<Ressource, 'name' | 'uuid' | 'avatar' | 'createdAt' | 'updatedAt'>, & Pick<Resource, 'name' | 'uuid' | 'avatar' | 'createdAt' | 'updatedAt'>,
) { ) {
super(props) super(props)
@ -128,7 +128,7 @@ export class Machine extends Ressource {
} }
} }
export interface Machine extends Ressource { export interface Machine extends Resource {
type: 'machine' type: 'machine'
tags: readonly string[] tags: readonly string[]
url: UrlString url: UrlString

View file

@ -1,12 +1,12 @@
import { Ref } from '../utils/ref.ts'
import type { DateString, ToJson, UrlString, UUID } from '../../../types.ts' import type { DateString, ToJson, UrlString, UUID } from '../../../types.ts'
import { regex } from '../../../utils.ts' import { regex } from '../../../utils.ts'
import { Ref } from '../utils/ref.ts'
export class Ressource { export class Resource {
static fromJSON( static fromJSON(
json: ToJson<Ressource>, json: ToJson<Resource>,
): Ressource { ): Resource {
return new Ressource({ return new Resource({
name: json.name, name: json.name,
avatar: json.avatar, avatar: json.avatar,
uuid: json.uuid, uuid: json.uuid,
@ -16,16 +16,16 @@ export class Ressource {
} }
static create( static create(
{ name, avatar }: Pick<Ressource, 'name' | 'avatar'>, { name, avatar }: Pick<Resource, 'name' | 'avatar'>,
): Ressource { ): Resource {
const uuid = crypto.randomUUID() as UUID const uuid = crypto.randomUUID() as UUID
const createdAt = new Date().toISOString() as DateString const createdAt = new Date().toISOString() as DateString
const updatedAt = createdAt const updatedAt = createdAt
return new Ressource({ name, avatar, uuid, createdAt, updatedAt }) return new Resource({ name, avatar, uuid, createdAt, updatedAt })
} }
static load(_options: never): Ressource { static load(_options: never): Resource {
throw new Error('not implemented') throw new Error('not implemented')
} }
@ -37,7 +37,7 @@ export class Ressource {
protected constructor( protected constructor(
{ name, avatar, uuid, createdAt, updatedAt }: Pick< { name, avatar, uuid, createdAt, updatedAt }: Pick<
Ressource, Resource,
'name' | 'avatar' | 'uuid' | 'createdAt' | 'updatedAt' 'name' | 'avatar' | 'uuid' | 'createdAt' | 'updatedAt'
>, >,
) { ) {
@ -83,9 +83,9 @@ export class Ressource {
} }
update( update(
props: Partial<Omit<Ressource, 'type' | 'uuid' | 'createdAt'>>, props: Partial<Omit<Resource, 'type' | 'uuid' | 'createdAt'>>,
): Ressource { ): Resource {
return new Ressource({ return new Resource({
name: this.name, name: this.name,
avatar: this.avatar, avatar: this.avatar,
uuid: this.uuid, uuid: this.uuid,
@ -110,12 +110,12 @@ export class Ressource {
return `Ressource (${JSON.stringify(this)})` return `Ressource (${JSON.stringify(this)})`
} }
toRef(): Ref<Ressource> { toRef(): Ref<Resource> {
return Ref.fromRessource(this) return Ref.fromResource(this)
} }
} }
export interface Ressource { export interface Resource {
type: 'user' | 'machine' | 'service' | 'group' | 'credential' type: 'user' | 'machine' | 'service' | 'group' | 'credential'
uuid: UUID uuid: UUID
name: string name: string

View file

@ -1,10 +1,10 @@
import { Ref } from '../utils/ref.ts'
import type { ToJson, UrlString } from '../../../types.ts' import type { ToJson, UrlString } from '../../../types.ts'
import type { Group } from './group.ts'
import { Ressource } from './ressource.ts'
import { Avatar } from '../utils/avatar.ts' import { Avatar } from '../utils/avatar.ts'
import { Ref } from '../utils/ref.ts'
import type { Group } from './group.ts'
import { Resource } from './resource.ts'
export class Service extends Ressource { export class Service extends Resource {
static fromJSON( static fromJSON(
{ groups, ...json }: ToJson<Service>, { groups, ...json }: ToJson<Service>,
): Service { ): Service {
@ -62,7 +62,7 @@ export class Service extends Ressource {
tags, tags,
url, url,
groups, groups,
...ressource ...resource
}: Pick< }: Pick<
Service, Service,
| 'uuid' | 'uuid'
@ -75,7 +75,7 @@ export class Service extends Ressource {
| 'groups' | 'groups'
| 'avatar' | 'avatar'
>) { >) {
super(ressource) super(resource)
if (!['web', 'fs', 'git'].includes(category)) { if (!['web', 'fs', 'git'].includes(category)) {
throw new TypeError( throw new TypeError(
@ -146,7 +146,7 @@ export class Service extends Ressource {
} }
} }
export interface Service extends Ressource { export interface Service extends Resource {
type: 'service' type: 'service'
category: ServiceCategory category: ServiceCategory
tags: readonly string[] tags: readonly string[]

View file

@ -1,12 +1,12 @@
import { Ref } from '../utils/ref.ts'
import type { Login, MailAddress, Posix, ToJson } from '../../../types.ts' import type { Login, MailAddress, Posix, ToJson } from '../../../types.ts'
import { regex, toLogin } from '../../../utils.ts' import { regex, toLogin } from '../../../utils.ts'
import { Avatar } from '../utils/avatar.ts'
import { Ref } from '../utils/ref.ts'
import type { Credential, CredentialCategory } from './credential.ts' import type { Credential, CredentialCategory } from './credential.ts'
import type { Group } from './group.ts' import type { Group } from './group.ts'
import { Ressource } from './ressource.ts' import { Resource } from './resource.ts'
import { Avatar } from '../utils/avatar.ts'
export class User extends Ressource { export class User extends Resource {
static fromJSON(json: ToJson<User>): User { static fromJSON(json: ToJson<User>): User {
if (json.type !== 'user') { if (json.type !== 'user') {
throw new TypeError(`type is "${json.type}" but "user" is required`) throw new TypeError(`type is "${json.type}" but "user" is required`)
@ -112,7 +112,7 @@ export class User extends Ressource {
groups, groups,
posix, posix,
credentials, credentials,
...ressource ...resource
}: Pick< }: Pick<
User, User,
| 'uuid' | 'uuid'
@ -127,7 +127,7 @@ export class User extends Ressource {
| 'avatar' | 'avatar'
| 'credentials' | 'credentials'
>) { >) {
super(ressource) super(resource)
this.#lastname = lastname this.#lastname = lastname
this.#firstname = firstname this.#firstname = firstname
if (!regex.mailAddress) { if (!regex.mailAddress) {
@ -213,7 +213,7 @@ export class User extends Ressource {
} }
} }
export interface User extends Ressource { export interface User extends Resource {
type: 'user' type: 'user'
lastname: string lastname: string
firstname: string firstname: string

View file

@ -1,28 +1,28 @@
import type { Db } from '../../db/mod.ts'
import type { UUID } from '../../../types.ts' import type { UUID } from '../../../types.ts'
import type { Db } from '../../db/mod.ts'
import { import {
Credential, Credential,
Group, Group,
Machine, Machine,
type Ressource, type Resource,
Service, Service,
User, User,
} from '../mod.ts' } from '../mod.ts'
export type RefString<T extends Ressource> = export type RefString<T extends Resource> =
| `@ref/${T['type']}#${UUID}` | `@ref/${T['type']}#${UUID}`
| Ref<T> | Ref<T>
export type RefResolver<T extends Ressource> = ( export type RefResolver<T extends Resource> = (
ref: RefString<T>, ref: RefString<T>,
) => T | Promise<T> ) => T | Promise<T>
export class Ref<T extends Ressource> extends String { export class Ref<T extends Resource> extends String {
static #toString<T extends Ressource>( static #toString<T extends Resource>(
{ uuid, type }: { uuid: UUID; type: T['type'] }, { uuid, type }: { uuid: UUID; type: T['type'] },
) { ) {
return `@ref/${type}#${uuid}` as const return `@ref/${type}#${uuid}` as const
} }
static parse<T extends Ressource>( static parse<T extends Resource>(
string: RefString<T>, string: RefString<T>,
): { type: T['type']; uuid: UUID } { ): { type: T['type']; uuid: UUID } {
const [_, value] = string.split('/') const [_, value] = string.split('/')
@ -31,28 +31,28 @@ export class Ref<T extends Ressource> extends String {
return { type, uuid } as const return { type, uuid } as const
} }
static fromRessource<T extends Ressource>(ressource: T): Ref<T> { static fromResource<T extends Resource>(resource: T): Ref<T> {
return new Ref<T>(ressource) return new Ref<T>(resource)
} }
static fromString<T extends Ressource>(string: RefString<T>): Ref<T> { static fromString<T extends Resource>(string: RefString<T>): Ref<T> {
return new Ref<T>(Ref.parse(string)) return new Ref<T>(Ref.parse(string))
} }
static dbResolver( static dbResolver(
db: Db, db: Db,
): <T extends Ressource>(ref: RefString<T>) => Promise<T> { ): <T extends Resource>(ref: RefString<T>) => Promise<T> {
return <T extends Ressource>(ref: RefString<T>): Promise<T> => { return <T extends Resource>(ref: RefString<T>): Promise<T> => {
const { type, uuid } = Ref.parse(ref) const { type, uuid } = Ref.parse(ref)
//@ts-expect-error force type casting to fix //@ts-expect-error force type casting to fix
return db.ressource[type].get({ uuid }) return db.resource[type].get({ uuid })
} }
} }
static restResolver( static restResolver(
endpoint: string | URL, endpoint: string | URL,
): <T extends Ressource>(ref: RefString<T>) => Promise<T> { ): <T extends Resource>(ref: RefString<T>) => Promise<T> {
return async <T extends Ressource>(ref: RefString<T>): Promise<T> => { return async <T extends Resource>(ref: RefString<T>): Promise<T> => {
const { type, uuid } = Ref.parse(ref) const { type, uuid } = Ref.parse(ref)
const url = new URL(`${type}s/${uuid}`, endpoint) const url = new URL(`${type}s/${uuid}`, endpoint)
const response = await fetch(url) const response = await fetch(url)