Skip to content

Reusing a subgraph of ORM in an another ORM instance #696

@IlyaSemenov

Description

@IlyaSemenov

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)?

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions