@cleverbrush/orm 0.0.0-beta-20260424142030
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 +349 -0
- package/dist/change-tracker.d.ts +118 -0
- package/dist/dbcontext.d.ts +130 -0
- package/dist/dbset.d.ts +181 -0
- package/dist/entity.d.ts +2 -0
- package/dist/errors.d.ts +55 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/result-types.d.ts +154 -0
- package/dist/save-graph.d.ts +27 -0
- package/dist/variant-write.d.ts +38 -0
- package/package.json +57 -0
package/dist/dbset.d.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { ColumnRef, Entity, EntityRelations, EntitySchema, PrimaryKeyValueOf } from '@cleverbrush/knex-schema';
|
|
2
|
+
import { type SchemaQueryBuilder } from '@cleverbrush/knex-schema';
|
|
3
|
+
import type { Knex } from 'knex';
|
|
4
|
+
import type { EntityResult, RelKeyTree, SaveGraph, VariantInsertPayload, VariantResult, VariantUpdatePayload, WithIncluded, WithVariantIncluded } from './result-types.js';
|
|
5
|
+
/**
|
|
6
|
+
* The typed query handle returned by every {@link DbSet} method that
|
|
7
|
+
* initiates a query (e.g. `.where()`, `.include()`, `.first()`).
|
|
8
|
+
*
|
|
9
|
+
* Structurally it IS a {@link SchemaQueryBuilder} for the entity's schema,
|
|
10
|
+
* with typed `.include()` / `.includeVariant()` methods overlaid. All other
|
|
11
|
+
* `SchemaQueryBuilder` methods are available and `this`-returning chains
|
|
12
|
+
* preserve the typed wrapper.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export interface EntityQuery<TEntity extends Entity<any, any, any>, TResult> extends Omit<SchemaQueryBuilder<EntitySchema<TEntity>, TResult>, 'include' | 'includeVariant'> {
|
|
17
|
+
/**
|
|
18
|
+
* Eager-load a relation declared on `TEntity` via `.hasOne()` /
|
|
19
|
+
* `.hasMany()` / `.belongsTo()` / `.belongsToMany()`. Selector returns
|
|
20
|
+
* the relation key as a string literal.
|
|
21
|
+
*/
|
|
22
|
+
include<K extends keyof EntityRelations<TEntity> & string>(sel: (t: RelKeyTree<TEntity>) => K, customize?: (q: SchemaQueryBuilder<any, any>) => void): EntityQuery<TEntity, WithIncluded<TEntity, TResult, K>>;
|
|
23
|
+
/**
|
|
24
|
+
* Eager-load a relation declared inside a polymorphic variant (CTI/STI).
|
|
25
|
+
* The relation is only populated on rows whose discriminator matches
|
|
26
|
+
* `variantKey`. Type-level: when `relationName` is a key of the
|
|
27
|
+
* entity's relations, it appears only on the matching branch of the
|
|
28
|
+
* discriminated-union result; otherwise the result type is unchanged.
|
|
29
|
+
*/
|
|
30
|
+
includeVariant<TVariant extends string, TRel extends string>(variantKey: TVariant, relationName: TRel, customize?: (q: SchemaQueryBuilder<any, any>) => void): EntityQuery<TEntity, TRel extends keyof EntityRelations<TEntity> & string ? WithVariantIncluded<TEntity, TResult, TVariant, TRel> : TResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Look up a single entity by primary key. Returns `undefined` when no
|
|
33
|
+
* row matches.
|
|
34
|
+
*
|
|
35
|
+
* The PK column(s) are resolved automatically from the entity's schema
|
|
36
|
+
* via the `primaryKey` / `hasPrimaryKey` extensions. Composite PKs are
|
|
37
|
+
* passed as a tuple in declaration order:
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* await db.todos.find(42); // single PK
|
|
41
|
+
* await db.userRoles.find([userId, role]); // composite PK
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Any chained `.include()` / `.includeVariant()` calls on this query
|
|
45
|
+
* are honoured by the underlying `SELECT`.
|
|
46
|
+
*/
|
|
47
|
+
find(pk: PrimaryKeyValueOf<EntitySchema<TEntity>>): Promise<TResult | undefined>;
|
|
48
|
+
/**
|
|
49
|
+
* Like {@link find} but throws {@link EntityNotFoundError} when no row
|
|
50
|
+
* matches.
|
|
51
|
+
*/
|
|
52
|
+
findOrFail(pk: PrimaryKeyValueOf<EntitySchema<TEntity>>): Promise<TResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Look up multiple entities by primary key in a single SQL statement.
|
|
55
|
+
* Returns rows in DB order (no ordering guarantee relative to `pks`).
|
|
56
|
+
*
|
|
57
|
+
* For composite PKs each element of `pks` is a tuple matching
|
|
58
|
+
* declaration order. Returns `[]` when `pks` is empty.
|
|
59
|
+
*/
|
|
60
|
+
findMany(pks: ReadonlyArray<PrimaryKeyValueOf<EntitySchema<TEntity>>>): Promise<TResult[]>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Typed query starter for a registered entity. Structurally it behaves like
|
|
64
|
+
* a fresh {@link EntityQuery} — every method invocation allocates a new
|
|
65
|
+
* underlying `SchemaQueryBuilder` so chains stay isolated.
|
|
66
|
+
*
|
|
67
|
+
* Additional members beyond `EntityQuery`:
|
|
68
|
+
*
|
|
69
|
+
* - `entity` — the wrapped {@link Entity} definition.
|
|
70
|
+
* - `query()` — explicit factory for a fresh `EntityQuery` (rarely needed).
|
|
71
|
+
* - `withTransaction(trx)` — return a new `DbSet` bound to a transaction.
|
|
72
|
+
*
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
75
|
+
export interface DbSet<TEntity extends Entity<any, any, any>> extends EntityQuery<TEntity, EntityResult<TEntity>> {
|
|
76
|
+
/** The wrapped entity definition. */
|
|
77
|
+
readonly entity: TEntity;
|
|
78
|
+
/**
|
|
79
|
+
* Allocate a fresh `EntityQuery` (rarely needed; method shortcuts on the
|
|
80
|
+
* `DbSet` itself already do this).
|
|
81
|
+
*/
|
|
82
|
+
query(): EntityQuery<TEntity, EntityResult<TEntity>>;
|
|
83
|
+
/**
|
|
84
|
+
* Return a new {@link DbSet} bound to `trx`. All queries via the returned
|
|
85
|
+
* set execute inside the transaction.
|
|
86
|
+
*/
|
|
87
|
+
withTransaction(trx: Knex.Transaction): DbSet<TEntity>;
|
|
88
|
+
/**
|
|
89
|
+
* Persist a nested write graph (root + related entities) in a single
|
|
90
|
+
* transaction. Each node is INSERTed when its primary-key fields are
|
|
91
|
+
* absent and UPDATEd when they are present (composite-PK aware).
|
|
92
|
+
*
|
|
93
|
+
* Topology:
|
|
94
|
+
* - `belongsTo` parents are saved first; their PKs feed the root's FK.
|
|
95
|
+
* - The root row is then written.
|
|
96
|
+
* - `hasOne` / `hasMany` / `belongsToMany` children inherit the root's
|
|
97
|
+
* PK into their FK column.
|
|
98
|
+
* - `belongsToMany` children may be passed as full nested graphs (new
|
|
99
|
+
* rows) or as `{ pk: value }` references (link existing rows).
|
|
100
|
+
*
|
|
101
|
+
* If called inside an existing transaction (via `withTransaction`), the
|
|
102
|
+
* outer transaction is reused; otherwise a fresh one is opened and
|
|
103
|
+
* automatically rolled back on failure.
|
|
104
|
+
*/
|
|
105
|
+
save(graph: SaveGraph<TEntity>): Promise<EntityResult<TEntity>>;
|
|
106
|
+
/**
|
|
107
|
+
* Return a typed variant view pre-filtered to `variantKey`.
|
|
108
|
+
*
|
|
109
|
+
* Behaves like EF Core's `Set<DerivedType>()`: all reads automatically
|
|
110
|
+
* filter by the discriminator value, and `insert` / `update` / `delete`
|
|
111
|
+
* apply the correct single-table (STI) or two-table (CTI) logic.
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* const assigned = db.activities.ofVariant('assigned');
|
|
115
|
+
*
|
|
116
|
+
* // Insert a new variant row (discriminator set automatically):
|
|
117
|
+
* await assigned.insert({ todoId: 42, userId: 7, assigneeId: 9 });
|
|
118
|
+
*
|
|
119
|
+
* // Update variant-specific columns for matching rows:
|
|
120
|
+
* await assigned.where(t => t.id, activityId).update({ assigneeId: 10 });
|
|
121
|
+
*
|
|
122
|
+
* // Delete variant rows (CTI: variant table first, then base):
|
|
123
|
+
* await assigned.where(t => t.id, activityId).delete();
|
|
124
|
+
*
|
|
125
|
+
* // Find by PK (result is narrowed to the variant branch):
|
|
126
|
+
* const a = await assigned.find(activityId);
|
|
127
|
+
* // a.type === 'assigned' and a.assigneeId is available
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
ofVariant<K extends string>(variantKey: K): VariantDbSet<TEntity, K>;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* A `DbSet`-like handle scoped to a single polymorphic variant (one branch
|
|
134
|
+
* of a discriminated union entity). All reads are automatically pre-filtered
|
|
135
|
+
* by the discriminator; all writes apply the correct STI / CTI logic.
|
|
136
|
+
*
|
|
137
|
+
* Obtain via `DbSet.ofVariant('key')` — analogous to EF Core's
|
|
138
|
+
* `Set<DerivedType>()`.
|
|
139
|
+
*
|
|
140
|
+
* @public
|
|
141
|
+
*/
|
|
142
|
+
export interface VariantDbSet<TEntity extends Entity<any, any, any>, K extends string> extends Omit<SchemaQueryBuilder<EntitySchema<TEntity>, VariantResult<TEntity, K>>, 'include' | 'includeVariant' | 'insert' | 'update' | 'delete' | 'where'> {
|
|
143
|
+
where(column: ColumnRef<EntitySchema<TEntity>>, operator: string, value: any): this;
|
|
144
|
+
where(column: ColumnRef<EntitySchema<TEntity>>, value: any): this;
|
|
145
|
+
where(raw: Knex.Raw, operator: string, value: any): this;
|
|
146
|
+
where(callback: (builder: Knex.QueryBuilder) => void): this;
|
|
147
|
+
where(record: Record<string, any>): this;
|
|
148
|
+
where(raw: Knex.Raw): this;
|
|
149
|
+
/**
|
|
150
|
+
* Eager-load a relation declared on `TEntity`. Identical to
|
|
151
|
+
* {@link EntityQuery.include}.
|
|
152
|
+
*/
|
|
153
|
+
include<R extends keyof EntityRelations<TEntity> & string>(sel: (t: RelKeyTree<TEntity>) => R, customize?: (q: SchemaQueryBuilder<any, any>) => void): VariantDbSet<TEntity, K>;
|
|
154
|
+
/** Look up a single row by PK, typed to this variant. */
|
|
155
|
+
find(pk: PrimaryKeyValueOf<EntitySchema<TEntity>>): Promise<VariantResult<TEntity, K> | undefined>;
|
|
156
|
+
/** Like {@link find} but throws {@link EntityNotFoundError} when absent. */
|
|
157
|
+
findOrFail(pk: PrimaryKeyValueOf<EntitySchema<TEntity>>): Promise<VariantResult<TEntity, K>>;
|
|
158
|
+
/** Look up multiple rows by PK, typed to this variant. */
|
|
159
|
+
findMany(pks: ReadonlyArray<PrimaryKeyValueOf<EntitySchema<TEntity>>>): Promise<VariantResult<TEntity, K>[]>;
|
|
160
|
+
/**
|
|
161
|
+
* Insert a new variant row. The discriminator is set automatically.
|
|
162
|
+
*
|
|
163
|
+
* For CTI: inserts the base row first, then the variant row (transactional).
|
|
164
|
+
*/
|
|
165
|
+
insert(payload: VariantInsertPayload<TEntity, K>): Promise<VariantResult<TEntity, K>>;
|
|
166
|
+
/**
|
|
167
|
+
* Update variant-specific columns for rows matched by the current
|
|
168
|
+
* WHERE clause. For CTI, only the variant table is updated.
|
|
169
|
+
*/
|
|
170
|
+
update(patch: VariantUpdatePayload<TEntity, K>): Promise<void>;
|
|
171
|
+
/** Delete rows matched by the current WHERE clause (CTI: atomic). */
|
|
172
|
+
delete(): Promise<void>;
|
|
173
|
+
/** Return a new variant view bound to `trx`. */
|
|
174
|
+
withTransaction(trx: Knex.Transaction): VariantDbSet<TEntity, K>;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Construct a typed {@link DbSet} for an entity, bound to a Knex instance.
|
|
178
|
+
*
|
|
179
|
+
* @internal — use {@link createDb} for normal application code.
|
|
180
|
+
*/
|
|
181
|
+
export declare function makeDbSet<TEntity extends Entity<any, any, any>>(knex: Knex, entity: TEntity, onResults?: (items: unknown[]) => unknown[] | undefined): DbSet<TEntity>;
|
package/dist/entity.d.ts
ADDED
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by {@link DbSet.findOrFail} when no row matches the supplied
|
|
3
|
+
* primary key value(s). The `entity` field carries the SQL table name (or
|
|
4
|
+
* the entity's schema name when a table name is unavailable); `pk` is the
|
|
5
|
+
* exact value (scalar or tuple) that was looked up.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export declare class EntityNotFoundError extends Error {
|
|
10
|
+
readonly entity: string;
|
|
11
|
+
readonly pk: unknown;
|
|
12
|
+
constructor(entity: string, pk: unknown);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Thrown by {@link DbContext.saveChanges} when an UPDATE or DELETE detects
|
|
16
|
+
* that the row's row-version column (`.rowVersion()`) has changed since the
|
|
17
|
+
* entity was last loaded — indicating a concurrent modification.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export declare class ConcurrencyError extends Error {
|
|
22
|
+
/** Table name or entity label. */
|
|
23
|
+
readonly entity: string;
|
|
24
|
+
/** Primary-key value(s) of the conflicting row. */
|
|
25
|
+
readonly pk: unknown;
|
|
26
|
+
/** The row-version value the ORM expected. */
|
|
27
|
+
readonly expected: unknown;
|
|
28
|
+
constructor(entity: string, pk: unknown, expected: unknown);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Thrown by {@link DbContext.saveChanges} when an invariant is violated on
|
|
32
|
+
* a tracked entity — e.g. the primary key or discriminator column was mutated.
|
|
33
|
+
*
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
export declare class InvariantViolationError extends Error {
|
|
37
|
+
readonly entity: string;
|
|
38
|
+
readonly pk: unknown;
|
|
39
|
+
readonly field: string;
|
|
40
|
+
constructor(entity: string, pk: unknown, field: string, detail: string);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Thrown by the `[Symbol.asyncDispose]` implementation on a tracked
|
|
44
|
+
* {@link DbContext} when there are unsaved changes at scope exit.
|
|
45
|
+
*
|
|
46
|
+
* Callers must explicitly call `await db.saveChanges()` or
|
|
47
|
+
* `db.discardChanges()` before the `await using` block exits.
|
|
48
|
+
*
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export declare class PendingChangesError extends Error {
|
|
52
|
+
/** Number of dirty / added / deleted entries. */
|
|
53
|
+
readonly pendingCount: number;
|
|
54
|
+
constructor(pendingCount: number, summary: string);
|
|
55
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from '@cleverbrush/knex-schema';
|
|
2
|
+
export type { EntityEntry, EntryState, SavingChangesHook } from './change-tracker.js';
|
|
3
|
+
export type { DbContext, EntityMap, TrackedDbContext } from './dbcontext.js';
|
|
4
|
+
export { createDb } from './dbcontext.js';
|
|
5
|
+
export type { DbSet, EntityQuery, VariantDbSet } from './dbset.js';
|
|
6
|
+
export { ConcurrencyError, EntityNotFoundError, InvariantViolationError, PendingChangesError } from './errors.js';
|
|
7
|
+
export type { EntityResult, EntityResultByVariant, ExtractBranch, HasVariants, RelKeyTree, ResolvedRel, SaveGraph, VariantInsertPayload, VariantResult, VariantUpdatePayload, WithIncluded, WithVariantIncluded } from './result-types.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export*from"@cleverbrush/knex-schema";import{buildColumnMap as D,getPrimaryKeyColumns as A,getRowVersionColumn as en}from"@cleverbrush/knex-schema";var M=class extends Error{entity;pk;constructor(n,e){super(`Entity "${n}" not found for primary key ${JSON.stringify(e)}`),this.name="EntityNotFoundError",this.entity=n,this.pk=e}},S=class extends Error{entity;pk;expected;constructor(n,e,t){super(`Concurrency conflict on "${n}" (pk=${JSON.stringify(e)}): the row has been modified or deleted since it was loaded. Expected rowVersion = ${JSON.stringify(t)}.`),this.name="ConcurrencyError",this.entity=n,this.pk=e,this.expected=t}},v=class extends Error{entity;pk;field;constructor(n,e,t,i){super(`Invariant violation on "${n}" (pk=${JSON.stringify(e)}): ${i}`),this.name="InvariantViolationError",this.entity=n,this.pk=e,this.field=t}},j=class extends Error{pendingCount;constructor(n,e){super(`DbContext disposed with ${n} pending change(s). Call saveChanges() or discardChanges() before the context goes out of scope. `+e),this.name="PendingChangesError",this.pendingCount=n}};var U=class{#n=new Map;#e;constructor(){this.#e=new FinalizationRegistry(({setKey:n,pkKey:e})=>{let t=this.#n.get(n);t&&(t.delete(e),t.size===0&&this.#n.delete(n))})}set(n){let e=this.#n.get(n.entitySetKey);e||(e=new Map,this.#n.set(n.entitySetKey,e));let t=new WeakRef(n);e.set(n.pkKey,t),this.#e.register(n.entity,{setKey:n.entitySetKey,pkKey:n.pkKey})}get(n,e){let t=this.#n.get(n)?.get(e);if(!t)return null;let i=t.deref();return i||(this.#n.get(n)?.delete(e),null)}findByEntity(n){for(let e of this.#n.values())for(let t of e.values()){let i=t.deref();if(i?.entity===n)return i}return null}delete(n,e){this.#n.get(n)?.delete(e)}*entries(){for(let n of this.#n.values())for(let e of n.values()){let t=e.deref();t&&(yield t)}}byState(...n){let e=new Set(n),t=[];for(let i of this.entries())e.has(i.state)&&t.push(i);return t}clear(){this.#n.clear()}};function tn(o){return JSON.stringify(o)}function O(o,n){return A(o).propertyKeys.map(t=>n[t])}function R(o,n){let e=n.introspect?.(),t=new Set(Object.keys(e?.properties??{})),i={};for(let[c,f]of Object.entries(o))t.has(c)&&(i[c]=f);return Object.freeze(i)}function N(o,n){for(let e of Object.keys(o))if(!Object.is(o[e],n[e]))return!0;for(let e of Object.keys(n))if(!(e in o)&&n[e]!==void 0)return!0;return!1}var I=class{#n=new U;#e=new Set;#r=[];#t=new Map;registerEntitySet(n){this.#t.set(n.entitySetKey,n)}onSavingChanges(n){this.#r.push(n)}attach(n,e,t="Unchanged"){let i=this.#t.get(n);if(!i)throw new Error(`ChangeTracker.attach: unknown entity set "${n}". Make sure it was registered via registerEntitySet().`);let c=O(i.schema,e);if(c.some(r=>r==null)){if(e[x])return e;let s=R(e,i.schema),u=en(i.schema),l={entity:e,entitySetKey:n,variantKey:rn(i.schema,e),pkKey:"",originalSnapshot:s,state:"Added",rowVersion:u?{...u,snapshotValue:e[u.propertyKey]}:null};return e[x]=l,this.#e.add(l),e}let f=tn(c),g=this.#n.get(n,f);if(g)return g.entity!==e?g.entity:(t==="Unchanged"&&(g.originalSnapshot=R(e,i.schema),g.state="Unchanged",g.rowVersion&&(g.rowVersion.snapshotValue=e[g.rowVersion.propertyKey])),e);let y=R(e,i.schema),a=en(i.schema),d={entity:e,entitySetKey:n,variantKey:rn(i.schema,e),pkKey:f,originalSnapshot:y,state:t,rowVersion:a?{...a,snapshotValue:e[a.propertyKey]}:null};return this.#n.set(d),e[x]=d,e}detach(n){let e=this.#n.findByEntity(n);if(e)this.#n.delete(e.entitySetKey,e.pkKey);else{let t=n[x];t&&this.#e.delete(t)}delete n[x]}remove(n){let e=this.#n.findByEntity(n);if(!e)throw new Error("ChangeTracker.remove: entity is not tracked. Attach it first or use `db.attach()` before calling `remove()`.");e.state="Deleted"}entry(n){let e=this.#n.findByEntity(n)??n[x];if(!e)throw new Error("ChangeTracker.entry: entity is not tracked. Attach it first or load it via a tracked DbSet.");let t=this.#t.get(e.entitySetKey);return{get state(){return e.state},get originalValues(){return e.originalSnapshot},get currentValues(){return n},isModified(i){let c=n;return i!==void 0?!Object.is(e.originalSnapshot[i],c[i]):N(e.originalSnapshot,c)},reset(){let i=n;for(let[c,f]of Object.entries(e.originalSnapshot))i[c]=f;e.state=e.pkKey?"Unchanged":"Added"}}}discardChanges(){for(let n of this.#e)delete n.entity[x];this.#e.clear();for(let n of this.#n.entries())if(n.state==="Added")this.#n.delete(n.entitySetKey,n.pkKey);else if(n.state==="Modified"||n.state==="Deleted"||n.state==="Unchanged"&&N(n.originalSnapshot,n.entity)){let e=n.entity;for(let[t,i]of Object.entries(n.originalSnapshot))e[t]=i;n.state="Unchanged"}}async reload(n,e){let t=this.#n.findByEntity(n);if(!t)return;let i=this.#t.get(t.entitySetKey);if(!i)return;let c=A(i.schema),f=i.schema.getExtension?.("tableName");if(!f||c.propertyKeys.length===0)return;let{propToCol:g,colToProp:y}=D(i.schema),a=e(f),d=O(i.schema,n);for(let u=0;u<c.propertyKeys.length;u++){let l=g.get(c.propertyKeys[u])??c.propertyKeys[u];a.andWhere(l,d[u])}let r=await a.first();if(!r)return;let s=n;for(let[u,l]of Object.entries(r))s[y.get(u)??u]=l;t.originalSnapshot=R(n,i.schema),t.rowVersion&&(t.rowVersion.snapshotValue=n[t.rowVersion.propertyKey]),t.state="Unchanged"}hasPendingChanges(){if(this.#e.size>0)return!0;for(let n of this.#n.entries())if(n.state!=="Unchanged"||N(n.originalSnapshot,n.entity))return!0;return!1}async saveChanges(n){let e=0,t=0,i=0,c=[...this.#e,...this.#n.byState("Added")],f=[],g=this.#n.byState("Deleted");for(let y of this.#n.byState("Unchanged","Modified")){if(y.state==="Modified"){f.push(y);continue}N(y.originalSnapshot,y.entity)&&(y.state="Modified",f.push(y))}if(c.length===0&&f.length===0&&g.length===0)return{inserted:0,updated:0,deleted:0};for(let y of[...f,...g]){let a=this.#t.get(y.entitySetKey);if(!a)continue;let d=y.entity,r=A(a.schema),s=a.schema.getExtension?.("tableName");for(let u of r.propertyKeys)if(!Object.is(y.originalSnapshot[u],d[u]))throw new v(s,y.pkKey,u,`Primary key column "${u}" must not be changed on a tracked entity. Original: ${JSON.stringify(y.originalSnapshot[u])}, Current: ${JSON.stringify(d[u])}.`);if(y.variantKey!==void 0){let l=a.schema.getExtension?.("variants")?.discriminatorKey;if(l&&!Object.is(y.originalSnapshot[l],d[l]))throw new v(s,y.pkKey,l,`Discriminator column "${l}" must not be changed on a tracked entity. To change a polymorphic entity's type, delete and re-insert it as a new variant.`)}}for(let y of[...c,...f,...g]){if(!this.#t.get(y.entitySetKey))continue;let d=this.entry(y.entity);for(let r of this.#r)await r(d)}await n.transaction(async y=>{for(let a of c){let d=this.#t.get(a.entitySetKey);if(!d)continue;let r=d.schema.getExtension?.("tableName");if(!r)continue;let{propToCol:s}=D(d.schema),u=a.entity,l={};for(let[w,T]of Object.entries(u)){let h=s.get(w)??w;T!==void 0&&(l[h]=T)}let p=await y(r).insert(l).returning("*"),m=Array.isArray(p)&&p.length>0?p[0]:null;if(m&&typeof m=="object"){let{colToProp:w}=D(d.schema),T=a.entity;for(let[h,E]of Object.entries(m))T[w.get(h)??h]=E}e++}for(let a of f){let d=this.#t.get(a.entitySetKey);if(!d)continue;let r=d.schema.getExtension?.("tableName");if(!r)continue;let{propToCol:s}=D(d.schema),u=A(d.schema),l=a.entity,p=new Set(u.propertyKeys),m={};for(let h of Object.keys(a.originalSnapshot))p.has(h)||Object.is(a.originalSnapshot[h],l[h])||(m[s.get(h)??h]=l[h]);for(let[h,E]of Object.entries(l))p.has(h)||!(h in a.originalSnapshot)&&E!==void 0&&(m[s.get(h)??h]=E);if(a.rowVersion){let h=a.rowVersion,E=s.get(h.propertyKey)??h.propertyKey;if(h.strategy==="increment"){let K=Number(h.snapshotValue??0)+1;m[E]=K,l[h.propertyKey]=K}else if(h.strategy==="timestamp"){let K=new Date;m[E]=K,l[h.propertyKey]=K}}if(Object.keys(m).length===0)continue;let w=y(r);for(let h=0;h<u.propertyKeys.length;h++){let E=s.get(u.propertyKeys[h])??u.propertyKeys[h];w=w.andWhere(E,l[u.propertyKeys[h]])}if(a.rowVersion){let h=a.rowVersion,E=s.get(h.propertyKey)??h.propertyKey;w=w.andWhere(E,h.snapshotValue)}if(await w.update(m)===0&&a.rowVersion){let h=d.schema.getExtension?.("tableName");throw new S(h,O(d.schema,l),a.rowVersion.snapshotValue)}t++}for(let a of[...g].reverse()){let d=this.#t.get(a.entitySetKey);if(!d)continue;let r=d.schema.getExtension?.("tableName");if(!r)continue;let{propToCol:s}=D(d.schema),u=A(d.schema),l=a.entity,p=y(r);for(let w=0;w<u.propertyKeys.length;w++){let T=s.get(u.propertyKeys[w])??u.propertyKeys[w];p=p.andWhere(T,l[u.propertyKeys[w]])}if(a.rowVersion){let w=a.rowVersion,T=s.get(w.propertyKey)??w.propertyKey;p=p.andWhere(T,w.snapshotValue)}if(await p.delete()===0&&a.rowVersion)throw new S(r,O(d.schema,l),a.rowVersion?.snapshotValue);i++}});for(let y of c){y.originalSnapshot=R(y.entity,this.#t.get(y.entitySetKey).schema),y.state="Unchanged";let a=this.#t.get(y.entitySetKey),d=O(a.schema,y.entity);d.every(r=>r!=null)&&(y.pkKey=tn(d),this.#n.set(y)),this.#e.delete(y),y.rowVersion&&(y.rowVersion.snapshotValue=y.entity[y.rowVersion.propertyKey])}for(let y of f)y.originalSnapshot=R(y.entity,this.#t.get(y.entitySetKey).schema),y.state="Unchanged",y.rowVersion&&(y.rowVersion.snapshotValue=y.entity[y.rowVersion.propertyKey]);for(let y of g)this.#n.delete(y.entitySetKey,y.pkKey);return{inserted:e,updated:t,deleted:i}}pendingSummary(){let n={Added:0,Modified:0,Deleted:0,Unchanged:0};n.Added+=this.#e.size;for(let t of this.#n.entries())t.state!=="Unchanged"?n[t.state]++:N(t.originalSnapshot,t.entity)&&n.Modified++;let e=[];return n.Added&&e.push(`${n.Added} Added`),n.Modified&&e.push(`${n.Modified} Modified`),n.Deleted&&e.push(`${n.Deleted} Deleted`),`(${e.join(", ")})`}clear(){for(let n of this.#n.entries())delete n.entity[x];for(let n of this.#e)delete n.entity[x];this.#e.clear(),this.#n.clear()}},x=Symbol("@cleverbrush/orm:trackerEntry");function rn(o,n){let t=o?.getExtension?.("variants")?.discriminatorKey;if(t)return n[t]}import{getPrimaryKeyColumns as J,getVariants as dn,query as G}from"@cleverbrush/knex-schema";import{buildColumnMap as kn,getPrimaryKeyColumns as Q,query as Tn}from"@cleverbrush/knex-schema";function En(o){return o?.getExtension?.("relations")??[]}function Kn(o,n){let{propToCol:e}=kn(o);return e.get(n)??n}function bn(o,n){let e=Q(o);return e.propertyKeys.length===0?!1:e.propertyKeys.every(t=>n[t]!==void 0)}async function on(o,n,e,t){return t?V(t,n,e):o.transaction(i=>V(i,n,e))}async function V(o,n,e){let t=En(n),i=new Map;for(let r of t)i.set(r.name,r);let c={},f={};for(let[r,s]of Object.entries(e)){let u=i.get(r);u?f[r]={spec:u,value:s}:c[r]=s}for(let{spec:r,value:s}of Object.values(f)){if(r.type!=="belongsTo"||s==null)continue;if(typeof s!="object"||Array.isArray(s))throw new Error(`save: relation "${r.name}" expects an object (belongsTo).`);let u=await V(o,r.schema,s),l=Q(r.schema);if(l.propertyKeys.length!==1)throw new Error(`save: belongsTo "${r.name}" requires the parent entity to have a single-column primary key.`);let p=u[l.propertyKeys[0]];if(!r.foreignKey)throw new Error(`save: belongsTo "${r.name}" is missing a foreignKey.`);c[r.foreignKey]=p}let g=bn(n,c),y=Tn(o,n),a;if(g){let r=Q(n),s={...c};for(let l of r.propertyKeys)delete s[l];for(let l of r.propertyKeys)y.andWhere(l,"=",c[l]);let u=await y.update(s);Array.isArray(u)?a=u[0]??c:u&&typeof u=="object"?a=u:a=c;for(let l of r.propertyKeys)a[l]=c[l]}else a=await y.insert(c)??c;let d=Q(n);if(d.propertyKeys.length===1){let r=d.propertyKeys[0];a[r]===void 0&&c[r]!==void 0&&(a[r]=c[r])}for(let[r,{spec:s,value:u}]of Object.entries(f))if(s.type!=="belongsTo"&&u!=null){if(s.type==="hasOne"){if(typeof u!="object"||Array.isArray(u))throw new Error(`save: relation "${r}" expects a single object (hasOne).`);let l=d.propertyKeys[0];if(d.propertyKeys.length!==1)throw new Error(`save: hasOne "${r}" requires this entity to have a single-column primary key.`);let p={...u};s.foreignKey&&(p[s.foreignKey]=a[l]);let m=await V(o,s.schema,p);a[r]=m;continue}if(s.type==="hasMany"){if(!Array.isArray(u))throw new Error(`save: relation "${r}" expects an array (hasMany).`);let l=d.propertyKeys[0];if(d.propertyKeys.length!==1)throw new Error(`save: hasMany "${r}" requires this entity to have a single-column primary key.`);let p=[];for(let m of u){let w={...m};s.foreignKey&&(w[s.foreignKey]=a[l]),p.push(await V(o,s.schema,w))}a[r]=p;continue}if(s.type==="belongsToMany"){if(!s.through)throw new Error(`save: belongsToMany "${r}" is missing a through config.`);if(!Array.isArray(u))throw new Error(`save: relation "${r}" expects an array (belongsToMany).`);let l=d.propertyKeys[0];if(d.propertyKeys.length!==1)throw new Error(`save: belongsToMany "${r}" requires this entity to have a single-column primary key.`);let p=[];for(let m of u){let w=Q(s.schema);if(w.propertyKeys.length!==1)throw new Error(`save: belongsToMany "${r}" requires the child entity to have a single-column primary key.`);let T=w.propertyKeys[0],h;m[T]!==void 0?h={...m}:h=await V(o,s.schema,m),p.push(h),await o(s.through.table).insert({[s.through.localKey]:a[l],[s.through.foreignKey]:h[T]}).onConflict([s.through.localKey,s.through.foreignKey]).ignore()}a[r]=p}}return a}import{buildColumnMap as C,getPrimaryKeyColumns as _,getVariants as xn,query as Sn}from"@cleverbrush/knex-schema";function z(o,n){let e=xn(o);if(!e)throw new Error("insertVariant / deleteVariant / updateVariant / findVariant: entity schema is not polymorphic (no variants declared).");let t=e.variants[n];if(!t){let i=Object.keys(e.variants).join(", ");throw new Error(`Variant key "${n}" is unknown. Known variants: ${i}.`)}return{config:e,spec:t}}async function an(o,n,e,t,i){let{config:c,spec:f}=z(n,e),g=async y=>{let a=c.discriminatorKey,d=n.getExtension?.("tableName");if(!d)throw new Error("insertVariant: base schema has no table name.");if(f.storage==="sti"){let k={...t,[a]:e};return await Sn(y,n).insert(k)??k}let r=f.schema,s=f.tableName,u=f.foreignKey,l=n.introspect?.(),p=r.introspect?.(),m=new Set(Object.keys(l?.properties??{})),w=new Set(Object.keys(p?.properties??{})),T={[a]:e},h={};for(let[k,b]of Object.entries(t))k===a||k===u||(m.has(k)?T[k]=b:w.has(k)&&(h[k]=b));let{propToCol:E}=C(n),K={};for(let[k,b]of Object.entries(T))K[E.get(k)??k]=b;let P=await y(d).insert(K).returning("*"),W=Array.isArray(P)&&P.length>0?P[0]:K,$=_(n);if($.propertyKeys.length===0)throw new Error("insertVariant: base schema has no primary key declared.");if($.propertyKeys.length>1)throw new Error("insertVariant: composite primary keys are not yet supported for polymorphic CTI inserts.");let gn=$.propertyKeys[0],hn=$.columnNames[0],X=W[gn]??W[hn];if(X===void 0)throw new Error("insertVariant: could not resolve PK value from base insert result.");let{propToCol:Z}=C(r),F={[u]:X};for(let[k,b]of Object.entries(h)){let wn=Z.get(k)??k;F[wn]=b}let nn=Z.get(a);nn&&(F[nn]=e),await y(s).insert(F);let{colToProp:mn}=C(n),B={};for(let[k,b]of Object.entries(W))B[mn.get(k)??k]=b;B[a]=e;for(let[k,b]of Object.entries(h))B[k]=b;return B};return i?g(i):o.transaction(y=>g(y))}async function sn(o,n,e,t,i,c){if(i.length===0)return;let f=c??o,{config:g,spec:y}=z(n,e),a=g.discriminatorKey;if(y.storage==="sti"){let{propToCol:p}=C(n),m=p.get(a)??a,w=_(n);if(w.columnNames.length===0)throw new Error("updateVariant: base schema has no primary key declared.");if(w.columnNames.length>1)throw new Error("updateVariant: composite primary keys are not supported for STI updates.");let T=w.columnNames[0],h=n.getExtension?.("tableName"),E={};for(let[K,P]of Object.entries(t))E[p.get(K)??K]=P;await f(h).whereIn(T,i).andWhere(m,e).update(E);return}let d=y.schema,r=y.tableName,s=y.foreignKey,{propToCol:u}=C(d),l={};for(let[p,m]of Object.entries(t))p!==s&&(l[u.get(p)??p]=m);Object.keys(l).length!==0&&await f(r).whereIn(s,i).update(l)}async function cn(o,n,e,t,i){if(t.length===0)return;let c=async f=>{let{config:g,spec:y}=z(n,e),a=g.discriminatorKey,d=n.getExtension?.("tableName"),r=_(n);if(r.columnNames.length===0)throw new Error("deleteVariant: base schema has no primary key declared.");if(r.columnNames.length>1)throw new Error("deleteVariant: composite primary keys are not supported for variant deletes.");let s=r.columnNames[0];if(y.storage==="sti"){let{propToCol:p}=C(n),m=p.get(a)??a;await f(d).whereIn(s,t).andWhere(m,e).delete();return}let u=y.tableName,l=y.foreignKey;await f(u).whereIn(l,t).delete(),await f(d).whereIn(s,t).delete()};return i?c(i):o.transaction(f=>c(f))}var yn=new WeakMap;function ln(o){let n=yn.get(o);if(n)return n;let e=o.schema.introspect?.()?.properties??{},t={};for(let i of Object.keys(e))t[i]=i;return yn.set(o,t),t}function un(o,n,e,t){let i=new Proxy(o,{get(c,f,g){if(f==="include")return(a,d)=>{let r=a(ln(n));return o.include(r,d),i};if(f==="includeVariant")return(a,d,r)=>(o.includeVariant(a,d,r),i);if(f==="_sqb")return o;if(f==="_entity")return n;if(f==="find"||f==="findOrFail"||f==="findMany")return fn(f,o,n,i);if((f==="update"||f==="delete"||f==="insert")&&dn(n.schema))return()=>{throw new Error(`${String(f)}() cannot be called directly on a polymorphic entity set. Use db.set.ofVariant('key').${String(f)}() instead.`)};let y=Reflect.get(c,f,g);return typeof y!="function"?y:function(...a){let d=y.apply(o,a);return d===o?i:t!=null&&d!=null&&typeof d.then=="function"?d.then(r=>{if(Array.isArray(r)){let s=r.filter(u=>u!=null&&typeof u=="object");if(s.length>0){let u=t(s);if(u){let l=new Map;for(let p=0;p<s.length;p++)l.set(s[p],u[p]);return r.map(p=>l.get(p)??p)}}}else if(r!=null&&typeof r=="object"){let s=t([r]);if(s&&s.length>0)return s[0]}return r}):d}}});return i}function fn(o,n,e,t){return async(...i)=>{let c=J(e.schema);if(c.propertyKeys.length===0)throw new Error(`${o}(): entity has no primary key declared. Use \`.primaryKey()\` on a column or \`.hasPrimaryKey([...])\` on the schema.`);let f=c.propertyKeys,g=f.length>1,y=e.schema.getExtension?.("tableName")??"<entity>",a=String(y),d=s=>{let u=H(s,g,o);if(u.length!==f.length)throw new Error(`${o}(): expected ${f.length} primary-key value(s), got ${u.length}.`);for(let l=0;l<f.length;l++)t.andWhere(f[l],"=",u[l])};if(o==="findMany"){let s=i[0]??[];if(!Array.isArray(s))throw new Error("findMany(): expected an array of primary-key values.");if(s.length===0)return[];if(!g){let l=f[0],p=s.map(m=>H(m,!1,"findMany"));return t.whereIn(l,p.map(m=>m[0])),await t.execute()}let u=c.columnNames;return n.apply(l=>{l.andWhere(function(){for(let p of s){let m=H(p,!0,"findMany");this.orWhere(function(){for(let w=0;w<u.length;w++)this.andWhere(u[w],m[w])})}})}),await t.execute()}d(i[0]);let r=await t.first();if(r===void 0&&o==="findOrFail")throw new M(a,i[0]);return r}}function H(o,n,e){if(n){if(!Array.isArray(o))throw new Error(`${e}(): composite primary key requires a tuple argument.`);return o}return Array.isArray(o)?o:[o]}function q(o,n,e){return new Proxy({},{get(i,c){if(c==="entity")return n;if(c==="query")return()=>{let a=G(o,n.schema);return un(a,n,o,e)};if(c==="withTransaction")return a=>q(a,n,e);if(c==="save")return a=>{let d=o.isTransaction?o:void 0;return on(o,n.schema,a,d)};if(c==="ofVariant")return a=>{if(!dn(n.schema))throw new Error("ofVariant(): entity schema is not polymorphic (no variants declared).");return pn(o,n,a,e)};let f=G(o,n.schema),g=un(f,n,o,e),y=g[c];return typeof y!="function"?y:y.bind(g)}})}function vn(o,n,e,t,i){let c=new Proxy(o,{get(f,g,y){if(g==="include")return(d,r)=>{let s=d(ln(n));return o.include(s,r),c};if(g==="includeVariant")return(d,r,s)=>(o.includeVariant(d,r,s),c);if(g==="_sqb")return o;if(g==="_entity")return n;if(g==="find"||g==="findOrFail"||g==="findMany")return fn(g,o,n,c);if(g==="insert")return async d=>{let r=t.isTransaction?t:void 0,s=await an(t,n.schema,e,d,r);return i&&s!=null&&typeof s=="object"&&i([s]),s};if(g==="update")return async d=>{let r=J(n.schema),s=await c.execute(),u=r.propertyKeys[0],l=s.map(m=>m[u]).filter(m=>m!==void 0),p=t.isTransaction?t:void 0;await sn(t,n.schema,e,d,l,p)};if(g==="delete")return async()=>{let d=J(n.schema),r=await c.execute(),s=d.propertyKeys[0],u=r.map(p=>p[s]).filter(p=>p!==void 0),l=t.isTransaction?t:void 0;await cn(t,n.schema,e,u,l)};let a=Reflect.get(f,g,y);return typeof a!="function"?a:function(...d){let r=a.apply(o,d);return r===o?c:i!=null&&r!=null&&typeof r.then=="function"?r.then(s=>{if(Array.isArray(s)){let u=s.filter(l=>l!=null&&typeof l=="object");if(u.length>0){let l=i(u);if(l){let p=new Map;for(let m=0;m<u.length;m++)p.set(u[m],l[m]);return s.map(m=>p.get(m)??m)}}}else if(s!=null&&typeof s=="object"){let u=i([s]);if(u&&u.length>0)return u[0]}return s}):r}}});return c}function pn(o,n,e,t){return new Proxy({},{get(c,f){if(f==="withTransaction")return d=>pn(d,n,e,t);let g=G(o,n.schema);g.selectVariants?.([e]);let y=vn(g,n,e,o,t),a=y[f];return typeof a!="function"?a:a.bind(y)}})}function Y(o,n){let e={};for(let i of Object.keys(n))e[i]=q(o,n[i]);return{...e,knex:o,withTransaction(i){return Y(i,n)},async transaction(i){return o.transaction(async c=>i(Y(c,n)))}}}function L(o,n,e){let t={};for(let c of Object.keys(n)){let f=c,g=y=>y.map(a=>a!==null&&typeof a=="object"?e.attach(f,a,"Unchanged"):a);t[c]=q(o,n[c],g)}return{...t,knex:o,withTransaction(c){return L(c,n,e)},async transaction(c){return o.transaction(async f=>c(L(f,n,e)))},attach(c,f){return e.attach(c,f,"Unchanged")},detach(c){e.detach(c)},remove(c){e.remove(c)},entry(c){return e.entry(c)},onSavingChanges(c){e.onSavingChanges(c)},async reload(c){return e.reload(c,o)},discardChanges(){e.discardChanges()},async saveChanges(){return e.saveChanges(o)},async[Symbol.asyncDispose](){if(e.hasPendingChanges()){let c=e.pendingSummary(),f=parseInt(c.match(/\d+/)?.[0]??"1",10);throw e.clear(),new j(f,c)}e.clear()}}}function Rn(o,n,e){if(e?.tracking){let t=new I;for(let i of Object.keys(n)){let c={schema:n[i].schema,entitySetKey:i};t.registerEntitySet(c)}return L(o,n,t)}return Y(o,n)}export{S as ConcurrencyError,M as EntityNotFoundError,v as InvariantViolationError,j as PendingChangesError,Rn as createDb};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|