@getstrata/core 0.5.99 → 0.5.101

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 0.5.101
4
+
5
+ - Tenant middleware falls back with explicit branches when a member, admin, or guest tenant lookup misses, so the request still scopes to the default tenant.
6
+
7
+ ## 0.5.100
8
+
9
+ HiroApp dogfood of 0.5.99: eager `with()` / nested `load("a.b")` missed related rows when a Postgres int4 PK arrived as a JS `number` and an int8 FK as a `bigint`. Map matching used `===`.
10
+
11
+ - Relation indexers and eager attach compare keys with `relationMatchKey` (`1`, `1n`, `"1"` match).
12
+ - Sequential `load("position")` / `application.position()` already used SQL `get()` and were unaffected.
13
+ - `registerModelRepository(User, users)` already names `User` and `$morphClass`. Do not also call `registerModelClass("User", User)`. `registerModelClass` is only for an alias that is neither `constructor.name` nor `$morphClass`.
14
+
15
+ Still non-conforming (honest): Bun cannot infer `morphTo()` method names. `hashed` cast is a no-op. `Factory.has()` without a bound model still needs an explicit FK.
16
+
3
17
  ## 0.5.99
4
18
 
5
19
  HiroApp dogfood of 0.5.98 found lookalike APIs. This release matches Laravel call shape and SQL, not just export names.
@@ -1,5 +1,5 @@
1
1
  import { type CursorPaginatedResult, type PaginatedResult } from "../pagination/index.ts";
2
- import { type BelongsToManyRelation, type BelongsToRelation, type HasManyRelation, type MorphManyRelation, type MorphOneRelation, type MorphToRelation } from "./relationships.ts";
2
+ import { type BelongsToManyRelation, type BelongsToRelation, type HasManyRelation, type HasManyThroughRelation, type MorphManyRelation, type MorphOneRelation, type MorphToRelation } from "./relationships.ts";
3
3
  import { RepositoryQuery } from "./repositoryQuery.ts";
4
4
  import type { TableDefinition } from "./table.ts";
5
5
  import type { MutationValues, QueryOptions, QueryWhere, UpdateValues } from "./types.ts";
@@ -59,6 +59,10 @@ declare class BaseRepository<TEntity extends object, PrimaryKey extends keyof TE
59
59
  }>>;
60
60
  findByHasManyRelation<TParent extends object, LocalKey extends keyof TParent & string, ForeignKey extends keyof TEntity & string>(relation: HasManyRelation<TParent, TEntity, LocalKey, ForeignKey>, parentId: TParent[LocalKey], options?: Omit<QueryOptions<TEntity>, "where">): Promise<TEntity[]>;
61
61
  loadHasManyForParents<TParent extends object, LocalKey extends keyof TParent & string, ForeignKey extends keyof TEntity & string>(parents: readonly TParent[], relation: HasManyRelation<TParent, TEntity, LocalKey, ForeignKey>, options?: Omit<QueryOptions<TEntity>, "where">): Promise<Map<TParent[LocalKey], TEntity[]>>;
62
+ findHasManyThrough<TParent extends object, LocalKey extends keyof TParent & string, FirstKey extends string, SecondLocalKey extends string, SecondKey extends keyof TEntity & string>(parentId: unknown, relation: HasManyThroughRelation<TParent, TEntity, LocalKey, FirstKey, SecondLocalKey, SecondKey>, options?: QueryOptions<TEntity>): Promise<TEntity[]>;
63
+ loadHasManyThroughForParents<TParent extends object, LocalKey extends keyof TParent & string, FirstKey extends string, SecondLocalKey extends string, SecondKey extends keyof TEntity & string>(parents: readonly TParent[], relation: HasManyThroughRelation<TParent, TEntity, LocalKey, FirstKey, SecondLocalKey, SecondKey>, options?: QueryOptions<TEntity>): Promise<Map<TParent[LocalKey], TEntity[]>>;
64
+ private throughSoftDeleteClause;
65
+ private buildThroughWhere;
62
66
  loadBelongsToForParents<TChild extends object, TParent extends object, ForeignKey extends keyof TChild & string, OwnerKey extends keyof TParent & string>(children: readonly TChild[], relation: BelongsToRelation<TChild, TParent, ForeignKey, OwnerKey>, parentRepository: BaseRepository<TParent, OwnerKey>, options?: Omit<QueryOptions<TParent>, "where">): Promise<Map<TChild[ForeignKey], TParent>>;
63
67
  loadMorphManyForParents<TParent extends object, LocalKey extends keyof TParent & string, MorphTypeKey extends keyof TEntity & string, MorphIdKey extends keyof TEntity & string>(parents: readonly TParent[], relation: MorphManyRelation<TParent, TEntity, LocalKey, MorphTypeKey, MorphIdKey>, options?: Omit<QueryOptions<TEntity>, "where">): Promise<Map<TParent[LocalKey], TEntity[]>>;
64
68
  loadMorphOneForParents<TParent extends object, LocalKey extends keyof TParent & string, MorphTypeKey extends keyof TEntity & string, MorphIdKey extends keyof TEntity & string>(parents: readonly TParent[], relation: MorphOneRelation<TParent, TEntity, LocalKey, MorphTypeKey, MorphIdKey>, options?: Omit<QueryOptions<TEntity>, "where">): Promise<Map<TParent[LocalKey], TEntity | undefined>>;
@@ -5,10 +5,10 @@ export { mapDatabaseError, withDatabaseErrorHandling } from "./errors.ts";
5
5
  export { Factory } from "./factory.ts";
