Saul  Alaniz

Saul Alaniz

1659693600

Diseño Del Generador De Consultas De TypeScript Definitivo

Nos propusimos diseñar un generador de consultas para TypeScript que pueda expresar consultas de complejidad arbitraria mientras infiere el tipo de devolución automáticamente.

No fue fácil, pero con la ayuda de algunas de las funciones modernas más geniales de JavaScript y algunos trucos oscuros de TypeScript, creemos que lo logramos. El resultado es algo que ofrece lo mejor de todos los mundos: la seguridad de tipo de un ORM junto con el poder expresivo de un lenguaje de consulta completo.

¡Ya puedes jugar con el generador de consultas! Recomendamos clonar nuestro repositorio de sandbox de MCU y seguir las instrucciones para inicializar el proyecto; debería tomar menos de un minuto.

La secuela de SQL

Nota importante: este es un generador de consultas para EdgeQL , el lenguaje de consulta orientado a objetos de EdgeDB. Está diseñado como un sucesor espiritual de SQL y resuelve algunos de sus mayores problemas de usabilidad. A lo largo de esta publicación, haga clic en la pestaña "EdgeQL" para ver el equivalente de EdgeQL de cada expresión del generador de consultas.

Mecanografiado

const query = e.select(e.Movie, movie => ({
  id: true,
  title: true,
  actors: {
    name: true,
  },
  filter: e.op(movie.title, '=', 'Iron Man 3')
}));

// inferred type:
// { id: string; title: string; actors: { name: string }[]}

EdgeQL

select Movie {
  id,
  title,
  actors: {
    name
  }
}
filter .title = "Iron Man 3"

Tenga en cuenta la "forma de selección" estilo GraphQL para especificar qué campos buscar. Como era de esperar, esta consulta devuelve un resultado similar a JSON estructurado, no una lista plana de filas.

{
  "id": "9278d96e-1932-44e4-a9b3-34e49b592c26",
  "title": "Iron Man 3",
  "release_year": 2013,
  "actors": [
    { "name": "Robert Downey Jr." },
    { "name": "Gwyneth Paltrow" },
    { "name": "Ben Kingsley" }
  ]
}

La consulta anterior asume el siguiente esquema, tal como se define con el lenguaje de definición de esquemas de EdgeDB .

type Movie {
  required property title -> str;
  property release_year -> int64;
  multi link actors -> Person;
}

type Person {
  required property name -> str;
}

Generación del generador de consultas

Para comenzar con el generador de consultas, deberá activar una instancia de EdgeDB. La forma más sencilla de hacerlo es instalar la CLI de EdgeDB y ejecutarla edgedb project initen el directorio de su proyecto. Siga el inicio rápido para una introducción más completa.

Luego instale el edgedbpaquete desde NPM y ejecute el siguiente npxcomando.

$ npm install edgedb
$ npx edgeql-js

Este comando hace una introspección de su base de datos y genera un puñado de archivos en el dbschema/edgeql-jsdirectorio. (Por convención, dbschemaes el directorio que se usa para almacenar todo lo relacionado con EdgeDB, como archivos de migración y de esquema). Recomendamos importar el generador de consultas como una sola variable llamada e.

import e from "./dbschema/edgeql-js";

Esta variable contiene todo lo que necesita para definir consultas EdgeQL arbitrariamente complejas, pero empecemos poco a poco: una consulta "Hello World".

import e from "./dbschema/edgeql-js";

const query = e.select("hello world!");

La e.selectfunción devuelve un objeto que representa una consulta de EdgeQL; nos referiremos a esto como una "expresión del generador de consultas" o simplemente "expresión".

Para ejecutar la expresión, pase un cliente.run() al método de la expresión .

La createClientfunción devuelve una instancia de Client: una clase que administra un conjunto de conexiones a su instancia de EdgeDB y proporciona una API simple para ejecutar consultas.

import {createClient} from "edgedb";
import e from "./dbschema/edgeql-js";

const client = createClient();

const query = e.select("Hello world!");
const result = await query.run(client);
// => "Hello world!"

El .runmétodo devuelve una Promesa fuertemente tipada; el generador de consultas infiere automáticamente el tipo de retorno de todas las expresiones. En el ejemplo anterior resulttiene un stringtipo. Puede extraer este tipo con el $inferayudante.

import {createClient} from "edgedb";
import e, {$infer} from "./dbschema/edgeql-js";

const client = createClient();

const query = e.select("Hello world!");
type query = $infer<typeof query>;
// string

Empecemos a ver algunas consultas no triviales.

Insertar objetos

Utilice la e.insertfunción para escribir insertconsultas.

Mecanografiado

e.insert(e.Movie, {
  title: "Doctor Strange in the Multiverse of Madness",
  release_year: 2022
});

EdgeQL

insert Movie {
  title := "Doctor Strange in the Multiverse of Madness",
  release_year := 2022
}

El primer argumento es un tipo de objeto ; estos son generados automáticamente por el generador de consultas. El segundo argumento contiene los datos a insertar. Tenga en cuenta que no estamos incluyendo una idpropiedad; que EdgeDB genera automáticamente.

Dado que la titlepropiedad tiene tipo str, e.insertnaturalmente espera un valor de cadena. De manera similar , release_yeartiene tipo int64, por lo que espera un número. La siguiente tabla asigna cada tipo escalar de EdgeDB a su equivalente de TypeScript más cercano.

Tipo EdgeDBtipo de JavaScript
strstring
boolboolean
float32 float64 int16 int32 int64number
jsonstring
uuidstring
bigintBigInt
decimalN/A (no compatible)
bytesBuffer
datetimeDate
durationDuration()
cal::local_dateLocalDate()
cal::local_timeLocalTime()
cal::local_datetimeLocalDateTime()

Para ciertos tipos como durationese que no tienen equivalente en JavaScript, hemos implementado clases personalizadas para representar ese tipo de datos.

Inserciones anidadas

 

Al igual que en EdgeQL, las subconsultas son completamente indoloras; para hacer inserciones anidadas, simplemente suelte una e.insertdentro de otra.

Mecanografiado

e.insert(e.Movie, {
  title: "Iron Man",
  release_year: 2008,
  actors: e.set(
    e.insert(e.Person, { name: "Robert Downey Jr." }),
    e.insert(e.Person, { name: "Gwyneth Paltrow" })
  ),
});

EdgeQL

insert Movie {
  title := "Iron Man",
  release_year : 2008,
  actors := {
    (insert Person { name := "Robert Downey Jr." }),
    (insert Person { name := "Gwyneth Paltrow" })
  }
}

Arriba, estamos usando la e.setfunción para definir un conjunto literal . En EdgeQL esto se indica con llaves:select {'a', 'b', 'c'}

Selección de objetos

 

Ahora en la carne y las papas: seleccionando objetos . Comencemos seleccionando todas las películas en la base de datos.

Mecanografiado

const query = e.select(e.Movie, () => ({
  id: true,
  title: true
}));

const result = await query.run(client);
// {id: string; title: string;}[]

EdgeQL

select Movie {
  id,
  title,
  release_year
}

Como forma abreviada de seleccionar todas las propiedades de un objeto, utilice el operador de extensión junto con la *propiedad especial. Esta es una característica del generador de consultas sin equivalente de EdgeQL (todavía); Plain EdgeQL no es compatible con select *la funcionalidad.

const query = e.select(e.Movie, () => ({
  ...e.Movie['*']
}));

const result = await query.run(client);
/* {
  id: string;
  title: string;
  release_year: number | null;
}[] */

Como era de esperar, el tipo de release_yearpropiedad es number | nulluna propiedad opcional.

Formas de anidamiento

Las formas se pueden anidar para obtener objetos vinculados, como actors.

Mecanografiado

const query = e.select(e.Movie, () => ({
  title: true,
  actors: {
    name: true,
  }
}));

const result = await query.run(client);
// { title: string, actors: {name: string}[] }[]

EdgeQL

select Movie {
  title,
  actors: {
    name
  }
}

Adición de propiedades calculadas

En este punto, puede que se pregunte por qué el segundo argumento e.selectes una función en lugar de un objeto simple. Bueno: el generador de consultas puede hacer mucho más que simples conjuntos de selección de estilo GraphQL. Para empezar, puede definir propiedades calculadas .

Mecanografiado

const query = e.select(e.Movie, (movie) => ({
  title: true,
  title_upper: e.str_upper(movie.title),
  cast_size: e.count(movie.actors)
}));

const result = await query.run(client);
// { title: string; title_upper: string; cast_size: number }[]

EdgeQL

select Movie {
  title,
  title_upper := str_upper(.title),
  cast_size := count(.actors)
}

Nuestra "función de forma" ahora tiene un argumento: movie. Esta variable representa el “alcance”; podemos usarlo para hacer referencia a las propiedades y enlaces de los usuarios que estamos seleccionando. En este caso, estamos definiendo algunas expresiones calculadas simples usando dos funciones integradas: e.county e.str_upper; el generador de consultas refleja toda la biblioteca estándar de EdgeDB .

Ah, y tenga en cuenta que el generador de consultas infirió correctamente el tipo de title_uppery cast_size! El resultado de esta consulta sería algo como esto:

[
  {
    title: "Iron Man",
    title_upper: "IRON MAN",
    cast_size: 2
  },
  // etc.
]

Agregar filtros

 

Para agregar una filtercláusula a su consulta de selección, incluya la filterclave especial en su forma. Esta clave espera una expresión booleana; lo más común es que esta expresión de voluntad incluya un operador como =, >=, ++, noty or; los operadores se expresan con la e.opfunción.

A continuación, seleccionamos todas las películas con un título que contiene "matriz" (sin distinción entre mayúsculas y minúsculas).

Mecanografiado

e.select(e.Movie, (movie) => ({
  title: true,
  release_year: true,
  filter: e.op(movie.title, "ilike", "%matrix%"),
}));

EdgeQL

select Movie {
  title,
  release_year
} filter .title ilike "%matrix%"

Dependiendo de su tipo EdgeDB asociado , las expresiones pueden tener varias propiedades y métodos. Por ejemplo, las expresiones correspondientes a strvalores (como las movie.titleanteriores) se pueden indexar y dividir fácilmente. (Esto también se aplica a las expresiones array, tupley ).json

¡Recuerda que movie.titleno es una cuerda! Es un objeto que representa una consulta que devuelve una cadena. Además, movie.title[0]devuelve otra expresión más. El generador de consultas implementa esta "indexación mágica" con la ayuda de la API de proxy .

Podemos usar esto para seleccionar todas las películas que comienzan con la letra "A".

Mecanografiado

e.select(e.Movie, (movie) => ({
  title: true,
  release_year: true,
  filter: e.op(movie.title[0], "=", "A"),
}));

EdgeQL

select Movie {
  title,
  release_year
} filter .title[0] = "A"

Una API plana

En este punto, puede estar pensando que la forma se está amontonando un poco. ¿Por qué usamos un solo objeto para definir nuestra selección de campos, propiedades calculadas y filtros? ¿No habrá conflictos clave?

¡En realidad no! Esta es una decisión muy intencional. EdgeQL reserva ciertas palabras clave como "filtro", por lo que no se puede usar fácilmente como propiedad o nombre de enlace. En cuanto a los campos calculados, no se les permite "sobrescribir" una propiedad/enlace dentro de una forma de selección; TypeScript (y EdgeQL) generará un error.

Con esta API, cada capa de profundidad de consulta corresponde a una única capa de anidamiento de objetos.

e.select(e.Movie, (movie) => ({ id: true, title: true, actors: (actor) => ({ name: true, filter: e.op(actor.name, "ilike", "chris") }), filter: e.op(movie.release_year, "=", 2022) }));

Prisma frente a EdgeDB

Compare esto con la sintaxis más detallada de los ORM de JavaScript modernos. Prisma requiere dos capas de anidamiento de objetos para cada capa adicional de profundidad de consulta. Esta es la misma consulta expresada con el cliente Prisma.

prisma.movie.findMany({
  where: {
    release_year: {
      eq: 2022
    }
  },
  select: {
    id: true,
    title: true,
    actors: {
      select: {
        name: true
      },
      where: {
        name: {
          contains: "chris",
          mode: "insensitive"
        }
      }
    }
  }
});

Inferir la cardinalidad

El generador de consultas es lo suficientemente inteligente como para saber cuándo intenta seleccionar un solo objeto. Por ejemplo:

const query = e.select(e.Movie, (movie) => ({
  title: true,
  filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
}));

const result = await query.run(client);
// { title: string } | null

El tipo de resultado inferido es { title: string } | null. Si, en cambio, filtra por una propiedad no exclusiva como release_year, el resultado será una matriz.

  const query = e.select(e.Movie, (movie) => ({
    title: true,
    filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
    filter: e.op(movie.release_year, '=', 2022)
  }));

  const result = await query.run(client);
  // { title: string }[]

El generador de consultas detecta cuando filtra una propiedad con una restricción exclusiva (unicidad) (por ejemplo, .id) con el operador de igualdad ( =). En estas circunstancias, la consulta solo puede devolver cero o un objeto; esto se refleja en el tipo inferido. Por lo tanto, no hay necesidad de API separadas para .findOney .findMany: el generador de consultas puede resolverlo.

Ordenar y paginar

La order_bytecla especial se puede usar para especificar operaciones de orden en el resultado de selecty limit/ offsetse puede usar para la paginación.

Mecanografiado

e.select(e.Movie, (movie) => ({
  title: true,
  order_by: e.count(movie.actors),
  limit: 10,
  offset: 40
}));

EdgeQL

select Movie {
  title
}
order by count(.actors)
limit 10
offset 40

La order_byllave admite pedidos compuestos con instrucciones personalizables y políticas de manejo de vacíos.

e.select(e.Movie, (movie) => ({
  title: true,
  order_by: [
    {
      expression: e.count(movie.actors),
      direction: e.ASC,
      empty: e.EMPTY_LAST,
    },
    {
      expression: movie.title,
      direction: e.DESC,
    }
  ]
}));

Actualización de objetos

Mecanografiado

e.update(e.Movie, (movie) => ({
  filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
  set: {
    title: 'Avengers: Endgame',
  },
}));

EdgeQL

 e.update(e.Movie, (movie) => ({
  filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
  set: {
    title: 'Avengers: Endgame',
  },
}));

Actualizaciones autorreferenciales

El generador de consultas es particularmente útil (o más bien, los ORM son particularmente malos) cuando se establecen propiedades en una versión modificada de su valor actual. Por ejemplo, esta consulta eliminaría los espacios en blanco adicionales de todos los títulos de películas.

Mecanografiado

e.update(e.Movie, (movie) => ({
  set: {
    title: e.str_trim(movie.title),
  },
}));

EdgeQL

 update Movie
set {
  title := str_trim(.title)
}

Con un ORM, esto es inexpresable; necesitaría escribir un script para iterar a través de todas las películas en su base de datos.

Actualización de enlaces

Al actualizar enlaces , el generador de consultas admite una sintaxis especial para agregar o sustraer del conjunto de objetos vinculados.

Mecanografiado

const actors = e.select(e.Person, person => ({
  filter: e.op(person.name, 'in', e.set('Benedict Cumberbatch', 'Rachel McAdams'))
}));

const query = e.update(e.Movie, (movie) => ({
  filter: e.op(movie.title, '=', "Doctor Strange"),
  set: {
    actors: {'+=': actors},
  }
})).run(client);

EdgeQL

with actors := (
  select Person
  filter .name in {'Benedict Cumberbatch', 'Rachel McAdams'}
)
update Movie
filter .title = "Doctor Strange"
set {
  actors += actors
}

Redacción de consultas

El updateejemplo anterior también demuestra una de las mayores fortalezas del generador de consultas: la composicionalidad . Debido a que la declaración y ejecución de consultas son dos pasos distintos, es posible declarar partes de una consulta por separado y luego juntarlas todas más tarde. Escribir consultas complejas es como escribir un guión.

Por ejemplo, podemos componer varias expresiones para realizar una inserción + selección anidada en una consulta.

Mecanografiado

const rdj = e.insert(e.Person, {
  name: "Robert Downey Jr."
});

const ironMan = e.insert(e.Movie, {
  title: "Iron Man",
  release_year: 2008,
  actors: rdj
});

const query = e.select(ironMan, () => ({
  title: true,
  release_year: true,
  num_actors: e.count(ironMan.actors)
}));

const result = await query.run(client);
// {title: string; release_year: number; num_actors: number}

EdgeQL

with
  rdj := (
    insert Person {
      name := "Robert Downey Jr."
    }
  ),
  ironMan := (
    insert Movie {
      title := "Iron Man",
      release_year := 2008
    }
  )
select ironMan {
  title,
  release_year,
  num_actors := count(.actors)
};

El generador de consultas detecta que newMovieocurre varias veces dentro queryy lo extrae en un withbloque (también conocido como "expresión de tabla común" en lenguaje SQL). Tenga en cuenta que solo hay uno await. No estamos ejecutando rdj y ironMan; son subconsultas que se componen en la "superconsulta" final, que se puede ejecutar en un solo viaje de ida y vuelta a la base de datos.

Uso de parámetros de consulta

Como guinda final, el generador de consultas facilita la parametrización de sus consultas. Esto le permite usar datos externos (por ejemplo, el cuerpo de una POSTsolicitud entrante) de forma segura.

Mecanografiado

const query = e.params(
  { title: e.str, release_year: e.int64 },
  ($) => {
    return e.insert(e.Movie, {
      title: $.title,
      release_year: $.release_year,
    });
  }
);

const result = await query.run(client, {
  title: 'Thor: Love and Thunder',
  release_year: 2022,
});

EdgeQL

with
 title := <str>$title,
 release_year := <int64>$release_year
insert Movie {
 title := title,
 release_year := release_year
}

Para una consulta parametrizada, pasa los parámetros como segundo argumento a .run(); están fuertemente tipados y validados en tiempo de ejecución.

Comparación con los ORM

Esperemos que quede claro a partir de los ejemplos anteriores, pero en términos de poder expresivo, el generador de consultas está más allá de todos los ORM que conocemos. En general, los ORM solo pueden representar operaciones de lectura/escritura relativamente básicas, mientras que un lenguaje de consulta adecuado puede expresar mucho más:

  • modificaciones de cadena
  • indexación y división de iterables
  • agregaciones
  • Matemáticas
  • lógica temporal
  • fusión y valores predeterminados ( ??en JavaScript)
  • condicionales ( a ? b : cen JavaScript)
  • parametrización
  • establecer la lógica como unionoin
  • tipo de lógica y conversión
  • composicionalidad de consultas (también conocidas como expresiones de tabla comunes)
  • propiedades calculadas
  • consultas polimórficas
  • actualizaciones autorreferenciales

Esto es por no hablar del modelado de esquemas. EdgeDB admite aún más funciones que faltan en la mayoría de los ORM:

  • propiedades calculadas: estas son propiedades "virtuales" correspondientes a un EdgeQL que se ejecuta dinámicamente cada vez que se obtiene la propiedad
  • valores predeterminados calculados
  • lógica de restricción compleja
  • mixins de esquema (herencia)
  • propiedades del enlace
  • una gama completa de tipos numéricos ( int{16,|32|64}, float{32|64}, biginty decimal)
  • tipos temporales como durationfechas y horas no conscientes de la zona horaria
  • un sistema de migración nativo de la base de datos que incluye un planificador y un sistema de seguimiento

Direcciones futuras

El generador de consultas ha estado disponible desde el lanzamiento de EdgeDB 1.0 en febrero de 2022 y es estable y está listo para la producción. El generador de consultas se actualizó recientemente para admitir todas las características de EdgeDB 2.0, como la e.groupdeclaración y las variables de esquema global. Esta publicación solo cubre un subconjunto de la funcionalidad completa del generador de consultas; consulte la Documentación para una mirada más completa.

Lanzaremos EdgeDB 2.0 durante un evento de lanzamiento transmitido en vivo el 28 de julio de 2022. Únase a nosotros para algunas charlas relámpago sobre las nuevas funciones más importantes y la primera demostración pública de la nueva interfaz de usuario de administración de EdgeDB.

Fuente: https://www.edgedb.com/blog/designing-the-ultimate-typescript-query-builder

 #typescript #EdgeDb

What is GEEK

Buddha Community

Diseño Del Generador De Consultas De TypeScript Definitivo
Saul  Alaniz

Saul Alaniz

1659693600

Diseño Del Generador De Consultas De TypeScript Definitivo

Nos propusimos diseñar un generador de consultas para TypeScript que pueda expresar consultas de complejidad arbitraria mientras infiere el tipo de devolución automáticamente.

No fue fácil, pero con la ayuda de algunas de las funciones modernas más geniales de JavaScript y algunos trucos oscuros de TypeScript, creemos que lo logramos. El resultado es algo que ofrece lo mejor de todos los mundos: la seguridad de tipo de un ORM junto con el poder expresivo de un lenguaje de consulta completo.

¡Ya puedes jugar con el generador de consultas! Recomendamos clonar nuestro repositorio de sandbox de MCU y seguir las instrucciones para inicializar el proyecto; debería tomar menos de un minuto.

La secuela de SQL

Nota importante: este es un generador de consultas para EdgeQL , el lenguaje de consulta orientado a objetos de EdgeDB. Está diseñado como un sucesor espiritual de SQL y resuelve algunos de sus mayores problemas de usabilidad. A lo largo de esta publicación, haga clic en la pestaña "EdgeQL" para ver el equivalente de EdgeQL de cada expresión del generador de consultas.

Mecanografiado

const query = e.select(e.Movie, movie => ({
  id: true,
  title: true,
  actors: {
    name: true,
  },
  filter: e.op(movie.title, '=', 'Iron Man 3')
}));

// inferred type:
// { id: string; title: string; actors: { name: string }[]}

EdgeQL

select Movie {
  id,
  title,
  actors: {
    name
  }
}
filter .title = "Iron Man 3"

Tenga en cuenta la "forma de selección" estilo GraphQL para especificar qué campos buscar. Como era de esperar, esta consulta devuelve un resultado similar a JSON estructurado, no una lista plana de filas.

{
  "id": "9278d96e-1932-44e4-a9b3-34e49b592c26",
  "title": "Iron Man 3",
  "release_year": 2013,
  "actors": [
    { "name": "Robert Downey Jr." },
    { "name": "Gwyneth Paltrow" },
    { "name": "Ben Kingsley" }
  ]
}

La consulta anterior asume el siguiente esquema, tal como se define con el lenguaje de definición de esquemas de EdgeDB .

type Movie {
  required property title -> str;
  property release_year -> int64;
  multi link actors -> Person;
}

type Person {
  required property name -> str;
}

Generación del generador de consultas

Para comenzar con el generador de consultas, deberá activar una instancia de EdgeDB. La forma más sencilla de hacerlo es instalar la CLI de EdgeDB y ejecutarla edgedb project initen el directorio de su proyecto. Siga el inicio rápido para una introducción más completa.

Luego instale el edgedbpaquete desde NPM y ejecute el siguiente npxcomando.

$ npm install edgedb
$ npx edgeql-js

Este comando hace una introspección de su base de datos y genera un puñado de archivos en el dbschema/edgeql-jsdirectorio. (Por convención, dbschemaes el directorio que se usa para almacenar todo lo relacionado con EdgeDB, como archivos de migración y de esquema). Recomendamos importar el generador de consultas como una sola variable llamada e.

import e from "./dbschema/edgeql-js";

Esta variable contiene todo lo que necesita para definir consultas EdgeQL arbitrariamente complejas, pero empecemos poco a poco: una consulta "Hello World".

import e from "./dbschema/edgeql-js";

const query = e.select("hello world!");

La e.selectfunción devuelve un objeto que representa una consulta de EdgeQL; nos referiremos a esto como una "expresión del generador de consultas" o simplemente "expresión".

Para ejecutar la expresión, pase un cliente.run() al método de la expresión .

La createClientfunción devuelve una instancia de Client: una clase que administra un conjunto de conexiones a su instancia de EdgeDB y proporciona una API simple para ejecutar consultas.

import {createClient} from "edgedb";
import e from "./dbschema/edgeql-js";

const client = createClient();

const query = e.select("Hello world!");
const result = await query.run(client);
// => "Hello world!"

El .runmétodo devuelve una Promesa fuertemente tipada; el generador de consultas infiere automáticamente el tipo de retorno de todas las expresiones. En el ejemplo anterior resulttiene un stringtipo. Puede extraer este tipo con el $inferayudante.

import {createClient} from "edgedb";
import e, {$infer} from "./dbschema/edgeql-js";

const client = createClient();

const query = e.select("Hello world!");
type query = $infer<typeof query>;
// string

Empecemos a ver algunas consultas no triviales.

Insertar objetos

Utilice la e.insertfunción para escribir insertconsultas.

Mecanografiado

e.insert(e.Movie, {
  title: "Doctor Strange in the Multiverse of Madness",
  release_year: 2022
});

EdgeQL

insert Movie {
  title := "Doctor Strange in the Multiverse of Madness",
  release_year := 2022
}

El primer argumento es un tipo de objeto ; estos son generados automáticamente por el generador de consultas. El segundo argumento contiene los datos a insertar. Tenga en cuenta que no estamos incluyendo una idpropiedad; que EdgeDB genera automáticamente.

Dado que la titlepropiedad tiene tipo str, e.insertnaturalmente espera un valor de cadena. De manera similar , release_yeartiene tipo int64, por lo que espera un número. La siguiente tabla asigna cada tipo escalar de EdgeDB a su equivalente de TypeScript más cercano.

Tipo EdgeDBtipo de JavaScript
strstring
boolboolean
float32 float64 int16 int32 int64number
jsonstring
uuidstring
bigintBigInt
decimalN/A (no compatible)
bytesBuffer
datetimeDate
durationDuration()
cal::local_dateLocalDate()
cal::local_timeLocalTime()
cal::local_datetimeLocalDateTime()

Para ciertos tipos como durationese que no tienen equivalente en JavaScript, hemos implementado clases personalizadas para representar ese tipo de datos.

Inserciones anidadas

 

Al igual que en EdgeQL, las subconsultas son completamente indoloras; para hacer inserciones anidadas, simplemente suelte una e.insertdentro de otra.

Mecanografiado

e.insert(e.Movie, {
  title: "Iron Man",
  release_year: 2008,
  actors: e.set(
    e.insert(e.Person, { name: "Robert Downey Jr." }),
    e.insert(e.Person, { name: "Gwyneth Paltrow" })
  ),
});

EdgeQL

insert Movie {
  title := "Iron Man",
  release_year : 2008,
  actors := {
    (insert Person { name := "Robert Downey Jr." }),
    (insert Person { name := "Gwyneth Paltrow" })
  }
}

Arriba, estamos usando la e.setfunción para definir un conjunto literal . En EdgeQL esto se indica con llaves:select {'a', 'b', 'c'}

Selección de objetos

 

Ahora en la carne y las papas: seleccionando objetos . Comencemos seleccionando todas las películas en la base de datos.

Mecanografiado

const query = e.select(e.Movie, () => ({
  id: true,
  title: true
}));

const result = await query.run(client);
// {id: string; title: string;}[]

EdgeQL

select Movie {
  id,
  title,
  release_year
}

Como forma abreviada de seleccionar todas las propiedades de un objeto, utilice el operador de extensión junto con la *propiedad especial. Esta es una característica del generador de consultas sin equivalente de EdgeQL (todavía); Plain EdgeQL no es compatible con select *la funcionalidad.

const query = e.select(e.Movie, () => ({
  ...e.Movie['*']
}));

const result = await query.run(client);
/* {
  id: string;
  title: string;
  release_year: number | null;
}[] */

Como era de esperar, el tipo de release_yearpropiedad es number | nulluna propiedad opcional.

Formas de anidamiento

Las formas se pueden anidar para obtener objetos vinculados, como actors.

Mecanografiado

const query = e.select(e.Movie, () => ({
  title: true,
  actors: {
    name: true,
  }
}));

const result = await query.run(client);
// { title: string, actors: {name: string}[] }[]

EdgeQL

select Movie {
  title,
  actors: {
    name
  }
}

Adición de propiedades calculadas

En este punto, puede que se pregunte por qué el segundo argumento e.selectes una función en lugar de un objeto simple. Bueno: el generador de consultas puede hacer mucho más que simples conjuntos de selección de estilo GraphQL. Para empezar, puede definir propiedades calculadas .

Mecanografiado

const query = e.select(e.Movie, (movie) => ({
  title: true,
  title_upper: e.str_upper(movie.title),
  cast_size: e.count(movie.actors)
}));

const result = await query.run(client);
// { title: string; title_upper: string; cast_size: number }[]

EdgeQL

select Movie {
  title,
  title_upper := str_upper(.title),
  cast_size := count(.actors)
}

Nuestra "función de forma" ahora tiene un argumento: movie. Esta variable representa el “alcance”; podemos usarlo para hacer referencia a las propiedades y enlaces de los usuarios que estamos seleccionando. En este caso, estamos definiendo algunas expresiones calculadas simples usando dos funciones integradas: e.county e.str_upper; el generador de consultas refleja toda la biblioteca estándar de EdgeDB .

Ah, y tenga en cuenta que el generador de consultas infirió correctamente el tipo de title_uppery cast_size! El resultado de esta consulta sería algo como esto:

[
  {
    title: "Iron Man",
    title_upper: "IRON MAN",
    cast_size: 2
  },
  // etc.
]

Agregar filtros

 

Para agregar una filtercláusula a su consulta de selección, incluya la filterclave especial en su forma. Esta clave espera una expresión booleana; lo más común es que esta expresión de voluntad incluya un operador como =, >=, ++, noty or; los operadores se expresan con la e.opfunción.

A continuación, seleccionamos todas las películas con un título que contiene "matriz" (sin distinción entre mayúsculas y minúsculas).

Mecanografiado

e.select(e.Movie, (movie) => ({
  title: true,
  release_year: true,
  filter: e.op(movie.title, "ilike", "%matrix%"),
}));

EdgeQL

select Movie {
  title,
  release_year
} filter .title ilike "%matrix%"

Dependiendo de su tipo EdgeDB asociado , las expresiones pueden tener varias propiedades y métodos. Por ejemplo, las expresiones correspondientes a strvalores (como las movie.titleanteriores) se pueden indexar y dividir fácilmente. (Esto también se aplica a las expresiones array, tupley ).json

¡Recuerda que movie.titleno es una cuerda! Es un objeto que representa una consulta que devuelve una cadena. Además, movie.title[0]devuelve otra expresión más. El generador de consultas implementa esta "indexación mágica" con la ayuda de la API de proxy .

Podemos usar esto para seleccionar todas las películas que comienzan con la letra "A".

Mecanografiado

e.select(e.Movie, (movie) => ({
  title: true,
  release_year: true,
  filter: e.op(movie.title[0], "=", "A"),
}));

EdgeQL

select Movie {
  title,
  release_year
} filter .title[0] = "A"

Una API plana

En este punto, puede estar pensando que la forma se está amontonando un poco. ¿Por qué usamos un solo objeto para definir nuestra selección de campos, propiedades calculadas y filtros? ¿No habrá conflictos clave?

¡En realidad no! Esta es una decisión muy intencional. EdgeQL reserva ciertas palabras clave como "filtro", por lo que no se puede usar fácilmente como propiedad o nombre de enlace. En cuanto a los campos calculados, no se les permite "sobrescribir" una propiedad/enlace dentro de una forma de selección; TypeScript (y EdgeQL) generará un error.

Con esta API, cada capa de profundidad de consulta corresponde a una única capa de anidamiento de objetos.

e.select(e.Movie, (movie) => ({ id: true, title: true, actors: (actor) => ({ name: true, filter: e.op(actor.name, "ilike", "chris") }), filter: e.op(movie.release_year, "=", 2022) }));

Prisma frente a EdgeDB

Compare esto con la sintaxis más detallada de los ORM de JavaScript modernos. Prisma requiere dos capas de anidamiento de objetos para cada capa adicional de profundidad de consulta. Esta es la misma consulta expresada con el cliente Prisma.

prisma.movie.findMany({
  where: {
    release_year: {
      eq: 2022
    }
  },
  select: {
    id: true,
    title: true,
    actors: {
      select: {
        name: true
      },
      where: {
        name: {
          contains: "chris",
          mode: "insensitive"
        }
      }
    }
  }
});

Inferir la cardinalidad

El generador de consultas es lo suficientemente inteligente como para saber cuándo intenta seleccionar un solo objeto. Por ejemplo:

const query = e.select(e.Movie, (movie) => ({
  title: true,
  filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
}));

const result = await query.run(client);
// { title: string } | null

El tipo de resultado inferido es { title: string } | null. Si, en cambio, filtra por una propiedad no exclusiva como release_year, el resultado será una matriz.

  const query = e.select(e.Movie, (movie) => ({
    title: true,
    filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
    filter: e.op(movie.release_year, '=', 2022)
  }));

  const result = await query.run(client);
  // { title: string }[]

El generador de consultas detecta cuando filtra una propiedad con una restricción exclusiva (unicidad) (por ejemplo, .id) con el operador de igualdad ( =). En estas circunstancias, la consulta solo puede devolver cero o un objeto; esto se refleja en el tipo inferido. Por lo tanto, no hay necesidad de API separadas para .findOney .findMany: el generador de consultas puede resolverlo.

Ordenar y paginar

La order_bytecla especial se puede usar para especificar operaciones de orden en el resultado de selecty limit/ offsetse puede usar para la paginación.

Mecanografiado

e.select(e.Movie, (movie) => ({
  title: true,
  order_by: e.count(movie.actors),
  limit: 10,
  offset: 40
}));

EdgeQL

select Movie {
  title
}
order by count(.actors)
limit 10
offset 40

La order_byllave admite pedidos compuestos con instrucciones personalizables y políticas de manejo de vacíos.

e.select(e.Movie, (movie) => ({
  title: true,
  order_by: [
    {
      expression: e.count(movie.actors),
      direction: e.ASC,
      empty: e.EMPTY_LAST,
    },
    {
      expression: movie.title,
      direction: e.DESC,
    }
  ]
}));

Actualización de objetos

Mecanografiado

e.update(e.Movie, (movie) => ({
  filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
  set: {
    title: 'Avengers: Endgame',
  },
}));

EdgeQL

 e.update(e.Movie, (movie) => ({
  filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
  set: {
    title: 'Avengers: Endgame',
  },
}));

Actualizaciones autorreferenciales

