Skip to main content

JSON:API generic CRUD

What you'll learn
  • Writing generic CRUD functions to use your models through a JSON:API backend

Classic CRUD

View many

import {
ModelRelationDotKey,
Model,
forModel,
when,
include,
all,
} from 'foscia/core';
import {
filterBy,
sortByDesc,
paginate,
usingDocument,
} from 'foscia/jsonapi';
import action from './action';
import Post from './models/post';

type FetchAllOptions<M extends Model> = {
search?: string;
include?: ModelRelationDotKey<M>[];
page?: number;
};

export default async function fetchAll<M extends Model>(
model: M,
query: FetchAllOptions<M> = {},
) {
return action()
.use(forModel(model))
.use(when(query.search, (a, s) => a.use(filterBy('search', s))))
.use(when(query.include, (a, i) => a.use(include(i))))
.use(sortByDesc('createdAt'))
.use(paginate({ number: query.page ?? 1 }))
.run(all(usingDocument));
}

// Somewhere in your app.
const { instances, document } = await fetchAll(Post, {
search: 'Hello',
include: ['tags'],
});

View one

import {
ModelRelationDotKey,
ModelId,
Model,
find,
when,
include,
oneOrFail,
} from 'foscia/core';
import action from './action';
import Post from './models/post';

type FetchOneOptions<M extends Model> = {
include?: ModelRelationDotKey<M>[];
};

export default async function fetchOne<M extends Model>(
model: M,
id: ModelId,
query: FetchOneOptions<M> = {},
) {
return action()
.use(find(model, id))
.use(when(query.include, (a, i) => a.use(include(i))))
.run(oneOrFail());
}

// Somewhere in your app.
const post = await fetchOne(Post, '123-abc', {
include: ['tags'],
});

Create or update one

import {
changed,
fill,
include,
ModelInstance,
ModelRelationDotKey,
ModelValues,
oneOrCurrent,
reset,
save,
when,
} from 'foscia/core';
import action from './action';
import Post from './models/post';

type SaveOneOptions<I extends ModelInstance> = {
include?: ModelRelationDotKey<I>[];
};

export default async function saveOne<I extends ModelInstance>(
instance: I,
values: Partial<ModelValues<I>>,
query: SaveOneOptions<I> = {},
) {
fill(instance, values);

try {
await action()
.use(save(instance))
.use(when(query.include, (a, i) => a.use(include(i))))
.run(
when(
!instance.exists || changed(instance),
oneOrCurrent(),
() => instance,
),
);
} catch (error) {
reset(instance);

throw error;
}

return instance;
}

// Somewhere in your app.
const post = new Post();

await saveOne(
post,
{
title: 'Hello World!',
publishedAt: new Date(),
},
{
include: ['tags'],
},
);

Delete one

import { destroy, none } from 'foscia/core';
import action from './action';
import Post from './models/post';

export default async function deleteOne(instance: ModelInstance) {
await action().use(destroy(instance)).run(none());
}

// Somewhere in your app.
const post = new Post();

await deleteOne(post);