Saltar al contenido principal

6 publicaciones etiquetados con "Schemas"

TypeSafe definition of a data model

Ver Todas las Etiquetas

v0.19: Controller.set() por lotes, TypeScript más rápido

· 29 min de lectura
Nathaniel Tucker
Creator of Reactive Data Client
nota

El original en inglés cambió después de esta traducción, así que puede estar desactualizada.

v0.19 permite que un Manager escriba un lote completo de filas recibidas por streaming en una sola actualización del store, verifica los tipos del código de endpoints unas 2 veces más rápido y corrige una ronda de problemas de TypeScript y Vue. Los agentes de programación también reciben un plugin de Claude Code, skills de Vue y llms.txt; consulta Data Client para agentes de programación.

Nuevas APIs:

Rendimiento:

Otras mejoras:

  • Se corrige la resolución de módulos de TypeScript 7 para los exports del paquete; los imports ahora resuelven a los archivos de declaración (#4019)
  • renderDataHook() ejecuta los efectos de montaje del provider cuando el primer renderizado suspende (#4099)
  • Los valores de Controller.set() se tipan según el schema, así que ctrl.set(new schema.All(Todo), 42) es un error de TypeScript (#4133)
  • Controller.set() elimina una sola entidad con un schema Invalidate sin la solución alternativa del lote de una fila (#4230)
  • process() recibe params tipados en new RestEndpoint(), .extend() y resource().extend(), así que un parámetro de ruta incorrecto es un error de TypeScript en lugar de undefined en tiempo de ejecución (#4183, #4184)
  • Los valores de retorno de getOptimisticResponse() se tipan según el schema, así que un tipo de campo incorrecto es un error de TypeScript en lugar de un destello de datos erróneos (#4198)
  • useSuspense() y useLive() de Vue conservan los datos anteriores mientras cargan los nuevos argumentos, en lugar de devolver undefined (#4131)
  • useSuspense() y useLive() de Vue envían los errores de obtención posteriores a un cambio de argumentos a onErrorCaptured() en lugar de producir un rechazo de promesa no manejado (#4135)
  • useSuspense(), useDLE() y useFetch() de Vue ya no vuelven a obtener los datos obsoletos en cada actualización del store, así que un controller.set() no se sobrescribe (#4134)
  • useSuspense() de Vue muestra los datos obsoletos al montarse y vuelve a obtenerlos en segundo plano, como en React, en lugar de mostrar el fallback de <Suspense> hasta que termina la nueva obtención (#4169)
  • useDLE() y useCache() de Vue conservan los datos invalidIfStale caducados a través de actualizaciones del store no relacionadas, en lugar de quedarse atascados cargando (#4142)
  • DataClientPlugin de Vue se instala en versiones de Vue anteriores a 3.5 en lugar de lanzar app.onUnmount is not a function (#4146)
  • DataClientPlugin de Vue recolecta basura de forma predeterminada, igual que DataProvider de React, así que ya no necesitas pasar gcPolicy; cómo decide qué eliminar (#4148)
  • Los composables de Vue aceptan argumentos getter como () => ({ id: props.id }); estaban tipados para permitirlos pero pasaban la propia función al endpoint (#4115)
  • Se corrigen los errores Cannot find name 'NoInfer' y export type en TypeScript 4.x con skipLibCheck desactivado (#4138)
  • Se corrigen los errores de tipo de Entity, Endpoint, Union y RestEndpoint en TypeScript 4.0–4.5 con skipLibCheck desactivado (#4140)
  • Se corrigen los errores ',' expected al importar @data-client/react/redux, @data-client/react/nextjs o @data-client/test en TypeScript 4.0–4.4 (#4232)
  • Se corrige que act y renderHook de @data-client/test se tiparan como any en TypeScript 4.0–4.4 (#4251)
  • Las clases Entity pueden usarse donde se espera un EntityInterface (#4149); prepara las sobrescrituras de pk() para una versión futura
  • useCache() y useDLE() devuelven undefined para una entidad eliminada cuya nueva obtención falló, en lugar de un Symbol verdadero que se colaba por las comprobaciones if (!data); elimina cualquier solución alternativa (#4150)
  • useFetch() de Vue evita que sus datos sean recolectados como basura mientras está montado, como useSuspense(), así que un gcPolicy configurado ya no expulsa los datos precargados que los componentes leen después (#4152)
  • Las aplicaciones con Redux DevTools abierto ya no se entrecortan en desarrollo con stores grandes o actualizaciones frecuentes; cada actualización se serializa entre 40 y 60 veces más rápido (#4163)
  • resource().extend() con un objeto de sobrescrituras de endpoints conserva en su tipo los endpoints añadidos antes con .extend('name', options), así que usarlos ya no es un error de TypeScript (#4184)
  • RestEndpoint.push, unshift y remove aceptan los campos de la Entity como cuerpo cuando un Query o Lazy envuelve la Collection, en lugar de solo FormData (#4226)
  • useFetch() de Vue se tipa como el Ref de solo lectura que devuelve, así que promise.resolved (siempre undefined) es ahora un error de TypeScript; lee promise.value.resolved en su lugar (#4114)
  • Una subclase de Controller pasada a DataProvider o DataClientPlugin ya no necesita una conversión de tipo para verificar los tipos (#4228)
  • El store de prepareStore() puede pasarse al <Provider> de react-redux sin una conversión de tipo, y tiene dispatch() en su tipo (#4238)
  • resource().create acepta FormData sin una conversión de tipo, como update, partialUpdate y getList.push (#4235)
  • RestEndpoint.push, unshift y remove envían un cuerpo de tipo array como JSON, así que crear o eliminar varios elementos a la vez ya no envía "[object Object]" a tu servidor; resource().create también acepta un array (#4262)
  • createDataClient() de Vue queda obsoleto, ya que es interno de DataClientPlugin; instala el plugin con app.use(DataClientPlugin, options) en lugar de llamarlo directamente (#4193)
  • waitForNextUpdate() de renderDataCompose() de Vue queda obsoleto, ya que se rinde en silencio después de 1 segundo y deja pasar una prueba mientras sigue suspendida; usa await result en su lugar, o await allSettled() después de un cambio, que ahora también espera las obtenciones que inicia un cambio de prop (pruebas unitarias de composables, #4221)

Cambios incompatibles:

Prepárate para versiones futuras:

v0.18: Scalar, descargas de archivos tipadas, Collections conscientes de filtros

· 13 min de lectura
Nathaniel Tucker
Creator of Reactive Data Client

v0.18 se centra en un modelado de datos más rico para valores que varían según el contexto de la petición, descargas tipadas más simples y una coincidencia de collections que refleja mejor los filtros reales de las APIs.

Nuevas funcionalidades:

Otras mejoras:

Fixtures
▶api/Company
import { Collection, Entity, RestEndpoint, Scalar } from '@data-client/rest';

export class Company extends Entity {
  id = '';
  name = '';
  price = 0;
  pct_equity = 0;
  shares = 0;
}

const PortfolioScalar = new Scalar({
  lens: args => args[0]?.portfolio,
  key: 'portfolio',
  entity: Company,
});
Company.schema = {
  pct_equity: PortfolioScalar,
  shares: PortfolioScalar,
};

export const getCompanies = new RestEndpoint({
  path: '/companies',
  searchParams: {} as { portfolio: string },
  // `portfolio` is a lens, not a filter — the returned Company list is the same
  // regardless of lens. Dropping it from `argsKey` collapses every portfolio to
  // one Collection pk, so `Collection.queryKey()` finds the list on every
  // switch and `useSuspense` reuses it without refetching.
  schema: new Collection([Company], { argsKey: () => ({}) }),
});

export const getPortfolioColumns = new RestEndpoint({
  path: '/companies/columns',
  searchParams: {} as { portfolio: string },
  schema: new Collection([PortfolioScalar], {
    argsKey: ({ portfolio }: { portfolio: string }) => ({ portfolio }),
  }),
});
▶CompanyGrid
▶PortfolioGrid
Resultado
Store▶

El ejemplo muestra las mismas entidades Company renderizadas a través de distintos lenses de portafolio. Los campos estables de la Entity se reutilizan, mientras que los valores dependientes del lens se seleccionan de celdas Scalar separadas.

Cambios incompatibles:

¿Usas un agente de IA? Instala la skill de migración: ejecuta el codemod, registra argsKey para los schemas que dependen de los argumentos y se encarga de los casos que el codemod no puede resolver (campos de clase, asignación dinámica de métodos, bloques <script> de SFC, helpers personalizados):

npx skills add reactive/data-client --skill data-client-v0.18-migration

De lo contrario, ejecuta el codemod directamente; cubre los casos comunes:

npx jscodeshift -t https://dataclient.io/codemods/v0.18.js --extensions=ts,tsx,js,jsx src/

v0.16: Obtención en paralelo, Collection.move, Lazy

· 14 min de lectura
Nathaniel Tucker
Creator of Reactive Data Client

Nuevas funcionalidades:

Otras mejoras:

Collection.move facilita mover entidades entre Collections con una sola operación:

import { useController } from '@data-client/react';
import { TaskResource, type Task } from './TaskResource';

export default function TaskCard({ task }: { task: Task }) {
  const handleMove = () => ctrl.fetch(
    TaskResource.getList.move,
    { id: task.id },
    { id: task.id, status: task.status === 'backlog' ? 'in-progress' : 'backlog' },
  );
  const ctrl = useController();
  return (
    <div className="listItem">
      <span style={{ flex: 1 }}>{task.title}</span>
      <button onClick={handleMove}>
        {task.status === 'backlog' ? '\u25bc' : '\u25b2'}
      </button>
    </div>
  );
}
Resultado

Cambios incompatibles:

Actualiza con un codemod automatizado para todos los cambios incompatibles:

npx jscodeshift -t https://dataclient.io/codemods/v0.16.js --extensions=ts,tsx,js,jsx src/

v0.15: Vue 3, Collection.remove, actualización de useDebounce

· 10 min de lectura
Nathaniel Tucker
Creator of Reactive Data Client

Nuevas plataformas:

  • Vue 3 con composables completos: useSuspense, useCache, useDLE, useController, useQuery

Nuevas funcionalidades:

Rendimiento:

Cambios incompatibles:

Actualiza con un codemod automatizado para los cambios incompatibles más comunes:

npx jscodeshift -t https://dataclient.io/codemods/v0.15.js --extensions=ts,tsx,js,jsx src/

v0.11 Queries, Queryable y useQuery

· 12 min de lectura
Nathaniel Tucker
Creator of Reactive Data Client

Además de las ventajas de rendimiento e integridad de datos de normalizar el estado, obtenemos el beneficio adicional de poder acceder al store y consultarlo directamente de forma segura.

En esta versión ajustamos y simplificamos esta funcionalidad unificándola en torno al concepto de Schemas Queryable. Estos incluyen Entity, All, Collection, Query y Union

El mayor impacto de este cambio es la introducción de un nuevo hook, useQuery(), que permite búsquedas directas en el store usando los Schemas Queryable.

class User extends Entity {
username = '';
id = '';
groupId = '';
pk() {
return this.id;
}
static index = ['username' as const];
}

const bob = useQuery(User, { username: 'bob' });
const bob = useQuery(User, { id: '5' });

De manera similar, podemos buscar Queryables con el controller y el snapshot usando controller.get

const bob = snapshot.get(User, { username: 'bob' });

Adicionalmente, hemos invertido en más mejoras de rendimiento, que resultan en un aumento de rendimiento de alrededor de 2x para la mayoría de las operaciones y en que las Queries sean 16x más rápidas.

Guía de migración

Cambios incompatibles:

Otros puntos destacados:

v0.10: Manejo consistente de null, utilidades de URL

· 4 min de lectura
Nathaniel Tucker
Creator of Reactive Data Client

Esta versión se centra en la consistencia y la extensibilidad. El manejo de null ahora es uniforme en todos los tipos de schema, lo que hace que las transformaciones de datos sean más predecibles. Las nuevas utilidades de URL permiten construir URLs personalizadas y codificar parámetros de búsqueda.

class MyEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
searchToString(searchParams) {
// Use qs library for complex nested object encoding
return qs.stringify(searchParams);
}
}
Guía de migración

Cambios incompatibles:

Otras mejoras: