Русский
Русский
English
Статистика
Реклама

Deno

Как создатель node.js сам разочаровался в нем

24.03.2021 12:04:07 | Автор: admin

Несколько лет назад на JSConf 2018 выступил Райан Даль, создатель Node.js. Его доклад вызвал сенсацию, он затронул много актуальных проблем и поднял громкий хайп, не оставив равнодушным практически никого, кто связан с серверным программированием. В его обсуждении бэкэнд программисты разделились на два лагеря: одни отстаивали Node.js, другие прочили ему скорую смерть.

С момента выступления Райана прошло немногим больше двух лет, а в IT это целая эпоха, за время которой может поменяться еще не все, то очень многое. Давайте вспомним этот доклад и попробуем посмотреть, что изменилось с тех пор, кто был прав.

Немного про Node.js


Однажды, когда Райан загружал фотографии на Flickr, он заметил необычную вещь индикатор, который наглядно показывал процесс загрузки фотографий на сервер. Сейчас никого не удивить прогресс-баром, а тогда такие интерфейсы еще не были распространены и, обычно, пользователь наблюдал гифку с банальными песочными часами, которые просто крутились и не давали никакой информации об оставшемся времени завершения операции.

Тогда он задумался о том, что веб-сервер должен уметь обрабатывать одновременно несколько задач. В то время Райан разрабатывал модуль для Nginx, где все работало асинхронно, и пришел к выводу, что эффективнее всего реализовать событийно-ориентированную систему, которой и стала платформа Node.js.

Первоначально среда называлась web.js, но ее возможности применения оказались намного шире, чем обычный веб-сервер, и потому платформу переименовали в Node.js. Готовый проект был представлен Райаном на JSConf 2009, 45-минутное выступление сорвало аплодисменты:


Система быстро получила признание и стала невероятно популярной. Помимо асинхронности у Node много других преимуществ, ее можно использовать как еще один слой абстракции над уже существующим бэкэндом и не переписывать серверную часть заново. Высокая скорость работы и хорошая масштабируемость позволила строить высоконагруженные системы, например, приложения для работы сетевых магазинов, не падающих даже в период сезонных распродаж, когда простой системы может лишить очень существенной части годового дохода.

Не обошлось без проблем, в частности было не просто найти разработчиков с подходящим бэкграундом. Чаще всего причины: I HATE NODE.JS!!!1111oneone, они проистекают как раз из того, что кажущаяся простота языка привлекает программистов, которые ранее не занимались разработкой для серверов. На JS обычно писали фронтэнд, а там решаются совсем другие задачи. С проблемами сталкивались и разработчики на Java, потому что у них было предубеждение: На языке называющемся JavaScript, я программировать смогу без проблем!, что на деле было совсем не так просто. Оказалось, что легче других с задачей программирования на JS под Node справляются программисты, работавшие на платформе .Net. Более того, в момент развития Node дотнетчики испытывали некоторые трудности с востребованностью, а программистам JS для бэкэнда хорошо платили.

Сам же Райан через некоторое время после завершения разработки, примерно в 2012-13 годах, увлекся языком Go и надолго отошел от всего, связанного с Node.js, потому что этот язык полностью устраивал его по возможностям и быстродействию, а позже он увлекся нейронными сетями.

Критика Node.js его создателем


Примерно за полгода до конференции JSConf 2018 Райан Даль решил попробовать работать со своим детищем, спустя почти 10 лет с момента его создания. Нельзя сказать, что он был доволен тем, что увидел. Несомненно, проект сильно развился, но во время разработки Node.js Райан был слишком зациклен на решении проблем ввода/вывода, потому допустил некоторые ошибки, которые потом превратились в системные проблемы. Поскольку платформа стала чрезвычайно популярной, то уже поздно было что-то глобально менять, не поломав все зависимости и совместимость.

Райан и раньше очень критично отзывался о всем программном обеспечении, не только о Node.js. В 2011 году он устроил небольшую пятиминутку ненависти на Google+. Сама соцсеть давно закрыта, но интернет все помнит. Перевод его слов можно прочитать здесь: Я ненавижу почти всё ПО. А за год до выступления в 2018 рассказывал о том, что: Для серверов я не могу представить другой язык кроме Go. Забегая вперед, он действительно начинал писать на Go, убийце node.js, но потом перешел на Rust.

Потому критичный тон его доклада в 2018 году никого особо не удивил, а свежий взгляд создателя платформы снова поднял актуальность некоторых проблем.


10 Things I Regret About Node.js


Давайте вспомним основные тезисы, которые были в нем рассмотрены, а потом сравним с тем, как обстоят дела на данный момент.

1.


Отсутствие Promises, от которых отказались на начальном этапе разработки.

2.


Проблема с безопасностью. Приложение на Node.js получает доступ к локальному диску, сети и вообще ко всей системе сразу.

3.


Сложная и устаревшая система сборки, от которой давно уже все отказались.

4.


Излишне громоздкий package.json.

5.


Семантика платформы несовместима с браузерами

6.


Централизованный репозиторий для модулей.

7.


Команда подключения модуля не требует обязательного указания типа файла, что может приводить к путанице.

8.


Использование index.js, по аналогии с index.html, привело к усложнению загрузки модулей и перестало быть необходимым после поддержки package.json.

Забавно, что в обсуждениях выступления не раз упоминали, что количество сожалений в докладе не соответствует количеству сожалений заголовке, но надо учесть, что Райан очень сильно волновался, его заметно потряхивало в самом начале речи. Более того, он не раз отмечал в своих интервью, что является программистом, а не медийной персоной, сожалел о том, что каждый его пост в блоке привлекает сотни комментариев и теперь надо аккуратнее высказывать свое мнение, потому что сообщество часто понимало его слова слишком буквально.

Анонс Deno


После перечисления недостатков Node.js Райан представил свой собственный проект среды выполнения JS на сервере, последовательно перечисляя его преимущества, в сравнении с Node:

1.


Безопасная песочница для выполнения кода, с предоставлением только необходимых привилегий.

2.


Упрощенное использование модулей, с загрузкой по прямым ссылкам и хранением в локальном кэше, который не будет обновляться до тех пор, пока не будет дана специальная команда.

3.


Полная поддержка TypeScript.

4.


Единственный исполняемый файл без лишних связей

5.


Deno позволяет нативно исполнять код на разных языках, в зависимости от того, какой удобнее для конкретной задачи.

6.


Падение приложений при Uncaught Errors, асинхронные операции будут возвращать Promise, большая совместимость с браузерами.

Кроме перечисленного Deno будет использовать ES Modules, которых тогда тоже не было в поддержке Node.

Что изменилось в Node.js



Доклад вышел неоднозначный, потому он и поднял такой шум в сообществе. С одной стороны, замечания были справедливы. С другой, не все они настолько критичны, что требуется все бросить и писать новую среду.

Пожалуй, самым справедливым было упоминание проблем с безопасностью. У Node.js нет песочницы, которая закроет сервер от запущенного приложения. Эта проблема так полностью и не решена до сих пор. Доступ в сеть можно закрыть фаерволом, доступ к диску и другие полномочия ограничить через запуск из контейнера, но это сторонние решения, которые везде могут быть удобны.

Упомянутые в докладе проблемы с выполнением асинхронного кода, на данный момент более-менее решены. Конечно, было бы лучше, если бы Райан не удалил Promises в 2010 году, посчитав, что они слишком все усложняют, но сейчас многие системные библиотеки уже предоставляют этот функционал, не говоря уже про сторонние. Появилась возможность запускать скрипты параллельно с помощью workers, большие компании, такие как Microsoft и Alibaba, разрабатывают свои версии Node с многопоточностью.

От GYP так и не избавились, это наследие будет с Node еще долго. Ссылки url, совместимые с браузером, уже не засунуть, зато, с некоторых пор Node поддерживает ES-модули.

Разрешение зависимостей сложное и работает не так как в браузере, но работает, что самое главное, хоть и не без некоторых проблем. А появление механизма package-lock.json сводит их к минимуму.

NPM сильно эволюционировал с 2012 года. Хотя, надо заметить, эволюция была болезненная и подталкивали ее проблемы типа набившего оскомину left-pad. Но пакеты уже не исчезают просто так, есть несколько уровней аудита безопасности и возможность ставить пакеты из разных источников. Хотя до сих пор ничего не мешает разработчику залить на github правильный код, а в npm код с эксплойтом.

Проблема с index.js выглядит скорее надуманной, чем реальной, как и отсутствие расширений при подключении модулей. Складывается впечатление, что он наспех придумал половину аргументов. Кстати, так и было, подробнее в конце статьи.

Стоит признать, что практически все эти проблемы были решены, и весьма успешно. Сам Даль отстранился от развития проекта, с 2012 года он не имеет отношения к Node, и его доклад выглядит довольно субъективным, где недостатки платформы преувеличиваются в пользу его собственного детища. Таким образом, это выступление больше похоже на рекламу своей разработки, а не на разоблачение Node.js.

Справедливости ради надо заметить, что активно развитие Node сильно сдерживается энтерпрайзом, в котором такое огромное количество легаси-кода, что избавиться от него слишком дорого. Это давно вызывает серьезные проблемы, когда Linux надо обновлять, а под новую систему не удается собрать все модули и рано или поздно приходится переписывать огромное количество кода, тратя на это много времени и денег. Большие корпорации могут позволить себе выделить на это ресурсы, но небольшой хоббийный OpenSource проект будет брошен, если у разработчика не будет времени на его обновление.

Тем не менее у Node.js огромное комьюнити, и оно помогает программистам разбираться со множеством проблем, хотя порой разработчики расставляют приоритеты не так, как хотелось бы сообществу, и потому развитие проекта идет неровно. Возможно, что рано или поздно Node окончательно упрется в те изначальные недостатки, которые описывал Райан, и решить их будет уже невозможно. Но для IT это не первый раз, когда приходится глобально что-то менять. Например, Flash, успешно похоронили, без особых хлопот.

Развитие Deno



Вскоре после доклада, сообщество быстро разгадало анаграмму Node-Deno. Даже прозвучали шутки, что когда-то будет написан проект под названием Done. Но шутки шутками, а к чему же пришло развитие Deno?

Во время доклада Райан честно сказал, что на данный момент Deno экспериментальный прототип и не предлагается как альтернатива Node


Но с тех пор прошло уже больше двух лет, весной прошлого года, наконец-то, появился релиз под номером 1.0.0.

Как уже упоминалось выше, проект начинался на Go, но потом был переделан на Rust. Проект хоть и перестал тормозить, как в начале, но так и не показал преимуществ по скорости. Работа с TypeScript стала быстрее, сказались оптимизации, которые внесла команда, потому что в момент презентации запуск транслятора TS занимал целую секунду. Решено это было, как Райан и предполагал в своем докладе 2018 года, с помощью снимков состояния движка.

Платформа совершенно не совместима с Node и задумывалась не как абсолютная замена, но как альтернатива, исследовательский проект, эксперимент, подсказывающий разработчикам другие пути развития подобных решений.

На данный момент Deno состоит из следующих частей: обертка из Typescript/ Javascript, над движком V8, который является песочницей, обеспечивающий интерфейс между слоем TS/JS и Rust Backend (серверная часть имеющая доступ к файловой системе). Многопоточная работа обеспечивается с помощью Tokio асинхронной среды исполнения Rust, отвечающая за создание и обработку событий, с помощью конструкций Rust Future, аналога Promise в JavaScript.

Среда изначально безопасна, по умолчанию не имеет никаких прав доступа. Для предоставление нужных привилегий, надо запустить платформу с определенными опциями командной строки. Решение неоднозначное. Рано или поздно, разработчикам может надоесть прописывать в скриптах бесконечные опции, потому что в крупных проектах их будет очень много и запускать будут --allow-all по дефолту.

При необходимости Deno позволяет вызывать программы на том языке, который удобен для выполнения каких-то отдельных вычислительных задач.

***


На данный момент уже вышла версия релиза под номером 1.7. Но все еще не слышно, чтобы какая-то крупная компания отказалась от Node.js в пользу Deno. Мы даже сделали в маркетплейсе для node.js



Проект Deno развивается на энтузиазме, сам же Райан сейчас работает в Google и занимается тем, что ставит задачи перед инженерами, занимающихся нейронными сетями и глубоким обучением.

Кто-то считает, что у Даль развился комплекс первого выстрелившего проекта, когда Node.js, совершенно неожиданно для Райана, почти сразу получил признание и очень резко набрал популярность. Но, скорее всего, он из тех людей, которые не могут сидеть без новых идей и терпеть несовершенства в своем собственном изобретении.

Хотя Даль начал работать над Deno задолго до выступления на конференции в 2018 году, первоначальная тема для нее была совсем другая. Он планировал выступить с докладом по машинному обучению, которым занимался последние несколько лет. Но не успел вовремя подготовиться и тема его выступления была снята буквально за пару дней до конференции. А поскольку он понимал свой вес в сообществе и то, как зрители будут ждать его выступления, то за день или два ему пришлось очень сильно постараться, чтобы найти новую достойную тему, оправдывающую ожидания публики.

Так что может он и не задумывал провозгласить убийцу Node.js, а просто поторопился с подготовкой нового доклада. Хотя ему таки удалось поднять очень изрядный шум, возможно куда больший, чем темой про глубокое обучение.

Подробнее..

Todo-лист для командной строки на Deno

23.06.2020 10:15:03 | Автор: admin

Вы уже наверняка слышали про Deno и, скорее всего, прочитали пару-тройку обзоров. В рамках своей статьи я предлагаю испытать Deno на практике, написав приложение для командной строки, попутно размышляя о преимуществах и возможных способах его применения.


Приложение для терминала позволит взглянуть на стандартную библиотеку, познакомиться с вводом и выводом без использования фреймворков.



Основные преимущества, такие как безопасность, TypeScript из коробки, встроенные инструменты, обширная стандартная библиотека, а также возможность импортировать и запускать скрипты по URL, выгодно отличают Deno от Node и Bash. С помощью Deno мы можем написать скрипт, залить на GitHub Gist и запускать при необходимости командой deno run https://path.to/script.ts, не заботясь о зависимостях и настройке окружения.


Для начала работы нужно только установить Deno одним из предложенных способов https://deno.land/#installation.


Todo для Todo


Todo-лист должен уметь:


  • добавлять, редактировать, удалять и отмечать задачи;
  • выводить список задач;
  • сохранять состояние;
  • работать в интерактивном режиме;
  • устанавливаться на компьютер.

Аргументы командной строки


Практически любая консольная утилита управляется с помощью передаваемых при запуске аргументов. В Deno они лежат массивом в Deno.args. А, к примеру, в Node мы бы использовали process.argv, в котором помимо аргументов находятся строки node и путь к скрипту.


// deno run args.ts first secondconsole.log(Deno.args) // [ "first", "second" ]

В простых случаях Deno.args будет достаточно, а в более сложных можно воспользоваться парсером из стандартной библиотеки: https://deno.land/std/flags или любым другим на выбор.


Итак, начнём


Реализуем первую команду вывод информации.


Создадим главный файл нашего приложения todo.ts. Лучше назвать файл осмысленно, так как в будущем он понадобится нам для запуска и установки.


Далее создадим файл /src/ui.ts, в котором будем хранить элементы пользовательского интерфейса. Добавим константу help, содержащую информационный текст.


export const help = `todo help - to show available commands`;

В todo.ts напишем свитч для обработки команд и выведем справку с помощью console.log().


#!/usr/bin/env deno runimport { help } from "./src/ui.ts";const [command, ...args] = Deno.args;switch (command) {  case "help":    console.log(help);}

Проверим работу команды:


deno run todo.ts help


Для того, чтобы при каждом запуске не писать deno run, добавим шебанг #!.


#!/usr/bin/env deno run

Разрешим запуск файла:


chmod +x todo.ts


Теперь для вывода справки можно использовать:


./todo.ts help


Стандартный вывод


В листинге выше мы использовали console.log(), но, мне кажется, идеологически log это инструмент разработчика, и реализовать с его помощью полноценный интерфейс не получится.


В Deno за стандартный вывод отвечает Deno.stdout, за ввод, соответственно, Deno.stdin.


/** A handle for `stdin`. */export const stdin: Reader & ReaderSync & Closer & { rid: number };/** A handle for `stdout`. */export const stdout: Writer & WriterSync & Closer & { rid: number };

И stdin, и stdout типизированы пересечением интерфейсов и объекта, содержащего rid, идентификатор ресурса.


Более подробно рассмотрим stdout и выведем Hello world! в консоль.


const textEncoder = new TextEncoder();const message: string = "Hello world!\n";const encodedMessage: Uint8Array = textEncoder.encode(message);await Deno.stdout.write(encodedMessage);

Запустим скрипт hello.ts:


deno run hello.ts


На экране появляется наш Hello world!.


export interface Writer {  write(p: Uint8Array): Promise<number>;}

Функция Deno.stdout.write принимает на вход массив байтов и возвращает количество записанных байтов. Deno.stdout.writeSync работает точно так же, только синхронно.


Строку необходимо предварительно закодировать с помощью объекта TextEncoder.
Можно не создавать TextEncoder, а импортировать функцию encode из стандартной библиотеки, которая делает то же самое, точно так же.


import { encode } from 'https://deno.land/std/encoding/utf8.ts';

Воспользуемся полученными знаниями и создадим файл terminal.ts с двумя функциями print и printLines. Последняя выводит строку и переводит каретку.


import { encode } from "https://deno.land/std/encoding/utf8.ts";export async function print(message: string) {  await Deno.stdout.write(encode(message));}export async function printLines(message: string) {  await print(message + "\n");}

Заменим console.log на printLines.


#!/usr/bin/env deno runimport { help } from "./src/ui.ts";import { printLines } from "./src/Terminal.ts";const [command, ...args] = Deno.args;switch (command) {  case "help":    await printLines(help);}

Чтение и запись файла


В Deno для чтения файла используется функция Deno.readFile и её синхронный аналог Deno.readFileSync.


function Deno.readFile(path: string | URL): Promise<Uint8Array>

const decoder = new TextDecoder("utf-8");const data = await Deno.readFile("hello.txt");console.log(decoder.decode(data));

Для записи в файл соответственно используются Deno.writeFile и Deno.writeFileSync.


function Deno.writeFile(path: string | URL, data: Uint8Array, options?: WriteFileOptions): Promise<void>

const encoder = new TextEncoder();const data = encoder.encode("Hello world\n");await Deno.writeFile("hello1.txt", data);  // overwrite "hello1.txt" or create it

В стандартной библиотеке Deno содержатся функции, позволяющие упростить взаимодействие с файловой системой: https://deno.land/std/fs. Например, код выше можно заменить вызовами функций readFileStr и writeFileStr.


Создадим интерфейс Task в src/Task.ts


export interface Task {   title: string;  isDone: boolean;}

и класс Todo в файле src/Todo.ts.


import {  writeJsonSync,  readJsonSync, } from "https://deno.land/std/fs/mod.ts"; import { Task } from "./Task.ts";export class Todo {  tasks: Task[] = [];  constructor(private file: string = "tasks.json") {    this.open();  }  open() {    try {      this.tasks = readJsonSync(this.file) as Task[];    } catch (e) {      console.log(e);      this.tasks = [];    }  }  save() {    writeJsonSync(this.file, this.tasks, { spaces: 2 });  }}

Как можно заметить, для чтения и записи данных я использовал специальные функции из стандартной библиотеки для работы с json-файлами.


Добавление задачи в список


Для реализации команды todo add "implement add command" добавляем в класс Todo метод add.


add(title: string) {    const task: Task = {        title,        isDone: false    };    this.tasks.push(task);    this.save();}

Так как мы добавили чтение и запись в файл, необходимо добавить права --allow-read --allow-write.


#!/usr/bin/env deno run --allow-read --allow-write import { help } from "./src/ui.ts";import { printLines } from "./src/terminal.ts";import { Todo } from "./src/Todo.ts";const [command, ...args] = Deno.args;const todo = new Todo();switch (command) {  case "help":    await printLines(help);    break;  case "add":    todo.add(args[0]);}

Вывод списка задач


В файл src/ui.ts добавляем функцию для форматирования списка задач.


import { Task } from "./Task.ts";import { green, red, yellow, bold } from "https://deno.land/std/fmt/colors.ts";export function formatTaskList(tasks: Task[]): string {  const title = `${bold("TODO LIST:")}`;  const list = tasks.map((task, index) => {    const number = yellow(index.toString());    const checkbox = task.isDone ? green("[*]") : red("[ ]");    return `${number} ${checkbox} ${task.title}`;  });  const lines = [    title,    ...list,  ];  return lines.join("\n");}

Для задания стиля и цвета текста используем функции форматирования из https://deno.land/std/fmt/colors.ts.


Добавляем метод list в класс Todo и команду ls в свитч.


async list() {  await printLines(formatTaskList(this.tasks));}

case "ls":  await todo.list();  break;

Таким же образом добавляем остальные методы и команды.


done(index: number): void {  const task = this.tasks[index];  if (task) {    task.isDone = true;    this.save();  }}undone(index: number): void {  const task = this.tasks[index];  if (task) {    task.isDone = false;    this.save();  }}edit(index: number, title: string) {  const task = this.tasks[index];  if (task) {    task.title = title;    this.save();  }}remove(index: number) {  this.tasks.splice(index);  this.save();}

case "edit": // todo edit 1 "edit second task"  await todo.edit(parseInt(args[0], 10), args[1]);  break;case "done" : // todo done 1  todo.done(parseInt(args[0], 10));  break;case "undone" : // todo undone 1  todo.undone(parseInt(args[0], 10));  break;case "remove" : // todo remove 1  todo.remove(parseInt(args[0], 10));  break;

Реализованные команды отвечают первым трём пунктам наших требований.


Работа в интерактивном режиме


Интерактивный режим подразумевает, что программа общается с пользователем, не закрываясь после выполнения команды.


Создадим в свитче кейс по умолчанию,


default:  await todo.interactive();

а в классе Todo асинхронный метод interactive.


async interactive() {  while (true) {    // show list    // read keypress    // do action  }}

Метод interactive содержит бесконечный цикл, в котором выводится пользовательский интерфейс, ожидается ответ пользователя и, в зависимости от нажатой клавиши, совершается действие.


Для управления курсором в интерактивном режиме необходимо импортировать функции из библиотеки cursor.
Добавляем права для доступа к энвайронменту, так как библиотека проверяет, в каком окружении программа запущена --allow-env.


import { clearDown, goUp, goLeft } from "https://denopkg.com/iamnathanj/cursor@v2.0.0/mod.ts";

В файле terminal.ts создадим функцию printInteractive.


export async function printInteractive(message: string) {  const lines = message.split("\n");  const numberOfLines = lines.length;  const lengthOfLastLine = lines[numberOfLines - 1].length;  await clearDown();  await print(message);  await goLeft(lengthOfLastLine);  if (numberOfLines > 1) {    await goUp(numberOfLines - 1);  }}

Она выводит текст на экран и возвращает курсор в начало. При следующем вызове всё стирается и печатается заново. Для того чтобы квадрат курсора не мешался, его можно скрыть функцией hideCursor.


Добавим в файл ui.ts константу toolbar, в которой будут перечислены доступные действия.


const toolbar = `${yellow("d")}one ${yellow("a")}dd ${yellow("e")}dit ${yellow("r")}emove`;

В функцию formatTaskList добавляем параметр showToolbar:


export function formatTaskList(tasks: Task[], showToolbar: boolean): string {// ...if (showToolbar) {  lines.push(toolbar);}

В зависимости от флага showToolbar мы будем показывать или скрывать подсказку.


Дорабатываем метод list:


async list(interactive: boolean = false) {  if (interactive) {    await printInteractive(formatTaskList(this.tasks, true));  } else {    await printLines(formatTaskList(this.tasks, false));  }}

Вывод реализован, перейдём к вводу.


Стандартный ввод


Интерфейс ввода, аналогично выводу работает с Uint8Array, поэтому для декодирования используется TextDecoder.


const textDecoder = new TextDecoder();const buffer: Uint8Array = new Uint8Array(1024);const n: number = <number>await Deno.stdin.read(buffer);const message: string = textDecoder.decode(buffer.subarray(0, n));

Создаём буфер размером, например, в 1024 байта. Передаём его в функцию Deno.stdin.read и ждём пока пользователь закончит ввод. Как только будет нажата клавиша enter, функция наполнит буфер и вернёт количество прочитанных байтов. Стоит отметить, что перевод строки \n будет в конце прочитанной последовательности. Обрезаем лишнее и декодируем полученную строку. Синхронная функция Deno.stdin.readSync работает аналогично.


Обработка клавиш


Для того чтобы получить информацию о нажатии клавиш, необходимо воспользоваться функцией Deno.setRaw. На текущий момент она доступна под флагом --unstable. setRaw и позволяет получить символы по одному, без обработки. В функцию необходимо передать идентификатор ресурса и флаг. Как только мы воспользовались этим режимом, нажатие CTRL+C перестаёт приводить к закрытию программы, и это поведение нужно реализовать самостоятельно.


Deno.setRaw(Deno.stdin.rid, true);const length = <number> await Deno.stdin.read(buffer);Deno.setRaw(Deno.stdin.rid, false);

В файл terminal.ts добавляем функцию readKeypress:


export async function readKeypress(): Promise<string> {  const buffer = new Uint8Array(1024);  Deno.setRaw(Deno.stdin.rid, true);  const length = <number> await Deno.stdin.read(buffer);  Deno.setRaw(Deno.stdin.rid, false);  return decode(buffer.subarray(0, length));}

В метод interactive добавляем возможность выйти из приложения.


async interactive() {  while (true) {    await this.list(true);    const key = await readKeypress();    if (key == "\u0003") { // ctrl-c      Deno.exit();    }  }}

Не отходя далеко от ввода текста, реализуем добавление задачи в интерактивном режиме.


В файл terminal.ts добавляем 2 функции:


export async function readLine(): Promise<string> {  const buffer = new Uint8Array(1024);  const length = <number> await Deno.stdin.read(buffer);  return decode(buffer.subarray(0, length - 1));}export async function prompt(question: string): Promise<string> {  await clearDown();  await print(question);  const answer = await readLine();  await goLeft(question.length + answer.length);  await goUp(1);  return answer;}

Функция readLine позволяет прочитать одну строку, исключая последний символ, перевод строки.


Функция prompt очищает экран, печатает вопрос, читает ввод пользователя, а затем перемещает курсор в начало, аналогично функции printInteractive.


В метод interactive добавляем обработку клавиши а:


if (key === "a") {  const title = await prompt("Add task: ");  if (title) {    await this.add(title);  }}

Перемещение по списку задач


Добавим поддержку стрелок клавиатуры. Для того чтобы понимать, какой элемент сейчас выбран, добавляем свойство currentIndex в класс Todo:


  private currentIndex: number = 0;

А в функции formatTaskList активную строку выделяем жирным и красим в жёлтый:


export function formatTaskList(  tasks: Task[],  showToolbar: boolean = false,  activeIndex?: number,): string {  const title = `${bold("TODO LIST:")}`;  const list = tasks.map((task, index) => {    // ...    const isActive = activeIndex === index;    const title = isActive ? bold(yellow(task.title)) : task.title;    return `${number} ${checkbox} ${title}`;  });  // ...}

В Todo создаём методы, которые будут изменять currentIndex:


up() {  this.currentIndex = this.currentIndex === 0    ? this.tasks.length - 1    : this.currentIndex - 1;}down() {  this.currentIndex = this.currentIndex === this.tasks.length - 1    ? 0    : this.currentIndex + 1;}

В метод interactive добавляем обработку стрелок:


if (key === "\u001B\u005B\u0041" || key === "\u001B\u005B\u0044") { // вверх или влево  this.up();} else if (key === "\u001B\u005B\u0042" || key === "\u001B\u005B\u0043") { // вниз или вправо  this.down();}

Аналогичным образом добавляем возможность отмечать выполненные задачи:


toggle(index: number = this.currentIndex) {  const task = this.tasks[index];  if (task) {    task.isDone = !task.isDone;    this.save();  }}

if (key === "d" || key === " ") {    this.toggle();} else if ('0' <= key && key <= '9') {    this.toggle(parseInt(key, 10));} 

Дорабатываем удаление:


remove(index: number = this.currentIndex) {  if (index === this.tasks.length - 1) {    this.up();  }  this.tasks.splice(index, 1);  this.save();}

if (key === "r") {    this.remove();} 

И, наконец, редактирование:


if (key === "e") {  if (!this.tasks[this.currentIndex]) {    return;  }  const title = await prompt("Edit task (" + this.tasks[this.currentIndex].title + "): ");  if (title) {    this.edit(this.currentIndex, title);  }}

Проверяем работу приложения:


deno run --allow-read --allow-write --allow-env --unstable ./todo.ts

или


./todo.ts

Установка


Для того что бы наша программа была доступна в терминале, воспользуемся замечательной командой deno install.


deno install --allow-read --allow-write --allow-env --unstable ./todo.ts

Установка, так же как и запуск, может производиться по URL.


deno install --allow-read --allow-write --allow-env --unstable  https://raw.githubusercontent.com/dmitriytat/todo/master/todo.ts

Имя файла будет использовано как название программы по умолчанию, но с помощью опции -n/--name можно задать другое.


После чего todo можно использовать в командной строке.


Импорт зависимостей


Последняя фича, которую мы использовали, но ещё не обсудили импорт зависимостей. В отличие от Node, в Deno подход к импортированию несколько иной: предполагается, что зависимости импортируются по URL. В этом подходе, на мой взгляд, есть как достоинства, так и недостатки.


При разработке однофайловых скриптов, возможность импортировать по URL является огромным плюсом, но как только мы разработаем что-то большее, уследить за версиями станет труднее.


Для управления сложностью существует два основных решения: файл dep.ts и файл с картой импортов.


В первом случае мы делаем реэкспорт библиотек, собирая все импорты в одном месте.


export { green, red, yellow, bold } from "https://deno.land/std@0.55.0/fmt/colors.ts";export { decode, encode } from "https://deno.land/std@v0.55.0/encoding/utf8.ts";export { clearDown, goUp, goLeft } from "https://denopkg.com/iamnathanj/cursor@v2.0.0/mod.ts"; 

Во втором случае в JSON файле мы пишем alias:


import_map.json


{   "imports": {      "fmt/": "https://deno.land/std@0.55.0/fmt/"   }}

color.ts


import { red } from "fmt/colors.ts";console.log(red("hello world"));

При запуске программы указываем путь до карты и флаг --unstable, так как фича не готова для продакшена.


deno run --importmap=import_map.json --unstable color.ts.

Резюме


В целом, работой Deno я доволен. Новый инструмент позволяет не тратить время на настройку окружения и сразу приступать к написанию кода, что оказалось очень удобно.
Таким образом, я могу рекомендовать Deno для написания небольших скриптов и программ, упрощающих жизнь разработчика, но пока воздержался бы от использования Deno для создания продакшен-приложений.

Подробнее..

Перевод Руководство по Deno примеры работы со средой выполнения TypeScript

26.07.2020 12:18:22 | Автор: admin


Доброго времени суток, друзья!

Представляю Вашему вниманию перевод статьи The Deno Handbook: A TypeScript Runtime Tutorial with Code Examples автора Flavio Copes.

В этой статье мы научимся работать с Deno. Мы сравним его с Node.js и создадим с его помощью простой REST API.

Что такое Deno?


Если вы знакомы с Node.js, популярной экосистемой серверного JavaScript, Deno это почти тоже самое. Почти, но не совсем.

Давайте начнем со списка возможностей Deno, которые мне больше всего нравятся:

  • Он основан на современном JavaScript
  • Он имеет расширяемую стандартную библиотеку
  • Он имеет первоклассную (в значении стандартной) поддержку TypeScript (это означает, что вам не нужно вручную компилировать TypeScript, Deno делает это автоматически)
  • Он поддерживает ES модули
  • Он не имеет пакетного менеджера
  • Он имеет первоклассный (в значении глобальный) await
  • Он имеет встроенное средство тестирования
  • Его цель максимальная совместимость с браузером. Для этого он предоставляет встроенный fetch и глобальный объект window

В данном руководстве мы изучим все эти возможности.

После знакомства с Deno и его возможностями, Node.js покажется вам немного устаревшим.

Особенно по причине того, что Node.js основан на функциях обратного вызова (он был написан до появления промисов и async/await). Едва ли они когда-нибудь там появятся, поскольку это означает необходимость внесения фундаментальных изменений.

Node.js прекрасен и останется фактическим стандартом в мире JavaScript. Однако, я полагаю, что популярность Deno будет быстро расти благодаря его поддержке TypeScript и современной стандартной библиотеке.

Deno может позволить себе современный код, поскольку не нуждается в обеспечении обратной совместимости. Разумеется, гарантии, что этот код будет оставаться современным в ближайшее десятилетие нет, но на сегодня это именно так.

Почему Deno? Почему сейчас?


Deno был анонсирован почти 2 года назад создателем Node.js Ryan Dahl на JSConf EU. Смотрите видео на YouTube, оно очень интересное, и является обязательным к просмотру, если вы работаете с Node.js и JavaScript.

Каждый менеджер (создатель) проекта вынужден принимать решения. Райан жалеет о некоторых ранних решениях в Node. Кроме того, технологии развиваются, и сегодня JavaScript это совершенно другой язык, чем он был в 2009, когда появился Node. Вспомните о возможностях ES6/2016/2017 и т.д.

Поэтому он решил начать новый проект, своего рода вторую волну приложений на серверном JavaScript.

Причина, по которой я пишу эту статью только сейчас, заключается в том, что требуется довольно много времени для созревания технологии. Наконец, мы получили Deno 1.0 (он был представлен 13 мая 2020 года), первый стабильный релиз.

Это может показаться обычным числом, однако 1.0 означает, что радикальных изменений не будет вплоть до Deno 2.0. При изучении новой технологии вы не хотите, чтобы она слишком быстро менялась.

Следует ли изучать Deno?


Хороший вопрос.

Изучение чего-то нового, такого как Deno, требует больших усилий. Мой совет: если вы только начинаете изучать серверный JS и пока не знаете Node.js, и никогда раньше не писали код на TypeScript, начните с Node.

За выбор Node еще никого не увольняли (перефразирование известной цитаты).

Но если вам нравится TypeScript, не зависящий от тонны npm-пакетов, и вы хотите везде использовать await, Deno может быть тем, что вы ищите.

Заменит ли он Node.js?


Нет. Node.js это гигант, большой авторитет, невероятно хорошо поддерживаемая технология, которая в ближайшее десятилетие никуда не денется.

Первоклассная поддержка TypeScript


Deno написан на Rust и TypeScript, очень популярных на сегодня языках.

Это означает, что мы получаем много выгод от TypeScript, даже если пишем код на JavaScript.

Запуск TypeScript-кода с помощью Deno не требует предварительной компиляции Deno делает это автоматически.

Вы не обязаны писать код на TypeScript, однако тот факт, что ядро Deno написано на TypeScript, имеет огромное значение.

Во-первых, большой процент JavaScript-разработчиков любит TypeScript.

Во-вторых, используемые вами инструменты могут получать много информации о ПО, написанном на TypeScript, таком как Deno.

Это означает, что когда мы пишем код в VS Code, например (который имеет тесную интеграцию с TypeScript с момента появления), мы получаем такие преимущества, как проверка типов при написании кода или продвинутые возможности IntelliSense. Другими словами, помощь редактора кода становится гораздо эффективнее.

Отличия от Node.js


Поскольку Deno это, по сути, замена Node.js, имеет смысл их сравнить.

Общее:

  • Оба основаны на движке V8
  • Оба отлично подходят для разработки серверного JavaScript

Отличия:

  • Node написан на C++ и JavaScript. Deno написан на Rust и TypeScript.
  • Node имеет официальный пакетный менеджер npm. У Deno такого менеджера нет, вместо этого он позволяет импортировать любой модуль с помощью URL.
  • Node использует синтаксис CommonJS для импорта пакетов. Deno использует официальный способ ES модули.
  • Deno использует современные возможности ECMAScript во всех прикладных интерфейсах и стандартной библиотеке, в то время как Node.js использует основанную на колбеках стандартную библиотеку и не планирует ее обновлять.
  • Deno предлагает уровень (слой) безопасности песочницы через предоставление разрешений. Программа получает разрешение на выполнение определенных действий через пользовательские флаги. Node.js имеет доступ ко всему, к чему имеет доступ пользователь.
  • Deno долгое время искал возможность компиляции программ в выполняемые, т.е. такие, которые можно запускать без внешних зависимостей, как в Go, однако достичь этого пока не удалось. Это изменит правила игры.

Отсутствие пакетного менеджера


Отсутствие пакетного менеджера и использование URL для получения и импорта пакетов имеет свои преимущества и недостатки. Одним из основных преимуществ является большая гибкость, проявляющаяся в возможности создания пакетов без необходимости их опубликования в таком репозитории, как npm.

Думаю, что некая альтернатива пакетному менеджеру в Deno рано или поздно появится.

Официальный сайт Deno имеет хостинг для сторонних пакетов: https://deno.land/x/

Установка Deno


Хватит болтать! Давайте установим Deno.

Простейшим способом это сделать является использование Homebrew:

    brew install deno 



Другие способы установки указаны здесь.

После установки становится доступна команда deno. Вот помощь, которую можно получить, набрав deno --help:

flavio@mbp~> deno --helpdeno 0.42.0A secure JavaScript and TypeScript runtimeDocs: https://deno.land/std/manual.mdModules: https://deno.land/std/ https://deno.land/x/Bugs: https://github.com/denoland/deno/issuesTo start the REPL, supply no arguments:  denoTo execute a script:  deno run https://deno.land/std/examples/welcome.ts  deno https://deno.land/std/examples/welcome.tsTo evaluate code in the shell:  deno eval "console.log(30933 + 404)"Run 'deno help run' for 'run'-specific flags.USAGE:    deno [OPTIONS] [SUBCOMMAND]OPTIONS:    -h, --help            Prints help information    -L, --log-level <log-level>            Set log level [possible values: debug, info]    -q, --quiet            Suppress diagnostic output            By default, subcommands print human-readable diagnostic messages to stderr.            If the flag is set, restrict these messages to errors.    -V, --version            Prints version informationSUBCOMMANDS:    bundle         Bundle module and dependencies into single file    cache          Cache the dependencies    completions    Generate shell completions    doc            Show documentation for a module    eval           Eval script    fmt            Format source files    help           Prints this message or the help of the given subcommand(s)    info           Show info about cache or info related to source file    install        Install script as an executable    repl           Read Eval Print Loop    run            Run a program given a filename or url to the module    test           Run tests    types          Print runtime TypeScript declarations    upgrade        Upgrade deno executable to newest versionENVIRONMENT VARIABLES:    DENO_DIR             Set deno's base directory (defaults to $HOME/.deno)    DENO_INSTALL_ROOT    Set deno install's output directory                         (defaults to $HOME/.deno/bin)    NO_COLOR             Set to disable color    HTTP_PROXY           Proxy address for HTTP requests                         (module downloads, fetch)    HTTPS_PROXY          Same but for HTTPS

Команды Deno


Заметили раздел SUBCOMMANDS? Это список всех команд, которые мы можем запускать. Какие команды у нас есть?

  • bundle собирает модуль и зависимости проекта в один файл
  • cache кэширует зависимости
  • completions генерирует пополнения оболочки
  • doc показывает документацию по модулю
  • eval используется для вычисления блока кода, например, deno eval "console.log(1 + 2)"
  • fmt встроенное средство форматирования кода (такое как goFmt в Go)
  • help выводит список вспомогательных команд
  • info показывает информацию о кэше или файле
  • install устанавливает скрипт как выполняемый
  • repl цикл чтение-вычисление-вывод (по умолчанию)
  • run запускает программу с заданным именем или URL для модуля
  • test запускает тесты
  • types выводит список возможностей TypeScript
  • upgrade обновляет Deno до последней версии

Вы можете запустить deno <subcommand> help для получения информации об определенной команде, например, deno run --help.

Мы можем использовать команду deno для запуска цикла чтение-вычисление-вывод:



Это аналогично запуску deno repl.

Обычно, deno используется для запуска Deno-приложения, содержащегося в TypeScript-файле.

Вы можете запускать как TypeScript-файлы (.ts), так и JavaScript-файлы (.js).

Если вы не знакомы с TypeScript, не переживайте: Deno написан на TypeScript, но вы вполне можете писать свои клиентские приложения на JavaScript.

Первое приложение на Deno


Давайте создадим наше первое приложение.

Для этого нам даже не придется писать код, мы запустим его в терминале посредством URL.

Deno скачивает программу, компилирует ее и запускает:



Разумеется, я бы не советовал запускать случайный код из Интернета. В данном случае, мы запускаем его с официального сайта Deno, плюс Deno имеет песочницу, запрещающую программам делать то, что мы им прямо не разрешили.

Эта программа является очень простой и представляет из себя вызов console.log():

console.log('Welcome to Deno 
Подробнее..

Дайджест материалов сообщества Deno (01.12 31.12)

03.01.2021 10:14:54 | Автор: admin

В последнее время на Хабре не так много материалов про Deno, хотя runtime активно развивается и продолжает еженедельно релизиться. В связи с этим предлагаю вам ознакомиться с материалами сообщества за последний месяц.

Подробнее..

Практическое знакомство с Deno разрабатываем REST API MongoDB Linux

26.01.2021 00:16:12 | Автор: admin

Всем привет. В этот раз я решил сделать нечто более интересное, чем очередной бот, поэтому далее я покажу как реализовать REST API с Deno, подключить и использовать MongoDB в качестве базы данных, и всё это запустить из под Linux.

Видео версия данной заметки доступна ниже:

Описание задачи

В качестве примера я выбрал Github Gists API и следующие методы:

  • [POST] Create a gist;

  • [GET] List public gists;

  • [GET] Get a gist;

  • [PATCH] Update a gist;

  • [DELETE] Delete a gist.

Создание проекта

Для начала мы добавляем файл api/mod.ts :

console.log('hello world');

И проверяем, что всё работает командой deno run mod.ts:

mod.tsmod.ts

Добавление зависимостей

Создаём файл api/deps.ts и добавляем следующие зависимости:

  • Пакет oak для работы с API;

  • Пакет mongo для работы с MongoDB;

/* REST API */export { Application, Router } from "&lt;https://deno.land/x/oak/mod.ts>";export type { RouterContext } from "&lt;https://deno.land/x/oak/mod.ts>";export { getQuery } from "&lt;https://deno.land/x/oak/helpers.ts>";/* MongoDB driver */export { MongoClient, Bson } from "&lt;https://deno.land/x/mongo@v0.21.0/mod.ts>";

Отступление: В отличие от NodeJS, авторы Deno отказались от поддержки npm и node_modules, а необходимые библиотеки подключаются по url и кешируются локально. Сами библиотеки можно найти в разделе Third Party Modules на сайте http://deno.land.

Добавление API Boilerplate

Далее, добавляем код для запуска API в файл mod.ts:

import { Application, Router } from "./deps.ts";const router = new Router();router  .get("/", (context) => {    context.response.body = "Hello world!";  });const app = new Application();app.use(router.routes());app.use(router.allowedMethods());await app.listen({ port: 8000 });

Причём функции Application и Router импортируем уже из локального файла deps.ts.

Проверим, что всё было сделано верно:

  • Запускаем приложение командой deno run --allow-net mod.ts;

  • Открываем в браузере http://localhost:8000;

  • Получаем страницу с сообщением 'Hello world!';

Отступление: Deno позиционируется как secure by default. Другими словами, у запускаемого приложения (скрипта) не будет доступа к сети (--allow-net, файловой системе (--allow-readи --allow-write, параметрам окружения (--allow-env) пока этот доступ явно не разрешён.

Добавление метода POST /gists

Пришло время добавить первый метод, который будет сохранять запись в базу данных.

Прежде всего опишем контракт:

  • [POST] /gists

  • Параметры:

    • content: string | body;

  • Ответы:

    • 201 Created;

    • 400 Bad Request;

Обработчик

Добавляем папку handlers и файл create.ts, в котором будет расположен handler (обработчик) запроса:

import { RouterContext } from "../deps.ts";import { createGist } from "../service.ts";export async function create(context: RouterContext) {  if (!context.request.hasBody) {    context.throw(400, "Bad Request: body is missing");  }  const body = context.request.body();  const { content } = await body.value;  if (!content) {    context.throw(400, "Bad Request: content is missing");  }  const gist = await createGist(content);  context.response.body = gist;  context.response.status = 201;}

В этой функции мы:

  • Валидируем входные значения (request.hasBody и !content);

  • Вызываем функцию createGist нашего сервиса (добавим далее);

  • Возвращаем добавленный объект в ответе и 201 Created.

Сервис

Далее, нам необходимо передать управление из обработчика в сервис (добавляем service.ts):

import { insertGist } from "./db.ts";export async function createGist(content: string): Promise&lt;IGist> {  const values = {    content,    created_at: new Date(),  };  const _id = await insertGist(values);  return {    _id,    ...values,  };}interface IGist {  _id: string;  content: string;  created_at: Date;}

В данном случае функция принимает единственный аргумент content: string и возвращает объект, структура которого описывается интерфейсом IGist.

Репозиторий

Последним этапом обработки запроса является сохранение записи в MongoDB. Для этого мы добавляем файл db.ts и соответствующую функцию:

import { Collection } from "&lt;https://deno.land/x/mongo@v0.21.0/src/collection/collection.ts>";import { Bson, MongoClient } from "./deps.ts";async function connect(): Promise&lt;Collection&lt;IGistSchema>> {  const client = new MongoClient();  await client.connect("mongodb://localhost:27017");  return client.database("gist_api").collection&lt;IGistSchema>("gists");}export async function insertGist(gist: any): Promise&lt;string> {  const collection = await connect();  return (await collection.insertOne(gist)).toString();}interface IGistSchema {  _id: { $oid: string };  content: string;  created_at: Date;}

В этом файле мы:

  • Импортируем необходимые типы и функции для работы с MongoDB;

  • Подключаемся к базе данных gist_api в функции connect;

  • Описываем формат объектов, которые хранятся в коллекции gist_api интерфейсом IGistSchema;

  • Сохраняем объект методом insertOne и возвращаем его идентификатор (inserted id);

Запускаем экземпляр MongoDB

Далее мы запускаем терминал, запускаем и проверяем статус нашей базы данных следующими командами:

sudo systemctl start mongodsudo systemctl status mongod

Если всё было сделано верно, то получим следующий результат:

Отступление: Как установить MongoDB на Ubuntu

Запускаем приложение

  • Компилируем и запускаем приложение командой deno run --allow-net mod.ts;

  • Открываем Postman и вызываем метод нашего API:

Если всё сделано верно, то в качестве ответа получаем 201 Created и сохранённый объект с проставленным _id:

Отступление: Как вы могли заметить, в процессе разработки мы используем TypeScript без транспайлеров. Причина проста - Deno поддерживает TypeScript из коробки.

Добавление метода GET /gists

Следующим методом мы сможем получить записи из базы данных, а заодно реализовать базовую пагинацию.

Прежде всего опишем контракт:

  • [GET] /gists

  • Параметры:

    • skip: string | query;

    • limit: string | query;

  • Ответы:

    • 200 OK;

Обработчик

Добавляем файл handlers/list.ts, в котором будет расположен handler (обработчик) запроса:

import { getQuery, RouterContext } from "../deps.ts";import { getGists } from "../service.ts";export async function list(context: RouterContext) {  const { skip, limit } = getQuery(context);  const gists = await getGists(+skip || 0, +limit || 0);  context.response.body = gists;  context.response.status = 200;}

В этой функции мы:

  • Получаем параметры с query string с помощь функции getQuery;

  • Вызываем функцию getGists нашего сервиса (добавим далее);

  • Возвращаем массив найденных объектов в ответе и 200 OK;

Отступление: Функция сервиса будет принимать аргументы типа number, в то время как в обработчик к нам приходят параметры типа string. Для этого мы делаем приведение типов следующей конструкцией +skip || 0 (корректные значения конвертируются, некорректные приводятся к NaN и игнорируются в пользу 0).

Сервис

Далее, передаём управление из обработчика в сервис:

export function getGists(skip: number, limit: number): Promise&lt;IGist[]> {  return fetchGists(skip, limit);}

В данном случае функция принимает аргументы skip: number и limit: number, и возвращает массив объектов, структура которых описывается интерфейсом IGist.

Репозиторий

Последним этапом обработки запроса является получение записей из MongoDB. Для этого мы добавляем функцию fetchGists в файл db.ts:

export async function fetchGists(skip: number, limit: number): Promise&lt;any> {  const collection = await connect();  return await collection.find().skip(skip).limit(limit).toArray();}

В этой функции мы:

  • Подключаемся к базе данных gist_api в функции connect;

  • Получаем все записи коллекции, пропускаем skip из них и возвращаем в кол-ве limit;

Запускаем приложение

  • Компилируем и запускаем приложение командой deno run --allow-net mod.ts;

  • Открываем Postman и вызываем метод нашего API:

Если всё сделано верно, то в качестве ответа получаем 200 OK и массив ранее добавленных объектов:

Добавление метода GET /gists/:id

Следующим методом мы получаем запись из базы данных по её идентификатору.

Прежде всего опишем контракт:

  • [GET] /gists/:id

  • Параметры:

    • id: string | path

  • Ответы:

    • 200 OK;

    • 400 Bad Request;

    • 404 Not Found.

Обработчик

Добавляем файл handlers/get.ts, в котором будет расположен handler (обработчик) запроса:

import { RouterContext } from "../deps.ts"import { getGist } from "../service.ts";export async function get(context: RouterContext) {    const { id } = context.params;    if(!id) {        context.throw(400, "Bad Request: id is missing");    }    const gist = await getGist(id);    if(!gist) {        context.throw(404, "Not Found: the gist is missing");    }    context.response.body = gist;    context.response.status = 200;}

В этой функции мы:

  • Проверяем наличие id и возвращаем 400 если он отсутствует;

  • Запрашиваем объект в базе данных функцией getGist и возвращаем 404 если он не найден (добавим далее);

  • Возвращаем найденный объект и 200 OK;

Сервис

Далее, передаём управление из обработчика в сервис:

export function getGist(id: string): Promise&lt;IGist> {    return fetchGist(id);}interface IGist {  _id: string;  content: string;  created_at: Date;}

В данном случае функция принимает аргумент id: string и возвращает объект, структура которого описывается интерфейсом IGist.

Репозиторий

Последним этапом обработки запроса является получение записи из MongoDB. Для этого мы добавляем функцию fetchGist в файл db.ts:

export async function fetchGist(id: string): Promise&lt;any> {  const collection = await connect();  return await collection.findOne({ _id: new Bson.ObjectId(id) });}

В этой функции мы:

  • Подключаемся к базе данных gist_api в функции connect;

  • Используем метод findOne для поиска записи удовлетворяющей фильтру по _id;

Запускаем приложение

  • Компилируем и запускаем приложение командой deno run --allow-net mod.ts;

  • Открываем Postman и вызываем метод нашего API:

Если всё сделано верно, то в качестве ответа получаем 200 OK и ранее добавленный объект:

Добавление метода PATCH /gists/:id

Следующим методом мы обновляем запись в базе данных по её идентификатору.

Как и прежде, начинаем с контракта:

  • [PATCH] /gists/:id

  • Параметры:

    • id: string | path

    • content: string | body

  • Ответы:

    • 200 OK;

    • 400 Bad Request;

    • 404 Not Found.

Обработчик

Добавляем файл handlers/update.ts, в котором будет расположен handler (обработчик) запроса:

import { RouterContext } from "../deps.ts";import { getGist, patchGist } from "../service.ts";export async function update(context: RouterContext) {  const { id } = context.params;  if (!id) {    context.throw(400, "Bad Request: id is missing");  }  const body = context.request.body();  const { content } = await body.value;  if (!content) {    context.throw(400, "Bad Request: content is missing");  }  const gist = await getGist(id);  if (!gist) {    context.throw(404, "Not Found: the gist is missing");  }  await patchGist(id, content);  context.response.status = 200;}

В этой функции мы:

  • По аналогии проверяем наличие id и возвращаем 400 если он отсутствует;

  • Валидируем входное значение !content;

  • Запрашиваем объект в базе данных функцией getGist и возвращаем 404 если он не найден;

  • Обновляем объект в базе данных функцией patchGist (добавим далее);

  • Возвращаем 200 OK.

Сервис

Далее, передаём управление из обработчика в сервис:

export async function patchGist(id: string, content: string): Promise&lt;any> {  return updateGist({ id, content });}interface IGist {  _id: string;  content: string;  created_at: Date;}

В данном случае функция принимает аргументы id: string и content: string, и возвращает any.

Репозиторий

Последним этапом обработки запроса является обновлении записи в MongoDB. Для этого мы добавляем функцию updateGist в файл db.ts:

export async function updateGist(gist: any): Promise&lt;any> {  const collection = await connect();  const filter = { _id: new Bson.ObjectId(gist.id) };  const update = { $set: { content: gist.content } };  return await collection.updateOne(filter, update);}

В этой функции мы:

  • Подключаемся к базе данных gist_api в функции connect;

  • Описываем фильтр filter объектов, которые мы хотим обновить;

  • Описываем инструкцию update, которую применяем для обновления найденных объектов;

  • Используем метод updateOne собрав всё воедино;

Запускаем приложение

  • Компилируем и запускаем приложение командой deno run --allow-net mod.ts;

  • Открываем Postman и вызываем метод нашего API:

Если всё сделано верно, то в качестве ответа получаем 200 OK:

Добавление метода DELETE /gists/:id

Последним по списку, но не по важности, мы добавляем метод удаления записей из базы данных по идентификатору.

По традиции, начинаем с контракта:

  • [DELETE] /gists/:id

  • Параметры:

    • id: string | path

  • Ответы:

    • 204 No Content;

    • 404 Not Found.

Обработчик

Добавляем файл handlers/remove.ts, в котором будет расположен handler (обработчик) запроса:

import { RouterContext } from "../deps.ts";import { getGist, removeGist } from "../service.ts";export async function remove(context: RouterContext) {  const { id } = context.params;  if (!id) {    context.throw(400, "Bad Request: id is missing");  }  const gist = await getGist(id);  if (!gist) {    context.throw(404, "Not Found: the gist is missing");  }  await removeGist(id);  context.response.status = 204;}

В этой функции мы:

  • По аналогии проверяем наличие id и возвращаем 400 если он отсутствует;

  • Запрашиваем объект в базе данных функцией getGist и возвращаем 404 если он не найден;

  • Удаляем объект из базы данных функцией removeGist (добавим далее);

  • Возвращаем 204 No Content.

Сервис

Далее, передаём управление из обработчика в сервис:

export function removeGist(id: string): Promise&lt;number> {  return deleteGist(id);}

В данном случае функция принимает единственный аргумент id: string и возвращает number.

Репозиторий

Последним этапом обработки запроса является удаление записи из коллекции MongoDB. Для этого мы добавляем функцию deleteGist в файл db.ts:

export async function deleteGist(id: string): Promise&lt;any> {  const collection = await connect();  return await collection.deleteOne({ _id: new Bson.ObjectId(id) });}

В этой функции мы:

  • Подключаемся к базе данных gist_api в функции connect;

  • Используем метод deleteOne для удаления объекта удовлетворяющего фильтру по _id;

Запускаем приложение

  • Компилируем и запускаем приложение командой deno run --allow-net mod.ts;

  • Открываем Postman и вызываем метод нашего API:

Если всё сделано верно, то в качестве ответа получаем 204 No Content:

Отступление: В данном случае фактическое удаление объекта из коллекции выбрано для наглядности. В реальных приложениях я предпочитаю добавить и обновлять у объекта поле isDeleted: boolean.

FAQ

Вызывая методы API я всегда получаю только 404 Not Found

Убедитесь что вы не забыли сконфигурировать router в файле mod.ts соответствующими обработчиками:

import { Application, Router } from "./deps.ts";import { list } from "./handlers/list.ts";import { create } from "./handlers/create.ts";import { remove } from "./handlers/remove.ts";import { get } from "./handlers/get.ts";import { update } from "./handlers/update.ts";const app = new Application();const router = new Router();router  .post("/gists", create)  .get("/gists", list)  .get("/gists/:id", get)  .delete("/gists/:id", remove)  .patch("/gists/:id", update);app.use(router.routes());app.use(router.allowedMethods());await app.listen({ port: 8000 });

Вызывая методы API я получаю 500 Internal Server Error

Отловить ошибку можно следующим способом:

const app = new Application();app.use(async (ctx, next) => {  try {    await next();  } catch (err) {    console.log(err);  }});...

Ссылки

Заключение

Спасибо за то что дочитали до конца.

В заключении упомяну, что к сожалению, не каждое из моих видео удаётся опубликовать в текстовом виде, поэтому если данные тема и формат вам интересны, то я приглашаю вас подписаться на телеграм-канал.

Подробнее..
Категории: Javascript , Typescript , Node.js , Linux , Deno , Tutorial , Rest api , Mongodb , Ubunty

Дайджест материалов сообщества Deno (01.01 31.01)

02.02.2021 12:18:59 | Автор: admin

На Хабре всё так же не так много материалов проDeno, хотя runtime активно развивается и продолжает еженедельно релизиться. В связи с этим предлагаю вам ознакомиться с материалами сообщества за прошедший месяц.

Всегда рад вашим пожеланиям, исправлениям, и материалам для следующего дайджеста.

Подробнее..

Контролируем JavaScript импорты с помощью Import maps

15.03.2021 22:22:10 | Автор: admin

Привет. С выходом Chrome 89 (а так же в Deno 1.8) появилась возможность использовать Карты импортов (Import maps) механизма, позволяющего получить контроль над поведением JavaScript-импортов.

Несмотря на то, что современная разработка строится с использованием нативных модулей, мы не можем запускать приложения без предварительной сборки. Одна из целей карт импортов как раз решает эту проблему.

Если кратко, то теперь можно будет совершенно законно и без всяких сборщиков писать, скажем, так:

import React from 'react';

Под катом разберём как это всё работает.

Для того, чтобы директива import или выражение import() могли разрешать пути к модулям в новом виде, нужно эти пути где-то описать. Да, оказалось никакой магии с подкапотным разрешением зависимостей как в той же Node.js или webpack тут нет.

Карты импортов задаются с помощью тега script с атрибутом type="importmap" в формате JSON.

А теперь на примере. Запускаем статический сервер (например, с помощью python -m SimpleHTTPServer 9000) и создаём два файла:

index.html

<!DOCTYPE html><html>  <body>    <script type="importmap">      {        "imports": {          "mylib": "./my-lib.mjs"        }      }    </script>    <script type="module">      import { sayHi } from "mylib";      sayHi();    </script>  </body></html>

и my-lib.mjs

export function sayHi() {  console.log("hi!");}

Открываем в браузере страничку, и вуаля: в консоль вывелось "hi!". Далее более подробно разберём, как оно устроено.

Структура

На данный момент, согласно спецификации, описывающий зависимости JSON может содержать два ключа: imports и scopes. Если появится какой-то неизвестный ключ, то должно выводиться предупреждение в консоль (хотя у меня Хром этого не делает).

Imports

Значение ключа imports объект, содержащий в качестве ключей имена модулей (к которым можно обращаться для последующего импорта) и адрес модуля. Адрес должен начинаться с /, ../, ./ или быть абсолютным URL.

"imports": {  "module-name": "address"}

Также есть возможность описывать "пакеты", содержащие несколько модулей. Для этого нужно к названию ключа добавить / в конец.

Создадим директорию "my-pack" добавив в неё index.mjs с содержимым:

export default function mainFunc() {  console.log("text from mainFunc");}

А также в "my-pack" добавим директорию "some-module" с файлом some-helper.mjs с содержимым:

export function someHelper() {  console.log("text from someHelper");}

Перепишем importmap нашего index.html:

  <script type="importmap">    {      "imports": {        "mypack": "./my-pack/index.mjs",        "mypack/": "./my-pack/"      }    }  </script>

Теперь, кроме обычного импорта основного пакета

import mainFunc from "mypack";

мы также можем получить доступ к его внутренним модулям

import { someHelper } from "mypack/some-module/some-helper.mjs";

Scopes

Бывают случаи, когда используя один и тот же импорт (точнее, спецификатор импорта), нам нужно получать разные версии библиотеки в зависимости от того, откуда её импортируют. На этот случай и нужны скоупы. Пример:

<script type="importmap">  {    "imports": {      "mypack": "./my-pack/index.mjs",      "mypack/": "./my-pack/"    },    "scopes": {      "some/other/url/": {        "mypack": "./my-pack/index-v2.jsm"      }    }  }</script>

В данном случае внутри любого модуля, url которого будет начинаться с some/other/url/ импорт "mypack" будет ссылаться на "./my-pack/index-v2.jsm", во всех остальных случаях будет использоваться "./my-pack/index.mjs".

Также есть возможность вложенных скоупов. Например:

<script type="importmap">{  "imports": {    "a": "/a-1.mjs",    "b": "/b-1.mjs",    "c": "/c-1.mjs"  },  "scopes": {    "/scope2/": {      "a": "/a-2.mjs"    },    "/scope2/scope3/": {      "b": "/b-3.mjs"    }  }}</script>

Это даст нам такое разрешение путей:

Specifier

Referrer

Resulting URL

a

/scope1/foo.mjs

/a-1.mjs

b

/scope1/foo.mjs

/b-1.mjs

c

/scope1/foo.mjs

/c-1.mjs

a

/scope2/foo.mjs

/a-2.mjs

b

/scope2/foo.mjs

/b-1.mjs

c

/scope2/foo.mjs

/c-1.mjs

a

/scope2/scope3/foo.mjs

/a-2.mjs

b

/scope2/scope3/foo.mjs

/b-3.mjs

c

/scope2/scope3/foo.mjs

/c-1.mjs

Подключение карт импортов

Как и с остальными ресурсами, подключаемыми через тег script. Можно заполнять содержимое тега:

<script type="importmap">{  "imports": { ... },  "scopes": { ... }}</script>

а можно импортировать карту используя атрибут src:

<script type="importmap" src="some/address/to/import-map.importmap"></script>

Важно, что по этому адресу ответ должен приходить с MIME type application/importmap+json.

Особенности

  1. Карты импортов блокируют остальные запросы импортов, поэтому рекомендуется использовать инлайновый вариант.

  2. Если добавить карту импорта после использования определения модулей, то это приведёт к ошибке:

    An import map is added after module script load was triggered.

  3. На момент написания этой статьи есть возможность добавить только одну карту импорта. Если добавить вторую, то это приведёт к ошибке. В Хроме выводит следующее:

    Multiple import maps are not yet supported. https://crbug.com/927119

Deno

В Deno карты импортов подключаются помощью флага --import-map:

deno run --import-map=import_map.json index.ts

Где import_map.json - это карта импортов, а index.ts - файл для запуска (компиляции).

Источники

https://wicg.github.io/import-maps

https://github.com/WICG/import-maps

https://deno.land/manual/linking_to_external_code/import_maps

Подробнее..
Категории: Javascript , Deno , Ecmascript

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru