Following #695, I'd like to discuss whether this real-world scenario can be supported, or at least made less fragile.
In a SaaS web app, I have two ORMs:
- per-tenant: user, form, field, submission, answer
- global: tenant, form, field (no answers)
The use case is, SaaS admin setups forms (more like "form templates"). Tenants choose a form template from the global form catalog and create a local form as a clone of the global form. Tenants then collect form submissions.
The business logic behind forms is fairly complex (1000+ LOC). Fields have multiple types, including heavy composite types like "RICE score", so I'd like to reuse that logic when working with tenant and global forms.
Currently, I use the tenant ORM as the "canonical" source of table definitions, then derive the global ORM from it:
export class FormTable extends TenantFormTable {
override schema = SAAS_SCHEMA
}
export class FormFieldTable extends TenantFormFieldTable {
override schema = SAAS_SCHEMA
// SaaS forms don't have answers; answers are tenant-only.
// @ts-expect-error I didn't manage to find out how to prevent TS errors on this override.
override relations = omit(
(TenantFormFieldTable.instance() as TenantFormFieldTable).relations,
["answers"],
)
}
Business-layer functions that work with forms, such as createForm, retrieve the ORM instance through a service locator. At runtime, they call const db = useDb() rather than relying on a module-level constant. In the SaaS context, I replace/mock the service locator so that it returns the SaaS ORM instance.
This is fragile and not type-safe. The shared form logic is supposed to use only the tables and relations that exist in both ORMs, but there is currently no way to enforce that.
The question is: are there ways to improve DX and make this case somehow better (even if not fully and truly supported)?
Following #695, I'd like to discuss whether this real-world scenario can be supported, or at least made less fragile.
In a SaaS web app, I have two ORMs:
The use case is, SaaS admin setups forms (more like "form templates"). Tenants choose a form template from the global form catalog and create a local form as a clone of the global form. Tenants then collect form submissions.
The business logic behind forms is fairly complex (1000+ LOC). Fields have multiple types, including heavy composite types like "RICE score", so I'd like to reuse that logic when working with tenant and global forms.
Currently, I use the tenant ORM as the "canonical" source of table definitions, then derive the global ORM from it:
Business-layer functions that work with forms, such as
createForm, retrieve the ORM instance through a service locator. At runtime, they callconst db = useDb()rather than relying on a module-level constant. In the SaaS context, I replace/mock the service locator so that it returns the SaaS ORM instance.This is fragile and not type-safe. The shared form logic is supposed to use only the tables and relations that exist in both ORMs, but there is currently no way to enforce that.
The question is: are there ways to improve DX and make this case somehow better (even if not fully and truly supported)?