Hello, I am trying to implement a base type and set of functions that operate on that type so that I can use the same logic for multiple ‘child’ types which extend the base type. The base and child types need to be plain JS objects of a certain shape that PouchDB expects (e.g. each item has an id
field and when deleting an item I need to set the _deleted
field to true
).
I spend a lot of time writing TypeScript for my job, so I am used to being able to do certain things that seem to be difficult to do in ReScript. Here is some code to show roughly how I would go about accomplishing what I want in TypeScript:
module PouchDB {
// stub
export type DB_T<ItemT> = {};
export function newDB<ItemT>(_name: string): DB_T<ItemT> {
// stub
return {}
}
export function getAll<ItemT>(_db: DB_T<ItemT>): Array<ItemT> {
// stub
return [];
}
export function putItem<ItemT>(_db: DB_T<ItemT>, _item: ItemT) {
// stub
}
}
interface BaseInterface {
id: string;
name: string;
_deleted?: boolean;
parents?: Array<string>;
}
abstract class BaseModel<ItemT extends BaseInterface> {
db: PouchDB.DB_T<ItemT>;
constructor(dbName: string) {
this.db = PouchDB.newDB(dbName);
}
getAll() {
return PouchDB.getAll(this.db)
}
get(id: string) {
return this.getAll().find(item => item.id === id)!;
}
add(item: ItemT) {
return PouchDB.putItem(this.db, item);
}
remove(item: ItemT) {
item._deleted = true;
return PouchDB.putItem(this.db, item);
}
}
interface Task extends BaseInterface {
note?: string;
due?: Date;
priority?: number;
repeat?: boolean;
tags?: Array<string>;
dependsOn?: Array<string>;
}
class TasksModel extends BaseModel<Task> {
constructor() {
super("tasks");
}
}
interface Tag extends BaseInterface {}
class TagsModel extends BaseModel<Tag> {
constructor() {
super("tags");
}
}
const tasksModel = new TasksModel();
const tagsModel = new TagsModel();
With all of the above, I am able to do things like the following and the compiler is happy:
tasksModel.add({
id: "aaa",
name: "some task"
});
tagsModel.add({
id: "aab",
name: "some tag"
});
Here is my attempt so far at doing the same thing in ReScript:
module PouchDB = {
type t<'item_t> = int
// stub
let new = (_name: string): t<'item_t> => 0
// stub
let getAll = (_dbr: t<'item_t>): array<'item_t> => []
let putItem = (_dbr: t<'item_t>, _item: 'item_t) => ()
}
module BaseInterface = {
type t = {
id: string,
name: string,
_deleted?: bool
}
let getId = (item: t) => item.id
let setDeleted = (item: t) => {...item, _deleted: true}
}
module type MakeModelParamsType = {
type item_t
let dbName: string
let getItemID: item_t => string
let setDeleted: item_t => item_t
}
module MakeModel = (Params: MakeModelParamsType) =>
{
type item_t = Params.item_t
let db: PouchDB.t<Params.item_t> = PouchDB.new(Params.dbName)
let getAll = () => db->PouchDB.getAll
let getFrom = (from, id) =>
from->Array.find(item => item->Params.getItemID === id)->Option.getExn
let get = id => getAll()->getFrom(id)
let add = (item: Params.item_t) => db->PouchDB.putItem(item)
let remove = (item: Params.item_t) =>
db->PouchDB.putItem(Params.setDeleted(item))
}
module TasksModelParams: MakeModelParamsType = {
type item_t = {
...BaseInterface.t,
note?: string,
due?: Js.Date.t,
priority?: int,
repeat?: bool,
tags?: array<string>,
dependsOn?: array<string>
}
let dbName = "tasks"
let getItemID = (task: item_t) =>
BaseInterface.getId(task :> BaseInterface.t)
external castFromBaseT: BaseInterface.t => item_t = "%identity"
let setDeleted = (task: item_t) =>
BaseInterface.setDeleted(task :> BaseInterface.t)->castFromBaseT
}
module TasksModel = MakeModel(TasksModelParams)
When I attempt to use the TasksModel
module it does not behave the way I would expect:
// Error: The record field id can't be found.
let task: TasksModel.item_t = {id: "aaa", name: "some task"}
// Error: The record field id can't be found.
TasksModel.add({id: "aaa", name: "some task"})
I would like to know if there is a way to expose the TasksModel.item_t
type for use outside of the resulting module. I tried with the (ModuleType with type x = y)
syntax but couldn’t get the result I wanted with that.