6
6
  export { foreignKeyFromTable, pivotTableName, singularize } from "./inflection.ts";
7
7
  export type { CastType, GlobalScopeFn, ModelConstructor } from "./model.ts";
8
- export { applyCasts, BelongsToManyRelationQuery, BelongsToRelationQuery, dehydrateValue, filterMassAssignable, HasManyRelationQuery, HasOneRelationQuery, hydrateValue, Model, ModelQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, registerModelClass, registerModelRepository, } from "./model.ts";
8
+ export { applyCasts, BelongsToManyRelationQuery, BelongsToRelationQuery, dehydrateValue, filterMassAssignable, HasManyRelationQuery, HasManyThroughRelationQuery, HasOneRelationQuery, hydrateValue, Model, ModelQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, registerModelClass, registerModelRepository, } from "./model.ts";
9
9
  export { buildAdvancedWhereClause, buildCountQuery, buildDeleteByIdQuery, buildGroupedCountQuery, buildInsertQuery, buildJoinClause, buildOrderByClause, buildProjectionQuery, buildQueryWhereClause, buildRestoreByIdQuery, buildSelectQuery, buildSoftDeleteByIdQuery, buildUpdateQuery, buildWhereClause, parseQualifiedColumn, qualifyColumn, quoteIdentifier, resolveQualifiedColumn, resolveSoftDeleteColumn, } from "./query.ts";
10
- export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, } from "./relationships.ts";
11
- export { belongsTo, belongsToMany, hasMany, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, } from "./relationships.ts";
10
+ export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasManyThroughRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, } from "./relationships.ts";
11
+ export { belongsTo, belongsToMany, hasMany, hasManyThrough, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasManyThroughRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, } from "./relationships.ts";
12
12
  export { RepositoryQuery } from "./repositoryQuery.ts";
13
13
  export type { BlueprintAction, BlueprintCallback, ColumnKind, DatabaseDriver, ForeignKeyOptions, Grammar, IndexDefinition, IndexKind, ResolveDatabaseDriverOptions, SchemaBuilder, } from "./schema/index.ts";
14
14
  export { Blueprint, ColumnDefinition, compileBlueprint, createSchemaBuilder, ForeignIdColumnDefinition, grammarForDriver, inferReferencedTable, MySqlGrammar, PostgresGrammar, resolveDatabaseDriver, Schema, SqliteGrammar, UnsupportedSchemaFeatureError, } from "./schema/index.ts";
@@ -1,6 +1,6 @@
1
1
  import type BaseRepository from "./baseRepository.ts";
2
2
  import type { AnyRelationQuery, RelatedModelClass } from "./relationQuery.ts";
3
- import { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery } from "./relationQuery.ts";
3
+ import { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasManyThroughRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery } from "./relationQuery.ts";
4
4
  import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation } from "./relationships.ts";
5
5
  import type { RepositoryQuery } from "./repositoryQuery.ts";
6
6
  import type { QueryOptions, QueryWhere } from "./types.ts";
@@ -25,6 +25,7 @@ type ModelObserver = {
25
25
  deleting?: (model: AnyModel) => unknown;
26
26
  deleted?: (model: AnyModel) => unknown;
27
27
  };
28
+ /** Alias for `hasMany("Name")` when `Name` is not `constructor.name` or `$morphClass`. */
28
29
  declare function registerModelClass(name: string, model: object): void;
29
30
  declare function hydrateValue(value: unknown, cast: CastType): unknown;
30
31
  declare function dehydrateValue(value: unknown, cast: CastType): unknown;
@@ -55,6 +56,9 @@ declare class ModelQuery {
55
56
  withMorphMany(...args: Parameters<RepositoryQuery<Record<string, unknown>, "id">["withMorphMany"]>): this;
56
57
  withMorphOne(...args: Parameters<RepositoryQuery<Record<string, unknown>, "id">["withMorphOne"]>): this;
57
58
  withMorphTo(...args: Parameters<RepositoryQuery<Record<string, unknown>, "id">["withMorphTo"]>): this;
59
+ withHasManyThrough(...args: Parameters<RepositoryQuery<Record<string, unknown>, "id">["withHasManyThrough"]>): this;
60
+ withTrashed(): this;
61
+ onlyTrashed(): this;
58
62
  get(): Promise<Array<Model<Record<string, unknown>, "id">>>;
59
63
  first(): Promise<Model<Record<string, unknown>, "id"> | null>;
60
64
  find(id: unknown): Promise<Model<Record<string, unknown>, "id"> | null>;
@@ -102,6 +106,21 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
102
106
  static newFromRecord(this: object, record: object, exists?: boolean): Model<Record<string, unknown>, "id">;
103
107
  static create(this: object, attributes: Record<string, unknown>, forced?: Record<string, unknown>): Promise<Model<Record<string, unknown>, "id">>;
104
108
  static with(this: object, ...relations: string[]): ModelQuery;
109
+ static withTrashed(this: object): ModelQuery;
110
+ static onlyTrashed(this: object): ModelQuery;
111
+ static chunk(this: object, count: number, callback: (models: Array<Model<Record<string, unknown>, "id">>) => Promise<boolean | void>): Promise<void>;
112
+ static cursorPaginate(this: object, options: {
113
+ perPage: number;
114
+ cursor?: unknown;
115
+ }): Promise<{
116
+ data: Array<Model<Record<string, unknown>, "id">>;
117
+ meta: {
118
+ per_page: number;
119
+ next_cursor: unknown;
120
+ prev_cursor: unknown;
121
+ has_more: boolean;
122
+ };
123
+ }>;
105
124
  static whereHas(this: object, name: string, constrain?: (query: AnyRelationQuery) => void): ModelQuery;
106
125
  static has(this: object, name: string): ModelQuery;
107
126
  static doesntHave(this: object, name: string): ModelQuery;
@@ -126,6 +145,7 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
126
145
  hasMany<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedRef<TRelated, RelatedKey>, foreignKey?: keyof TRelated & string, localKey?: PrimaryKey): HasManyRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
127
146
  hasOne<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedRef<TRelated, RelatedKey>, foreignKey?: keyof TRelated & string, localKey?: PrimaryKey): HasOneRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
128
147
  belongsTo<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedRef<TRelated, RelatedKey>, foreignKey?: keyof TEntity & string, ownerKey?: RelatedKey): BelongsToRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
148
+ hasManyThrough<TRelated extends object, TThrough extends object, RelatedKey extends keyof TRelated & string, ThroughKey extends keyof TThrough & string>(related: RelatedRef<TRelated, RelatedKey>, through: RelatedRef<TThrough, ThroughKey>, firstKey?: string, secondKey?: keyof TRelated & string, localKey?: PrimaryKey, secondLocalKey?: ThroughKey): HasManyThroughRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
129
149
  belongsToMany<TRelated extends object, RelatedKey extends keyof TRelated & string, Pivot extends object = Record<string, unknown>>(related: RelatedRef<TRelated, RelatedKey>, pivotTable?: string, foreignPivotKey?: keyof Pivot & string, relatedPivotKey?: keyof Pivot & string): BelongsToManyRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey, Pivot>;
130
150
  morphMany<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedRef<TRelated, RelatedKey>, morphName: string, typeKey?: keyof TRelated & string, idKey?: keyof TRelated & string, morphType?: string): MorphManyRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
131
151
  morphOne<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedRef<TRelated, RelatedKey>, morphName: string, typeKey?: keyof TRelated & string, idKey?: keyof TRelated & string, morphType?: string): MorphOneRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
@@ -135,7 +155,8 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
135
155
  setLoaded(name: string, value: unknown): this;
136
156
  mergeAttributes(patch: Partial<TEntity>): this;
137
157
  }
158
+ /** Binds a Model class to its table/connection and names it for `hasMany("User")`. */
138
159
  declare function registerModelRepository<TModelClass>(model: TModelClass, repository: object): TModelClass;
139
- export { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, } from "./relationQuery.ts";
160
+ export { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasManyThroughRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, } from "./relationQuery.ts";
140
161
  export type { CastType, GlobalScopeFn, ModelClassType, ModelConstructor };
141
162
  export { applyCasts, dehydrateValue, filterMassAssignable, hydrateValue, Model, ModelQuery, registerModelClass, registerModelRepository, };
@@ -1,5 +1,5 @@
1
1
  import type BaseRepository from "./baseRepository.ts";
2
- import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation } from "./relationships.ts";
2
+ import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasManyThroughRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation } from "./relationships.ts";
3
3
  import type { RepositoryQuery } from "./repositoryQuery.ts";
4
4
  import type { QueryOptions, QueryWhere } from "./types.ts";
5
5
  type RelatedRecord = {
@@ -17,7 +17,7 @@ interface RelationHost<TEntity extends object, PrimaryKey extends keyof TEntity
17
17
  get<K extends keyof TEntity>(key: K): TEntity[K];
18
18
  getRepository(): BaseRepository<TEntity, PrimaryKey>;
19
19
  }
