Use basic infer map of Sequelize models. Implement local and server-side deletion of notes.feature/transactions
@@ -7,7 +7,7 @@ import generateId from '../utilities/Id' | |||||
import Link from 'next/link' | import Link from 'next/link' | ||||
import { formatDate } from '../utilities/Date' | import { formatDate } from '../utilities/Date' | ||||
import { useRouter } from 'next/router' | import { useRouter } from 'next/router' | ||||
import { Trash2, FilePlus, FolderPlus } from 'react-feather' | |||||
import { Trash2, FilePlus, FolderPlus, FileText, GitBranch, User } from 'react-feather' | |||||
const Navbar = styled('aside')({ | const Navbar = styled('aside')({ | ||||
width: 360, | width: 360, | ||||
@@ -22,6 +22,43 @@ const Navbar = styled('aside')({ | |||||
}, | }, | ||||
}) | }) | ||||
const PrimaryNavItems = styled('nav')({ | |||||
height: '100%', | |||||
display: 'flex', | |||||
flexDirection: 'column', | |||||
justifyContent: 'space-between', | |||||
alignItems: 'stretch', | |||||
'@media (min-width: 1080px)': { | |||||
width: '4rem', | |||||
}, | |||||
}) | |||||
const SecondaryNavItems = styled('nav')({ | |||||
height: '100%', | |||||
position: 'relative', | |||||
'::before': { | |||||
content: "''", | |||||
display: 'block', | |||||
top: 0, | |||||
left: 0, | |||||
width: '100%', | |||||
height: '100%', | |||||
position: 'absolute', | |||||
zIndex: -1, | |||||
backgroundColor: 'white', | |||||
opacity: 0.5, | |||||
}, | |||||
'@media (min-width: 1080px)': { | |||||
flex: 'auto', | |||||
}, | |||||
}) | |||||
const SecondaryNavItemsOverflow = styled('div')({ | |||||
overflow: 'auto', | |||||
width: '100%', | |||||
height: '100%', | |||||
}) | |||||
const Main = styled('main')({ | const Main = styled('main')({ | ||||
margin: '2rem 0', | margin: '2rem 0', | ||||
'@media (min-width: 1080px)': { | '@media (min-width: 1080px)': { | ||||
@@ -40,11 +77,17 @@ const Container = styled('div')({ | |||||
}, | }, | ||||
}) | }) | ||||
const NavbarContainer = styled('span')({ | |||||
const NavbarItems = styled('div')({ | |||||
display: 'flex', | |||||
width: '100%', | |||||
height: '100%', | |||||
}) | |||||
const NavbarContainer = styled('div')({ | |||||
display: 'block', | display: 'block', | ||||
width: '100%', | width: '100%', | ||||
height: '100%', | |||||
margin: '0 0 0 auto', | margin: '0 0 0 auto', | ||||
padding: '0 1rem', | |||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
maxWidth: 360, | maxWidth: 360, | ||||
}) | }) | ||||
@@ -59,7 +102,6 @@ const TitleInput = styled('input')({ | |||||
fontSize: '3rem', | fontSize: '3rem', | ||||
fontWeight: 'bold', | fontWeight: 'bold', | ||||
outline: 0, | outline: 0, | ||||
marginBottom: '2rem', | |||||
}) | }) | ||||
const NoteLink = styled('a')({ | const NoteLink = styled('a')({ | ||||
@@ -127,6 +169,30 @@ const BinIcon = styled(Trash2)({ | |||||
marginRight: '0.5rem', | marginRight: '0.5rem', | ||||
}) | }) | ||||
const NavbarItemContent = styled('span')({ | |||||
padding: '0 1rem', | |||||
boxSizing: 'border-box', | |||||
}) | |||||
const PrimaryNavItem = styled('a')({ | |||||
width: '4rem', | |||||
height: '4rem', | |||||
display: 'grid', | |||||
placeContent: 'center', | |||||
color: 'inherit', | |||||
}) | |||||
const PostMeta = styled('small')({ | |||||
opacity: 0.5, | |||||
height: '1.25rem', | |||||
display: 'block', | |||||
lineHeight: 1.25, | |||||
}) | |||||
const PostPrimary = styled('div')({ | |||||
marginBottom: '2rem', | |||||
}) | |||||
type NoteInstance = { id: string, title: string, content?: object, updatedAt: string, } | type NoteInstance = { id: string, title: string, content?: object, updatedAt: string, } | ||||
const Notes = ({ id: idProp }) => { | const Notes = ({ id: idProp }) => { | ||||
@@ -145,7 +211,7 @@ const Notes = ({ id: idProp }) => { | |||||
timeoutRef.current = window.setTimeout(async () => { | timeoutRef.current = window.setTimeout(async () => { | ||||
const newNote = await Storage.saveNote({ | const newNote = await Storage.saveNote({ | ||||
...stateRef.current, | ...stateRef.current, | ||||
updatedAt: new Date().toISOString(), | |||||
updatedAt: (stateRef.current.updatedAt = new Date().toISOString()), | |||||
}) | }) | ||||
if (router.query.id !== id) { | if (router.query.id !== id) { | ||||
await router.push(`/notes/${id}`, undefined, { shallow: true }) | await router.push(`/notes/${id}`, undefined, { shallow: true }) | ||||
@@ -174,6 +240,14 @@ const Notes = ({ id: idProp }) => { | |||||
autoSave() | autoSave() | ||||
} | } | ||||
const deleteNote = note => async () => { | |||||
setNotes(notes.filter(n => n.id !== note.id)) | |||||
const result = await Storage.deleteNote(note) | |||||
if (!result) { | |||||
setNotes(notes) | |||||
} | |||||
} | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const loadNotes = async () => { | const loadNotes = async () => { | ||||
const theNotes = await Storage.loadNotes() | const theNotes = await Storage.loadNotes() | ||||
@@ -210,121 +284,168 @@ const Notes = ({ id: idProp }) => { | |||||
<link rel="icon" href="/favicon.ico" /> | <link rel="icon" href="/favicon.ico" /> | ||||
</Head> | </Head> | ||||
<Navbar> | <Navbar> | ||||
<Link | |||||
href={{ | |||||
pathname: '/profile', | |||||
}} | |||||
passHref | |||||
> | |||||
<NoteLink> | |||||
<NavbarContainer> | |||||
<NoteLinkPrimary> | |||||
<NewFolderIcon /> | |||||
<NoteLinkTitle> | |||||
Personal | |||||
</NoteLinkTitle> | |||||
</NoteLinkPrimary> | |||||
</NavbarContainer> | |||||
</NoteLink> | |||||
</Link> | |||||
<Link | |||||
href={{ | |||||
pathname: '/folders/new', | |||||
}} | |||||
passHref | |||||
> | |||||
<NoteLink> | |||||
<NavbarContainer> | |||||
<NoteLinkPrimary> | |||||
<NewFolderIcon /> | |||||
<NoteLinkTitle> | |||||
Create Folder | |||||
</NoteLinkTitle> | |||||
</NoteLinkPrimary> | |||||
</NavbarContainer> | |||||
</NoteLink> | |||||
</Link> | |||||
<Link | |||||
href={{ | |||||
pathname: '/notes', | |||||
}} | |||||
passHref | |||||
> | |||||
<NoteLink> | |||||
<NavbarContainer> | |||||
<NoteLinkPrimary> | |||||
<NewIcon /> | |||||
<NoteLinkTitle> | |||||
Create Note | |||||
</NoteLinkTitle> | |||||
</NoteLinkPrimary> | |||||
</NavbarContainer> | |||||
</NoteLink> | |||||
</Link> | |||||
{ | |||||
Array.isArray(notes!) | |||||
&& notes.map(n => ( | |||||
<LinkContainer | |||||
key={n.id} | |||||
> | |||||
{ | |||||
n.id === id | |||||
&& ( | |||||
<NoteLinkBackground /> | |||||
) | |||||
} | |||||
<Link | |||||
href={{ | |||||
pathname: '/notes/[id]', | |||||
query: { id: n.id }, | |||||
}} | |||||
passHref | |||||
> | |||||
<NoteLink> | |||||
<NavbarContainer> | |||||
<NoteLinkPrimary> | |||||
<NoteLinkTitle | |||||
style={{ opacity: n.title.length > 0 ? 1 : 0.5, }} | |||||
<NavbarContainer> | |||||
<NavbarItems> | |||||
<PrimaryNavItems> | |||||
<div> | |||||
<Link | |||||
href={{ | |||||
pathname: '/notes', | |||||
}} | |||||
passHref | |||||
> | |||||
<PrimaryNavItem> | |||||
<FileText /> | |||||
</PrimaryNavItem> | |||||
</Link> | |||||
<Link | |||||
href={{ | |||||
pathname: '/graph', | |||||
}} | |||||
passHref | |||||
> | |||||
<PrimaryNavItem> | |||||
<GitBranch /> | |||||
</PrimaryNavItem> | |||||
</Link> | |||||
</div> | |||||
<div> | |||||
<Link | |||||
href={{ | |||||
pathname: '/me', | |||||
}} | |||||
passHref | |||||
> | |||||
<PrimaryNavItem> | |||||
<User /> | |||||
</PrimaryNavItem> | |||||
</Link> | |||||
</div> | |||||
</PrimaryNavItems> | |||||
<SecondaryNavItems> | |||||
<SecondaryNavItemsOverflow> | |||||
<Link | |||||
href={{ | |||||
pathname: '/profile', | |||||
}} | |||||
passHref | |||||
> | |||||
<NoteLink> | |||||
<NavbarItemContent> | |||||
<NoteLinkPrimary> | |||||
<NewFolderIcon /> | |||||
<NoteLinkTitle> | |||||
Personal | |||||
</NoteLinkTitle> | |||||
</NoteLinkPrimary> | |||||
</NavbarItemContent> | |||||
</NoteLink> | |||||
</Link> | |||||
<Link | |||||
href={{ | |||||
pathname: '/folders/new', | |||||
}} | |||||
passHref | |||||
> | |||||
<NoteLink> | |||||
<NavbarItemContent> | |||||
<NoteLinkPrimary> | |||||
<NewFolderIcon /> | |||||
<NoteLinkTitle> | |||||
Create Folder | |||||
</NoteLinkTitle> | |||||
</NoteLinkPrimary> | |||||
</NavbarItemContent> | |||||
</NoteLink> | |||||
</Link> | |||||
<Link | |||||
href={{ | |||||
pathname: '/notes', | |||||
}} | |||||
passHref | |||||
> | |||||
<NoteLink> | |||||
<NavbarItemContent> | |||||
<NoteLinkPrimary> | |||||
<NewIcon /> | |||||
<NoteLinkTitle> | |||||
Create Note | |||||
</NoteLinkTitle> | |||||
</NoteLinkPrimary> | |||||
</NavbarItemContent> | |||||
</NoteLink> | |||||
</Link> | |||||
{ | |||||
Array.isArray(notes!) | |||||
&& notes.map(n => ( | |||||
<LinkContainer | |||||
key={n.id} | |||||
> | |||||
{ | |||||
n.id === id | |||||
&& ( | |||||
<NoteLinkBackground /> | |||||
) | |||||
} | |||||
<Link | |||||
href={{ | |||||
pathname: '/notes/[id]', | |||||
query: { id: n.id }, | |||||
}} | |||||
replace | |||||
passHref | |||||
> | > | ||||
{n.title.length > 0 ? n.title : '(untitled)'} | |||||
</NoteLinkTitle> | |||||
</NoteLinkPrimary> | |||||
{' '} | |||||
<small> | |||||
<time | |||||
dateTime={new Date(n.updatedAt).toISOString()} | |||||
> | |||||
Last updated {formatDate(new Date(n.updatedAt))} | |||||
</time> | |||||
</small> | |||||
</NavbarContainer> | |||||
</NoteLink> | |||||
</Link> | |||||
<NoteActions> | |||||
<NoteAction> | |||||
<Trash2 /> | |||||
</NoteAction> | |||||
</NoteActions> | |||||
</LinkContainer> | |||||
)) | |||||
} | |||||
<Link | |||||
href={{ | |||||
pathname: '/bin', | |||||
}} | |||||
passHref | |||||
> | |||||
<NoteLink> | |||||
<NavbarContainer> | |||||
<NoteLinkPrimary> | |||||
<BinIcon /> | |||||
<NoteLinkTitle> | |||||
View Binned Notes | |||||
</NoteLinkTitle> | |||||
</NoteLinkPrimary> | |||||
</NavbarContainer> | |||||
</NoteLink> | |||||
</Link> | |||||
<NoteLink> | |||||
<NavbarItemContent> | |||||
<NoteLinkPrimary> | |||||
<NoteLinkTitle | |||||
style={{ opacity: n.title.length > 0 ? 1 : 0.5, }} | |||||
> | |||||
{n.title.length > 0 ? n.title : '(untitled)'} | |||||
</NoteLinkTitle> | |||||
</NoteLinkPrimary> | |||||
{' '} | |||||
<PostMeta> | |||||
<time | |||||
dateTime={new Date(n.updatedAt).toISOString()} | |||||
> | |||||
Last updated {formatDate(new Date(n.updatedAt))} | |||||
</time> | |||||
</PostMeta> | |||||
</NavbarItemContent> | |||||
</NoteLink> | |||||
</Link> | |||||
<NoteActions> | |||||
<NoteAction | |||||
onClick={deleteNote(n)} | |||||
> | |||||
<Trash2 /> | |||||
</NoteAction> | |||||
</NoteActions> | |||||
</LinkContainer> | |||||
)) | |||||
} | |||||
<Link | |||||
href={{ | |||||
pathname: '/bin', | |||||
}} | |||||
passHref | |||||
> | |||||
<NoteLink> | |||||
<NavbarItemContent> | |||||
<NoteLinkPrimary> | |||||
<BinIcon /> | |||||
<NoteLinkTitle> | |||||
View Binned Notes | |||||
</NoteLinkTitle> | |||||
</NoteLinkPrimary> | |||||
</NavbarItemContent> | |||||
</NoteLink> | |||||
</Link> | |||||
</SecondaryNavItemsOverflow> | |||||
</SecondaryNavItems> | |||||
</NavbarItems> | |||||
</NavbarContainer> | |||||
</Navbar> | </Navbar> | ||||
<Main> | <Main> | ||||
<Container> | <Container> | ||||
@@ -332,11 +453,26 @@ const Notes = ({ id: idProp }) => { | |||||
Array.isArray(notes!) | Array.isArray(notes!) | ||||
&& ( | && ( | ||||
<React.Fragment> | <React.Fragment> | ||||
<TitleInput | |||||
placeholder="Title" | |||||
value={title} | |||||
onChange={handleTitleChange} | |||||
/> | |||||
<PostPrimary> | |||||
<TitleInput | |||||
placeholder="Title" | |||||
value={title} | |||||
onChange={handleTitleChange} | |||||
/> | |||||
<PostMeta> | |||||
{ | |||||
stateRef.current.updatedAt | |||||
&& router.query.id | |||||
&& ( | |||||
<time | |||||
dateTime={new Date(stateRef.current.updatedAt).toISOString()} | |||||
> | |||||
Last updated {formatDate(new Date(stateRef.current.updatedAt))} | |||||
</time> | |||||
) | |||||
} | |||||
</PostMeta> | |||||
</PostPrimary> | |||||
<Editor | <Editor | ||||
autoFocus={false} | autoFocus={false} | ||||
key={id} | key={id} | ||||
@@ -37,6 +37,7 @@ export const item = (Model, Service) => async (req, res) => { | |||||
const methodHandlers = { | const methodHandlers = { | ||||
'GET': Service.getSingle(repository), | 'GET': Service.getSingle(repository), | ||||
'PUT': Service.save(repository)(req.body), | 'PUT': Service.save(repository)(req.body), | ||||
'DELETE': Service.remove(repository) | |||||
} | } | ||||
const { [req.method as keyof typeof methodHandlers]: handler = null } = methodHandlers | const { [req.method as keyof typeof methodHandlers]: handler = null } = methodHandlers | ||||
@@ -50,7 +51,11 @@ export const item = (Model, Service) => async (req, res) => { | |||||
try { | try { | ||||
const { status, data, } = await handler(id) | const { status, data, } = await handler(id) | ||||
res.statusCode = status | res.statusCode = status | ||||
res.json(data) | |||||
if (data) { | |||||
res.json(data) | |||||
return | |||||
} | |||||
res.end() | |||||
} catch (err) { | } catch (err) { | ||||
console.log('ERROR', err) | console.log('ERROR', err) | ||||
const { status, data, } = err | const { status, data, } = err | ||||
@@ -1,7 +1,8 @@ | |||||
import Model from '../../models/Folder' | |||||
import FolderModel from '../../models/Folder' | |||||
import Instance from '../utilities/Instance' | import Instance from '../utilities/Instance' | ||||
import * as Response from '../utilities/Response' | import * as Response from '../utilities/Response' | ||||
type ModelInstance = Instance<typeof Model.rawAttributes> | |||||
type Folder = Instance<typeof FolderModel> | |||||
export const getSingle = repository => async (id: string) => { | export const getSingle = repository => async (id: string) => { | ||||
const instance = await repository.findByPk(id) | const instance = await repository.findByPk(id) | ||||
@@ -20,7 +21,7 @@ export const getMultiple = repository => async (query: Record<string, unknown>) | |||||
}) | }) | ||||
} | } | ||||
export const save = repository => (body: Partial<ModelInstance>) => async (id: string, idColumnName = 'id') => { | |||||
export const save = repository => (body: Partial<Folder>) => async (id: string, idColumnName = 'id') => { | |||||
const [newInstance, created] = await repository.findOrCreate({ | const [newInstance, created] = await repository.findOrCreate({ | ||||
where: { [idColumnName]: id }, | where: { [idColumnName]: id }, | ||||
defaults: { | defaults: { | ||||
@@ -44,3 +45,12 @@ export const save = repository => (body: Partial<ModelInstance>) => async (id: s | |||||
data: updatedInstance.toJSON() | data: updatedInstance.toJSON() | ||||
}) | }) | ||||
} | } | ||||
export const remove = repository => async (id: string) => { | |||||
const instanceDAO = repository.findByPk(id) | |||||
if (instanceDAO === null) { | |||||
throw new Response.NotFound({ message: 'Not found.' }) | |||||
} | |||||
await instanceDAO.destroy() | |||||
return new Response.Destroyed() | |||||
} |
@@ -1,7 +1,8 @@ | |||||
import Model from '../../models/Note' | import Model from '../../models/Note' | ||||
import Instance from '../utilities/Instance' | |||||
import InferType from '../utilities/Instance' | |||||
import * as Response from '../utilities/Response' | import * as Response from '../utilities/Response' | ||||
type ModelInstance = Instance<typeof Model.rawAttributes> | |||||
type ModelInstance = InferType<typeof Model> | |||||
export const getSingle = repository => async (id: string) => { | export const getSingle = repository => async (id: string) => { | ||||
const instanceDAO = await repository.findByPk(id) | const instanceDAO = await repository.findByPk(id) | ||||
@@ -66,3 +67,12 @@ export const save = repository => (body: Partial<ModelInstance>) => async (id: s | |||||
} | } | ||||
}) | }) | ||||
} | } | ||||
export const remove = repository => async (id: string) => { | |||||
const instanceDAO = await repository.findByPk(id) | |||||
if (instanceDAO === null) { | |||||
throw new Response.NotFound({ message: 'Not found.' }) | |||||
} | |||||
await instanceDAO.destroy() | |||||
return new Response.Destroyed() | |||||
} |
@@ -1,13 +1,13 @@ | |||||
import {addTime, TimeDivision} from '../utilities/Date' | |||||
import { addTime, TimeDivision } from '../utilities/Date' | |||||
import * as Serialization from '../utilities/Serialization' | import * as Serialization from '../utilities/Serialization' | ||||
import LocalStorage from './LocalStorage' | import LocalStorage from './LocalStorage' | ||||
type LoadItemParams = { | |||||
type StorageParams = { | |||||
id: string, | id: string, | ||||
url: string, | url: string, | ||||
} | } | ||||
type LoadItems = <T extends Record<string, unknown>>(params: LoadItemParams) => () => Promise<T[]> | |||||
type LoadItems = <T extends Record<string, unknown>>(params: StorageParams) => () => Promise<T[]> | |||||
const loadItems: LoadItems = <T extends Record<string, unknown>>(params) => async (): Promise<T[]> => { | const loadItems: LoadItems = <T extends Record<string, unknown>>(params) => async (): Promise<T[]> => { | ||||
const { id, url, } = params | const { id, url, } = params | ||||
@@ -39,7 +39,7 @@ const loadItems: LoadItems = <T extends Record<string, unknown>>(params) => asyn | |||||
return localData.items | return localData.items | ||||
} | } | ||||
type SaveItem = <T extends Record<string, unknown>>(p: LoadItemParams) => (item: T) => Promise<T> | |||||
type SaveItem = <T extends Record<string, unknown>>(p: StorageParams) => (item: T) => Promise<T> | |||||
const saveItem: SaveItem = <T extends Record<string, unknown>>(params) => async (item) => { | const saveItem: SaveItem = <T extends Record<string, unknown>>(params) => async (item) => { | ||||
const { id: storageId, url } = params | const { id: storageId, url } = params | ||||
@@ -50,10 +50,9 @@ const saveItem: SaveItem = <T extends Record<string, unknown>>(params) => async | |||||
) | ) | ||||
const localData = storage.getItem(storageId, { | const localData = storage.getItem(storageId, { | ||||
expiry: addTime(new Date(), 30)(TimeDivision.DAYS).getTime(), | expiry: addTime(new Date(), 30)(TimeDivision.DAYS).getTime(), | ||||
items: [] | |||||
items: [], | |||||
}) | }) | ||||
console.log(localData) | |||||
const localItems = localData.items | |||||
const { items: localItems } = localData | |||||
const { id: itemId, ...theBody } = item | const { id: itemId, ...theBody } = item | ||||
const theItems: T[] = ( | const theItems: T[] = ( | ||||
localItems.some(i => i.id === itemId) | localItems.some(i => i.id === itemId) | ||||
@@ -76,7 +75,32 @@ const saveItem: SaveItem = <T extends Record<string, unknown>>(params) => async | |||||
return responseBody as T | return responseBody as T | ||||
} | } | ||||
type DeleteItem = <T extends Record<string, unknown>>(p: StorageParams) => (item: T) => Promise<boolean> | |||||
const deleteItem: DeleteItem = <T extends Record<string, unknown>>(params) => async (item) => { | |||||
const { id: storageId, url } = params | |||||
const storage = new LocalStorage( | |||||
window.localStorage, | |||||
Serialization.serialize, | |||||
Serialization.deserialize | |||||
) | |||||
const localData = storage.getItem(storageId, { | |||||
expiry: addTime(new Date(), 30)(TimeDivision.DAYS).getTime(), | |||||
items: [], | |||||
}) | |||||
const { items: localItems } = localData | |||||
const { id: itemId } = item | |||||
const theItems: T[] = localItems.filter(i => i.id !== itemId) | |||||
storage.setItem(storageId, { ...localData, items: theItems }) | |||||
const response = await window.fetch(`${url}/${itemId}`, { | |||||
method: 'delete', | |||||
}) | |||||
return response.status === 204 | |||||
} | |||||
export const loadNotes = loadItems({ id: 'notes', url: '/api/notes' }) | export const loadNotes = loadItems({ id: 'notes', url: '/api/notes' }) | ||||
export const loadFolders = loadItems({ id: 'folders', url: '/api/folders' }) | export const loadFolders = loadItems({ id: 'folders', url: '/api/folders' }) | ||||
export const saveNote = saveItem({ id: 'notes', url: '/api/notes' }) | export const saveNote = saveItem({ id: 'notes', url: '/api/notes' }) | ||||
export const saveFolder = saveItem({ id: 'folders', url: '/api/folders' }) | export const saveFolder = saveItem({ id: 'folders', url: '/api/folders' }) | ||||
export const deleteNote = deleteItem({ id: 'notes', url: '/api/notes' }) |
@@ -1,5 +1,35 @@ | |||||
type Instance<T> = { | |||||
[key in keyof T]: unknown | |||||
import * as Sequelize from 'sequelize' | |||||
type ModelAttribute = { | |||||
allowNull?: boolean, | |||||
primaryKey?: boolean, | |||||
type: Sequelize.DataType, | |||||
} | |||||
type Model = { | |||||
tableName?: string, | |||||
modelName?: string, | |||||
options?: { | |||||
timestamps?: boolean, | |||||
paranoid?: boolean, | |||||
createdAt?: string | boolean, | |||||
updatedAt?: string | boolean, | |||||
deletedAt?: string | boolean, | |||||
}, | |||||
rawAttributes: Record<string, ModelAttribute>, | |||||
} | |||||
type InferType<V extends Sequelize.DataType> = ( | |||||
V extends typeof Sequelize.STRING ? string : | |||||
V extends typeof Sequelize.TEXT ? string : | |||||
V extends typeof Sequelize.DATE ? Date : | |||||
V extends typeof Sequelize.DATEONLY ? Date : | |||||
V extends typeof Sequelize.UUIDV4 ? string : | |||||
unknown | |||||
) | |||||
type InferProps<M extends Model> = { | |||||
[K in keyof M['rawAttributes']]-?: InferType<M['rawAttributes'][K]['type']> | |||||
} | } | ||||
export default Instance | |||||
export default InferProps |
@@ -43,3 +43,7 @@ export class Retrieved<T extends Record<string, unknown>> implements Response { | |||||
this.data = params.data | this.data = params.data | ||||
} | } | ||||
} | } | ||||
export class Destroyed implements Response { | |||||
public readonly status = 204 | |||||
} |