El generador de consultas es particularmente útil (o más bien, los ORM son particularmente malos) cuando se establecen propiedades en una versión modificada de su valor actual. Por ejemplo, esta consulta eliminaría los espacios en blanco adicionales de todos los títulos de películas.

Mecanografiado

e.update(e.Movie, (movie) => ({
  set: {
    title: e.str_trim(movie.title),
  },
}));

EdgeQL

 update Movie
set {
  title := str_trim(.title)
}

Con un ORM, esto es inexpresable; necesitaría escribir un script para iterar a través de todas las películas en su base de datos.

Actualización de enlaces

Al actualizar enlaces , el generador de consultas admite una sintaxis especial para agregar o sustraer del conjunto de objetos vinculados.

Mecanografiado

const actors = e.select(e.Person, person => ({
  filter: e.op(person.name, 'in', e.set('Benedict Cumberbatch', 'Rachel McAdams'))
}));

const query = e.update(e.Movie, (movie) => ({
  filter: e.op(movie.title, '=', "Doctor Strange"),
  set: {
    actors: {'+=': actors},
  }
})).run(client);

EdgeQL

with actors := (
  select Person
  filter .name in {'Benedict Cumberbatch', 'Rachel McAdams'}
)
update Movie
filter .title = "Doctor Strange"
set {
  actors += actors
}

Redacción de consultas

El updateejemplo anterior también demuestra una de las mayores fortalezas del generador de consultas: la composicionalidad . Debido a que la declaración y ejecución de consultas son dos pasos distintos, es posible declarar partes de una consulta por separado y luego juntarlas todas más tarde. Escribir consultas complejas es como escribir un guión.

Por ejemplo, podemos componer varias expresiones para realizar una inserción + selección anidada en una consulta.

Mecanografiado

const rdj = e.insert(e.Person, {
  name: "Robert Downey Jr."
});

const ironMan = e.insert(e.Movie, {
  title: "Iron Man",
  release_year: 2008,
  actors: rdj
});

const query = e.select(ironMan, () => ({
  title: true,
  release_year: true,
  num_actors: e.count(ironMan.actors)
}));

const result = await query.run(client);
// {title: string; release_year: number; num_actors: number}

EdgeQL

with
  rdj := (
    insert Person {
      name := "Robert Downey Jr."
    }
  ),
  ironMan := (
    insert Movie {
      title := "Iron Man",
      release_year := 2008
    }
  )
select ironMan {
  title,
  release_year,
  num_actors := count(.actors)
};

El generador de consultas detecta que newMovieocurre varias veces dentro queryy lo extrae en un withbloque (también conocido como "expresión de tabla común" en lenguaje SQL). Tenga en cuenta que solo hay uno await. No estamos ejecutando rdj y ironMan; son subconsultas que se componen en la "superconsulta" final, que se puede ejecutar en un solo viaje de ida y vuelta a la base de datos.

Uso de parámetros de consulta

Como guinda final, el generador de consultas facilita la parametrización de sus consultas. Esto le permite usar datos externos (por ejemplo, el cuerpo de una POSTsolicitud entrante) de forma segura.

Mecanografiado

const query = e.params(
  { title: e.str, release_year: e.int64 },
  ($) => {
    return e.insert(e.Movie, {
      title: $.title,
      release_year: $.release_year,
    });
  }
);

const result = await query.run(client, {
  title: 'Thor: Love and Thunder',
  release_year: 2022,
});

EdgeQL

with
 title := <str>$title,
 release_year := <int64>$release_year
insert Movie {
 title := title,
 release_year := release_year
}

Para una consulta parametrizada, pasa los parámetros como segundo argumento a .run(); están fuertemente tipados y validados en tiempo de ejecución.

Comparación con los ORM

Esperemos que quede claro a partir de los ejemplos anteriores, pero en términos de poder expresivo, el generador de consultas está más allá de todos los ORM que conocemos. En general, los ORM solo pueden representar operaciones de lectura/escritura relativamente básicas, mientras que un lenguaje de consulta adecuado puede expresar mucho más:

  • modificaciones de cadena
  • indexación y división de iterables
  • agregaciones
  • Matemáticas
  • lógica temporal
  • fusión y valores predeterminados ( ??en JavaScript)
  • condicionales ( a ? b : cen JavaScript)
  • parametrización
  • establecer la lógica como unionoin
  • tipo de lógica y conversión
  • composicionalidad de consultas (también conocidas como expresiones de tabla comunes)
  • propiedades calculadas
  • consultas polimórficas
  • actualizaciones autorreferenciales

Esto es por no hablar del modelado de esquemas. EdgeDB admite aún más funciones que faltan en la mayoría de los ORM:

  • propiedades calculadas: estas son propiedades "virtuales" correspondientes a un EdgeQL que se ejecuta dinámicamente cada vez que se obtiene la propiedad
  • valores predeterminados calculados
  • lógica de restricción compleja
  • mixins de esquema (herencia)
  • propiedades del enlace
  • una gama completa de tipos numéricos ( int{16,|32|64}, float{32|64}, biginty decimal)
  • tipos temporales como durationfechas y horas no conscientes de la zona horaria
  • un sistema de migración nativo de la base de datos que incluye un planificador y un sistema de seguimiento

Direcciones futuras

El generador de consultas ha estado disponible desde el lanzamiento de EdgeDB 1.0 en febrero de 2022 y es estable y está listo para la producción. El generador de consultas se actualizó recientemente para admitir todas las características de EdgeDB 2.0, como la e.groupdeclaración y las variables de esquema global. Esta publicación solo cubre un subconjunto de la funcionalidad completa del generador de consultas; consulte la Documentación para una mirada más completa.

Lanzaremos EdgeDB 2.0 durante un evento de lanzamiento transmitido en vivo el 28 de julio de 2022. Únase a nosotros para algunas charlas relámpago sobre las nuevas funciones más importantes y la primera demostración pública de la nueva interfaz de usuario de administración de EdgeDB.

Fuente: https://www.edgedb.com/blog/designing-the-ultimate-typescript-query-builder

 #typescript #EdgeDb

The Definitive Guide to TypeScript & Possibly The Best TypeScript Book

TypeScript Deep Dive

I've been looking at the issues that turn up commonly when people start using TypeScript. This is based on the lessons from Stack Overflow / DefinitelyTyped and general engagement with the TypeScript community. You can follow for updates and don't forget to ★ on GitHub 🌹

Reviews

  • Thanks for the wonderful book. Learned a lot from it. (link)
  • Its probably the Best TypeScript book out there. Good Job (link)
  • Love how precise and clear the examples and explanations are! (link)
  • For the low, low price of free, you get pages of pure awesomeness. Chock full of source code examples and clear, concise explanations, TypeScript Deep Dive will help you learn TypeScript development. (link)
  • Just a big thank you! Best TypeScript 2 detailed explanation! (link)
  • This gitbook got my project going pronto. Fluent easy read 5 stars. (link)
  • I recommend the online #typescript book by @basarat you'll love it.(link)
  • I've always found this by @basarat really helpful. (link)
  • We must highlight TypeScript Deep Dive, an open source book.(link)
  • Great online resource for learning. (link)
  • Thank you for putting this book together, and for all your hard work within the TypeScript community. (link)
  • TypeScript Deep Dive is one of the best technical texts I've read in a while. (link)
  • Thanks @basarat for the TypeScript Deep Dive Book. Help me a lot with my first TypeScript project. (link)
  • Thanks to @basarat for this great #typescript learning resource. (link)
  • Guyz excellent book on Typescript(@typescriptlang) by @basarat (link)
  • Leaning on the legendary @basarat's "TypeScript Deep Dive" book heavily at the moment (link)
  • numTimesPointedPeopleToBasaratsTypeScriptBook++; (link)
  • A book not only for typescript, a good one for deeper JavaScript knowledge as well. link
  • In my new job, we're using @typescriptlang, which I am new to. This is insanely helpful huge thanks, @basarat! link
  • Thank you for writing TypeScript Deep Dive. I have learned so much. link
  • Loving @basarat's @typescriptlang online book basarat.gitbooks.io/typescript/# loaded with great recipes! link
  • Microsoft doc is great already, but if want to "dig deeper" into TypeScript I find this book of great value link
  • Thanks, this is a great book 🤓🤓 link
  • Deep dive to typescript is awesome in so many levels. i find it very insightful. Thanks link
  • @basarat's intro to @typescriptlang is still one of the best going (if not THE best) link
  •  
  • This is sweet! So many #typescript goodies! link

Get Started

If you are here to read the book online get started.

Translations

Book is completely free so you can copy paste whatever you want without requiring permission. If you have a translation you want me to link here. Send a PR.

Other Options

You can also download one of the Epub, Mobi, or PDF formats from the actions tab by clicking on the latest build run. You will find the files in the artifacts section.

Special Thanks

All the amazing contributors 🌹

Share

Share URL: https://basarat.gitbook.io/typescript/

Author: Basarat
Source Code: https://github.com/basarat/typescript-book/ 
License: View license

#typescript #opensource 

joe biden

1617257581

Software de restauración de Exchange para restaurar sin problemas PST en Exchange Server

¿Quiere restaurar los buzones de correo de PST a Exchange Server? Entonces, estás en la página correcta. Aquí, lo guiaremos sobre cómo puede restaurar fácilmente mensajes y otros elementos de PST a MS Exchange Server.

Muchas veces, los usuarios necesitan restaurar los elementos de datos de PST en Exchange Server, pero debido a la falta de disponibilidad de una solución confiable, los usuarios no pueden obtener la solución. Háganos saber primero sobre el archivo PST y MS Exchange Server.

Conozca PST y Exchange Server

PST es un formato de archivo utilizado por MS Outlook, un cliente de correo electrónico de Windows y muy popular entre los usuarios domésticos y comerciales.

Por otro lado, Exchange Server es un poderoso servidor de correo electrónico donde todos los datos se almacenan en un archivo EDB. Los usuarios generalmente guardan la copia de seguridad de los buzones de correo de Exchange en el archivo PST, pero muchas veces, los usuarios deben restaurar los datos del archivo PST en Exchange. Para resolver este problema, estamos aquí con una solución profesional que discutiremos en la siguiente sección de esta publicación.

Un método profesional para restaurar PST a Exchange Server

No le recomendamos que elija una solución al azar para restaurar los datos de PST en Exchange Server. Por lo tanto, al realizar varias investigaciones, estamos aquí con una solución inteligente y conveniente, es decir, Exchange Restore Software. Es demasiado fácil de manejar por todos los usuarios y restaurar cómodamente todos los datos del archivo PST a Exchange Server.

Funciones principales ofrecidas por Exchange Restore Software

El software es demasiado simple de usar y se puede instalar fácilmente en todas las versiones de Windows. Con unos pocos clics, la herramienta puede restaurar los elementos del buzón de Exchange.

No es necesario que MS Outlook restaure los datos PST en Exchange. Todos los correos electrónicos, contactos, notas, calendarios, etc. se restauran desde el archivo PST a Exchange Server.

Todas las versiones de Outlook son compatibles con la herramienta, como Outlook 2019, 2016, 2013, 2010, 2007, etc. La herramienta proporciona varios filtros mediante los cuales se pueden restaurar los datos deseados desde un archivo PST a Exchange Server. El programa se puede instalar en todas las versiones de Windows como Windows 10, 8.1, 8, 7, XP, Vista, etc.

Descargue la versión de demostración del software de restauración de Exchange y analice el funcionamiento del software restaurando los primeros 50 elementos por carpeta.

Líneas finales

No existe una solución manual para restaurar los buzones de correo de Exchange desde el archivo PST. Por lo tanto, hemos explicado una solución fácil e inteligente para restaurar datos de archivos PST en Exchange Server. Simplemente puede usar este software y restaurar todos los datos de PST a Exchange Server.

Más información:- https://www.datavare.com/software/exchange-restore.html

#intercambio de software de restauración #intercambio de restauración #buzón del servidor de intercambio #herramienta de restauración de intercambio

joe biden

1617255938

¿Cómo migrar los buzones de correo de Exchange a la nube de Office 365?

Si tiene problemas para migrar los buzones de correo de Exchange a Office 365, debe leer este artículo para saber cómo migrar los buzones de correo de Exchange EDB a Office 365. Al migrar a Office 365, los usuarios pueden acceder a sus buzones de correo desde cualquier lugar y desde cualquier dispositivo.

En esta publicación, explicaremos las razones detrás de esta migración y una solución profesional para migrar de Exchange a Office 365.

Razones para migrar Exchange Server a la nube de Office 365

Office 365 apareció por primera vez en 2011 y, dado que se considera la mejor plataforma para aquellas organizaciones que desean administrar todo su sistema de correo electrónico en la nube. Estas son las características clave de Office 365:

  1. Permite trabajar desde cualquier lugar y desde cualquier lugar.
  2. No se preocupe por el spam y el malware.
  3. La seguridad proporcionada por Office 365 es altamente confiable.
  4. Controla el costo total y brinda flexibilidad financiera.
  5. Todas las actualizaciones y mejoras son administradas por Microsoft.

¿Cómo migrar los buzones de correo de Exchange a Office 365?

Hay varias formas manuales de migrar los buzones de correo de Exchange EDB a Office 365, pero para evitar estos complicados y prolongados procedimientos, presentamos una solución de terceros, es decir, la herramienta de migración de Exchange, que es automatizada y directa para la migración de Exchange a Office 365. La herramienta funciona rápidamente y migra todos los elementos del buzón de Exchange Server a Office 365.

La herramienta de migración de Datavare Exchange es demasiado fácil de usar y ofrece pasos sencillos para migrar EDB a Office 365:

  1. Descargue e instale el software en su sistema.
  2. Agregue el archivo EDB de Exchange con el botón Examinar.
  3. Seleccione exportar a buzones de correo de Office 365.
  4. Proporcione los detalles de inicio de sesión de la cuenta de Office 365.
  5. Seleccione la carpeta y presione el botón Finalizar.

Por lo tanto, todos sus buzones de correo de Exchange EDB ahora se migran a Office 365.
Nota: puede usar filtros para migrar los elementos de datos deseados de la cuenta de Exchange a la de Office 365

Líneas finales

Este blog le indica una solución profesional para la migración de buzones de correo de Exchange a la cuenta de Office 365. Dado que las soluciones manuales son complicadas, sugerimos la herramienta de migración de Exchange, que es demasiado simple de usar. Los usuarios no se enfrentan a problemas al operar el programa. La mejor parte de este software es que no necesita habilidades técnicas para realizar la migración. Se puede comprender el funcionamiento del software descargando la versión de demostración que permite la migración de los primeros 50 elementos por carpeta.

Más información:- https://www.datavare.com/software/edb-migration.html

#herramienta de migración de intercambio #migración de intercambio #migrar buzones de correo de exchange

Projetando O Construtor De Consultas TypeScript Definitivo

Nós nos propusemos a projetar um construtor de consultas para TypeScript que possa expressar consultas de complexidade arbitrária enquanto infere o tipo de retorno automaticamente.

Não foi fácil, mas com a ajuda de alguns dos recursos modernos mais legais do JavaScript e alguma magia obscura do TypeScript, achamos que conseguimos. O resultado é algo que oferece o melhor de todos os mundos: a segurança de tipo de um ORM juntamente com o poder expressivo de uma linguagem de consulta completa.

Você pode jogar com o construtor de consultas agora! Recomendamos clonar nosso repositório de sandbox do MCU e seguir as instruções para inicializar o projeto - deve levar menos de um minuto.

A continuação do SQL

Nota importante: este é um construtor de consultas para EdgeQL , a linguagem de consulta orientada a objetos do EdgeDB. Ele foi projetado como um sucessor espiritual do SQL e resolve alguns dos maiores problemas de usabilidade. Ao longo desta postagem, clique na guia “EdgeQL” para ver o equivalente EdgeQL de cada expressão do construtor de consultas.

TypeScript

const query = e.select(e.Movie, movie => ({
  id: true,
  title: true,
  actors: {
    name: true,
  },
  filter: e.op(movie.title, '=', 'Iron Man 3')
}));

// inferred type:
// { id: string; title: string; actors: { name: string }[]}

EdgeQL

select Movie {
  id,
  title,
  actors: {
    name
  }
}
filter .title = "Iron Man 3"

Observe a “forma de seleção” no estilo GraphQL para especificar quais campos buscar. Como seria de esperar, essa consulta retorna um resultado estruturado do tipo JSON, não uma lista simples de linhas.

{
  "id": "9278d96e-1932-44e4-a9b3-34e49b592c26",
  "title": "Iron Man 3",
  "release_year": 2013,
  "actors": [
    { "name": "Robert Downey Jr." },
    { "name": "Gwyneth Paltrow" },
    { "name": "Ben Kingsley" }
  ]
}

A consulta acima assume o seguinte esquema, conforme definido com a linguagem de definição de esquema do EdgeDB .

type Movie {
  required property title -> str;
  property release_year -> int64;
  multi link actors -> Person;
}

type Person {
  required property name -> str;
}

Gerando o construtor de consultas

Para começar com o construtor de consultas, você precisará ativar uma instância do EdgeDB. A maneira mais fácil de fazer isso é instalar a CLI do EdgeDB e executá-la edgedb project initno diretório do seu projeto. Siga o início rápido para uma introdução mais completa.

