@getstrata/core 0.5.98 → 0.5.100

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,35 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 0.5.100
4
+
5
+ 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 `===`.
6
+
7
+ - Relation indexers and eager attach compare keys with `relationMatchKey` (`1`, `1n`, `"1"` match).
8
+ - Sequential `load("position")` / `application.position()` already used SQL `get()` and were unaffected.
9
+ - `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`.
10
+
11
+ 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.
12
+
13
+ ## 0.5.99
14
+
15
+ HiroApp dogfood of 0.5.98 found lookalike APIs. This release matches Laravel call shape and SQL, not just export names.
16
+
17
+ - Relation queries are thenable: `await user.applications()` delegates to `get()`.
18
+ - `Model.with()` / `where()` / `whereHas()` return a `ModelQuery` that hydrates models and chains into `where` / `find` / `findOrFail` / `first` / `get`. `first()` / `find()` use `LIMIT` / PK lookup.
19
+ - `belongsTo.where()` threads constraints into `whereHas` EXISTS (HiroApp application search).
20
+ - `{ ilike }` uses the value as-is (Laravel). Pass `%term%` yourself; the operator no longer wraps extra `%`.
21
+ - Morph type defaults to `$morphClass` / class name, not `table.name`. Override with `$morphClass = "App\\Models\\User"` or the last `morphMany` argument. `morphTo()` no longer silently defaults to `imageable_*`.
22
+ - `primaryKey()` defaults to the table PK (`id`).
23
+ - Nested `load("a.b")` / `with("a.b")` skip already-loaded heads and batch the next level.
24
+ - `count()` is `COUNT(*)` (relations and `RepositoryQuery`).
25
+ - `belongsToMany` eager-loads in two queries; `toggle()` and `withPivotValues()` exist. `hasMany.save($model)` sets the FK and saves.
26
+ - Factory `for(parent)` infers `user_id` from a Model parent. Bound `model` uses `Model.create()` so observers fire.
27
+ - `JsonResource.collection().toResponse()` wraps once: `{ data: [...] }`.
28
+ - Observers: `saving` / `saved` / `retrieved`. Integer casts: `integer` / `int`.
29
+ - `hasMany("Application")` / `() => Application` / `registerModelClass()` for ESM cycles.
30
+
31
+ Still non-conforming (honest): Bun cannot infer `morphTo()` method names (`debug_backtrace` equivalent is empty). `hashed` cast is a no-op (bcrypt is async). `Factory.has()` without a model class still needs an explicit FK.
32
+
3
33
  ## 0.5.98
4
34
 
5
35
  - Eloquent-shaped relations: `this.hasMany(Related)` returns a relation query (`get`/`where`/`create`/`attach`). `load()` / `loaded()` and `Model.with()` replace PHP `__get`. `whereHas`/`has`/`doesntHave`, morph* methods, and nested `with("a.b")` are supported.
@@ -1,5 +1,5 @@
1
1
  import { type CursorPaginatedResult, type PaginatedResult } from "../pagination/index.ts";
