@kavo/mikroorm 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -0
- package/dist/error-mapping.d.ts +32 -0
- package/dist/error-mapping.d.ts.map +1 -0
- package/dist/error-mapping.js +51 -0
- package/dist/error-mapping.js.map +1 -0
- package/dist/filter-translator.d.ts +49 -0
- package/dist/filter-translator.d.ts.map +1 -0
- package/dist/filter-translator.js +109 -0
- package/dist/filter-translator.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure.d.ts +35 -0
- package/dist/infrastructure.d.ts.map +1 -0
- package/dist/infrastructure.js +49 -0
- package/dist/infrastructure.js.map +1 -0
- package/dist/metadata.d.ts +15 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +202 -0
- package/dist/metadata.js.map +1 -0
- package/dist/mikro-orm-repository-adapter.d.ts +110 -0
- package/dist/mikro-orm-repository-adapter.d.ts.map +1 -0
- package/dist/mikro-orm-repository-adapter.js +428 -0
- package/dist/mikro-orm-repository-adapter.js.map +1 -0
- package/dist/plain-entity.d.ts +31 -0
- package/dist/plain-entity.d.ts.map +1 -0
- package/dist/plain-entity.js +36 -0
- package/dist/plain-entity.js.map +1 -0
- package/package.json +41 -0
package/dist/metadata.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { ConfigurationException } from "@kavo/core";
|
|
2
|
+
/**
|
|
3
|
+
* Translate a MikroORM property's declared type into the ORM-independent
|
|
4
|
+
* `FieldKind` core coerces against.
|
|
5
|
+
*
|
|
6
|
+
* `runtimeType` is preferred over `type`: MikroORM normalizes the former to
|
|
7
|
+
* the JavaScript type the property actually holds (`"string"`, `"Date"`),
|
|
8
|
+
* which is what core must coerce toward — a `decimal` column surfaces as
|
|
9
|
+
* `"string"` there, and `string` is genuinely the right target for it. The
|
|
10
|
+
* declared `type` is still consulted as a fallback, because `runtimeType` is
|
|
11
|
+
* `"any"` for the custom types that carry no JavaScript equivalent
|
|
12
|
+
* (`JsonType`). An unrecognized type degrades to `string` — comparison still
|
|
13
|
+
* works, coercion just doesn't narrow.
|
|
14
|
+
*
|
|
15
|
+
* A `bigint` column is a caveat, not a case this function special-cases: as
|
|
16
|
+
* of MikroORM v7, `BigIntType`'s default mode hands JavaScript a native
|
|
17
|
+
* `bigint`, and `runtimeType` reports that as `"bigint"`, matched below
|
|
18
|
+
* alongside `"number"`. An app that needs the old, precision-safe string
|
|
19
|
+
* representation back must construct the type explicitly —
|
|
20
|
+
* `new BigIntType("string")` — see doc 17 §1.
|
|
21
|
+
*/
|
|
22
|
+
function fieldKindOf(property) {
|
|
23
|
+
if (property.enum === true)
|
|
24
|
+
return "enum";
|
|
25
|
+
const type = String(property.runtimeType ?? property.type).toLowerCase();
|
|
26
|
+
if (type === "date")
|
|
27
|
+
return "date";
|
|
28
|
+
if (type === "boolean" || type === "bool")
|
|
29
|
+
return "boolean";
|
|
30
|
+
if (type === "number" || type === "bigint")
|
|
31
|
+
return "number";
|
|
32
|
+
if (type === "string")
|
|
33
|
+
return "string";
|
|
34
|
+
if (type === "object" || type === "json" || type === "jsonb")
|
|
35
|
+
return "json";
|
|
36
|
+
// Fall back to the declared column type for anything `runtimeType` did not
|
|
37
|
+
// already answer (a custom `type: "int8"`, a driver-native column type).
|
|
38
|
+
const declared = String(property.type).toLowerCase();
|
|
39
|
+
if (/^(int|integer|tinyint|smallint|mediumint|bigint|float|double|real|decimal|numeric|number)/.test(declared)) {
|
|
40
|
+
return "number";
|
|
41
|
+
}
|
|
42
|
+
if (/^(bool|boolean)/.test(declared))
|
|
43
|
+
return "boolean";
|
|
44
|
+
if (/^(date|datetime|timestamp|time)/.test(declared))
|
|
45
|
+
return "date";
|
|
46
|
+
if (/^(json|jsonb)/.test(declared))
|
|
47
|
+
return "json";
|
|
48
|
+
return "string";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Whether a property is written by the database or the ORM rather than by
|
|
52
|
+
* the caller — excluded from the derived `create`/`update`/`patch` defaults
|
|
53
|
+
* and stripped from write payloads by the default deserializer.
|
|
54
|
+
*
|
|
55
|
+
* MikroORM has no single flag for this, so the four independent ways a
|
|
56
|
+
* property becomes non-caller-writable are each checked: an auto-increment
|
|
57
|
+
* or database-generated column, an `onCreate`/`onUpdate` hook (the
|
|
58
|
+
* equivalent of TypeORM's `@CreateDateColumn`/`@UpdateDateColumn`), an
|
|
59
|
+
* optimistic-lock `@Property({ version: true })`, and `persist: false`,
|
|
60
|
+
* which is not stored at all.
|
|
61
|
+
*/
|
|
62
|
+
function isGenerated(property) {
|
|
63
|
+
return (property.autoincrement === true ||
|
|
64
|
+
property.generated !== undefined ||
|
|
65
|
+
property.onCreate !== undefined ||
|
|
66
|
+
property.onUpdate !== undefined ||
|
|
67
|
+
property.version === true ||
|
|
68
|
+
property.persist === false);
|
|
69
|
+
}
|
|
70
|
+
/** MikroORM's relation `kind` discriminators that carry many rows. */
|
|
71
|
+
const TO_MANY = new Set(["1:m", "m:n"]);
|
|
72
|
+
/**
|
|
73
|
+
* The lazy target-class thunk for one relation.
|
|
74
|
+
*
|
|
75
|
+
* A relation target can be declared two ways, and they do *not* arrive the
|
|
76
|
+
* same: `@ManyToOne(() => Owner)` leaves `property.entity` a thunk resolving
|
|
77
|
+
* to the class, while `@ManyToOne((): any => "Owner")` — the spelling that
|
|
78
|
+
* keeps a bidirectional relation's import cycle off the runtime graph, and
|
|
79
|
+
* therefore the one a `dependency-cruiser`-checked codebase reaches for —
|
|
80
|
+
* leaves it a thunk resolving to a plain **string** (MikroORM v7's decorator
|
|
81
|
+
* types no longer accept a bare string; `any` is what makes the thunk
|
|
82
|
+
* type-check). Calling it would return a string, not a class, for half the
|
|
83
|
+
* codebases that use this adapter.
|
|
84
|
+
*
|
|
85
|
+
* `targetMeta` is what both spellings have in common: MikroORM resolves it
|
|
86
|
+
* during metadata discovery either way. The metadata-storage lookup behind
|
|
87
|
+
* it covers a target discovered after this thunk was built, and the declared
|
|
88
|
+
* thunk is the last resort. The resolution is deferred until the thunk is
|
|
89
|
+
* called, because a bidirectional relation's target may not be registered at
|
|
90
|
+
* the time this entity's metadata is derived.
|
|
91
|
+
*/
|
|
92
|
+
function targetOf(orm, property, owner) {
|
|
93
|
+
return () => {
|
|
94
|
+
const resolved = property.targetMeta?.class ?? orm.getMetadata().getByClassName(String(property.type), false)?.class;
|
|
95
|
+
if (resolved !== undefined)
|
|
96
|
+
return resolved;
|
|
97
|
+
if (typeof property.entity === "function") {
|
|
98
|
+
return property.entity();
|
|
99
|
+
}
|
|
100
|
+
throw new ConfigurationException(owner, `relations.${property.name}`, `cannot resolve the target entity of relation '${property.name}'; ` +
|
|
101
|
+
`MikroORM reports its type as '${String(property.type)}', which is not a registered entity`);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Build the core `EntityMetadata` for one entity from MikroORM's own
|
|
106
|
+
* `MetadataStorage`: the adapter feeds core's metadata seam; core never sees
|
|
107
|
+
* MikroORM types.
|
|
108
|
+
*
|
|
109
|
+
* Unlike `@kavo/prisma`, no marker class is needed (ADR-0017 exists because
|
|
110
|
+
* Prisma erases its models at compile time) — a MikroORM entity is a real
|
|
111
|
+
* runtime class that already carries its own metadata, so the class the
|
|
112
|
+
* caller passes to `createCrud` *is* the identity, exactly as in
|
|
113
|
+
* `@kavo/typeorm`.
|
|
114
|
+
*/
|
|
115
|
+
export function buildEntityMetadata(orm, entity) {
|
|
116
|
+
const metadata = orm.getMetadata().getByClassName(entity.name, false);
|
|
117
|
+
if (metadata === undefined) {
|
|
118
|
+
throw new ConfigurationException(entity.name, "entity", `MikroORM has no registered entity named '${entity.name}'; ` +
|
|
119
|
+
`add it to the 'entities' array the ORM was initialized with`);
|
|
120
|
+
}
|
|
121
|
+
if (metadata.primaryKeys.length !== 1) {
|
|
122
|
+
throw new ConfigurationException(metadata.className, "primaryKeys", `Kavo v6 requires exactly one primary key; found ${metadata.primaryKeys.length}`);
|
|
123
|
+
}
|
|
124
|
+
const properties = Object.values(metadata.properties);
|
|
125
|
+
const discriminatorColumn = metadata.root?.discriminatorColumn ?? metadata.discriminatorColumn;
|
|
126
|
+
const fields = properties
|
|
127
|
+
// An embeddable contributes *two* kinds of property: the object-valued
|
|
128
|
+
// parent (`kind: "embedded"`, what a caller addresses) and one child per
|
|
129
|
+
// inner column, carrying an `embedded: [parent, child]` back-reference
|
|
130
|
+
// and a name that is an implementation detail (`addr~city`,
|
|
131
|
+
// `inline_city`). Only the parent belongs on the wire, so the children
|
|
132
|
+
// are dropped rather than leaked into DTOs and allowlists.
|
|
133
|
+
.filter((property) => property.embedded === undefined)
|
|
134
|
+
// `hidden: true` is MikroORM's "never expose this" — `toObject` drops it,
|
|
135
|
+
// so it can never reach a response. Excluding it from the metadata seam
|
|
136
|
+
// as well, rather than relying on that, is what stops it from landing on
|
|
137
|
+
// the *default allowlists*: a filterable-but-invisible column is a blind
|
|
138
|
+
// extraction oracle, where `filter[passwordHash][like]=a%` is answered by
|
|
139
|
+
// the row count even though the value is projected out of the body.
|
|
140
|
+
// `lazy: true` is excluded for the same reason — it is not loaded with
|
|
141
|
+
// the entity, so a DTO naming it would emit `undefined` while a filter on
|
|
142
|
+
// it still ran in the database. `@kavo/mongoose` excludes its
|
|
143
|
+
// `select: false` paths on identical reasoning (doc 15 §1). The trade is
|
|
144
|
+
// the same one it documents: Kavo does not manage such a property at all,
|
|
145
|
+
// so write it through a custom operation or the ORM directly.
|
|
146
|
+
.filter((property) => property.hidden !== true && property.lazy !== true)
|
|
147
|
+
.filter((property) => property.kind === "scalar" || property.kind === "embedded")
|
|
148
|
+
.map((property) => ({
|
|
149
|
+
name: property.name,
|
|
150
|
+
kind: property.kind === "embedded" ? "json" : fieldKindOf(property),
|
|
151
|
+
nullable: property.nullable === true,
|
|
152
|
+
// The single-table-inheritance discriminator: write-only bookkeeping
|
|
153
|
+
// MikroORM manages itself from the subtype being persisted. It never
|
|
154
|
+
// hydrates onto an entity and never survives `toObject`, so it cannot
|
|
155
|
+
// reach a response either way — what this flag stops is the *inbound*
|
|
156
|
+
// direction. Left un-generated it would join the writable projection,
|
|
157
|
+
// and a client sending `species: "cat"` on a Dog would produce a row
|
|
158
|
+
// that entity's own repository can no longer load.
|
|
159
|
+
//
|
|
160
|
+
// Read from `root`, because MikroORM records `discriminatorColumn` only
|
|
161
|
+
// on the inheritance root while the property itself is inherited by
|
|
162
|
+
// every subtype — and a subtype is what a caller passes to `createCrud`.
|
|
163
|
+
generated: isGenerated(property) || property.name === discriminatorColumn,
|
|
164
|
+
...(property.items !== undefined && {
|
|
165
|
+
enumValues: property.items.map((value) => String(value)),
|
|
166
|
+
}),
|
|
167
|
+
}));
|
|
168
|
+
const relations = properties
|
|
169
|
+
.filter((property) => property.kind !== "scalar" && property.kind !== "embedded")
|
|
170
|
+
.map((property) => ({
|
|
171
|
+
name: property.name,
|
|
172
|
+
// Resolved lazily and from `targetMeta` rather than by calling
|
|
173
|
+
// `property.entity` — see `targetOf`, which exists because a
|
|
174
|
+
// string-declared target (the spelling that keeps a bidirectional
|
|
175
|
+
// relation's cycle off the runtime graph) leaves `entity` a string.
|
|
176
|
+
target: targetOf(orm, property, metadata.className),
|
|
177
|
+
cardinality: TO_MANY.has(property.kind) ? "many" : "one",
|
|
178
|
+
// Inclusion is an opt-in allowlist; ORM metadata only supplies shape,
|
|
179
|
+
// never permission.
|
|
180
|
+
includable: false,
|
|
181
|
+
strategy: "auto",
|
|
182
|
+
}));
|
|
183
|
+
return {
|
|
184
|
+
entity,
|
|
185
|
+
name: metadata.className,
|
|
186
|
+
idField: metadata.primaryKeys[0],
|
|
187
|
+
fields,
|
|
188
|
+
relations,
|
|
189
|
+
// MikroORM declares no delete-date marker of its own — its soft-delete
|
|
190
|
+
// pattern is a user-defined `@Filter`, which is a query concern rather
|
|
191
|
+
// than a column declaration, so there is nothing here to detect.
|
|
192
|
+
//
|
|
193
|
+
// This is *not* the same as "soft delete is off until configured".
|
|
194
|
+
// `softDelete` defaults to `{ field: "deletedAt", strategy: "auto" }`, and
|
|
195
|
+
// core matches that name against the entity's own columns — so a plain
|
|
196
|
+
// `deletedAt` property enables soft delete with no config at all. What
|
|
197
|
+
// reporting `null` here really costs is the ability to mark the marker
|
|
198
|
+
// generated, which is why it stays client-writable. See doc 17 §5 and §7.
|
|
199
|
+
softDeleteField: null,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { EntityId, EntityMetadata, KavoContext, NormalizedQueryContext, RepositoryAdapter } from "@kavo/core";
|
|
2
|
+
import { type MikroORM } from "@mikro-orm/core";
|
|
3
|
+
/**
|
|
4
|
+
* `RepositoryAdapter` over a MikroORM `EntityManager`: CRUD with hard *or*
|
|
5
|
+
* soft delete, restore, purge, filtering, sorting, pagination, optional
|
|
6
|
+
* counting, and nested relation includes.
|
|
7
|
+
*
|
|
8
|
+
* Two things are specific to MikroORM and load-bearing:
|
|
9
|
+
*
|
|
10
|
+
* **Every method forks the EntityManager.** MikroORM is a Unit-of-Work ORM:
|
|
11
|
+
* an `EntityManager` owns an identity map that caches every entity it has
|
|
12
|
+
* loaded, and reusing one across requests would serve stale rows and leak
|
|
13
|
+
* one caller's entities into another's. `orm.em` is the *root* manager and
|
|
14
|
+
* is not meant to be queried directly; `orm.em.fork()` gives each operation
|
|
15
|
+
* a clean, isolated one, which is the same scope a request-scoped
|
|
16
|
+
* `RequestContext` would give a hand-written MikroORM application.
|
|
17
|
+
*
|
|
18
|
+
* **`IncludeNode.strategy` is deliberately ignored**, exactly as in
|
|
19
|
+
* `@kavo/prisma` and unlike `@kavo/typeorm`. The TypeORM adapter translates
|
|
20
|
+
* the join/batch split because it drives a raw SQL query builder, where a
|
|
21
|
+
* to-many `JOIN` multiplies root rows and separate batched queries are how
|
|
22
|
+
* that is avoided. MikroORM resolves `populate` with its own queries and
|
|
23
|
+
* applies `limit`/`offset` to the root regardless of the load strategy, so a
|
|
24
|
+
* to-many include never disturbs pagination here. There is nothing left for
|
|
25
|
+
* the distinction to control, and MikroORM's `strategy` option is per-query
|
|
26
|
+
* rather than per-relation anyway — it could not express a mixed tree.
|
|
27
|
+
*/
|
|
28
|
+
export declare class MikroOrmRepositoryAdapter<Entity extends object> implements RepositoryAdapter<Entity> {
|
|
29
|
+
private readonly orm;
|
|
30
|
+
private readonly entity;
|
|
31
|
+
private readonly idField;
|
|
32
|
+
private readonly filterOptions;
|
|
33
|
+
/**
|
|
34
|
+
* Relation property name → the target entity's primary-key property.
|
|
35
|
+
* Resolved lazily: a bidirectional relation's target may not be registered
|
|
36
|
+
* with MikroORM's metadata storage at the time this adapter is built.
|
|
37
|
+
*/
|
|
38
|
+
private readonly relationIdFields;
|
|
39
|
+
constructor(orm: MikroORM, metadata: EntityMetadata<Entity>, options?: {
|
|
40
|
+
caseInsensitiveFilters?: boolean;
|
|
41
|
+
});
|
|
42
|
+
/** A clean, isolated `EntityManager` for one operation — see the class doc. */
|
|
43
|
+
private fork;
|
|
44
|
+
findOneById(id: EntityId, query: NormalizedQueryContext<Entity> | null, context: KavoContext<Entity>): Promise<Entity | null>;
|
|
45
|
+
findOne(query: NormalizedQueryContext<Entity>, context: KavoContext<Entity>): Promise<Entity | null>;
|
|
46
|
+
findMany(query: NormalizedQueryContext<Entity>, context: KavoContext<Entity>): Promise<readonly Entity[]>;
|
|
47
|
+
count(query: NormalizedQueryContext<Entity>, context: KavoContext<Entity>): Promise<number>;
|
|
48
|
+
/** The translated filter, narrowed to the request's soft-delete scope. */
|
|
49
|
+
private buildWhere;
|
|
50
|
+
private buildFindOptions;
|
|
51
|
+
/**
|
|
52
|
+
* Translate a validated `IncludeTree` into MikroORM's `populate` paths.
|
|
53
|
+
*
|
|
54
|
+
* Soft-delete scoping of included rows is deliberately **not** done here —
|
|
55
|
+
* see {@link pruneIncluded}. MikroORM's `populateWhere` cannot express it:
|
|
56
|
+
* a nested condition (`{ articles: { deletedAt: null, notes: { deletedAt:
|
|
57
|
+
* null } } }`) is read as a relation-path predicate on the *parent*, so an
|
|
58
|
+
* article with no live notes is dropped from the parent's collection
|
|
59
|
+
* altogether rather than coming back with an empty one. The dotted spelling
|
|
60
|
+
* MikroORM rejects outright, and the parent-only spelling silently leaves
|
|
61
|
+
* every deeper level unscoped.
|
|
62
|
+
*/
|
|
63
|
+
private populateOptions;
|
|
64
|
+
/**
|
|
65
|
+
* Three-way soft-delete scope: exclude deleted rows by default, include
|
|
66
|
+
* both live and deleted with `withDeleted`, or restrict to only deleted
|
|
67
|
+
* with `onlyDeleted` (mutually exclusive — validated upstream).
|
|
68
|
+
*
|
|
69
|
+
* There is one shape to handle rather than TypeORM's two: MikroORM
|
|
70
|
+
* declares no delete-date column of its own, so the marker is always an
|
|
71
|
+
* ordinary property and always needs its predicate spelled out.
|
|
72
|
+
*/
|
|
73
|
+
private scopeToLive;
|
|
74
|
+
/** The resolved strategy, refused when an operation requires soft. */
|
|
75
|
+
private requireSoftDelete;
|
|
76
|
+
private isDeleted;
|
|
77
|
+
/** One row by id, as a plain object, under the given soft-delete scope. */
|
|
78
|
+
private byId;
|
|
79
|
+
create(data: Partial<Entity>, context: KavoContext<Entity>): Promise<Entity>;
|
|
80
|
+
update(id: EntityId, data: Partial<Entity>, context: KavoContext<Entity>): Promise<Entity>;
|
|
81
|
+
patch(id: EntityId, data: Partial<Entity>, context: KavoContext<Entity>): Promise<Entity>;
|
|
82
|
+
/**
|
|
83
|
+
* update and patch share one load-merge-flush primitive: the *shape* of
|
|
84
|
+
* `data` differs (full body vs. sparse) because the DTO layer differs, not
|
|
85
|
+
* the persistence mechanics.
|
|
86
|
+
*
|
|
87
|
+
* This goes through the Unit of Work rather than `nativeUpdate` — one
|
|
88
|
+
* managed entity, assigned and flushed — so lifecycle hooks,
|
|
89
|
+
* `onUpdate` properties, and relation diffing all behave as they would in
|
|
90
|
+
* a hand-written MikroORM application. The row is loaded first anyway, to
|
|
91
|
+
* turn a missing id into `NotFoundException`, so this costs no extra
|
|
92
|
+
* query.
|
|
93
|
+
*/
|
|
94
|
+
private mergeAndFlush;
|
|
95
|
+
delete(id: EntityId, context: KavoContext<Entity>): Promise<void>;
|
|
96
|
+
restore(id: EntityId, context: KavoContext<Entity>): Promise<Entity>;
|
|
97
|
+
purge(id: EntityId, context: KavoContext<Entity>): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Normalize the engine's write payload for MikroORM.
|
|
100
|
+
*
|
|
101
|
+
* Core's default deserializer narrows a relation value to `{ id }` (or an
|
|
102
|
+
* array of them) — association by id, ADR-0014. MikroORM associates by
|
|
103
|
+
* *primary key value*, and would read a nested `{ id }` object as a
|
|
104
|
+
* request to create a new entity, so each relation value is unwrapped to
|
|
105
|
+
* the bare key before it reaches `create`/`assign`.
|
|
106
|
+
*/
|
|
107
|
+
private toWriteData;
|
|
108
|
+
private notFound;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=mikro-orm-repository-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mikro-orm-repository-adapter.d.ts","sourceRoot":"","sources":["../src/mikro-orm-repository-adapter.ts"],"names":[],"mappings":"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"}
|