20
- type RelationKind = "hasMany" | "hasOne" | "belongsTo" | "belongsToMany" | "morphMany" | "morphOne" | "morphTo";
20
+ type RelationKind = "hasMany" | "hasOne" | "belongsTo" | "belongsToMany" | "hasManyThrough" | "morphMany" | "morphOne" | "morphTo";
21
21
  type ExistsClause = {
22
22
  sql: string;
23
23
  params: unknown[];
@@ -159,6 +159,25 @@ declare class MorphToRelationQuery<TChild extends object, ChildKey extends keyof
159
159
  get(): Promise<RelatedRecord | null>;
160
160
  then(onfulfilled?: ((value: RelatedRecord | null) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
161
161
  }
162
+ declare class HasManyThroughRelationQuery<TParent extends object, ParentKey extends keyof TParent & string, TFar extends object, FarKey extends keyof TFar & string> {
163
+ private readonly parent;
164
+ private readonly related;
165
+ readonly relation: HasManyThroughRelation<TParent, TFar, ParentKey, string, string, keyof TFar & string>;
166
+ readonly kind: RelationKind;
167
+ private extraWhere;
168
+ private extraOptions;
169
+ constructor(parent: RelationHost<TParent, ParentKey>, related: RelatedModelClass<TFar, FarKey>, relation: HasManyThroughRelation<TParent, TFar, ParentKey, string, string, keyof TFar & string>);
170
+ where(where: QueryWhere<TFar>): this;
171
+ orderBy(orderBy: QueryOptions<TFar>["orderBy"]): this;
172
+ limit(limit: number): this;
173
+ applyEagerLoad(query: RepositoryQuery<TParent, ParentKey>, alias: string): void;
174
+ hydrateEager(row: Record<string, unknown>, alias: string): unknown;
175
+ toExistsClause(parentTable: string): ExistsClause;
176
+ get(): Promise<RelatedRecord[]>;
177
+ first(): Promise<RelatedRecord | null>;
178
+ count(): Promise<number>;
179
+ then(onfulfilled?: ((value: RelatedRecord[]) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
180
+ }
162
181
  type AnyRelationQuery = {
163
182
  kind: RelationKind;
164
183
  applyEagerLoad(query: unknown, alias: string): void;
@@ -169,4 +188,4 @@ type AnyRelationQuery = {
169
188
  then?: (onfulfilled?: ((value: unknown) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null) => Promise<unknown>;
170
189
  };
171
190
  export type { AnyRelationQuery, RelatedModelClass, RelatedRecord, RelationHost };
172
- export { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, };
191
+ export { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasManyThroughRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, };
@@ -25,6 +25,16 @@ interface BelongsToManyRelation<TParent, TRelated, Pivot extends object, ParentK
25
25
  foreignPivotKey: ForeignPivotKey;
26
26
  relatedPivotKey: RelatedPivotKey;
27
27
  }
28
+ interface HasManyThroughRelation<TParent, TFar, LocalKey extends keyof TParent & string = keyof TParent & string, FirstKey extends string = string, SecondLocalKey extends string = string, SecondKey extends keyof TFar & string = keyof TFar & string> {
29
+ type: "hasManyThrough";
30
+ name: string;
31
+ throughTable: string;
32
+ localKey: LocalKey;
33
+ firstKey: FirstKey;
34
+ secondLocalKey: SecondLocalKey;
35
+ secondKey: SecondKey;
36
+ throughParentKey?: string;
37
+ }
28
38
  declare function hasMany<TParent, TChild, LocalKey extends keyof TParent & string = keyof TParent & string, ForeignKey extends keyof TChild & string = keyof TChild & string>(definition: {
29
39
  name: string;
30
40
  localKey: LocalKey;
@@ -40,6 +50,15 @@ declare function belongsTo<TChild, TParent, ForeignKey extends keyof TChild & st
40
50
  foreignKey: ForeignKey;
41
51
  ownerKey: OwnerKey;
42
52
  }): BelongsToRelation<TChild, TParent, ForeignKey, OwnerKey>;
53
+ declare function hasManyThrough<TParent, TFar, LocalKey extends keyof TParent & string = keyof TParent & string, FirstKey extends string = string, SecondLocalKey extends string = string, SecondKey extends keyof TFar & string = keyof TFar & string>(definition: {
54
+ name: string;
55
+ throughTable: string;
56
+ localKey: LocalKey;
57
+ firstKey: FirstKey;
58
+ secondLocalKey: SecondLocalKey;
59
+ secondKey: SecondKey;
60
+ throughParentKey?: string;
61
+ }): HasManyThroughRelation<TParent, TFar, LocalKey, FirstKey, SecondLocalKey, SecondKey>;
43
62
  declare function belongsToMany<TParent, TRelated, Pivot extends object, ParentKey extends keyof TParent & string = keyof TParent & string, RelatedKey extends keyof TRelated & string = keyof TRelated & string, ForeignPivotKey extends keyof Pivot & string = keyof Pivot & string, RelatedPivotKey extends keyof Pivot & string = keyof Pivot & string>(definition: {
44
63
  name: string;
45
64
  pivotTable: string;
@@ -48,6 +67,8 @@ declare function belongsToMany<TParent, TRelated, Pivot extends object, ParentKe
48
67
  foreignPivotKey: ForeignPivotKey;
49
68
  relatedPivotKey: RelatedPivotKey;
50
69
  }): BelongsToManyRelation<TParent, TRelated, Pivot, ParentKey, RelatedKey, ForeignPivotKey, RelatedPivotKey>;
70
+ declare function relationMatchKey(value: unknown): string;
71
+ declare function getByRelationKey<K, V>(map: ReadonlyMap<K, V>, key: unknown): V | undefined;
51
72
  declare function indexHasManyRelation<TParent, TChild, LocalKey extends keyof TParent & string, ForeignKey extends keyof TChild & string>(parents: readonly TParent[], children: readonly TChild[], relation: HasManyRelation<TParent, TChild, LocalKey, ForeignKey>): Map<TParent[LocalKey], TChild[]>;
52
73
  declare function indexHasOneRelation<TParent, TChild, LocalKey extends keyof TParent & string, ForeignKey extends keyof TChild & string>(parents: readonly TParent[], children: readonly TChild[], relation: HasOneRelation<TParent, TChild, LocalKey, ForeignKey>): Map<TParent[LocalKey], TChild | undefined>;
53
74
  declare function indexBelongsToRelation<TChild, TParent, ForeignKey extends keyof TChild & string, OwnerKey extends keyof TParent & string>(children: readonly TChild[], parents: readonly TParent[], relation: BelongsToRelation<TChild, TParent, ForeignKey, OwnerKey>): Map<TChild[ForeignKey], TParent>;
@@ -95,6 +116,7 @@ declare function morphTo<TChild, MorphTypeKey extends keyof TChild & string = ke
95
116
  }): MorphToRelation<TChild, MorphTypeKey, MorphIdKey>;
96
117
  declare function indexMorphManyRelation<TParent, TChild, LocalKey extends keyof TParent & string, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string>(parents: readonly TParent[], children: readonly TChild[], relation: MorphManyRelation<TParent, TChild, LocalKey, MorphTypeKey, MorphIdKey>): Map<TParent[LocalKey], TChild[]>;
97
118
  declare function indexMorphOneRelation<TParent, TChild, LocalKey extends keyof TParent & string, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string>(parents: readonly TParent[], children: readonly TChild[], relation: MorphOneRelation<TParent, TChild, LocalKey, MorphTypeKey, MorphIdKey>): Map<TParent[LocalKey], TChild | undefined>;
98
- declare function indexMorphToRelation<TChild, TParent extends object, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, OwnerKey extends keyof TParent & string>(children: readonly TChild[], parentsByType: ReadonlyMap<string, ReadonlyMap<TParent[OwnerKey], TParent>>, relation: MorphToRelation<TChild, MorphTypeKey, MorphIdKey>): Map<TChild[MorphIdKey], TParent>;
99
- export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, };
100
- export { belongsTo, belongsToMany, hasMany, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, };
119
+ declare function indexHasManyThroughRelation<TParent, TFar, LocalKey extends keyof TParent & string, FirstKey extends string, SecondLocalKey extends string, SecondKey extends keyof TFar & string>(parents: readonly TParent[], children: readonly (TFar & Record<string, unknown>)[], relation: HasManyThroughRelation<TParent, TFar, LocalKey, FirstKey, SecondLocalKey, SecondKey>): Map<TParent[LocalKey], TFar[]>;
120
+ declare function indexMorphToRelation<TChild, TParent extends object, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, _OwnerKey extends keyof TParent & string = keyof TParent & string>(children: readonly TChild[], parentsByType: ReadonlyMap<string, ReadonlyMap<unknown, TParent>>, relation: MorphToRelation<TChild, MorphTypeKey, MorphIdKey>): Map<TChild[MorphIdKey], TParent>;
121
+ export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasManyThroughRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, };
122
+ export { belongsTo, belongsToMany, getByRelationKey, hasMany, hasManyThrough, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasManyThroughRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, relationMatchKey, };
@@ -1,6 +1,6 @@
1
1
  import type { PaginatedResult } from "../pagination/index.ts";
2
2
  import type BaseRepository from "./baseRepository.ts";
3
- import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, MorphManyRelation, MorphOneRelation, MorphToRelation } from "./relationships.ts";
3
+ import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasManyThroughRelation, MorphManyRelation, MorphOneRelation, MorphToRelation } from "./relationships.ts";
4
4
  import type { QueryOptions, QueryWhere } from "./types.ts";
5
5
  import { WhereBuilder } from "./whereBuilder.ts";
6
6
  type LoadedRow = Record<string, unknown>;
@@ -31,6 +31,9 @@ declare class RepositoryQuery<TEntity extends object, PrimaryKey extends keyof T
31
31
  withMorphOne<TChild extends object, LocalKey extends keyof TEntity & string, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, Alias extends string>(as: Alias, relation: MorphOneRelation<TEntity, TChild, LocalKey, MorphTypeKey, MorphIdKey>, childRepository: BaseRepository<TChild, keyof TChild & string>, options?: Omit<QueryOptions<TChild>, "where">): this;
32
32
  withMorphTo<TParent extends object, MorphTypeKey extends keyof TEntity & string, MorphIdKey extends keyof TEntity & string, Alias extends string>(as: Alias, relation: MorphToRelation<TEntity, MorphTypeKey, MorphIdKey>, repositoriesByType: ReadonlyMap<string, BaseRepository<TParent, keyof TParent & string>>, options?: Omit<QueryOptions<TParent>, "where">): this;
33
33
  withBelongsToMany<TRelated extends object, Pivot extends object, ParentKey extends keyof TEntity & string, RelatedKey extends keyof TRelated & string, ForeignPivotKey extends keyof Pivot & string, RelatedPivotKey extends keyof Pivot & string, Alias extends string>(as: Alias, relation: BelongsToManyRelation<TEntity, TRelated, Pivot, ParentKey, RelatedKey, ForeignPivotKey, RelatedPivotKey>, relatedRepository: BaseRepository<TRelated, RelatedKey>, options?: Omit<QueryOptions<TRelated>, "where">): this;
34
+ withHasManyThrough<TFar extends object, LocalKey extends keyof TEntity & string, SecondKey extends keyof TFar & string, Alias extends string>(as: Alias, relation: HasManyThroughRelation<TEntity, TFar, LocalKey, string, string, SecondKey>, farRepository: BaseRepository<TFar, keyof TFar & string>, options?: Omit<QueryOptions<TFar>, "where">): this;
35
+ withTrashed(): this;
36
+ onlyTrashed(): this;
34
37
  get(): Promise<Array<TEntity & LoadedRow>>;
35
38
  first(): Promise<(TEntity & LoadedRow) | null>;
36
39
  count(): Promise<number>;
@@ -775,7 +775,8 @@ class BelongsToManyRelationQuery {
775
775
  return;
776
776
  }
777
777
  const list = Array.isArray(ids) ? ids : [ids];
778
- await this.connection().unsafe(`DELETE FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1 AND ${String(this.relation.relatedPivotKey)} = ANY($2)`, [parentId, list]);
778
+ const placeholders = list.map((_, index) => `$${index + 2}`).join(", ");
779
+ await this.connection().unsafe(`DELETE FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1 AND ${String(this.relation.relatedPivotKey)} IN (${placeholders})`, [parentId, ...list]);
779
780
  }
780
781
  async sync(ids) {
781
782
  await this.detach();
@@ -957,6 +958,65 @@ class MorphToRelationQuery {
957
958
  }
958
959
  }
959
960
 
961
+ class HasManyThroughRelationQuery {
962
+ parent;
963
+ related;
964
+ relation;
965
+ kind = "hasManyThrough";
966
+ extraWhere = {};
967
+ extraOptions = {};
968
+ constructor(parent, related, relation) {
969
+ this.parent = parent;
970
+ this.related = related;
971
+ this.relation = relation;
972
+ }
973
+ where(where) {
974
+ this.extraWhere = { ...this.extraWhere, ...where };
975
+ return this;
976
+ }
977
+ orderBy(orderBy) {
978
+ this.extraOptions = { ...this.extraOptions, orderBy };
979
+ return this;
980
+ }
981
+ limit(limit) {
982
+ this.extraOptions = { ...this.extraOptions, limit };
983
+ return this;
984
+ }
985
+ applyEagerLoad(query, alias) {
986
+ query.withHasManyThrough(alias, this.relation, this.related.repository(), this.extraOptions);
987
+ }
988
+ hydrateEager(row, alias) {
989
+ const value = row[alias] ?? [];
990
+ const rows = Array.isArray(value) ? value : [];
991
+ return rows.map((item) => this.related.newFromRecord(item));
992
+ }
993
+ toExistsClause(parentTable) {
994
+ const farTable = this.related.repository().getTable().name;
995
+ const extra = buildAdvancedWhereClause(farTable, this.extraWhere, [], []);
996
+ const extraSql = extra.clause.replace(/^ WHERE /, "");
997
+ const sql = `SELECT 1 FROM ${quoteIdentifier(farTable)} INNER JOIN ${quoteIdentifier(this.relation.throughTable)} ON ${qualifyColumn(this.relation.throughTable, this.relation.secondLocalKey)} = ${qualifyColumn(farTable, this.relation.secondKey)} WHERE ${qualifyColumn(this.relation.throughTable, this.relation.firstKey)} = ${qualifyColumn(parentTable, this.relation.localKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
998
+ return { sql, params: extra.params };
999
+ }
1000
+ async get() {
1001
+ const rows = await this.related.repository().withConnection(this.parent.getRepository().getConnection()).findHasManyThrough(this.parent.get(this.relation.localKey), this.relation, {
1002
+ ...this.extraOptions,
1003
+ where: this.extraWhere
1004
+ });
1005
+ return rows.map((row) => this.related.newFromRecord(row));
1006
+ }
1007
+ async first() {
1008
+ const rows = await this.limit(1).get();
1009
+ return rows[0] ?? null;
1010
+ }
1011
+ async count() {
1012
+ const rows = await this.get();
1013
+ return rows.length;
1014
+ }
1015
+ then(onfulfilled, onrejected) {
1016
+ return thenGet(() => this.get(), onfulfilled, onrejected);
1017
+ }
1018
+ }
1019
+
960
1020
  // ../../src/core/database/relationships.ts
961
1021
  function hasMany(definition) {
962
1022
  return {
@@ -976,32 +1036,80 @@ function belongsTo(definition) {
976
1036
  ...definition
977
1037
  };
978
1038
  }
1039
+ function hasManyThrough(definition) {
1040
+ return {
1041
+ type: "hasManyThrough",
1042
+ throughParentKey: "__through_parent_id",
1043
+ ...definition
1044
+ };
1045
+ }
979
1046
  function belongsToMany(definition) {
980
1047
  return {
981
1048
  type: "belongsToMany",
982
1049
  ...definition
983
1050
  };
984
1051
  }
1052
+ function relationMatchKey(value) {
1053
+ if (value === null || value === undefined) {
1054
+ return "";
1055
+ }
1056
+ if (typeof value === "bigint") {
1057
+ return value.toString();
1058
+ }
1059
+ if (typeof value === "number" && Number.isFinite(value)) {
1060
+ return String(value);
1061
+ }
1062
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
1063
+ return BigInt(value).toString();
1064
+ }
1065
+ return String(value);
1066
+ }
1067
+ function getByRelationKey(map, key) {
1068
+ if (map.has(key)) {
1069
+ return map.get(key);
1070
+ }
1071
+ const want = relationMatchKey(key);
1072
+ if (want === "") {
1073
+ return;
1074
+ }
1075
+ for (const [existing, value] of map) {
1076
+ if (relationMatchKey(existing) === want) {
1077
+ return value;
1078
+ }
1079
+ }
1080
+ return;
1081
+ }
985
1082
  function indexHasManyRelation(parents, children, relation) {
986
1083
  const groups = new Map;
1084
+ const originalKeys = new Map;
987
1085
  for (const parent of parents) {
988
- groups.set(parent[relation.localKey], []);
1086
+ const key = relationMatchKey(parent[relation.localKey]);
1087
+ if (!groups.has(key)) {
1088
+ groups.set(key, []);
1089
+ originalKeys.set(key, parent[relation.localKey]);
1090
+ }
989
1091
  }
990
1092
  for (const child of children) {
991
- const key = child[relation.foreignKey];
992
- const group = groups.get(key);
1093
+ const group = groups.get(relationMatchKey(child[relation.foreignKey]));
993
1094
  if (!group) {
994
1095
  continue;
995
1096
  }
996
1097
  group.push(child);
997
1098
  }
998
- return groups;
1099
+ const result = new Map;
1100
+ for (const [key, group] of groups) {
1101
+ const original = originalKeys.get(key);
1102
+ if (original !== undefined) {
1103
+ result.set(original, group);
1104
+ }
1105
+ }
1106
+ return result;
999
1107
  }
1000
1108
  function indexHasOneRelation(parents, children, relation) {
1001
1109
  const grouped = indexHasManyRelation(parents, children, relation);
1002
1110
  const result = new Map;
1003
1111
  for (const parent of parents) {
1004
- const matches = grouped.get(parent[relation.localKey]) ?? [];
1112
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
1005
1113
  result.set(parent[relation.localKey], matches[0]);
1006
1114
  }
1007
1115
  return result;
@@ -1009,12 +1117,12 @@ function indexHasOneRelation(parents, children, relation) {
1009
1117
  function indexBelongsToRelation(children, parents, relation) {
1010
1118
  const parentsById = new Map;
1011
1119
  for (const parent of parents) {
1012
- parentsById.set(parent[relation.ownerKey], parent);
1120
+ parentsById.set(relationMatchKey(parent[relation.ownerKey]), parent);
1013
1121
  }
1014
1122
  const result = new Map;
1015
1123
  for (const child of children) {
1016
1124
  const foreignKey = child[relation.foreignKey];
1017
- const parent = parentsById.get(foreignKey);
1125
+ const parent = parentsById.get(relationMatchKey(foreignKey));
1018
1126
  if (parent) {
1019
1127
  result.set(foreignKey, parent);
1020
1128
  }
@@ -1024,23 +1132,33 @@ function indexBelongsToRelation(children, parents, relation) {
1024
1132
  function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
1025
1133
  const relatedById = new Map;
1026
1134
  for (const related of relatedRows) {
1027
- relatedById.set(related[relation.relatedKey], related);
1135
+ relatedById.set(relationMatchKey(related[relation.relatedKey]), related);
1028
1136
  }
1029
1137
  const groups = new Map;
1138
+ const originalKeys = new Map;
1030
1139
  for (const parent of parents) {
1031
- groups.set(parent[relation.parentKey], []);
1140
+ const key = relationMatchKey(parent[relation.parentKey]);
1141
+ if (!groups.has(key)) {
1142
+ groups.set(key, []);
1143
+ originalKeys.set(key, parent[relation.parentKey]);
1144
+ }
1032
1145
  }
1033
1146
  for (const pivot of pivotRows) {
1034
- const parentId = pivot[relation.foreignPivotKey];
1035
- const relatedId = pivot[relation.relatedPivotKey];
1036
- const group = groups.get(parentId);
1037
- const related = relatedById.get(relatedId);
1147
+ const group = groups.get(relationMatchKey(pivot[relation.foreignPivotKey]));
1148
+ const related = relatedById.get(relationMatchKey(pivot[relation.relatedPivotKey]));
1038
1149
  if (!group || !related) {
1039
1150
  continue;
1040
1151
  }
1041
1152
  group.push(related);
1042
1153
  }
1043
- return groups;
1154
+ const result = new Map;
1155
+ for (const [key, group] of groups) {
1156
+ const original = originalKeys.get(key);
1157
+ if (original !== undefined) {
1158
+ result.set(original, group);
1159
+ }
1160
+ }
1161
+ return result;
1044
1162
  }
1045
1163
  function morphMany(definition) {
1046
1164
  return {
@@ -1062,31 +1180,62 @@ function morphTo(definition) {
1062
1180
  }
1063
1181
  function indexMorphManyRelation(parents, children, relation) {
1064
1182
  const groups = new Map;
1183
+ const originalKeys = new Map;
1065
1184
  for (const parent of parents) {
1066
- groups.set(parent[relation.localKey], []);
1185
+ const key = relationMatchKey(parent[relation.localKey]);
1186
+ if (!groups.has(key)) {
1187
+ groups.set(key, []);
1188
+ originalKeys.set(key, parent[relation.localKey]);
1189
+ }
1067
1190
  }
1068
1191
  for (const child of children) {
1069
1192
  if (child[relation.morphTypeKey] !== relation.morphType) {
1070
1193
  continue;
1071
1194
  }
1072
- const key = child[relation.morphIdKey];
1073
- const group = groups.get(key);
1195
+ const group = groups.get(relationMatchKey(child[relation.morphIdKey]));
1074
1196
  if (!group) {
1075
1197
  continue;
1076
1198
  }
1077
1199
  group.push(child);
1078
1200
  }
1079
- return groups;
1201
+ const result = new Map;
1202
+ for (const [key, group] of groups) {
1203
+ const original = originalKeys.get(key);
1204
+ if (original !== undefined) {
1205
+ result.set(original, group);
1206
+ }
1207
+ }
1208
+ return result;
1080
1209
  }
1081
1210
  function indexMorphOneRelation(parents, children, relation) {
1082
1211
  const grouped = indexMorphManyRelation(parents, children, relation);
1083
1212
  const result = new Map;
1084
1213
  for (const parent of parents) {
1085
- const matches = grouped.get(parent[relation.localKey]) ?? [];
1214
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
1086
1215
  result.set(parent[relation.localKey], matches[0]);
1087
1216
  }
1088
1217
  return result;
1089
1218
  }
1219
+ function indexHasManyThroughRelation(parents, children, relation) {
1220
+ const throughKey = relation.throughParentKey ?? "__through_parent_id";
1221
+ const grouped = new Map;
1222
+ for (const child of children) {
1223
+ const key = relationMatchKey(child[throughKey]);
1224
+ if (key === "") {
1225
+ continue;
1226
+ }
1227
+ const existing = grouped.get(key) ?? [];
1228
+ const { [throughKey]: _through, ...far } = child;
1229
+ existing.push(far);
1230
+ grouped.set(key, existing);
1231
+ }
1232
+ const result = new Map;
1233
+ for (const parent of parents) {
1234
+ const key = relationMatchKey(parent[relation.localKey]);
1235
+ result.set(parent[relation.localKey], grouped.get(key) ?? []);
1236
+ }
1237
+ return result;
1238
+ }
1090
1239
  function indexMorphToRelation(children, parentsByType, relation) {
1091
1240
  const result = new Map;
1092
1241
  for (const child of children) {
@@ -1095,7 +1244,7 @@ function indexMorphToRelation(children, parentsByType, relation) {
1095
1244
  if (!parents) {
1096
1245
  continue;
1097
1246
  }
1098
- const parent = parents.get(child[relation.morphIdKey]);
1247
+ const parent = getByRelationKey(parents, child[relation.morphIdKey]);
1099
1248
  if (parent) {
1100
1249
  result.set(child[relation.morphIdKey], parent);
1101
1250
  }
@@ -1431,6 +1580,18 @@ class ModelQuery {
1431
1580
  this.query.withMorphTo(...args);
1432
1581
  return this;
1433
1582
  }
1583
+ withHasManyThrough(...args) {
1584
+ this.query.withHasManyThrough(...args);
1585
+ return this;
1586
+ }
1587
+ withTrashed() {
1588
+ this.query.withTrashed();
1589
+ return this;
1590
+ }
1591
+ onlyTrashed() {
1592
+ this.query.onlyTrashed();
1593
+ return this;
1594
+ }
1434
1595
  async get() {
1435
1596
  const statics = modelStatics(this.modelClass);
1436
1597
  const rows = await this.query.get();
@@ -1638,6 +1799,31 @@ class Model {
1638
1799
  static with(...relations) {
1639
1800
  return Model.query.call(this).with(...relations);
1640
1801
  }
1802
+ static withTrashed() {
1803
+ return Model.query.call(this).withTrashed();
1804
+ }
1805
+ static onlyTrashed() {
1806
+ return Model.query.call(this).onlyTrashed();
1807
+ }
1808
+ static async chunk(count, callback) {
1809
+ const statics = modelStatics(this);
1810
+ ensureBooted(this);
1811
+ await resolveModelRepository(this).chunk(count, async (rows) => {
1812
+ return await callback(rows.map((row) => statics.newFromRecord(row, true)));
1813
+ });
1814
+ }
1815
+ static async cursorPaginate(options) {
1816
+ const statics = modelStatics(this);
1817
+ ensureBooted(this);
1818
+ const page = await resolveModelRepository(this).cursorPaginate({
1819
+ perPage: options.perPage,
1820
+ cursor: options.cursor
1821
+ });
1822
+ return {
1823
+ data: page.data.map((row) => statics.newFromRecord(row, true)),
1824
+ meta: page.meta
1825
+ };
1826
+ }
1641
1827
  static whereHas(name, constrain) {
1642
1828
  return Model.query.call(this).whereHas(name, constrain);
1643
1829
  }
@@ -1762,7 +1948,7 @@ class Model {
1762
1948
  }
1763
1949
  async loadHasMany(as, relation, childRepository, options = {}) {
1764
1950
  const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
1765
- const loaded = grouped.get(this.attributes[relation.localKey]) ?? [];
1951
+ const loaded = getByRelationKey(grouped, this.attributes[relation.localKey]) ?? [];
1766
1952
  return Object.assign(this, { [as]: loaded });
1767
1953
  }
1768
1954
  async loadHasOne(as, relation, childRepository, options = {}) {
@@ -1772,7 +1958,7 @@ class Model {
1772
1958
  }
1773
1959
  async loadBelongsTo(as, relation, parentRepository, options = {}) {
1774
1960
  const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
1775
- const loaded = grouped.get(this.attributes[relation.foreignKey]);
1961
+ const loaded = getByRelationKey(grouped, this.attributes[relation.foreignKey]);
1776
1962
  return Object.assign(this, { [as]: loaded });
1777
1963
  }
1778
1964
  async loadBelongsToMany(as, relation, relatedRepository, options = {}) {
@@ -1792,7 +1978,7 @@ class Model {
1792
1978
  }
1793
1979
  });
1794
1980
  const grouped = indexBelongsToManyRelation([this.attributes], pivotRows, relatedRows, relation);
1795
- const loaded = grouped.get(parentId) ?? [];
1981
+ const loaded = getByRelationKey(grouped, parentId) ?? [];
1796
1982
  return Object.assign(this, { [as]: loaded });
1797
1983
  }
1798
1984
  hasMany(related, foreignKey, localKey) {
@@ -1822,6 +2008,20 @@ class Model {
1822
2008
  ownerKey: ownerKey ?? relatedTable.primaryKey
1823
2009
  }));
1824
2010
  }
2011
+ hasManyThrough(related, through, firstKey, secondKey, localKey, secondLocalKey) {
2012
+ const table = this.repository.getTable();
2013
+ const relatedClass = resolveRelated(related);
2014
+ const throughClass = resolveRelated(through);
2015
+ const throughTable = throughClass.repository().getTable();
2016
+ return new HasManyThroughRelationQuery(this, relatedClass, hasManyThrough({
2017
+ name: relatedClass.repository().getTable().name,
2018
+ throughTable: throughTable.name,
2019
+ localKey: localKey ?? table.primaryKey,
2020
+ firstKey: firstKey ?? foreignKeyFromTable(table.name),
2021
+ secondLocalKey: secondLocalKey ?? throughTable.primaryKey,
2022
+ secondKey: secondKey ?? foreignKeyFromTable(throughTable.name)
2023
+ }));
2024
+ }
1825
2025
  belongsToMany(related, pivotTable, foreignPivotKey, relatedPivotKey) {
1826
2026
  const table = this.repository.getTable();
1827
2027
  const relatedClass = resolveRelated(related);
@@ -1913,6 +2113,7 @@ export {
1913
2113
  BelongsToManyRelationQuery,
1914
2114
  BelongsToRelationQuery,
1915
2115
  HasManyRelationQuery,
2116
+ HasManyThroughRelationQuery,
1916
2117
  HasOneRelationQuery,
1917
2118
  Model,
1918
2119
  ModelQuery,