2
- import { type BelongsToRelation, type HasManyRelation, type MorphManyRelation, type MorphOneRelation, type MorphToRelation } from "./relationships.ts";
2
+ import { type BelongsToManyRelation, type BelongsToRelation, type HasManyRelation, 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";
@@ -20,6 +20,7 @@ declare class BaseRepository<TEntity extends object, PrimaryKey extends keyof TE
20
20
  protected readonly table: TableDefinition<TEntity, PrimaryKey>;
21
21
  protected readonly connection: DatabaseConnection;
22
22
  constructor(table: TableDefinition<TEntity, PrimaryKey>, connection?: DatabaseConnection);
23
+ count(options?: ExtendedQueryOptions<TEntity>): Promise<number>;
23
24
  findAll(options?: ExtendedQueryOptions<TEntity>): Promise<TEntity[]>;
24
25
  paginate(options: {
25
26
  page: number;
@@ -62,6 +63,7 @@ declare class BaseRepository<TEntity extends object, PrimaryKey extends keyof TE
62
63
  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[]>>;
63
64
  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>>;
64
65
  loadMorphToForChildren<TChild extends object, TParent extends object, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, OwnerKey extends keyof TParent & string>(children: readonly TChild[], relation: MorphToRelation<TChild, MorphTypeKey, MorphIdKey>, repositoriesByType: ReadonlyMap<string, BaseRepository<TParent, OwnerKey>>, options?: Omit<QueryOptions<TParent>, "where">): Promise<Map<TChild[MorphIdKey], TParent>>;
66
+ loadBelongsToManyForParents<TParent extends object, TRelated extends object, Pivot extends object, ParentKey extends keyof TParent & string, RelatedKey extends keyof TRelated & string, ForeignPivotKey extends keyof Pivot & string, RelatedPivotKey extends keyof Pivot & string>(parents: readonly TParent[], relation: BelongsToManyRelation<TParent, TRelated, Pivot, ParentKey, RelatedKey, ForeignPivotKey, RelatedPivotKey>, relatedRepository: BaseRepository<TRelated, RelatedKey>, options?: Omit<QueryOptions<TRelated>, "where">): Promise<Map<TParent[ParentKey], TRelated[]>>;
65
67
  }
66
68
  export { BaseRepository };
67
69
  export default BaseRepository;
@@ -11,6 +11,11 @@ declare class Factory<TRecord extends object, Counted extends boolean = false> {
11
11
  private children;
12
12
  private afterMakingCallbacks;
13
13
  private afterCreatingCallbacks;
14
+ protected model?: {
15
+ create(attributes: Record<string, unknown>): Promise<{
16
+ toObject(): object;
17
+ }>;
18
+ };
14
19
  protected definition(): TRecord;
15
20
  protected clone(): this;
16
21
  count(quantity: number): Factory<TRecord, true>;
@@ -18,19 +23,29 @@ declare class Factory<TRecord extends object, Counted extends boolean = false> {
18
23
  sequence(...items: Array<FactorySequence<TRecord>>): Factory<TRecord, Counted>;
19
24
  for(parent: {
20
25
  id?: unknown;
21
- }, foreignKey: keyof TRecord & string): Factory<TRecord, Counted>;
26
+ getRepository?: () => {
27
+ getTable(): {
28
+ name: string;
29
+ };
30
+ };
31
+ }, foreignKey?: keyof TRecord & string): Factory<TRecord, Counted>;
22
32
  recycle(parent: {
23
33
  id?: unknown;
24
- }, foreignKey: keyof TRecord & string): Factory<TRecord, Counted>;
34
+ getRepository?: () => {
35
+ getTable(): {
36
+ name: string;
37
+ };
38
+ };
39
+ }, foreignKey?: keyof TRecord & string): Factory<TRecord, Counted>;
25
40
  afterMaking(callback: (record: TRecord) => void): Factory<TRecord, Counted>;
26
41
  afterCreating(callback: (record: TRecord) => void | Promise<void>): Factory<TRecord, Counted>;
27
- has<TRelated extends object, RelatedCounted extends boolean>(factory: Factory<TRelated, RelatedCounted>, foreignKey: keyof TRelated & string): Factory<TRecord, Counted>;
42
+ has<TRelated extends object, RelatedCounted extends boolean>(factory: Factory<TRelated, RelatedCounted>, foreignKey?: keyof TRelated & string): Factory<TRecord, Counted>;
28
43
  make(overrides?: Partial<TRecord>): FactoryMakeResult<TRecord, Counted>;
29
44
  create(overrides?: Partial<TRecord>): Promise<FactoryMakeResult<TRecord, Counted>>;
30
45
  protected makeOne(overrides?: Partial<TRecord>): TRecord;
31
46
  protected createOne(overrides?: Partial<TRecord>): Promise<TRecord>;
32
47
  protected insertable(record: TRecord): Partial<TRecord>;
33
- protected persist(_values: Partial<TRecord>): Promise<TRecord>;
48
+ protected persist(values: Partial<TRecord>): Promise<TRecord>;
34
49
  }
35
50
  export type { FactoryMakeResult };
36
51
  export { Factory };
@@ -5,7 +5,7 @@ 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, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, registerModelRepository, } from "./model.ts";
8
+ export { applyCasts, BelongsToManyRelationQuery, BelongsToRelationQuery, dehydrateValue, filterMassAssignable, HasManyRelationQuery, 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
10
  export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, } from "./relationships.ts";
11
11
  export { belongsTo, belongsToMany, hasMany, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, } from "./relationships.ts";
@@ -4,7 +4,7 @@ import { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuer
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";
7
- type CastType = "date" | "datetime" | "json" | "bool" | "boolean";
7
+ type CastType = "date" | "datetime" | "json" | "bool" | "boolean" | "integer" | "int" | "hashed";
8
8
  type LoadedAttributes = Record<string, unknown>;
9
9
  type ModelCasts = Partial<Record<string, CastType>>;
10
10
  type GlobalScopeFn<TEntity extends object, PrimaryKey extends keyof TEntity & string> = (query: RepositoryQuery<TEntity, PrimaryKey>) => RepositoryQuery<TEntity, PrimaryKey>;
@@ -13,18 +13,56 @@ interface ModelConstructor<TEntity extends object, PrimaryKey extends keyof TEnt
13
13
  new (attributes: TEntity, repository: BaseRepository<TEntity, PrimaryKey>, exists?: boolean): TModel;
14
14
  }
15
15
  type AnyModel = Model<Record<string, unknown>, "id">;
16
+ type RelatedRef<TRelated extends object, RelatedKey extends keyof TRelated & string> = RelatedModelClass<TRelated, RelatedKey> | string | (() => RelatedModelClass<TRelated, RelatedKey>);
16
17
  type ModelObserver = {
18
+ retrieved?: (model: AnyModel) => unknown;
17
19
  creating?: (model: AnyModel) => unknown;
18
20
  created?: (model: AnyModel) => unknown;
19
21
  updating?: (model: AnyModel) => unknown;
20
22
  updated?: (model: AnyModel) => unknown;
23
+ saving?: (model: AnyModel) => unknown;
24
+ saved?: (model: AnyModel) => unknown;
21
25
  deleting?: (model: AnyModel) => unknown;
22
26
  deleted?: (model: AnyModel) => unknown;
23
27
  };
28
+ /** Alias for `hasMany("Name")` when `Name` is not `constructor.name` or `$morphClass`. */
29
+ declare function registerModelClass(name: string, model: object): void;
24
30
  declare function hydrateValue(value: unknown, cast: CastType): unknown;
25
31
  declare function dehydrateValue(value: unknown, cast: CastType): unknown;
26
32
  declare function filterMassAssignable(fillable: readonly string[] | undefined, guarded: readonly string[] | true | undefined, input: LoadedAttributes): LoadedAttributes;
27
33
  declare function applyCasts(values: LoadedAttributes, casts: ModelCasts, direction: "hydrate" | "dehydrate"): LoadedAttributes;
34
+ declare class ModelQuery {
35
+ private readonly modelClass;
36
+ readonly query: RepositoryQuery<Record<string, unknown>, "id">;
37
+ private readonly eager;
38
+ constructor(modelClass: object, query: RepositoryQuery<Record<string, unknown>, "id">);
39
+ with(...relations: string[]): this;
40
+ where(input: QueryWhere<object> | ((builder: import("./whereBuilder.ts").WhereBuilder<object>) => void)): this;
41
+ orWhere(input: QueryWhere<object> | ((builder: import("./whereBuilder.ts").WhereBuilder<object>) => void)): this;
42
+ orderBy(orderBy: QueryOptions<object>["orderBy"]): this;
43
+ limit(limit: number): this;
44
+ offset(offset: number): this;
45
+ whereNull(column: string): this;
46
+ whereIn(column: string, values: readonly unknown[]): this;
47
+ whereExists(sql: string, params?: readonly unknown[]): this;
48
+ whereNotExists(sql: string, params?: readonly unknown[]): this;
49
+ whereHas(name: string, constrain?: (query: AnyRelationQuery) => void): this;
50
+ has(name: string): this;
51
+ doesntHave(name: string): this;
52
+ whereDoesntHave(name: string, constrain?: (query: AnyRelationQuery) => void): this;
53
+ withHasMany(...args: Parameters<RepositoryQuery<Record<string, unknown>, "id">["withHasMany"]>): this;
54
+ withBelongsTo(...args: Parameters<RepositoryQuery<Record<string, unknown>, "id">["withBelongsTo"]>): this;
55
+ withBelongsToMany(...args: Parameters<RepositoryQuery<Record<string, unknown>, "id">["withBelongsToMany"]>): this;
56
+ withMorphMany(...args: Parameters<RepositoryQuery<Record<string, unknown>, "id">["withMorphMany"]>): this;
57
+ withMorphOne(...args: Parameters<RepositoryQuery<Record<string, unknown>, "id">["withMorphOne"]>): this;
58
+ withMorphTo(...args: Parameters<RepositoryQuery<Record<string, unknown>, "id">["withMorphTo"]>): this;
59
+ get(): Promise<Array<Model<Record<string, unknown>, "id">>>;
60
+ first(): Promise<Model<Record<string, unknown>, "id"> | null>;
61
+ find(id: unknown): Promise<Model<Record<string, unknown>, "id"> | null>;
62
+ findOrFail(id: unknown, errorFactory?: (id: unknown) => Error): Promise<Model<Record<string, unknown>, "id">>;
63
+ then(onfulfilled?: ((value: Array<Model<Record<string, unknown>, "id">>) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
64
+ private constrainExists;
65
+ }
28
66
  declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & string> {
29
67
  protected attributes: TEntity;
30
68
  protected readonly repository: BaseRepository<TEntity, PrimaryKey>;
@@ -35,6 +73,7 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
35
73
  static $hidden?: readonly string[];
36
74
  static $visible?: readonly string[];
37
75
  static $appends?: readonly string[];
76
+ static $morphClass?: string;
38
77
  private _exists;
39
78
  private readonly loadedRelations;
40
79
  private hiddenOverrides;
@@ -60,21 +99,18 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
60
99
  static observe(this: object, observer: ModelObserver): void;
61
100
  static addGlobalScope<TEntity extends object, PrimaryKey extends keyof TEntity & string>(this: object, _name: string, scope: GlobalScopeFn<TEntity, PrimaryKey>): void;
62
101
  static repository<TEntity extends object, PrimaryKey extends keyof TEntity & string>(this: object): BaseRepository<TEntity, PrimaryKey>;
63
- static query<TEntity extends object, PrimaryKey extends keyof TEntity & string>(this: object): RepositoryQuery<TEntity, PrimaryKey>;
102
+ static query(this: object): ModelQuery;
64
103
  static newFromRecord(this: object, record: object, exists?: boolean): Model<Record<string, unknown>, "id">;
65
104
  static create(this: object, attributes: Record<string, unknown>, forced?: Record<string, unknown>): Promise<Model<Record<string, unknown>, "id">>;
66
- static with(this: object, ...relations: string[]): {
67
- get(): Promise<Array<Model<Record<string, unknown>, "id">>>;
68
- first(): Promise<Model<Record<string, unknown>, "id"> | null>;
69
- };
70
- static whereHas(this: object, name: string, constrain?: (query: AnyRelationQuery) => void): RepositoryQuery<Record<string, unknown>, "id">;
71
- static has(this: object, name: string): RepositoryQuery<Record<string, unknown>, "id">;
72
- static doesntHave(this: object, name: string): RepositoryQuery<Record<string, unknown>, "id">;
73
- static whereDoesntHave(this: object, name: string, constrain?: (query: AnyRelationQuery) => void): RepositoryQuery<Record<string, unknown>, "id">;
105
+ static with(this: object, ...relations: string[]): ModelQuery;
106
+ static whereHas(this: object, name: string, constrain?: (query: AnyRelationQuery) => void): ModelQuery;
107
+ static has(this: object, name: string): ModelQuery;
108
+ static doesntHave(this: object, name: string): ModelQuery;
109
+ static whereDoesntHave(this: object, name: string, constrain?: (query: AnyRelationQuery) => void): ModelQuery;
74
110
  static find(this: object, id: unknown): Promise<Model<Record<string, unknown>, "id"> | null>;
75
111
  static findOrFail(this: object, id: unknown, errorFactory?: (id: unknown) => Error): Promise<Model<Record<string, unknown>, "id">>;
76
112
  static all(this: object, options?: Omit<QueryOptions<object>, "where">): Promise<Array<Model<Record<string, unknown>, "id">>>;
77
- static where(this: object, where: QueryWhere<object>): RepositoryQuery<Record<string, unknown>, "id">;
113
+ static where(this: object, where: QueryWhere<object>): ModelQuery;
78
114
  static firstWhere(this: object, where: QueryWhere<object>, options?: Omit<QueryOptions<object>, "where">): Promise<Model<Record<string, unknown>, "id"> | null>;
79
115
  static firstOrNew(this: object, where: QueryWhere<object>, values?: Record<string, unknown>): Promise<Model<Record<string, unknown>, "id">>;
80
116
  static firstOrCreate(this: object, where: QueryWhere<object>, values?: Record<string, unknown>): Promise<Model<Record<string, unknown>, "id">>;
@@ -88,19 +124,20 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
88
124
  loadHasOne<TChild extends object, LocalKey extends keyof TEntity & string, ForeignKey extends keyof TChild & string, Alias extends string>(as: Alias, relation: HasOneRelation<TEntity, TChild, LocalKey, ForeignKey>, childRepository: BaseRepository<TChild, keyof TChild & string>, options?: Omit<QueryOptions<TChild>, "where">): Promise<this & Record<Alias, TChild | undefined>>;
89
125
  loadBelongsTo<TParent extends object, ForeignKey extends keyof TEntity & string, OwnerKey extends keyof TParent & string, Alias extends string>(as: Alias, relation: BelongsToRelation<TEntity, TParent, ForeignKey, OwnerKey>, parentRepository: BaseRepository<TParent, OwnerKey>, options?: Omit<QueryOptions<TParent>, "where">): Promise<this & Record<Alias, TParent | undefined>>;
90
126
  loadBelongsToMany<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">): Promise<this & Record<Alias, TRelated[]>>;
91
- hasMany<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedModelClass<TRelated, RelatedKey>, foreignKey?: keyof TRelated & string, localKey?: PrimaryKey): HasManyRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
92
- hasOne<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedModelClass<TRelated, RelatedKey>, foreignKey?: keyof TRelated & string, localKey?: PrimaryKey): HasOneRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
93
- belongsTo<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedModelClass<TRelated, RelatedKey>, foreignKey?: keyof TEntity & string, ownerKey?: RelatedKey): BelongsToRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
94
- belongsToMany<TRelated extends object, RelatedKey extends keyof TRelated & string, Pivot extends object = Record<string, unknown>>(related: RelatedModelClass<TRelated, RelatedKey>, pivotTable?: string, foreignPivotKey?: keyof Pivot & string, relatedPivotKey?: keyof Pivot & string): BelongsToManyRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey, Pivot>;
95
- morphMany<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedModelClass<TRelated, RelatedKey>, morphName: string, typeKey?: keyof TRelated & string, idKey?: keyof TRelated & string): MorphManyRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
96
- morphOne<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedModelClass<TRelated, RelatedKey>, morphName: string, typeKey?: keyof TRelated & string, idKey?: keyof TRelated & string): MorphOneRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
97
- morphTo(relatedByType: Record<string, RelatedModelClass<Record<string, unknown>, "id">>, morphName?: string, typeKey?: keyof TEntity & string, idKey?: keyof TEntity & string): MorphToRelationQuery<TEntity, PrimaryKey>;
127
+ hasMany<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedRef<TRelated, RelatedKey>, foreignKey?: keyof TRelated & string, localKey?: PrimaryKey): HasManyRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
128
+ hasOne<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedRef<TRelated, RelatedKey>, foreignKey?: keyof TRelated & string, localKey?: PrimaryKey): HasOneRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
129
+ belongsTo<TRelated extends object, RelatedKey extends keyof TRelated & string>(related: RelatedRef<TRelated, RelatedKey>, foreignKey?: keyof TEntity & string, ownerKey?: RelatedKey): BelongsToRelationQuery<TEntity, PrimaryKey, TRelated, RelatedKey>;
130
+ 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>;
131
+ 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>;
132
+ 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>;
133
+ morphTo(relatedByType: Record<string, RelatedRef<Record<string, unknown>, "id">>, morphName?: string, typeKey?: keyof TEntity & string, idKey?: keyof TEntity & string): MorphToRelationQuery<TEntity, PrimaryKey>;
98
134
  load(...names: string[]): Promise<this>;
99
135
  loaded<T = unknown>(name: string): T | undefined;
100
136
  setLoaded(name: string, value: unknown): this;
101
137
  mergeAttributes(patch: Partial<TEntity>): this;
102
138
  }
139
+ /** Binds a Model class to its table/connection and names it for `hasMany("User")`. */
103
140
  declare function registerModelRepository<TModelClass>(model: TModelClass, repository: object): TModelClass;
104
141
  export { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, } from "./relationQuery.ts";
105
142
  export type { CastType, GlobalScopeFn, ModelClassType, ModelConstructor };
106
- export { applyCasts, dehydrateValue, filterMassAssignable, hydrateValue, Model, registerModelRepository, };
143
+ export { applyCasts, dehydrateValue, filterMassAssignable, hydrateValue, Model, ModelQuery, registerModelClass, registerModelRepository, };
@@ -40,7 +40,12 @@ declare class HasManyRelationQuery<TParent extends object, ParentKey extends key
40
40
  get(): Promise<RelatedRecord[]>;
41
41
  first(): Promise<RelatedRecord | null>;
42
42
  count(): Promise<number>;
43
+ then(onfulfilled?: ((value: RelatedRecord[]) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
43
44
  create(attributes?: Record<string, unknown>): Promise<RelatedRecord>;
45
+ save(related: RelatedRecord | (Record<string, unknown> & {
46
+ save?: () => Promise<unknown>;
47
+ mergeAttributes?: (patch: Record<string, unknown>) => unknown;
48
+ })): Promise<RelatedRecord>;
44
49
  createMany(records: ReadonlyArray<Record<string, unknown>>): Promise<RelatedRecord[]>;
45
50
  }
46
51
  declare class HasOneRelationQuery<TParent extends object, ParentKey extends keyof TParent & string, TChild extends object, ChildKey extends keyof TChild & string> {
@@ -56,21 +61,26 @@ declare class HasOneRelationQuery<TParent extends object, ParentKey extends keyo
56
61
  get(): Promise<RelatedRecord | null>;
57
62
  first(): Promise<RelatedRecord | null>;
58
63
  count(): Promise<number>;
64
+ then(onfulfilled?: ((value: RelatedRecord | null) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
59
65
  create(attributes?: Record<string, unknown>): Promise<RelatedRecord>;
66
+ save(related: RelatedRecord | Record<string, unknown>): Promise<RelatedRecord>;
60
67
  }
61
68
  declare class BelongsToRelationQuery<TChild extends object, ChildKey extends keyof TChild & string, TParent extends object, ParentKey extends keyof TParent & string> {
62
69
  private readonly parent;
63
70
  private readonly related;
64
71
  readonly relation: BelongsToRelation<TChild, TParent, keyof TChild & string, ParentKey>;
65
72
  readonly kind: RelationKind;
73
+ private extraWhere;
66
74
  private extraOptions;
67
75
  constructor(parent: RelationHost<TChild, ChildKey>, related: RelatedModelClass<TParent, ParentKey>, relation: BelongsToRelation<TChild, TParent, keyof TChild & string, ParentKey>);
76
+ where(where: QueryWhere<TParent>): this;
68
77
  orderBy(orderBy: QueryOptions<TParent>["orderBy"]): this;
69
78
  applyEagerLoad(query: RepositoryQuery<TChild, ChildKey>, alias: string): void;
70
79
  hydrateEager(row: Record<string, unknown>, alias: string): unknown;
71
80
  toExistsClause(parentTable: string): ExistsClause;
72
81
  get(): Promise<RelatedRecord | null>;
73
82
  first(): Promise<RelatedRecord | null>;
83
+ then(onfulfilled?: ((value: RelatedRecord | null) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
74
84
  associate(owner: {
75
85
  id?: unknown;
76
86
  } | RelatedRecord): Promise<void>;
@@ -83,17 +93,21 @@ declare class BelongsToManyRelationQuery<TParent extends object, ParentKey exten
83
93
  readonly kind: RelationKind;
84
94
  private extraWhere;
85
95
  private extraOptions;
96
+ private pivotValues;
86
97
  constructor(parent: RelationHost<TParent, ParentKey>, related: RelatedModelClass<TRelated, RelatedKey>, relation: BelongsToManyRelation<TParent, TRelated, Pivot, ParentKey, RelatedKey, keyof Pivot & string, keyof Pivot & string>);
87
98
  where(where: QueryWhere<TRelated>): this;
88
99
  orderBy(orderBy: QueryOptions<TRelated>["orderBy"]): this;
89
- applyEagerLoad(_query: RepositoryQuery<TParent, ParentKey>, _alias: string): void;
100
+ applyEagerLoad(query: RepositoryQuery<TParent, ParentKey>, alias: string): void;
101
+ withPivotValues(values: Record<string, unknown>): this;
90
102
  hydrateEager(row: Record<string, unknown>, alias: string): unknown;
91
103
  toExistsClause(parentTable: string): ExistsClause;
92
104
  private connection;
93
105
  get(): Promise<RelatedRecord[]>;
94
106
  first(): Promise<RelatedRecord | null>;
95
107
  count(): Promise<number>;
108
+ then(onfulfilled?: ((value: RelatedRecord[]) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
96
109
  attach(ids: unknown | readonly unknown[]): Promise<void>;
110
+ toggle(ids: unknown | readonly unknown[]): Promise<void>;
97
111
  detach(ids?: unknown | readonly unknown[]): Promise<void>;
98
112
  sync(ids: readonly unknown[]): Promise<void>;
99
113
  create(attributes?: Record<string, unknown>): Promise<RelatedRecord>;
@@ -112,6 +126,8 @@ declare class MorphManyRelationQuery<TParent extends object, ParentKey extends k
112
126
  toExistsClause(parentTable: string): ExistsClause;
113
127
  get(): Promise<RelatedRecord[]>;
114
128
  first(): Promise<RelatedRecord | null>;
129
+ count(): Promise<number>;
130
+ then(onfulfilled?: ((value: RelatedRecord[]) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
115
131
  create(attributes?: Record<string, unknown>): Promise<RelatedRecord>;
116
132
  }
117
133
  declare class MorphOneRelationQuery<TParent extends object, ParentKey extends keyof TParent & string, TChild extends object, ChildKey extends keyof TChild & string> {
@@ -124,6 +140,9 @@ declare class MorphOneRelationQuery<TParent extends object, ParentKey extends ke
124
140
  hydrateEager(row: Record<string, unknown>, alias: string): unknown;
125
141
  toExistsClause(parentTable: string): ExistsClause;
126
142
  get(): Promise<RelatedRecord | null>;
143
+ first(): Promise<RelatedRecord | null>;
144
+ count(): Promise<number>;
145
+ then(onfulfilled?: ((value: RelatedRecord | null) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
127
146
  create(attributes?: Record<string, unknown>): Promise<RelatedRecord>;
128
147
  }
129
148
  declare class MorphToRelationQuery<TChild extends object, ChildKey extends keyof TChild & string> {
@@ -131,11 +150,14 @@ declare class MorphToRelationQuery<TChild extends object, ChildKey extends keyof
131
150
  private readonly relatedByType;
132
151
  readonly relation: MorphToRelation<TChild, keyof TChild & string, keyof TChild & string>;
133
152
  readonly kind: RelationKind;
153
+ private extraWhere;
134
154
  constructor(parent: RelationHost<TChild, ChildKey>, relatedByType: Record<string, RelatedModelClass<Record<string, unknown>, "id">>, relation: MorphToRelation<TChild, keyof TChild & string, keyof TChild & string>);
155
+ where(where: QueryWhere<Record<string, unknown>>): this;
135
156
  applyEagerLoad(query: RepositoryQuery<TChild, ChildKey>, alias: string): void;
136
157
  hydrateEager(row: Record<string, unknown>, alias: string): unknown;
137
158
  toExistsClause(parentTable: string): ExistsClause;
138
159
  get(): Promise<RelatedRecord | null>;
160
+ then(onfulfilled?: ((value: RelatedRecord | null) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
139
161
  }
140
162
  type AnyRelationQuery = {
141
163
  kind: RelationKind;
@@ -144,6 +166,7 @@ type AnyRelationQuery = {
144
166
  get(): Promise<unknown>;
145
167
  toExistsClause(parentTable: string): ExistsClause;
146
168
  where?(where: Record<string, unknown>): unknown;
169
+ then?: (onfulfilled?: ((value: unknown) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null) => Promise<unknown>;
147
170
  };
148
171
  export type { AnyRelationQuery, RelatedModelClass, RelatedRecord, RelationHost };
149
172
  export { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, };
@@ -48,6 +48,8 @@ declare function belongsToMany<TParent, TRelated, Pivot extends object, ParentKe
48
48
  foreignPivotKey: ForeignPivotKey;
49
49
  relatedPivotKey: RelatedPivotKey;
50
50
  }): BelongsToManyRelation<TParent, TRelated, Pivot, ParentKey, RelatedKey, ForeignPivotKey, RelatedPivotKey>;
51
+ declare function relationMatchKey(value: unknown): string;
52
+ declare function getByRelationKey<K, V>(map: ReadonlyMap<K, V>, key: unknown): V | undefined;
51
53
  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
54
  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
55
  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 +97,6 @@ declare function morphTo<TChild, MorphTypeKey extends keyof TChild & string = ke
95
97
  }): MorphToRelation<TChild, MorphTypeKey, MorphIdKey>;
96
98
  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
99
  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>;
100
+ 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>;
99
101
  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, };
102
+ export { belongsTo, belongsToMany, getByRelationKey, hasMany, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, 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 { BelongsToRelation, HasManyRelation, MorphManyRelation, MorphOneRelation, MorphToRelation } from "./relationships.ts";
3
+ import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, 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>;
@@ -30,8 +30,11 @@ declare class RepositoryQuery<TEntity extends object, PrimaryKey extends keyof T
30
30
  withMorphMany<TChild extends object, LocalKey extends keyof TEntity & string, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, Alias extends string>(as: Alias, relation: MorphManyRelation<TEntity, TChild, LocalKey, MorphTypeKey, MorphIdKey>, childRepository: BaseRepository<TChild, keyof TChild & string>, options?: Omit<QueryOptions<TChild>, "where">): this;
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
+ 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;
33
34
  get(): Promise<Array<TEntity & LoadedRow>>;
34
35
  first(): Promise<(TEntity & LoadedRow) | null>;
36
+ count(): Promise<number>;
37
+ attachToRows(rows: readonly TEntity[]): Promise<Array<TEntity & LoadedRow>>;
35
38
  paginate(options: {
36
39
  page: number;
37
40
  perPage: number;
@@ -5,7 +5,7 @@ declare function whenLoaded<T>(model: {
5
5
  declare class JsonResource<T = unknown> {
6
6
  protected readonly resource: T;
7
7
  static wrap: string | null;
8
- private extra;
8
+ protected extra: Record<string, unknown>;
9
9
  constructor(resource: T);
10
10
  static make<TResource>(resource: TResource): JsonResource<TResource>;
11
11
  static collection<TResource>(items: readonly TResource[]): ResourceCollection<TResource>;
@@ -17,6 +17,7 @@ declare class JsonResource<T = unknown> {
17
17
  }
18
18
  declare class ResourceCollection<T> extends JsonResource<readonly T[]> {
19
19
  toArray(): Record<string, unknown>;
20
+ toResponse(): Record<string, unknown>;
20
21
  }
21
22
  declare function toResourceCollection<TInput, TOutput>(items: readonly TInput[], transformer: (item: TInput) => TOutput): TOutput[];
22
23
  declare function toPaginatedResourceCollection<TInput, TOutput, TMeta extends object>(items: readonly TInput[], meta: TMeta, transformer: (item: TInput) => TOutput): {
@@ -1,5 +1,38 @@
1
1
  // @bun
2
+ // ../../src/core/database/inflection.ts
3
+ function singularize(word) {
4
+ if (word.endsWith("ies") && word.length > 3) {
5
+ return `${word.slice(0, -3)}y`;
6
+ }
7
+ if (/(ses|xes|zes|ches|shes)$/i.test(word)) {
8
+ return word.slice(0, -2);
9
+ }
10
+ if (word.endsWith("s") && !word.endsWith("ss")) {
11
+ return word.slice(0, -1);
12
+ }
13
+ return word;
14
+ }
15
+ function foreignKeyFromTable(tableName) {
16
+ return `${singularize(tableName)}_id`;
17
+ }
18
+ function pivotTableName(leftTable, rightTable) {
19
+ return [singularize(leftTable), singularize(rightTable)].sort().join("_");
20
+ }
21
+
2
22
  // ../../src/core/database/factory.ts
23
+ function inferFactoryForeignKey(parent, explicit) {
24
+ if (explicit) {
25
+ if (explicit.endsWith("_id")) {
26
+ return explicit;
27
+ }
28
+ return `${explicit}_id`;
29
+ }
30
+ if (typeof parent.getRepository === "function") {
31
+ return foreignKeyFromTable(parent.getRepository().getTable().name);
32
+ }
33
+ throw new Error("Factory.for() requires a foreign key or a parent Model.");
34
+ }
35
+
3
36
  class Factory {
4
37
  quantity = 1;
5
38
  counted = false;
@@ -10,6 +43,7 @@ class Factory {
10
43
  children = [];
11
44
  afterMakingCallbacks = [];
12
45
  afterCreatingCallbacks = [];
46
+ model;
13
47
  definition() {
14
48
  throw new Error("Factory definition must be implemented by subclass.");
15
49
  }
@@ -50,8 +84,9 @@ class Factory {
50
84
  if (parent.id === undefined || parent.id === null) {
51
85
  throw new Error("Factory.for() requires a parent with an id.");
52
86
  }
87
+ const key = inferFactoryForeignKey(parent, foreignKey);
53
88
  const next = this.clone();
54
- next.parentAssociations = [...this.parentAssociations, { foreignKey, value: parent.id }];
89
+ next.parentAssociations = [...this.parentAssociations, { foreignKey: key, value: parent.id }];
55
90
  return next;
56
91
  }
57
92
  recycle(parent, foreignKey) {
@@ -132,7 +167,14 @@ class Factory {
132
167
  }
133
168
  return values;
134
169
  }
135
- persist(_values) {
170
+ async persist(values) {
171
+ if (this.model) {
172
+ const created = await this.model.create(values);
173
+ if (created && typeof created.toObject === "function") {
174
+ return created.toObject();
175
+ }
176
+ return created;
177
+ }
136
178
  throw new Error("Factory.persist() must be implemented to use create().");
137
179
  }
138
180
  }