Migration from typeorm-extension
@rapiq/adapter-typeorm succeeds typeorm-extension's applyQuery/applyQueryParseOutput pipeline. The contract is deliberately close, but a few defaults flipped, and the ones that did are security-relevant.
API mapping
| typeorm-extension | rapiq v2 |
|---|---|
applyQuery(qb, req.query, options) | codec.decode(req.query, { schema }) + new TypeormAdapter({ queryBuilder: qb }).execute(query) (see the Express recipe) |
per-call allowed/default options | a named Schema in a SchemaRegistry |
| returned parse output (pagination) | adapter.execute(query) returns the applied pagination |
relations.onJoin hook | relations.onJoin on the adapter options |
Behavior differences
Parameters without an allow-list are open (breaking, security-relevant)
typeorm-extension disables any parameter whose allowed/default options are missing. rapiq's default is the opposite: a parameter without an allow-list accepts any syntactically valid key.
Enable strict: true on your schemas to keep the closed-by-default semantics when migrating:
defineSchema<User>({
name: 'user',
strict: true, // reject undeclared parameters
filters: { allowed: ['id', 'name'] },
});Joins default to LEFT
typeorm-extension used inner joins for relations; @rapiq/adapter-typeorm defaults to left joins, keeping records whose relation is absent. Restore inner joins per adapter via relations: { joinType: 'inner' }.
Join aliases are path-qualified
typeorm-extension aliased joins by the relation path's last segment (role.realm joined as realm), so relation paths ending in the same segment collided. @rapiq/adapter-typeorm uses the collision-free buildRelationAlias(path) helper (realm → r5_realm, role.realm → r4_role_5_realm); see the alias convention. This surfaces in code that references join aliases directly, e.g. hand-written andWhere clauses; onJoin hooks keep working because they receive the derived alias. A custom derivation can be injected via relations: { relationAlias }, but it must stay collision-free.
Defaults that carried over
joinAndSelectbehavior matchesleftJoinAndSelect: setrelations: { joinAndSelect: true }.executereturns the applied pagination, mirroringapplyQuery's parse-output contract.
Suggested migration path
- Model each resource's per-call options as a
Schemawithstrict: true. - Replace
applyQuerycall sites with decode → execute (the recipe is the template). - If your code relied on inner-join semantics, set
joinType: 'inner'. - Turn on
throwOnFailurewhere you want contract violations to become400s instead of silent drops.