Em seguida, instale o edgedbpacote do NPM e execute o seguinte npxcomando.

$ npm install edgedb
$ npx edgeql-js

Este comando examina seu banco de dados e gera um punhado de arquivos no dbschema/edgeql-jsdiretório. (Por convenção, dbschemaé o diretório usado para armazenar qualquer coisa relacionada ao EdgeDB, como arquivos de esquema e migração.) Recomendamos importar o construtor de consultas como uma única variável chamada e.

import e from "./dbschema/edgeql-js";

Essa variável contém tudo o que você precisa para definir consultas EdgeQL arbitrariamente complexas, mas vamos começar pequeno: uma consulta “Hello World”.

import e from "./dbschema/edgeql-js";

const query = e.select("hello world!");

A e.selectfunção retorna um objeto que representa uma consulta EdgeQL; vamos nos referir a isso como uma “expressão do construtor de consultas” ou simplesmente “expressão”.

Para executar a expressão, passe um cliente para o método da expressão .run().

A createClientfunção retorna uma instância de Client: uma classe que gerencia um pool de conexões com sua instância EdgeDB e fornece uma API simples para executar consultas.

import {createClient} from "edgedb";
import e from "./dbschema/edgeql-js";

const client = createClient();

const query = e.select("Hello world!");
const result = await query.run(client);
// => "Hello world!"

O .runmétodo retorna uma promessa fortemente tipada; o construtor de consultas infere automaticamente o tipo de retorno de todas as expressões. No exemplo acima resulttem um stringtipo. Você pode extrair esse tipo com o $inferauxiliar.

import {createClient} from "edgedb";
import e, {$infer} from "./dbschema/edgeql-js";

const client = createClient();

const query = e.select("Hello world!");
type query = $infer<typeof query>;
// string

Vamos começar a analisar algumas consultas não triviais.

Inserindo objetos

Use a e.insertfunção para escrever insertconsultas.

TypeScript

e.insert(e.Movie, {
  title: "Doctor Strange in the Multiverse of Madness",
  release_year: 2022
});

EdgeQL

insert Movie {
  title := "Doctor Strange in the Multiverse of Madness",
  release_year := 2022
}

O primeiro argumento é um tipo de objeto ; elas são geradas automaticamente pelo construtor de consultas. O segundo argumento contém os dados a serem inseridos. Observe que não estamos incluindo uma idpropriedade; que é gerado automaticamente pelo EdgeDB.

Como a titlepropriedade tem type str, e.insertnaturalmente espera um valor de string. Da mesma forma release_yeartem type int64, então espera um número. A tabela abaixo mapeia cada tipo escalar EdgeDB para seu equivalente TypeScript mais próximo.

Tipo EdgeDBTipo de JavaScript
strstring
boolboolean
float32 float64 int16 int32 int64number
jsonstring
uuidstring
bigintBigInt
decimalN/A (não suportado)
bytesBuffer
datetimeDate
durationDuration()
cal::local_dateLocalDate()
cal::local_timeLocalTime()
cal::local_datetimeLocalDateTime()

Para certos tipos como durationesse não têm equivalente em JavaScript, implementamos classes personalizadas para representar esse tipo de dados.

Inserções aninhadas

 

Como no EdgeQL, as subconsultas são completamente indolores; para fazer inserções aninhadas, basta soltar uma e.insertna outra.

TypeScript

e.insert(e.Movie, {
  title: "Iron Man",
  release_year: 2008,
  actors: e.set(
    e.insert(e.Person, { name: "Robert Downey Jr." }),
    e.insert(e.Person, { name: "Gwyneth Paltrow" })
  ),
});

EdgeQL

insert Movie {
  title := "Iron Man",
  release_year : 2008,
  actors := {
    (insert Person { name := "Robert Downey Jr." }),
    (insert Person { name := "Gwyneth Paltrow" })
  }
}

Acima, estamos usando a e.setfunção para definir um literal definido . No EdgeQL isso é indicado com chaves:select {'a', 'b', 'c'}

Selecionando objetos

 

Agora sobre a carne e as batatas: selecionando objetos . Vamos começar selecionando todos os filmes no banco de dados.

TypeScript

const query = e.select(e.Movie, () => ({
  id: true,
  title: true
}));

const result = await query.run(client);
// {id: string; title: string;}[]

EdgeQL

select Movie {
  id,
  title,
  release_year
}

Como um atalho para selecionar todas as propriedades de um objeto, use o operador de propagação em conjunto com a *propriedade especial. Este é um recurso do construtor de consultas sem equivalente ao EdgeQL (ainda); O EdgeQL simples não oferece suporte à select *funcionalidade.

const query = e.select(e.Movie, () => ({
  ...e.Movie['*']
}));

const result = await query.run(client);
/* {
  id: string;
  title: string;
  release_year: number | null;
}[] */

Como seria de esperar, o tipo da release_yearpropriedade é number | nulluma propriedade opcional.

Formas de aninhamento

As formas podem ser aninhadas para buscar objetos vinculados, como actors.

TypeScript

const query = e.select(e.Movie, () => ({
  title: true,
  actors: {
    name: true,
  }
}));

const result = await query.run(client);
// { title: string, actors: {name: string}[] }[]

EdgeQL

select Movie {
  title,
  actors: {
    name
  }
}

Adicionando propriedades computadas

Neste ponto, você pode estar se perguntando por que o segundo argumento e.selecté uma função em vez de um objeto simples. Bem: o construtor de consultas pode fazer muito mais do que simples conjuntos de seleção no estilo GraphQL. Para começar, você pode definir propriedades computadas .

TypeScript

const query = e.select(e.Movie, (movie) => ({
  title: true,
  title_upper: e.str_upper(movie.title),
  cast_size: e.count(movie.actors)
}));

const result = await query.run(client);
// { title: string; title_upper: string; cast_size: number }[]

EdgeQL

select Movie {
  title,
  title_upper := str_upper(.title),
  cast_size := count(.actors)
}

Nossa “função shape” agora tem um argumento: movie. Esta variável representa o “escopo”; podemos usá-lo para referenciar as propriedades e links do(s) usuário(s) que estamos selecionando. Neste caso, estamos definindo algumas expressões computadas simples usando duas funções internas: e.counte e.str_upper; o construtor de consultas reflete toda a biblioteca padrão do EdgeDB .

Ah, e observe que o construtor de consultas inferiu corretamente o tipo de title_uppere cast_size! O resultado dessa consulta seria algo assim:

[
  {
    title: "Iron Man",
    title_upper: "IRON MAN",
    cast_size: 2
  },
  // etc.
]

Adicionando filtros

 

Para adicionar uma filtercláusula à sua consulta de seleção, inclua a filterchave especial em sua forma. Essa chave espera uma expressão booleana; mais comumente esta expressão irá envolver um operador como =, >=, ++, not, e or; operadores são expressos com a e.opfunção.

Abaixo, selecionamos todos os filmes com título contendo “matriz” (não diferencia maiúsculas de minúsculas).

TypeScript

e.select(e.Movie, (movie) => ({
  title: true,
  release_year: true,
  filter: e.op(movie.title, "ilike", "%matrix%"),
}));

EdgeQL

select Movie {
  title,
  release_year
} filter .title ilike "%matrix%"

Dependendo do tipo EdgeDB associado , as expressões podem ter várias propriedades e métodos. Por exemplo, expressões correspondentes a strvalores (como movie.titleacima) podem ser facilmente indexadas e segmentadas. (Isso também é verdade para expressões array, tuple, e .)json

Lembre-se que movie.titlenão é uma string! É um objeto que representa uma consulta que retorna uma string. Além disso, movie.title[0]retorna ainda outra expressão. O construtor de consultas implementa essa “indexação mágica” com a ajuda da API Proxy .

Podemos usar isso para selecionar todos os filmes que começam com a letra “A”.

TypeScript

e.select(e.Movie, (movie) => ({
  title: true,
  release_year: true,
  filter: e.op(movie.title[0], "=", "A"),
}));

EdgeQL

select Movie {
  title,
  release_year
} filter .title[0] = "A"

Uma API plana

Neste ponto, você pode estar pensando que a forma está ficando um pouco cheia. Por que estamos usando um único objeto para definir nossa seleção de campo, propriedades computadas e filtros? Não haverá conflitos importantes?

Na verdade não! Esta é uma decisão muito intencional. O EdgeQL reserva certas palavras-chave como “filtro” para que não possa ser facilmente usado como uma propriedade ou nome de link. Quanto aos campos computados, eles não podem “sobrescrever” uma propriedade/link dentro de uma forma de seleção; O TypeScript (e EdgeQL) gerará um erro.

Com essa API, cada camada de profundidade de consulta corresponde a uma única camada de aninhamento de objetos.

e.select(e.Movie, (movie) => ({ id: true, title: true, actors: (actor) => ({ name: true, filter: e.op(actor.name, "ilike", "chris") }), filter: e.op(movie.release_year, "=", 2022) }));

Prisma vs EdgeDB

Compare isso com a sintaxe mais detalhada dos ORMs JavaScript modernos. O Prisma requer duas camadas de aninhamento de objetos para cada camada adicional de profundidade de consulta. Aqui está a mesma consulta expressa com o cliente Prisma.

prisma.movie.findMany({
  where: {
    release_year: {
      eq: 2022
    }
  },
  select: {
    id: true,
    title: true,
    actors: {
      select: {
        name: true
      },
      where: {
        name: {
          contains: "chris",
          mode: "insensitive"
        }
      }
    }
  }
});

Inferindo cardinalidade

 

O construtor de consultas é inteligente o suficiente para saber quando você está tentando selecionar um único objeto. Por exemplo:

const query = e.select(e.Movie, (movie) => ({
  title: true,
  filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
}));

const result = await query.run(client);
// { title: string } | null

O tipo de resultado inferido é { title: string } | null. Se você filtrar em uma propriedade não exclusiva como release_year, o resultado será uma matriz.

  const query = e.select(e.Movie, (movie) => ({
    title: true,
    filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
    filter: e.op(movie.release_year, '=', 2022)
  }));

  const result = await query.run(client);
  // { title: string }[]

O construtor de consultas detecta quando você filtra uma propriedade com uma restrição exclusiva (exclusividade) (por exemplo, .id) com o operador de igualdade ( =). Nessas circunstâncias, a consulta só pode retornar zero ou um objeto; isso se reflete no tipo inferido. Portanto, não há necessidade de APIs separadas para .findOnee .findMany— o construtor de consultas pode descobrir.

Ordenação e paginação

A order_bychave especial pode ser usada para especificar operações de ordenação no resultado de select, e limit/ offsetpode ser usado para paginação.

TypeScript

e.select(e.Movie, (movie) => ({
  title: true,
  order_by: e.count(movie.actors),
  limit: 10,
  offset: 40
}));

EdgeQL

select Movie {
  title
}
order by count(.actors)
limit 10
offset 40

A order_bychave suporta pedidos compostos com instruções personalizáveis ​​e políticas de manipulação de vazios.

e.select(e.Movie, (movie) => ({
  title: true,
  order_by: [
    {
      expression: e.count(movie.actors),
      direction: e.ASC,
      empty: e.EMPTY_LAST,
    },
    {
      expression: movie.title,
      direction: e.DESC,
    }
  ]
}));

Atualizando objetos

 

TypeScript

e.update(e.Movie, (movie) => ({
  filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
  set: {
    title: 'Avengers: Endgame',
  },
}));

EdgeQL

 e.update(e.Movie, (movie) => ({
  filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
  set: {
    title: 'Avengers: Endgame',
  },
}));

Atualizações autorreferenciais

 

O construtor de consultas é particularmente útil (ou melhor, os ORMs são particularmente ruins) ao definir propriedades para uma versão modificada de seu valor atual. Por exemplo, essa consulta cortaria espaços em branco extras de todos os títulos de filmes.

TypeScript

e.update(e.Movie, (movie) => ({
  set: {
    title: e.str_trim(movie.title),
  },
}));

EdgeQL

 update Movie
set {
  title := str_trim(.title)
}

Com um ORM, isso é inexprimível; você precisaria escrever um script para percorrer todos os filmes em seu banco de dados.

Atualizando links

 

Ao atualizar links , o construtor de consultas suporta sintaxe especial para anexar ou subtrair do conjunto de objetos vinculados.

TypeScript

const actors = e.select(e.Person, person => ({
  filter: e.op(person.name, 'in', e.set('Benedict Cumberbatch', 'Rachel McAdams'))
}));

const query = e.update(e.Movie, (movie) => ({
  filter: e.op(movie.title, '=', "Doctor Strange"),
  set: {
    actors: {'+=': actors},
  }
})).run(client);

EdgeQL

with actors := (
  select Person
  filter .name in {'Benedict Cumberbatch', 'Rachel McAdams'}
)
update Movie
filter .title = "Doctor Strange"
set {
  actors += actors
}

Compondo consultas

 

O updateexemplo anterior também demonstra um dos maiores pontos fortes do construtor de consultas: composicionalidade . Como a declaração e a execução de consultas são duas etapas distintas, é possível declarar partes de uma consulta separadamente e depois juntá-las. Escrever consultas complexas é como escrever um script.

Por exemplo, podemos compor várias expressões para realizar uma inserção + seleção aninhada em uma consulta.

TypeScript

const rdj = e.insert(e.Person, {
  name: "Robert Downey Jr."
});

const ironMan = e.insert(e.Movie, {
  title: "Iron Man",
  release_year: 2008,
  actors: rdj
});

const query = e.select(ironMan, () => ({
  title: true,
  release_year: true,
  num_actors: e.count(ironMan.actors)
}));

const result = await query.run(client);
// {title: string; release_year: number; num_actors: number}

EdgeQL

with
  rdj := (
    insert Person {
      name := "Robert Downey Jr."
    }
  ),
  ironMan := (
    insert Movie {
      title := "Iron Man",
      release_year := 2008
    }
  )
select ironMan {
  title,
  release_year,
  num_actors := count(.actors)
};

O construtor de consultas detecta que newMovieocorre várias vezes dentro querye o extrai em um withbloco (também conhecido como “expressão de tabela comum” no jargão SQL). Observe que há apenas um await. Não estamos executando rdj e ironMan; são subconsultas que são compostas na “superconsulta” final, que pode ser executada em uma única viagem de ida e volta ao banco de dados.

Usando parâmetros de consulta

 

Como a cereja do bolo final, o construtor de consultas facilita a parametrização de suas consultas. Isso permite que você use dados externos (digamos, o corpo de uma POSTsolicitação recebida) de maneira segura.

TypeScript

const query = e.params(
  { title: e.str, release_year: e.int64 },
  ($) => {
    return e.insert(e.Movie, {
      title: $.title,
      release_year: $.release_year,
    });
  }
);

const result = await query.run(client, {
  title: 'Thor: Love and Thunder',
  release_year: 2022,
});

EdgeQL

with
 title := <str>$title,
 release_year := <int64>$release_year
insert Movie {
 title := title,
 release_year := release_year
}

Para uma consulta parametrizada, você passa os parâmetros como segundo argumento para .run(); eles são fortemente tipados e validados em tempo de execução.

Comparação com ORMs

Espero que esteja claro nos exemplos acima, mas em termos de poder expressivo, o construtor de consultas está além de todos os ORMs que conhecemos. Em geral, os ORMs podem representar apenas operações de leitura/gravação relativamente básicas, enquanto uma linguagem de consulta adequada pode expressar muito mais:

  • modificações de string
  • indexação e fatiamento de iteráveis
  • agregações
  • matemática
  • lógica temporal
  • coalescência e padrões ( ??em JavaScript)
  • condicionais ( a ? b : cem JavaScript)
  • parametrização
  • definir lógica como unionouin
  • lógica de tipo e conversão
  • composicionalidade da consulta (expressões de tabela comuns AKA)
  • propriedades computadas
  • consultas polimórficas
  • atualizações autorreferenciais

Isso é para não falar da modelagem de esquema. O EdgeDB suporta ainda mais funcionalidades que faltam na maioria dos ORMs:

  • propriedades computadas — são propriedades “virtuais” correspondentes a um EdgeQL que é executado dinamicamente sempre que a propriedade é buscada
  • padrões calculados
  • lógica de restrição complexa
  • mixins de esquema (herança)
  • propriedades do link
  • uma gama completa de tipos numéricos ( int{16,|32|64}, float{32|64}, bigint, e decimal)
  • tipos temporais como durationdatas e horas que não reconhecem fuso horário
  • um sistema de migração nativo de banco de dados, incluindo um planejador e sistema de rastreamento

Direções futuras

O construtor de consultas está disponível desde o lançamento do EdgeDB 1.0 em fevereiro de 2022 e está estável e pronto para produção. O construtor de consultas foi atualizado recentemente para suportar todos os recursos do EdgeDB 2.0, como a e.groupinstrução e as variáveis ​​de esquema global. Esta postagem abrange apenas um subconjunto da funcionalidade completa do construtor de consultas; consulte a Documentação para uma visão mais abrangente!

Estamos lançando o EdgeDB 2.0 durante um evento de lançamento transmitido ao vivo em 28 de julho de 2022. Junte-se a nós para algumas palestras relâmpago sobre os maiores novos recursos e a primeira demonstração pública da nova interface de administração do EdgeDB.

Fonte: https://www.edgedb.com/blog/designing-the-ultimate-typescript-query-builder

#typescript