import { z } from 'zod'; import type { Attachment } from '../../forms/FormDataHelpers.js'; import { createViaForm } from '../../operators/createViaForm.js'; import { deleteById } from '../../operators/deleteById.js'; import { get } from '../../operators/get.js'; import { getById } from '../../operators/getById.js'; import { request } from '../../operators/request.js'; import { restoreById } from '../../operators/restoreById.js'; import { updateById } from '../../operators/updateById.js'; import { Caching, EntityMetadata, Pagination } from '../../shared.js'; import type { ThreekitAuthProps } from '../../ThreekitAuthProps.js'; import { Route } from '../Route.js'; export enum DatatableColumnType { Asset = 'Asset', String = 'String', Number = 'Number' } export const DatatableColumn = z.object({ name: z.string(), type: z.nativeEnum(DatatableColumnType) }); export type DatatableColumn = z.infer; export const Datatable = EntityMetadata.merge( z.object({ id: z.string().uuid(), orgId: z.string().uuid(), name: z.string(), delimiter: z.string().optional().nullable(), columnInfo: z.array(DatatableColumn) }) ); export type Datatable = z.infer; export const DatatableListing = Pagination.merge( z.object({ datatables: Datatable.omit({ columnInfo: true }) .merge(z.object({ columnInfo: z.string().uuid() })) .array() }) ); export type DatatableListing = z.infer; export const DatatableRow = z.record( z.string(), z.union([z.string(), z.number()]) ); export type DatatableRow = z.infer; export const DatatableRowListing = z.object({ id: z.string().uuid(), orgId: z.string().uuid(), tableId: z.string().uuid(), createdAt: z.string().optional(), value: DatatableRow }); export type DatatableRowListing = z.infer; export const DatatableRowsListing = Pagination.merge( z.object({ rows: z.array(DatatableRowListing) }) ); export type DatatableRowsListing = z.infer; export const CreateDatatableProps = Datatable.pick({ name: true, delimiter: true, columnInfo: true }); export type CreateDatatableProps = z.infer & { file: Attachment; }; export const UpdateDatatableProps = z.object({}); export type UpdateDatatableProps = z.infer & { file: Attachment; }; export const DatatableQueryProps = z.object({ name: z.string().optional(), deleted: z.boolean().optional() }); export type DatatableQueryProps = z.infer; // NOTE: this is purposely an array as it is easier for users to create, rather than a record export const DatatableRowQueryProps = z.record( z.string(), z.object({ column: z.string(), comparator: z.union([z.literal('='), z.literal('!=')]), value: z.string() }) ); export type DatatableRowQueryProps = z.infer; const API_ROUTE = `/api/datatables`; export class Datatables extends Route { constructor(auth: ThreekitAuthProps) { super(auth, API_ROUTE); } /* BEN: This route does not exist on the /datatables REST API endpoint as of Dec 6, 2023 healthcheck() { return healthcheck(this.context); }*/ create(createProps: CreateDatatableProps) { return createViaForm( this.context, createProps ); } deleteById(id: string) { const datatableId = z.string().uuid().parse(id); return deleteById(this.context, datatableId); } // TODO: Support streams on Node.JS to ensure low memory usage downloadCsvById(id: string) { const datatableId = z.string().uuid().parse(id); return request(this.context, { url: `${datatableId}/download`, responseType: 'text' }); } get(queryProps?: DatatableQueryProps) { return get(this.context, queryProps); } getById(id: string, caching: Caching = {}) { const datatableId = z.string().uuid().parse(id); return getById(this.context, datatableId, caching); } getRow( id: string, queryProps: DatatableRowQueryProps, caching: Caching = {} ) { // validate query is correct by iteresting over each element in the object, comparing its key name with the column name, they should be the same. Object.entries(queryProps).forEach(([key, value]) => { if (!Object.hasOwn(queryProps, key)) { return; } const column = value.column; if (key !== column) { throw new Error( `The key ${key} does not match the column name ${column}` ); } }); return request(this.context, { url: `${id}/row`, params: { where: queryProps, ...caching } }); } getRowsById(id: string, all = true, caching: Caching = {}) { const datatableId = z.string().uuid().parse(id); return request(this.context, { url: `${datatableId}/rows`, params: { all, ...caching } }); } restoreById = (id: string) => { const datatableId = z.string().uuid().parse(id); return restoreById(this.context, datatableId); }; updateById(id: string, updateProps: UpdateDatatableProps) { const datatableId = z.string().uuid().parse(id); return updateById( this.context, datatableId, updateProps ); } }