@mongorm/orm 0.1.1-beta.4 → 0.1.1

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.
Files changed (3) hide show
  1. package/dist/index.d.mts +232 -191
  2. package/dist/index.mjs +166 -108
  3. package/package.json +1 -1
package/dist/index.d.mts CHANGED
@@ -1,5 +1,171 @@
1
1
  import { Collection, Condition, Db as Db$1, DeleteResult, Document, FindCursor, IndexDescription, IndexDirection, MongoClientOptions, ObjectId, ObjectId as ObjectId$1, RootFilterOperators, WithId } from "mongodb";
2
2
  import { z } from "zod";
3
+ //#region src/schema/scalars.d.ts
4
+ declare module 'zod' {
5
+ interface ZodType {
6
+ hidden(): this & {
7
+ readonly __hidden: true;
8
+ };
9
+ }
10
+ }
11
+ /** A schema field that can be marked as hidden from default query results. */
12
+ type HiddenCapable<T extends z.ZodType> = Omit<T, 'optional' | 'nullable' | 'nullish'> & HiddenMethods<T>;
13
+ /** A schema field marked as hidden from default query results. */
14
+ type HiddenSchema<T extends z.ZodType> = Omit<T, 'optional' | 'nullable' | 'nullish'> & HiddenMethods<T> & {
15
+ readonly __hidden: true;
16
+ };
17
+ type HiddenMethods<T extends z.ZodType> = {
18
+ hidden(): HiddenSchema<T>;
19
+ optional(): HiddenCapable<z.ZodOptional<T>>;
20
+ nullable(): HiddenCapable<z.ZodNullable<T>>;
21
+ nullish(): HiddenCapable<z.ZodOptional<z.ZodNullable<T>>>;
22
+ };
23
+ /** Create a MongoDB ObjectId schema. */
24
+ declare const objectId: () => HiddenCapable<z.ZodInstanceOf<ObjectId$1>>;
25
+ //#endregion
26
+ //#region src/schema/options.d.ts
27
+ /** Built-in persistence behavior applied by a schema. */
28
+ interface SchemaOptions {
29
+ readonly timestamps?: boolean;
30
+ readonly softdelete?: boolean;
31
+ /** Hide Mongorm-managed fields from default query results. */
32
+ readonly hideManaged?: boolean;
33
+ }
34
+ type ManagedSchema<Field extends z.ZodType, Options extends SchemaOptions> = Options['hideManaged'] extends true ? HiddenSchema<Field> : Field;
35
+ type TimestampShape<Options extends SchemaOptions> = {
36
+ createdAt: ManagedSchema<z.ZodDefault<z.ZodDate>, Options>;
37
+ updatedAt: ManagedSchema<z.ZodDefault<z.ZodDate>, Options>;
38
+ };
39
+ type SoftDeleteShape<Options extends SchemaOptions> = {
40
+ deletedAt: ManagedSchema<z.ZodDefault<z.ZodNullable<z.ZodDate>>, Options>;
41
+ };
42
+ type ManagedShape<Options extends SchemaOptions> = (Options['timestamps'] extends true ? TimestampShape<Options> : {}) & (Options['softdelete'] extends true ? SoftDeleteShape<Options> : {});
43
+ type ManagedField<Options extends SchemaOptions> = (Options['timestamps'] extends true ? 'createdAt' | 'updatedAt' : never) | (Options['softdelete'] extends true ? 'deletedAt' : never);
44
+ type SoftDeleteEnabled<Options extends SchemaOptions> = Options['softdelete'] extends true ? true : false;
45
+ export declare const hasSoftDelete: (options: SchemaOptions) => boolean;
46
+ //#endregion
47
+ //#region src/schema/contracts.d.ts
48
+ /** A Zod object shape used as the source of truth for a model. */
49
+ type SchemaShape = z.ZodRawShape;
50
+ /** The runtime Zod object generated from a schema shape. */
51
+ type SchemaDefinition<Shape extends SchemaShape> = z.ZodObject<Shape>;
52
+ /** Population scopes keyed by scope name. */
53
+ type ScopeDefinitions = Record<string, readonly object[]>;
54
+ //#endregion
55
+ //#region src/query/cursor/cursor.d.ts
56
+ /** A lazy async iterable for one cursor-pagination page. */
57
+ export declare class ModelCursor<Shape extends SchemaShape, Result extends object> implements AsyncIterable<Result> {
58
+ private readonly open;
59
+ private readonly pageSize;
60
+ private readonly transform?;
61
+ next: ObjectId$1 | null;
62
+ constructor(open: () => FindCursor<WithId<StoredDocument<Shape>>>, pageSize: number, transform?: ((documents: Result[]) => Promise<Result[]>) | undefined);
63
+ [Symbol.asyncIterator](): AsyncGenerator<Result>;
64
+ }
65
+ //#endregion
66
+ //#region src/query/types/document.d.ts
67
+ /** Persisted data inferred from a schema, including MongoDB's generated identifier. */
68
+ type ModelDocument<Shape extends SchemaShape> = z.output<SchemaDefinition<Shape>> & {
69
+ _id: ObjectId$1;
70
+ };
71
+ /** Document type passed to MongoDB's collection APIs. */
72
+ type StoredDocument<Shape extends SchemaShape> = ModelDocument<Shape> & Document;
73
+ type HiddenKey<Shape extends SchemaShape> = { [Key in keyof Shape]: Shape[Key] extends {
74
+ readonly __hidden: true;
75
+ } ? Key : never; }[keyof Shape];
76
+ type HiddenDocumentKey<Shape extends SchemaShape> = Extract<HiddenKey<Shape>, keyof ModelDocument<Shape>> & string;
77
+ /** Fields exposed by an unprojected model query. */
78
+ type VisibleDocument<Shape extends SchemaShape> = Omit<ModelDocument<Shape>, Extract<HiddenKey<Shape>, keyof ModelDocument<Shape>>>;
79
+ type CursorMethod<Shape extends SchemaShape, Result extends object, Ready extends boolean> = Ready extends true ? (after?: ObjectId$1) => ModelCursor<Shape, Result> : undefined;
80
+ //#endregion
81
+ //#region src/query/types/filter.d.ts
82
+ type NestedFilterKey<Value, Prefix extends string = ''> = Value extends object ? Value extends ObjectId$1 | Date | readonly unknown[] ? never : { [Key in Extract<keyof Value, string>]: NonNullable<Value[Key]> extends object ? `${Prefix}${Key}` | `${Prefix}${Key}.${NestedFilterKey<NonNullable<Value[Key]>>}` : `${Prefix}${Key}`; }[Extract<keyof Value, string>] : never;
83
+ type NestedFilterValue<Value, Path extends string> = Path extends `${infer Head}.${infer Tail}` ? Head extends keyof Value ? NestedFilterValue<NonNullable<Value[Head]>, Tail> : never : Path extends keyof Value ? Value[Path] : never;
84
+ type FilterForDocument<DocumentShape extends Document, FieldShape extends object> = Partial<{ [Key in keyof FieldShape]: Condition<FieldShape[Key]>; }> & Partial<{ [Path in NestedFilterKey<FieldShape>]: Condition<NestedFilterValue<FieldShape, Path>>; }> & Partial<Pick<RootFilterOperators<DocumentShape>, '$comment' | '$expr' | '$jsonSchema' | '$text' | '$where'>> & {
85
+ $and?: FilterForDocument<DocumentShape, FieldShape>[];
86
+ $nor?: FilterForDocument<DocumentShape, FieldShape>[];
87
+ $or?: FilterForDocument<DocumentShape, FieldShape>[];
88
+ };
89
+ /** Schema-checked MongoDB filters, including nested dot-notation paths. */
90
+ type ModelFilter<Shape extends SchemaShape> = FilterForDocument<StoredDocument<Shape>, ModelDocument<Shape>>;
91
+ /** MongoDB sort directions supported by the model query API. */
92
+ type SortDirection = 'asc' | 'desc';
93
+ /** Schema-checked sort specifications. */
94
+ type ModelSort<Shape extends SchemaShape> = Partial<Record<Extract<keyof ModelDocument<Shape>, string>, SortDirection>>;
95
+ //#endregion
96
+ //#region src/query/types/utils.d.ts
97
+ /** Flatten mapped and intersection types to make editor hints easier to read. */
98
+ type Simplify<Value> = { [Key in keyof Value]: Value[Key]; };
99
+ /** Convert a union into an intersection when merging selected field paths. */
100
+ type UnionToIntersection<Value> = (Value extends unknown ? (input: Value) => void : never) extends ((input: infer Intersection) => void) ? Intersection : never;
101
+ //#endregion
102
+ //#region src/query/types/selection.d.ts
103
+ type NestedDocumentKeys<Value> = Value extends object ? Value extends ObjectId$1 | Date ? never : { [Key in Extract<keyof Value, string>]: NonNullable<Value[Key]> extends object ? Key | `${Key}.${NestedDocumentKeys<NonNullable<Value[Key]>>}` : Key; }[Extract<keyof Value, string>] : never;
104
+ type NestedSelectableKey<Shape extends SchemaShape> = { [Key in Extract<keyof Shape, string>]: Key extends keyof ModelDocument<Shape> ? NonNullable<ModelDocument<Shape>[Key]> extends object ? `${Key}.${NestedDocumentKeys<NonNullable<ModelDocument<Shape>[Key]>>}` : never : never; }[Extract<keyof Shape, string>];
105
+ /** Field paths accepted by `.select()`. */
106
+ type SelectableKey<Shape extends SchemaShape> = Exclude<Extract<keyof ModelDocument<Shape>, string>, '_id' | HiddenDocumentKey<Shape>> | NestedSelectableKey<Shape>;
107
+ type PathSelection<Value, Path extends string> = Path extends `${infer Head}.${infer Tail}` ? Head extends keyof Value ? { [Key in Head]: PathSelection<NonNullable<Value[Head]>, Tail>; } : never : Path extends keyof Value ? Pick<Value, Path> : never;
108
+ /** Projected result type that preserves the structure of selected nested paths. */
109
+ type SelectedDocument<Shape extends SchemaShape, Key extends SelectableKey<Shape>> = [Key] extends [never] ? VisibleDocument<Shape> : Simplify<Pick<ModelDocument<Shape>, '_id'> & UnionToIntersection<PathSelection<ModelDocument<Shape>, Extract<Key, string>>>>;
110
+ //#endregion
111
+ //#region src/query/population/types.d.ts
112
+ type RelationTarget<Relation> = Relation extends {
113
+ resolve: () => infer Target;
114
+ } ? Target : never;
115
+ type RelationDocument<Relation> = Infer<RelationTarget<Relation>>;
116
+ type RelationMapOf$1<Relation> = Relation extends {
117
+ readonly __targetRelations?: infer Relations;
118
+ } ? NonNullable<Relations> extends SchemaRelationMap ? NonNullable<Relations> : {} : RelationTarget<Relation> extends {
119
+ readonly relationMap: infer TargetRelations;
120
+ } ? TargetRelations extends SchemaRelationMap ? TargetRelations : {} : {};
121
+ type RelationSelect<Relation> = RelationTarget<Relation> extends Schema<infer TargetShape, any> ? Exclude<SelectableKey<TargetShape>, '_id'> : never;
122
+ type ScopeName<Scopes> = Extract<keyof Scopes, string>;
123
+ type PopulationMode = 'none' | 'populate' | 'scope';
124
+ /** A typed description of one declared relation to load. */
125
+ type PopulateSpec<Relations extends SchemaRelationMap> = { [Name in Extract<keyof Relations, string>]: {
126
+ ref: Name;
127
+ select?: readonly RelationSelect<Relations[Name]>[];
128
+ populate?: PopulateSpecs<RelationMapOf$1<Relations[Name]>>;
129
+ }; }[Extract<keyof Relations, string>];
130
+ /** A list of relation-population specifications. */
131
+ type PopulateSpecs<Relations extends SchemaRelationMap> = readonly PopulateSpec<Relations>[];
132
+ /** Query result after replacing relation IDs with populated documents. */
133
+ type PopulatedResult<Result extends object, Relations extends SchemaRelationMap, Specs extends PopulateSpecs<Relations>> = Simplify<Omit<Result, Extract<Specs[number]['ref'], keyof Result>> & { [Spec in Specs[number] as Spec['ref']]: PopulatedRelation<Relations[Spec['ref']], Spec> | null; }>;
134
+ type PopulatedRelation<Relation, Spec> = Spec extends {
135
+ populate: infer Nested extends PopulateSpecs<RelationMapOf$1<Relation>>;
136
+ } ? RelationDocument<Relation> extends (infer PopulatedDocument extends object) ? PopulatedResult<PopulatedDocument, RelationMapOf$1<Relation>, Nested> : never : RelationDocument<Relation>;
137
+ //#endregion
138
+ //#region src/schema/indexes.d.ts
139
+ type IndexFieldMap<Shape extends SchemaShape> = Record<Extract<keyof Shape, string>, IndexDirection>;
140
+ /** Non-empty, schema-checked MongoDB index keys. */
141
+ type SchemaIndexFields<Shape extends SchemaShape> = { [Key in keyof IndexFieldMap<Shape>]: Pick<IndexFieldMap<Shape>, Key> & Partial<Omit<IndexFieldMap<Shape>, Key>>; }[keyof IndexFieldMap<Shape>];
142
+ /** Partial-index filter restricted to this schema's fields and operators. */
143
+ type SchemaPartialFilter<Shape extends SchemaShape> = ModelFilter<Shape>;
144
+ type SchemaIndexOptions<Shape extends SchemaShape> = Omit<IndexDescription, 'key' | 'partialFilterExpression'> & {
145
+ readonly partialFilterExpression?: SchemaPartialFilter<Shape>;
146
+ };
147
+ type SchemaIndex<Shape extends SchemaShape> = {
148
+ readonly fields: SchemaIndexFields<Shape>;
149
+ readonly options?: SchemaIndexOptions<Shape>;
150
+ };
151
+ type ExactPartialFilter<Shape extends SchemaShape, Filter> = Filter & Record<Exclude<keyof Filter, keyof SchemaPartialFilter<Shape>>, never>;
152
+ type ValidateIndexDefinition<Shape extends SchemaShape, Definition> = Definition extends {
153
+ readonly options?: infer Options;
154
+ } ? Definition & {
155
+ readonly options?: Options extends {
156
+ readonly partialFilterExpression?: infer Filter;
157
+ } ? Options & {
158
+ readonly partialFilterExpression?: ExactPartialFilter<Shape, Filter>;
159
+ } : Options;
160
+ } : Definition;
161
+ type ValidateIndexDefinitions<Shape extends SchemaShape, Definitions extends readonly unknown[]> = { [Key in keyof Definitions]: ValidateIndexDefinition<Shape, Definitions[Key]>; };
162
+ /** Explicit names declared for typed per-model index management. */
163
+ type SchemaIndexNames<Indexes extends readonly SchemaIndex<any>[]> = Extract<Indexes[number] extends (infer Index) ? Index extends {
164
+ readonly options?: {
165
+ readonly name?: infer Name;
166
+ };
167
+ } ? Name : never : never, string>;
168
+ //#endregion
3
169
  //#region src/schema/inference.d.ts
4
170
  /** The raw parsed shape produced by a schema before persistence fields are added. */
5
171
  type InferShape<T> = T extends {
@@ -14,21 +180,40 @@ type Infer<T> = InferShape<T> & {
14
180
  _id: ObjectId$1;
15
181
  };
16
182
  //#endregion
17
- //#region src/schema/contracts.d.ts
18
- /** A Zod object shape used as the source of truth for a model. */
19
- type SchemaShape = z.ZodRawShape;
20
- /** The runtime Zod object generated from a schema shape. */
21
- type SchemaDefinition<Shape extends SchemaShape> = z.ZodObject<Shape>;
183
+ //#region src/relations/ref-fields.d.ts
184
+ /** ObjectId Zod field carrying a lazily resolved relation target. */
185
+ type RefField<Target extends SchemaLike = SchemaLike> = z.ZodType<ObjectId$1> & {
186
+ readonly __ref?: RefDefinition<Target>;
187
+ optional(): OptionalRefField<Target>;
188
+ nullable(): NullableRefField<Target>;
189
+ nullish(): NullishRefField<Target>;
190
+ };
191
+ type OptionalRefField<Target extends SchemaLike = SchemaLike> = z.ZodOptional<RefField<Target>> & {
192
+ readonly __ref?: RefDefinition<Target>;
193
+ };
194
+ type NullableRefField<Target extends SchemaLike = SchemaLike> = z.ZodNullable<RefField<Target>> & {
195
+ readonly __ref?: RefDefinition<Target>;
196
+ };
197
+ type NullishRefField<Target extends SchemaLike = SchemaLike> = z.ZodOptional<NullableRefField<Target>> & {
198
+ readonly __ref?: RefDefinition<Target>;
199
+ };
200
+ type RelationDefinition<Field> = Field extends {
201
+ readonly __ref?: infer Definition;
202
+ } ? NonNullable<Definition> : Field extends z.ZodOptional<infer Inner> ? RelationDefinition<Inner> : Field extends z.ZodNullable<infer Inner> ? RelationDefinition<Inner> : never;
203
+ /** Relation fields inferred from a schema shape. */
204
+ type RelationMap<Shape extends SchemaShape> = { [Key in keyof Shape as RelationDefinition<Shape[Key]> extends never ? never : Key]: RelationDefinition<Shape[Key]>; };
22
205
  //#endregion
23
- //#region src/model/model.d.ts
206
+ //#region src/model/types.d.ts
24
207
  type CreateInput<Shape extends SchemaShape, Options extends SchemaOptions> = Omit<InferInput<Schema<Shape, {}, {}, Options>>, '_id'>;
25
208
  type UpdateInput<Shape extends SchemaShape, Options extends SchemaOptions> = Partial<Omit<InferInput<Schema<Shape, {}, {}, Options>>, '_id'>>;
26
- type ModelDocument$1<Shape extends SchemaShape, Relations extends SchemaRelationMap, Scopes extends ScopeDefinitions, Options extends SchemaOptions> = Infer<Schema<Shape, Relations, Scopes, Options>>;
209
+ type ModelResult<Shape extends SchemaShape, Relations extends SchemaRelationMap, Scopes extends ScopeDefinitions, Options extends SchemaOptions> = Infer<Schema<Shape, Relations, Scopes, Options>>;
27
210
  type IndexNames<Indexes extends readonly SchemaIndex<any>[]> = SchemaIndexNames<Indexes>;
28
211
  type IndexManager<Indexes extends readonly SchemaIndex<any>[]> = {
29
212
  readonly drop: (names: readonly IndexNames<Indexes>[]) => Promise<void>;
30
213
  readonly purge: () => Promise<void>;
31
214
  };
215
+ //#endregion
216
+ //#region src/model/model.d.ts
32
217
  /** A MongoDB collection with CRUD operations derived from a schema. */
33
218
  export declare class Model<Shape extends SchemaShape, Relations extends SchemaRelationMap = {}, Scopes extends ScopeDefinitions = {}, Options extends SchemaOptions = {}, Indexes extends readonly SchemaIndex<any>[] = []> {
34
219
  private readonly db;
@@ -36,9 +221,9 @@ export declare class Model<Shape extends SchemaShape, Relations extends SchemaRe
36
221
  private readonly schema;
37
222
  readonly index: IndexManager<Indexes>;
38
223
  readonly bulk: {
39
- create: (inputs: readonly CreateInput<Shape, Options>[]) => Promise<ModelDocument$1<Shape, Relations, Scopes, Options>[]>;
224
+ create: (inputs: readonly CreateInput<Shape, Options>[]) => Promise<ModelResult<Shape, Relations, Scopes, Options>[]>;
40
225
  };
41
- readonly restore: SoftDeleteEnabled<Options> extends true ? (filter: ModelFilter<Shape>) => Promise<Infer<Schema<Shape, Relations, Scopes, Options>> | null> : never;
226
+ readonly restore: SoftDeleteEnabled<Options> extends true ? (filter: ModelFilter<Shape>) => Promise<ModelResult<Shape, Relations, Scopes, Options> | null> : never;
42
227
  readonly purge: SoftDeleteEnabled<Options> extends true ? (filter: ModelFilter<Shape>) => Promise<DeleteResult> : never;
43
228
  /** Create a model bound to a database collection and schema. */
44
229
  constructor(db: Db, name: string, schema: Schema<Shape, Relations, Scopes, Options, Indexes>);
@@ -46,13 +231,13 @@ export declare class Model<Shape extends SchemaShape, Relations extends SchemaRe
46
231
  private dropIndexes;
47
232
  private activeFilter;
48
233
  /** Validate and insert one document, generating its ObjectId. */
49
- create(input: CreateInput<Shape, Options>): Promise<ModelDocument$1<Shape, Relations, Scopes, Options>>;
234
+ create(input: CreateInput<Shape, Options>): Promise<ModelResult<Shape, Relations, Scopes, Options>>;
50
235
  private prepareDocument;
51
236
  private bulkCreate;
52
237
  /** Build a query for all documents matching a MongoDB filter. */
53
238
  find(filter?: ModelFilter<Shape>): ModelQuery<Shape, VisibleDocument<Shape>, true, Relations, Scopes, 'none', SoftDeleteEnabled<Options>>;
54
239
  /** Validate and apply a partial update to the first matching document. */
55
- update(filter: ModelFilter<Shape>, patch: UpdateInput<Shape, Options>): Promise<Infer<Schema<Shape, Relations, Scopes, Options>> | null>;
240
+ update(filter: ModelFilter<Shape>, patch: UpdateInput<Shape, Options>): Promise<ModelResult<Shape, Relations, Scopes, Options> | null>;
56
241
  /** Restore matching soft-deleted documents. */
57
242
  private restoreDocument;
58
243
  /** Delete every document matching a MongoDB filter. */
@@ -61,9 +246,10 @@ export declare class Model<Shape extends SchemaShape, Relations extends SchemaRe
61
246
  private purgeDocuments;
62
247
  }
63
248
  //#endregion
64
- //#region src/connection/database.d.ts
249
+ //#region src/connection/types.d.ts
250
+ /** Schema registry supplied to a database handle. */
65
251
  type SchemaRegistry = Record<string, SchemaLike>;
66
- /** Configuration for a MongoDB connection. */
252
+ /** Configuration for a MongoDB connection and its registered collections. */
67
253
  interface DbOptions<Registry extends SchemaRegistry = SchemaRegistry> {
68
254
  /** MongoDB connection string. */
69
255
  uri: string;
@@ -71,14 +257,17 @@ interface DbOptions<Registry extends SchemaRegistry = SchemaRegistry> {
71
257
  database: string;
72
258
  /** Optional native MongoDB client options. */
73
259
  clientOptions?: MongoClientOptions;
74
- /** Schemas registered as plural database model properties. */
75
- schema?: Registry;
260
+ /** Registered schemas, exposed as matching model properties and collection names. */
261
+ schemas?: Registry;
76
262
  }
77
263
  type ModelForSchema<SchemaType> = SchemaType extends Schema<infer Shape, infer Relations, infer Scopes, infer Options, infer Indexes extends readonly SchemaIndex<any>[]> ? Model<Shape, Relations, Scopes, Options, Indexes> : never;
264
+ /** Model properties generated from a schema registry. */
78
265
  type DatabaseModels<Registry extends SchemaRegistry> = { readonly [Name in keyof Registry]: ModelForSchema<Registry[Name]>; };
79
266
  type RegistryOfBuilder<Builder extends {
80
267
  readonly __registry?: SchemaRegistry;
81
268
  }> = NonNullable<Builder['__registry']>;
269
+ //#endregion
270
+ //#region src/connection/database.d.ts
82
271
  /** Owns a MongoDB client and creates schema-bound models. */
83
272
  export declare class Db<Registry extends SchemaRegistry = SchemaRegistry> {
84
273
  private readonly options;
@@ -105,74 +294,17 @@ export declare class Db<Registry extends SchemaRegistry = SchemaRegistry> {
105
294
  }
106
295
  /** Create a disconnected MongoDB database handle. */
107
296
  export declare function createDatabase<const Registry extends SchemaRegistry>(options: DbOptions<Registry> & {
108
- schema: Registry;
297
+ schemas: Registry;
109
298
  }): Db<Registry> & DatabaseModels<Registry>;
110
299
  export declare function createDatabase<const Builder extends {
111
300
  readonly __registry?: SchemaRegistry;
112
- }>(options: Omit<DbOptions<RegistryOfBuilder<Builder>>, 'schema'> & {
113
- schema: Builder;
301
+ }>(options: Omit<DbOptions<RegistryOfBuilder<Builder>>, 'schemas'> & {
302
+ schemas: Builder;
114
303
  }): Db<RegistryOfBuilder<Builder>> & DatabaseModels<RegistryOfBuilder<Builder>>;
115
304
  export declare function createDatabase(options: DbOptions): Db;
116
305
  //#endregion
117
- //#region src/query/cursor.d.ts
118
- /** A lazy async iterable for one cursor-pagination page. */
119
- export declare class ModelCursor<Shape extends SchemaShape, Result extends object> implements AsyncIterable<Result> {
120
- private readonly open;
121
- private readonly pageSize;
122
- private readonly transform?;
123
- next: ObjectId$1 | null;
124
- constructor(open: () => FindCursor<WithId<StoredDocument<Shape>>>, pageSize: number, transform?: ((documents: Result[]) => Promise<Result[]>) | undefined);
125
- [Symbol.asyncIterator](): AsyncGenerator<Result>;
126
- }
127
- //#endregion
128
- //#region src/query/types.d.ts
129
- type StoredDocument<Shape extends SchemaShape> = Infer<Schema<Shape>> & Document;
130
- type ModelFilterForDocument<DocumentShape extends Document, FieldShape extends object = DocumentShape> = Partial<{ [Key in keyof FieldShape]: Condition<FieldShape[Key]>; }> & Partial<{ [Path in NestedFilterKey<FieldShape>]: Condition<NestedFilterValue<FieldShape, Path>>; }> & Partial<Pick<RootFilterOperators<DocumentShape>, '$comment' | '$expr' | '$jsonSchema' | '$text' | '$where'>> & {
131
- $and?: ModelFilterForDocument<DocumentShape, FieldShape>[];
132
- $nor?: ModelFilterForDocument<DocumentShape, FieldShape>[];
133
- $or?: ModelFilterForDocument<DocumentShape, FieldShape>[];
134
- };
135
- type NestedFilterKey<Value, Prefix extends string = ''> = Value extends object ? Value extends ObjectId$1 | Date | readonly unknown[] ? never : { [Key in Extract<keyof Value, string>]: NonNullable<Value[Key]> extends object ? `${Prefix}${Key}` | `${Prefix}${Key}.${NestedFilterKey<NonNullable<Value[Key]>>}` : `${Prefix}${Key}`; }[Extract<keyof Value, string>] : never;
136
- type NestedFilterValue<Value, Path extends string> = Path extends `${infer Head}.${infer Tail}` ? Head extends keyof Value ? NestedFilterValue<NonNullable<Value[Head]>, Tail> : never : Path extends keyof Value ? Value[Path] : never;
137
- type ModelFilter<Shape extends SchemaShape> = ModelFilterForDocument<StoredDocument<Shape>, Infer<Schema<Shape>>>;
138
- type SortDirection = 'asc' | 'desc';
139
- type ModelSort<Shape extends SchemaShape> = Partial<Record<Extract<keyof Infer<Schema<Shape>>, string>, SortDirection>>;
140
- type ModelDocument<Shape extends SchemaShape> = Infer<Schema<Shape>>;
141
- type HiddenKey<Shape extends SchemaShape> = { [Key in keyof Shape]: Shape[Key] extends {
142
- readonly __hidden: true;
143
- } ? Key : never; }[keyof Shape];
144
- type HiddenDocumentKey<Shape extends SchemaShape> = Extract<HiddenKey<Shape>, keyof ModelDocument<Shape>> & string;
145
- type NestedDocumentKeys<Value, Prefix extends string = ''> = Value extends object ? Value extends ObjectId$1 | Date ? never : { [Key in Extract<keyof Value, string>]: NonNullable<Value[Key]> extends object ? `${Prefix}${Key}` | `${Prefix}${Key}.${NestedDocumentKeys<NonNullable<Value[Key]>>}` : `${Prefix}${Key}`; }[Extract<keyof Value, string>] : never;
146
- type NestedSelectableKey<Shape extends SchemaShape> = { [Key in Extract<keyof Shape, string>]: Key extends keyof ModelDocument<Shape> ? NonNullable<ModelDocument<Shape>[Key]> extends object ? `${Key}.${NestedDocumentKeys<NonNullable<ModelDocument<Shape>[Key]>>}` : never : never; }[Extract<keyof Shape, string>];
147
- type CursorMethod<Shape extends SchemaShape, Result extends object, Ready extends boolean> = Ready extends true ? (after?: ObjectId$1) => ModelCursor<Shape, Result> : undefined;
148
- type SelectableKey<Shape extends SchemaShape> = Exclude<Extract<keyof ModelDocument<Shape>, string>, '_id' | HiddenDocumentKey<Shape>> | NestedSelectableKey<Shape>;
149
- type PathSelection<Value, Path extends string> = Path extends `${infer Head}.${infer Tail}` ? Head extends keyof Value ? { [Key in Head]: PathSelection<NonNullable<Value[Key]>, Tail>; } : never : Path extends keyof Value ? Pick<Value, Path> : never;
150
- type UnionToIntersection<Value> = (Value extends unknown ? (input: Value) => void : never) extends ((input: infer Intersection) => void) ? Intersection : never;
151
- type Simplify<Value> = { [Key in keyof Value]: Value[Key]; };
152
- type VisibleDocument<Shape extends SchemaShape> = Omit<ModelDocument<Shape>, Extract<HiddenKey<Shape>, keyof ModelDocument<Shape>>>;
153
- type SelectedDocument<Shape extends SchemaShape, Key extends SelectableKey<Shape>> = [Key] extends [never] ? VisibleDocument<Shape> : Simplify<Pick<ModelDocument<Shape>, '_id'> & UnionToIntersection<PathSelection<ModelDocument<Shape>, Extract<Key, string>>>>;
154
- type RelationTarget<Relation> = Relation extends {
155
- resolve: () => infer Target;
156
- } ? Target : never;
157
- type RelationDocument<Relation> = RelationTarget<Relation> extends Schema<infer TargetShape, any> ? Infer<Schema<TargetShape>> : never;
158
- type RelationMapOf$1<Relation> = RelationTarget<Relation> extends {
159
- readonly relationMap: infer TargetRelations;
160
- } ? TargetRelations extends SchemaRelationMap ? TargetRelations : {} : {};
161
- type ScopeName<Scopes> = Extract<keyof Scopes, string>;
162
- type PopulationMode = 'none' | 'populate' | 'scope';
163
- type RelationSelect<Relation> = RelationTarget<Relation> extends Schema<infer TargetShape, any> ? Exclude<SelectableKey<TargetShape>, '_id'> : never;
164
- type PopulateSpec<Relations extends SchemaRelationMap> = { [Name in Extract<keyof Relations, string>]: {
165
- ref: Name;
166
- select?: readonly RelationSelect<Relations[Name]>[];
167
- populate?: PopulateSpecs<RelationMapOf$1<Relations[Name]>>;
168
- }; }[Extract<keyof Relations, string>];
169
- type PopulateSpecs<Relations extends SchemaRelationMap> = readonly PopulateSpec<Relations>[];
170
- type PopulatedResult<Result extends object, Relations extends SchemaRelationMap, Specs extends PopulateSpecs<Relations>> = Omit<Result, Extract<Specs[number]['ref'], keyof Result>> & { [Spec in Specs[number] as Spec['ref']]: PopulatedRelation<Relations[Spec['ref']], Spec> | null; };
171
- type PopulatedRelation<Relation, Spec> = Spec extends {
172
- populate: infer Nested extends PopulateSpecs<RelationMapOf$1<Relation>>;
173
- } ? RelationDocument<Relation> extends (infer PopulatedDocument extends object) ? PopulatedResult<PopulatedDocument, RelationMapOf$1<Relation>, Nested> : never : RelationDocument<Relation>;
174
- //#endregion
175
- //#region src/query/many-query.d.ts
306
+ //#region src/query/builder/find-query.d.ts
307
+ type ScopeResult<Result extends object, Relations extends SchemaRelationMap, Scopes extends ScopeDefinitions, Name extends ScopeName<Scopes>> = PopulatedResult<Result, Relations, Extract<Scopes[Name], PopulateSpecs<Relations>>>;
176
308
  /** A typed, awaitable MongoDB find query. */
177
309
  export declare class ModelQuery<Shape extends SchemaShape, Result extends object = VisibleDocument<Shape>, CursorReady extends boolean = true, Relations extends SchemaRelationMap = {}, Scopes extends ScopeDefinitions = {}, Mode extends PopulationMode = 'none', SoftDelete extends boolean = false> implements PromiseLike<Result[]> {
178
310
  private readonly collection;
@@ -198,6 +330,7 @@ export declare class ModelQuery<Shape extends SchemaShape, Result extends object
198
330
  /** Include both active and soft-deleted documents in this query. */
199
331
  private deletedMode;
200
332
  private effectiveFilter;
333
+ private executionContext;
201
334
  /** Sort results by one or more schema fields. */
202
335
  sort(spec: ModelSort<Shape>): ModelQuery<Shape, Result, false, Relations, Scopes, Mode, SoftDelete>;
203
336
  /** Skip a non-negative number of matching documents. */
@@ -211,91 +344,18 @@ export declare class ModelQuery<Shape extends SchemaShape, Result extends object
211
344
  /** Populate declared one-way relations, including nested relation arrays. */
212
345
  populate<Specs extends PopulateSpecs<Relations>>(this: Mode extends 'scope' ? never : ModelQuery<Shape, Result, CursorReady, Relations, Scopes, Mode, SoftDelete>, specs: Specs): ModelQuery<Shape, PopulatedResult<Result, Relations, Specs>, CursorReady, Relations, Scopes, 'populate', SoftDelete>;
213
346
  /** Apply a named population scope. */
214
- with<Name extends ScopeName<Scopes>>(this: Mode extends 'populate' ? never : ModelQuery<Shape, Result, CursorReady, Relations, Scopes, Mode, SoftDelete>, name: Name): ModelQuery<Shape, PopulatedResult<Result, Relations, Scopes[Name] & PopulateSpecs<Relations>>, CursorReady, Relations, Scopes, 'scope', SoftDelete>;
347
+ with<Name extends ScopeName<Scopes>>(this: Mode extends 'populate' ? never : ModelQuery<Shape, Result, CursorReady, Relations, Scopes, Mode, SoftDelete>, name: Name): ModelQuery<Shape, ScopeResult<Result, Relations, Scopes, Name>, CursorReady, Relations, Scopes, 'scope', SoftDelete>;
215
348
  /** Count matching documents, optionally using MongoDB's collection estimate. */
216
349
  count(estimate?: boolean): Promise<number>;
217
- /** Return one `_id`-ordered page and the cursor for the next page. */
350
+ /** Return one bounded `_id`-ordered page and its continuation cursor. */
218
351
  private createCursor;
219
352
  private execute;
220
353
  first(): Promise<Result | null>;
221
- private createQueryCursor;
222
354
  then<TResult1 = Result[], TResult2 = never>(onfulfilled?: ((value: Result[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
223
355
  }
224
356
  //#endregion
225
- //#region src/schema/scalars.d.ts
226
- declare module 'zod' {
227
- interface ZodType {
228
- hidden(): this & {
229
- readonly __hidden: true;
230
- };
231
- }
232
- }
233
- /** A schema field that can be marked as hidden from default query results. */
234
- type HiddenCapable<T extends z.ZodType> = Omit<T, 'optional' | 'nullable' | 'nullish'> & HiddenMethods<T>;
235
- /** A schema field marked as hidden from default query results. */
236
- type HiddenSchema<T extends z.ZodType> = Omit<T, 'optional' | 'nullable' | 'nullish'> & HiddenMethods<T> & {
237
- readonly __hidden: true;
238
- };
239
- type HiddenMethods<T extends z.ZodType> = {
240
- hidden(): HiddenSchema<T>;
241
- optional(): HiddenCapable<z.ZodOptional<T>>;
242
- nullable(): HiddenCapable<z.ZodNullable<T>>;
243
- nullish(): HiddenCapable<z.ZodOptional<z.ZodNullable<T>>>;
244
- };
245
- /** Create a MongoDB ObjectId schema. */
246
- declare const objectId: () => HiddenCapable<z.ZodInstanceOf<ObjectId$1>>;
247
- //#endregion
248
357
  //#region src/schema/schema.d.ts
249
- /** Built-in persistence behavior applied by a schema. */
250
- interface SchemaOptions {
251
- readonly timestamps?: boolean;
252
- readonly softdelete?: boolean;
253
- /** Hide Mongorm-managed fields from default query results. */
254
- readonly hideManaged?: boolean;
255
- }
256
- type IndexFieldMap<Shape extends SchemaShape> = Record<Extract<keyof Shape, string>, IndexDirection>;
257
- /** A non-empty, autocomplete-friendly MongoDB index key definition. */
258
- type SchemaIndexFields<Shape extends SchemaShape> = { [Key in keyof IndexFieldMap<Shape>]: Pick<IndexFieldMap<Shape>, Key> & Partial<Omit<IndexFieldMap<Shape>, Key>>; }[keyof IndexFieldMap<Shape>];
259
- /** A schema-aware MongoDB partial-index filter. */
260
- type SchemaPartialFilter<Shape extends SchemaShape> = ModelFilter<Shape>;
261
- type SchemaIndexOptions<Shape extends SchemaShape> = Omit<IndexDescription, 'key' | 'partialFilterExpression'> & {
262
- /** Restrict indexed documents using schema-aware MongoDB filter operators. */
263
- readonly partialFilterExpression?: SchemaPartialFilter<Shape>;
264
- };
265
- type SchemaIndex<Shape extends SchemaShape> = {
266
- readonly fields: SchemaIndexFields<Shape>;
267
- readonly options?: SchemaIndexOptions<Shape>;
268
- };
269
- type ExactPartialFilter<Shape extends SchemaShape, Filter> = Filter & Record<Exclude<keyof Filter, keyof SchemaPartialFilter<Shape>>, never>;
270
- type ValidateIndexDefinition<Shape extends SchemaShape, Definition> = Definition extends {
271
- readonly options?: infer Options;
272
- } ? Definition & {
273
- readonly options?: Options extends {
274
- readonly partialFilterExpression?: infer Filter;
275
- } ? Options & {
276
- readonly partialFilterExpression?: ExactPartialFilter<Shape, Filter>;
277
- } : Options;
278
- } : Definition;
279
- type ValidateIndexDefinitions<Shape extends SchemaShape, Definitions extends readonly unknown[]> = { [Key in keyof Definitions]: ValidateIndexDefinition<Shape, Definitions[Key]>; };
280
- type SchemaIndexNames<Indexes extends readonly SchemaIndex<any>[]> = Extract<Indexes[number] extends (infer Index) ? Index extends {
281
- readonly options?: {
282
- readonly name?: infer Name;
283
- };
284
- } ? Name : never : never, string>;
285
- type ManagedSchema<T extends z.ZodType, Options extends SchemaOptions> = Options['hideManaged'] extends true ? HiddenSchema<T> : T;
286
- type TimestampShape<Options extends SchemaOptions> = {
287
- createdAt: ManagedSchema<z.ZodDefault<z.ZodDate>, Options>;
288
- updatedAt: ManagedSchema<z.ZodDefault<z.ZodDate>, Options>;
289
- };
290
- type SoftDeleteShape<Options extends SchemaOptions> = {
291
- deletedAt: ManagedSchema<z.ZodDefault<z.ZodNullable<z.ZodDate>>, Options>;
292
- };
293
- type ManagedShape<Options extends SchemaOptions> = (Options['timestamps'] extends true ? TimestampShape<Options> : {}) & (Options['softdelete'] extends true ? SoftDeleteShape<Options> : {});
294
- type ManagedField<Options extends SchemaOptions> = (Options['timestamps'] extends true ? 'createdAt' | 'updatedAt' : never) | (Options['softdelete'] extends true ? 'deletedAt' : never);
295
- type SoftDeleteEnabled<Options extends SchemaOptions> = Options['softdelete'] extends true ? true : false;
296
- export declare const hasSoftDelete: (options: SchemaOptions) => boolean;
297
358
  type ObjectIdFieldKeys<Shape extends SchemaShape> = { [Key in keyof InferShape<Schema<Shape>>]-?: NonNullable<InferShape<Schema<Shape>>[Key]> extends ObjectId$1 ? Key : never; }[keyof InferShape<Schema<Shape>>];
298
- type ScopeDefinitions = Record<string, readonly object[]>;
299
359
  /** A typed, runtime-validated schema definition. */
300
360
  export declare class Schema<Shape extends SchemaShape, Relations extends SchemaRelationMap = {}, Scopes extends ScopeDefinitions = {}, Options extends SchemaOptions = {}, Indexes extends readonly SchemaIndex<any>[] = []> {
301
361
  /** The underlying Zod object for advanced validation use cases. */
@@ -320,7 +380,6 @@ export declare class Schema<Shape extends SchemaShape, Relations extends SchemaR
320
380
  constructor(shape: Shape, relations?: Relations, scopeMap?: Scopes, optionsConfig?: Options, indexDefinitions?: Indexes);
321
381
  /** Enable managed timestamps and/or soft deletion for this schema. */
322
382
  options<const Enabled extends SchemaOptions>(options: Enabled): Schema<Shape & ManagedShape<Enabled>, Relations, Scopes, Enabled, Indexes>;
323
- private managedField;
324
383
  /** Declare MongoDB indexes for explicit synchronization with the database. */
325
384
  indexes<const Definitions extends readonly SchemaIndex<Shape>[]>(definitions: readonly SchemaIndex<Shape>[] & ValidateIndexDefinitions<Shape, Definitions>): Schema<Shape, Relations, Scopes, Options, Definitions>;
326
385
  /** Add one or more one-way relations without requiring circular schema declarations. */
@@ -338,69 +397,51 @@ export declare class Schema<Shape extends SchemaShape, Relations extends SchemaR
338
397
  }
339
398
  //#endregion
340
399
  //#region src/relations/definitions.d.ts
341
- /** A schema-like target resolved lazily to support circular module imports. */
342
- type SchemaLike = Schema<SchemaShape, any, any, any, readonly SchemaIndex<any>[]>;
343
- /** Metadata attached to a forward relation field. */
400
+ /** Schema object accepted as a lazy relation target or registry entry. */
401
+ type SchemaLike = Schema<any, any, any, any, any>;
402
+ /** Metadata for a schema target resolved lazily to support circular imports. */
344
403
  interface RefDefinition<Target extends SchemaLike = SchemaLike> {
345
- /** Resolve the related schema after module initialization completes. */
346
404
  resolve: () => Target;
347
405
  }
348
- /** Metadata for a one-way relation between two MongoDB schemas. */
349
- interface SchemaRelation<Target extends SchemaLike = SchemaLike, LocalField extends string = string, ForeignField extends string = string> {
406
+ /** Metadata for a one-way relation between two registered schemas. */
407
+ interface SchemaRelation<Target extends SchemaLike = SchemaLike, LocalField extends string = string, ForeignField extends string = string, TargetRelations = Target extends {
408
+ readonly relationMap: infer Relations;
409
+ } ? Relations : {}> {
350
410
  readonly resolve: () => Target;
351
411
  readonly localField: LocalField;
352
412
  readonly foreignField: ForeignField;
413
+ /** Target relations are carried separately to avoid recursively wrapping its schema type. */
414
+ readonly __targetRelations?: TargetRelations;
353
415
  }
354
- /** Relation metadata attached to a schema. */
355
416
  type SchemaRelationMap = Record<string, SchemaRelation>;
356
- /** A relation target declaration accepted by Schema.relations(). */
417
+ /** Relation target declaration accepted by `Schema.relations()`. */
357
418
  type RelationInput<Target extends SchemaLike = SchemaLike> = (() => Target) | {
358
419
  target: () => Target;
359
420
  foreignField?: string;
360
421
  };
361
- /** Extract a relation target from a relation declaration. */
362
422
  type RelationInputTarget<Input> = Input extends (() => infer Target) ? Target : Input extends {
363
423
  target: () => infer Target;
364
424
  } ? Target : never;
365
- /** A string identifier field carrying a typed relation target. */
366
- type RefField<Target extends SchemaLike = SchemaLike> = z.ZodType<ObjectId$1> & {
367
- readonly __ref?: RefDefinition<Target>;
368
- optional(): OptionalRefField<Target>;
369
- nullable(): NullableRefField<Target>;
370
- nullish(): NullishRefField<Target>;
371
- };
372
- /** An optional relation field that retains its target metadata. */
373
- type OptionalRefField<Target extends SchemaLike = SchemaLike> = z.ZodOptional<RefField<Target>> & {
374
- readonly __ref?: RefDefinition<Target>;
375
- };
376
- /** A nullable relation field that retains its target metadata. */
377
- type NullableRefField<Target extends SchemaLike = SchemaLike> = z.ZodNullable<RefField<Target>> & {
378
- readonly __ref?: RefDefinition<Target>;
379
- };
380
- /** An optional and nullable relation field that retains its target metadata. */
381
- type NullishRefField<Target extends SchemaLike = SchemaLike> = z.ZodOptional<NullableRefField<Target>> & {
382
- readonly __ref?: RefDefinition<Target>;
383
- };
384
- type RelationDefinition<Field> = Field extends {
385
- readonly __ref?: infer Definition;
386
- } ? NonNullable<Definition> : Field extends z.ZodOptional<infer Inner> ? RelationDefinition<Inner> : Field extends z.ZodNullable<infer Inner> ? RelationDefinition<Inner> : never;
387
- /** The relation metadata inferred from a schema shape. */
388
- type RelationMap<Shape extends SchemaShape> = { [Key in keyof Shape as RelationDefinition<Shape[Key]> extends never ? never : Key]: RelationDefinition<Shape[Key]>; };
389
425
  //#endregion
390
- //#region src/relations/registry.d.ts
426
+ //#region src/relations/registry-types.d.ts
391
427
  type ObjectIdKeys<Shape extends SchemaShape> = { [Key in keyof InferShape<Schema<Shape>>]-?: NonNullable<InferShape<Schema<Shape>>[Key]> extends ObjectId$1 ? Key : never; }[keyof InferShape<Schema<Shape>>];
428
+ /** Valid local ObjectId fields and targets accepted by `defineRelations`. */
392
429
  type RelationDefinitions<Registry extends Record<string, SchemaLike>> = { [Name in keyof Registry]?: Registry[Name] extends Schema<infer Shape, any, any, any> ? Partial<Record<Extract<ObjectIdKeys<Shape>, string>, Extract<keyof Registry, string>>> : never; };
393
- type EnrichedSchema<Registry extends Record<string, SchemaLike>, AllDefinitions extends RelationDefinitions<Registry>, Name> = Name extends keyof Registry ? Registry[Name] extends Schema<infer Shape, infer Relations, infer Scopes, infer Options> ? Schema<Shape, Relations & RelationsFor<Registry, AllDefinitions, NonNullable<AllDefinitions[Name]>>, Scopes, Options, Registry[Name] extends Schema<any, any, any, any, infer Indexes extends readonly SchemaIndex<any>[]> ? Indexes : []> : never : never;
394
- type RelationsFor<Registry extends Record<string, SchemaLike>, AllDefinitions extends RelationDefinitions<Registry>, Definitions> = { [Field in keyof Definitions & string]: Definitions[Field] extends keyof Registry ? SchemaRelation<EnrichedSchema<Registry, AllDefinitions, Definitions[Field]>, Field, '_id'> : never; };
430
+ type RelationsFor<Registry extends Record<string, SchemaLike>, AllDefinitions extends RelationDefinitions<Registry>, Definitions> = { [Field in keyof Definitions & string]: Definitions[Field] extends keyof Registry ? SchemaRelation<Registry[Definitions[Field]], Field, '_id', RelationsFor<Registry, AllDefinitions, NonNullable<AllDefinitions[Definitions[Field]]>>> : never; };
395
431
  type RegistryWithRelations<Registry extends Record<string, SchemaLike>, Definitions extends RelationDefinitions<Registry>> = { [Name in keyof Registry]: Registry[Name] extends Schema<infer Shape, infer Relations, infer Scopes, infer Options> ? Schema<Shape, Relations & RelationsFor<Registry, Definitions, NonNullable<Definitions[Name]>>, Scopes, Options, Registry[Name] extends Schema<any, any, any, any, infer Indexes extends readonly SchemaIndex<any>[]> ? Indexes : []> : Registry[Name]; };
396
- type RelationMapOf<Value> = Value extends Schema<any, infer Relations, any, any> ? Relations : {};
432
+ type RelationMapOf<Value> = Value extends SchemaRelation<any, any, any, infer Relations> ? Relations : Value extends Schema<any, infer Relations, any, any> ? Relations : {};
433
+ /** Scope declarations accepted for each registered model. */
397
434
  type ScopeDefinitionsBySchema<Registry extends Record<string, SchemaLike>> = { [Name in keyof Registry]?: Record<string, PopulateSpecs<RelationMapOf<Registry[Name]>>>; };
398
435
  type RegistryWithScopes<Registry extends Record<string, SchemaLike>, Definitions extends ScopeDefinitionsBySchema<Registry>> = { [Name in keyof Registry]: Registry[Name] extends Schema<infer Shape, infer Relations, infer Scopes, infer Options> ? Schema<Shape, Relations, Scopes & (Definitions[Name] extends ScopeDefinitions ? Definitions[Name] : {}), Options, Registry[Name] extends Schema<any, any, any, any, infer Indexes extends readonly SchemaIndex<any>[]> ? Indexes : []> : Registry[Name]; };
436
+ /** Typed registry builder API, with relations and scopes reflected in model types. */
399
437
  type SchemaRegistryBuilder<Registry extends Record<string, SchemaLike>> = Registry & {
400
438
  readonly __registry?: Registry;
401
439
  defineRelations<const Definitions extends RelationDefinitions<Registry>>(definitions: Definitions): SchemaRegistryBuilder<RegistryWithRelations<Registry, Definitions>>;
402
440
  defineScopes<const Definitions extends ScopeDefinitionsBySchema<Registry>>(definitions: Definitions): SchemaRegistryBuilder<RegistryWithScopes<Registry, Definitions>>;
403
441
  };
442
+ //#endregion
443
+ //#region src/relations/registry.d.ts
444
+ /** Create a registry of named schemas with typed relation and scope builders. */
404
445
  declare const createSchemaRegistry: <const Registry extends Record<string, SchemaLike>>(registry: Registry) => SchemaRegistryBuilder<Registry>;
405
446
  //#endregion
406
447
  //#region src/api.d.ts
@@ -450,4 +491,4 @@ export declare class EstimatedCountError extends InvalidQueryError {
450
491
  constructor();
451
492
  }
452
493
  //#endregion
453
- export { type DatabaseModels, type DbOptions, type HiddenDocumentKey, type Infer, type InferInput, type InferShape, type ManagedField, type ModelFilter, type ModelSort, ObjectId, type OrmApi, type OrmErrorCode, type PopulateSpec, type PopulateSpecs, type PopulatedResult, type RefDefinition, type RefField, type RelationDefinitions, type RelationMap, type SchemaDefinition, type SchemaIndex, type SchemaIndexFields, type SchemaLike, type SchemaOptions, type SchemaRegistry, type SchemaRegistryBuilder, type SchemaRelation, type SchemaRelationMap, type SchemaShape, type ScopeDefinitionsBySchema, type SelectableKey, type SelectedDocument, type SoftDeleteEnabled, type StoredDocument, type VisibleDocument };
494
+ export { type DatabaseModels, type DbOptions, type HiddenDocumentKey, type Infer, type InferInput, type InferShape, type ManagedField, type ModelFilter, type ModelSort, type NullableRefField, type NullishRefField, ObjectId, type OptionalRefField, type OrmApi, type OrmErrorCode, type PopulateSpec, type PopulateSpecs, type PopulatedResult, type RefDefinition, type RefField, type RelationDefinitions, type RelationMap, type SchemaDefinition, type SchemaIndex, type SchemaIndexFields, type SchemaLike, type SchemaOptions, type SchemaRegistry, type SchemaRegistryBuilder, type SchemaRelation, type SchemaRelationMap, type SchemaShape, type ScopeDefinitionsBySchema, type SelectableKey, type SelectedDocument, type SoftDeleteEnabled, type StoredDocument, type VisibleDocument };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { MongoClient, ObjectId, ObjectId as ObjectId$1 } from "mongodb";
2
2
  import { z } from "zod";
3
- //#region src/relations/definitions.ts
4
- /** Create a string ID field linked to a lazily resolved target schema. */
3
+ //#region src/relations/ref-fields.ts
4
+ /** Create a typed ObjectId field linked to a lazily resolved schema. */
5
5
  const createRef = (resolve) => {
6
6
  const field = z.instanceof(ObjectId$1);
7
7
  const createOptional = field.optional.bind(field);
@@ -37,22 +37,21 @@ const attachRef = (field, resolve) => {
37
37
  });
38
38
  return field;
39
39
  };
40
- /** Collect relation metadata from a schema shape without evaluating targets. */
41
- const collectRefs = (shape) => {
42
- return Object.fromEntries(Object.entries(shape).filter(([, field]) => "__ref" in field).map(([name, field]) => [name, field.__ref]));
43
- };
40
+ /** Collect ref field metadata without evaluating target schemas. */
41
+ const collectRefs = (shape) => Object.fromEntries(Object.entries(shape).filter(([, field]) => "__ref" in field).map(([name, field]) => [name, field.__ref]));
44
42
  //#endregion
45
43
  //#region src/relations/registry.ts
44
+ /** Attach relation/scope builder methods and apply definitions to schema metadata. */
46
45
  const attachMethods = (registry) => {
47
46
  const defineRelations = (definitions) => {
48
47
  for (const [name, relations] of Object.entries(definitions)) {
49
- const targetSchema = registry[name];
50
- if (!targetSchema) throw new Error(`Unknown schema "${name}" in relation definitions`);
48
+ const source = registry[name];
49
+ if (!source) throw new Error(`Unknown schema "${name}" in relation definitions`);
51
50
  for (const [field, targetName] of Object.entries(relations ?? {})) {
52
51
  const target = registry[targetName];
53
52
  if (!target) throw new Error(`Unknown relation target "${targetName}"`);
54
- if (!(field in targetSchema.definition.shape)) throw new Error(`Unknown relation field "${name}.${field}"`);
55
- targetSchema.relationMap[field] = {
53
+ if (!(field in source.definition.shape)) throw new Error(`Unknown relation field "${name}.${field}"`);
54
+ source.relationMap[field] = {
56
55
  resolve: () => target,
57
56
  localField: field,
58
57
  foreignField: "_id"
@@ -63,9 +62,9 @@ const attachMethods = (registry) => {
63
62
  };
64
63
  const defineScopes = (definitions) => {
65
64
  for (const [name, scopes] of Object.entries(definitions)) {
66
- const targetSchema = registry[name];
67
- if (!targetSchema) throw new Error(`Unknown schema "${name}" in scope definitions`);
68
- Object.assign(targetSchema.scopeMap, scopes);
65
+ const schema = registry[name];
66
+ if (!schema) throw new Error(`Unknown schema "${name}" in scope definitions`);
67
+ Object.assign(schema.scopeMap, scopes);
69
68
  }
70
69
  return attachMethods(registry);
71
70
  };
@@ -88,6 +87,7 @@ const attachMethods = (registry) => {
88
87
  });
89
88
  return registry;
90
89
  };
90
+ /** Create a registry of named schemas with typed relation and scope builders. */
91
91
  const createSchemaRegistry = (registry) => attachMethods(registry);
92
92
  //#endregion
93
93
  //#region src/schema/scalars.ts
@@ -133,8 +133,11 @@ const withZodNamespace = (namespace) => new Proxy(namespace, { get(target, prope
133
133
  /** Create a MongoDB ObjectId schema. */
134
134
  const objectId = () => withHidden(z.instanceof(ObjectId$1));
135
135
  //#endregion
136
- //#region src/schema/schema.ts
136
+ //#region src/schema/options.ts
137
137
  const hasSoftDelete = (options) => options.softdelete === true;
138
+ const managedField = (field, options) => options.hideManaged ? withHidden(field).hidden() : field;
139
+ //#endregion
140
+ //#region src/schema/schema.ts
138
141
  /** A typed, runtime-validated schema definition. */
139
142
  var Schema = class Schema {
140
143
  /** The underlying Zod object for advanced validation use cases. */
@@ -171,19 +174,16 @@ var Schema = class Schema {
171
174
  if (options.softdelete && "deletedAt" in this.definition.shape) throw new Error("The deletedAt field is managed by Mongorm");
172
175
  const managedShape = {
173
176
  ...options.timestamps ? {
174
- createdAt: this.managedField(z.date().default(() => /* @__PURE__ */ new Date()), options),
175
- updatedAt: this.managedField(z.date().default(() => /* @__PURE__ */ new Date()), options)
177
+ createdAt: managedField(z.date().default(() => /* @__PURE__ */ new Date()), options),
178
+ updatedAt: managedField(z.date().default(() => /* @__PURE__ */ new Date()), options)
176
179
  } : {},
177
- ...options.softdelete ? { deletedAt: this.managedField(z.date().nullable().default(null), options) } : {}
180
+ ...options.softdelete ? { deletedAt: managedField(z.date().nullable().default(null), options) } : {}
178
181
  };
179
182
  return new Schema({
180
183
  ...this.definition.shape,
181
184
  ...managedShape
182
185
  }, this.relationMap, this.scopeMap, options, this.indexDefinitions);
183
186
  }
184
- managedField(field, options) {
185
- return options.hideManaged ? withHidden(field).hidden() : field;
186
- }
187
187
  /** Declare MongoDB indexes for explicit synchronization with the database. */
188
188
  indexes(definitions) {
189
189
  if (definitions.some(({ fields }) => Object.keys(fields).length === 0)) throw new Error("Index definitions must include at least one field");
@@ -274,40 +274,8 @@ var EstimatedCountError = class extends InvalidQueryError {
274
274
  }
275
275
  };
276
276
  //#endregion
277
- //#region src/query/cursor.ts
278
- /** A lazy async iterable for one cursor-pagination page. */
279
- var ModelCursor = class {
280
- open;
281
- pageSize;
282
- transform;
283
- next = null;
284
- constructor(open, pageSize, transform) {
285
- this.open = open;
286
- this.pageSize = pageSize;
287
- this.transform = transform;
288
- }
289
- async *[Symbol.asyncIterator]() {
290
- const cursor = this.open();
291
- try {
292
- const documents = [];
293
- for (let index = 0; index < this.pageSize && await cursor.hasNext(); index += 1) documents.push(await cursor.next());
294
- const transformed = this.transform ? await this.transform(documents) : documents;
295
- for (const document of transformed) yield document;
296
- const last = transformed.at(-1);
297
- this.next = await cursor.hasNext() && last ? last._id : null;
298
- } finally {
299
- await cursor.close();
300
- }
301
- }
302
- };
303
- //#endregion
304
- //#region src/model/soft-delete.ts
305
- const applySoftDeleteFilter = (filter, mode) => {
306
- if (mode === "all") return filter;
307
- return { $and: [mode === "deleted" ? { deletedAt: { $ne: null } } : { deletedAt: null }, filter] };
308
- };
309
- //#endregion
310
- //#region src/query/runtime.ts
277
+ //#region src/query/projection/runtime.ts
278
+ /** Remove duplicate and ancestor-overlapping MongoDB projection paths. */
311
279
  const normalizeProjectionFields = (fields) => {
312
280
  const unique = new Set(fields);
313
281
  return [...unique].filter((field) => {
@@ -319,6 +287,7 @@ const normalizeProjectionFields = (fields) => {
319
287
  return true;
320
288
  });
321
289
  };
290
+ /** Build the MongoDB projection from schema visibility and query selections. */
322
291
  const projectionFor = (fields, hiddenFields, selectedFields, shownFields) => {
323
292
  const effectiveSelectedFields = selectedFields?.length ? selectedFields : void 0;
324
293
  if (!effectiveSelectedFields && hiddenFields.length === 0 && shownFields.length === 0) return;
@@ -326,25 +295,9 @@ const projectionFor = (fields, hiddenFields, selectedFields, shownFields) => {
326
295
  const visibleFields = effectiveSelectedFields ?? fields.filter((field) => !hidden.has(field));
327
296
  return Object.fromEntries(normalizeProjectionFields([...visibleFields, ...shownFields]).map((field) => [field, 1]));
328
297
  };
329
- var SoftDeleteState = class {
330
- enabled;
331
- mode = "active";
332
- constructor(enabled) {
333
- this.enabled = enabled;
334
- }
335
- includeDeleted() {
336
- this.mode = "all";
337
- }
338
- deleted(mode) {
339
- this.mode = mode === "include" ? "all" : "deleted";
340
- }
341
- isFiltered() {
342
- return this.enabled && this.mode !== "all";
343
- }
344
- effectiveFilter(filter) {
345
- return applySoftDeleteFilter(filter, this.mode);
346
- }
347
- };
298
+ //#endregion
299
+ //#region src/query/population/executor.ts
300
+ /** Executes already-validated population instructions against related collections. */
348
301
  var PopulationExecutor = class {
349
302
  db;
350
303
  relations;
@@ -375,9 +328,111 @@ var PopulationExecutor = class {
375
328
  document[spec.ref] = related;
376
329
  }
377
330
  };
331
+ //#endregion
332
+ //#region src/model/soft-delete.ts
333
+ const applySoftDeleteFilter = (filter, mode) => {
334
+ if (mode === "all") return filter;
335
+ return { $and: [mode === "deleted" ? { deletedAt: { $ne: null } } : { deletedAt: null }, filter] };
336
+ };
337
+ //#endregion
338
+ //#region src/query/soft-delete/state.ts
339
+ /** Query-local state for composing a schema's default soft-delete filter. */
340
+ var SoftDeleteState = class {
341
+ enabled;
342
+ mode = "active";
343
+ constructor(enabled) {
344
+ this.enabled = enabled;
345
+ }
346
+ includeDeleted() {
347
+ this.mode = "all";
348
+ }
349
+ deleted(mode) {
350
+ this.mode = mode === "include" ? "all" : "deleted";
351
+ }
352
+ isFiltered() {
353
+ return this.enabled && this.mode !== "all";
354
+ }
355
+ effectiveFilter(filter) {
356
+ return applySoftDeleteFilter(filter, this.mode);
357
+ }
358
+ };
359
+ //#endregion
360
+ //#region src/query/cursor/cursor.ts
361
+ /** A lazy async iterable for one cursor-pagination page. */
362
+ var ModelCursor = class {
363
+ open;
364
+ pageSize;
365
+ transform;
366
+ next = null;
367
+ constructor(open, pageSize, transform) {
368
+ this.open = open;
369
+ this.pageSize = pageSize;
370
+ this.transform = transform;
371
+ }
372
+ async *[Symbol.asyncIterator]() {
373
+ const cursor = this.open();
374
+ try {
375
+ const documents = [];
376
+ for (let index = 0; index < this.pageSize && await cursor.hasNext(); index += 1) documents.push(await cursor.next());
377
+ const transformed = this.transform ? await this.transform(documents) : documents;
378
+ for (const document of transformed) yield document;
379
+ const last = transformed.at(-1);
380
+ this.next = await cursor.hasNext() && last ? last._id : null;
381
+ } finally {
382
+ await cursor.close();
383
+ }
384
+ }
385
+ };
386
+ //#endregion
387
+ //#region src/query/cursor/filter.ts
388
+ /** Add the `_id` continuation boundary used by cursor pagination. */
378
389
  const createCursorFilter = (filter, after) => after ? { $and: [filter, { _id: { $gt: after } }] } : filter;
379
390
  //#endregion
380
- //#region src/query/many-query.ts
391
+ //#region src/query/builder/executor.ts
392
+ /** Execute a list query or MongoDB's estimated collection count. */
393
+ const countQuery = async (context, estimate) => {
394
+ if (estimate) {
395
+ if (Object.keys(context.filter).length > 0 || context.softDelete.isFiltered()) throw new EstimatedCountError();
396
+ return context.collection.estimatedDocumentCount();
397
+ }
398
+ return context.collection.countDocuments(context.effectiveFilter);
399
+ };
400
+ /** Open an `_id`-ordered, bounded cursor page. */
401
+ const createCursorPage = (context, after) => {
402
+ if (context.limitCount === void 0 || context.limitCount === 0) throw new CursorQueryError("Cursor queries require a positive limit");
403
+ if (context.skipCount !== void 0) throw new CursorQueryError("Cursor queries do not support skip");
404
+ if (context.sortSpec) {
405
+ if (Object.keys(context.sortSpec).length !== 1 || context.sortSpec._id !== "asc") throw new CursorQueryError("Cursor queries require the default _id ascending sort");
406
+ }
407
+ const filter = createCursorFilter(context.effectiveFilter, after);
408
+ return new ModelCursor(() => {
409
+ let cursor = context.collection.find(filter).sort({ _id: 1 }).limit(context.limitCount + 1);
410
+ const projection = projectionFor(context.fields, context.hiddenFields, context.selectedFields, context.shownFields);
411
+ if (projection) cursor = cursor.project(projection);
412
+ return cursor;
413
+ }, context.limitCount, (documents) => context.population.apply(documents, context.populateSpecs));
414
+ };
415
+ /** Execute a list query and populate its results. */
416
+ const executeQuery = async (context) => {
417
+ const documents = await createFindCursor(context).toArray();
418
+ return context.population.apply(documents, context.populateSpecs);
419
+ };
420
+ /** Execute a query for its first matching document. */
421
+ const executeFirst = async (context) => {
422
+ const documents = await createFindCursor(context).limit(1).toArray();
423
+ return (await context.population.apply(documents, context.populateSpecs))[0] ?? null;
424
+ };
425
+ const createFindCursor = (context) => {
426
+ let cursor = context.collection.find(context.effectiveFilter);
427
+ if (context.sortSpec) cursor = cursor.sort(context.sortSpec);
428
+ if (context.skipCount !== void 0) cursor = cursor.skip(context.skipCount);
429
+ if (context.limitCount !== void 0) cursor = cursor.limit(context.limitCount);
430
+ const projection = projectionFor(context.fields, context.hiddenFields, context.selectedFields, context.shownFields);
431
+ if (projection) cursor = cursor.project(projection);
432
+ return cursor;
433
+ };
434
+ //#endregion
435
+ //#region src/query/builder/find-query.ts
381
436
  /** A typed, awaitable MongoDB find query. */
382
437
  var ModelQuery = class {
383
438
  collection;
@@ -423,6 +478,34 @@ var ModelQuery = class {
423
478
  effectiveFilter() {
424
479
  return this.softDelete.effectiveFilter(this.filterSpec);
425
480
  }
481
+ executionContext() {
482
+ const context = {
483
+ collection: this.collection,
484
+ filter: this.filterSpec,
485
+ effectiveFilter: this.effectiveFilter(),
486
+ fields: this.fields,
487
+ hiddenFields: this.hiddenFields,
488
+ selectedFields: this.selectedFields,
489
+ shownFields: this.shownFields,
490
+ sortSpec: this.sortSpec,
491
+ skipCount: this.skipCount,
492
+ limitCount: this.limitCount,
493
+ softDelete: this.softDelete,
494
+ population: this.population,
495
+ populateSpecs: this.populateSpecs
496
+ };
497
+ Object.defineProperties(context, {
498
+ effectiveFilter: { get: () => this.effectiveFilter() },
499
+ selectedFields: { get: () => this.selectedFields },
500
+ shownFields: { get: () => this.shownFields },
501
+ sortSpec: { get: () => this.sortSpec },
502
+ skipCount: { get: () => this.skipCount },
503
+ limitCount: { get: () => this.limitCount },
504
+ softDelete: { get: () => this.softDelete },
505
+ populateSpecs: { get: () => this.populateSpecs }
506
+ });
507
+ return context;
508
+ }
426
509
  /** Sort results by one or more schema fields. */
427
510
  sort(spec) {
428
511
  this.sortSpec = spec;
@@ -465,43 +548,18 @@ var ModelQuery = class {
465
548
  return this;
466
549
  }
467
550
  /** Count matching documents, optionally using MongoDB's collection estimate. */
468
- async count(estimate = false) {
469
- if (estimate) {
470
- if (Object.keys(this.filterSpec).length > 0 || this.softDelete.isFiltered()) throw new EstimatedCountError();
471
- return this.collection.estimatedDocumentCount();
472
- }
473
- return this.collection.countDocuments(this.effectiveFilter());
551
+ count(estimate = false) {
552
+ return countQuery(this.executionContext(), estimate);
474
553
  }
475
- /** Return one `_id`-ordered page and the cursor for the next page. */
554
+ /** Return one bounded `_id`-ordered page and its continuation cursor. */
476
555
  createCursor(after) {
477
- if (this.limitCount === void 0 || this.limitCount === 0) throw new CursorQueryError("Cursor queries require a positive limit");
478
- if (this.skipCount !== void 0) throw new CursorQueryError("Cursor queries do not support skip");
479
- if (this.sortSpec) {
480
- if (Object.keys(this.sortSpec).length !== 1 || this.sortSpec._id !== "asc") throw new CursorQueryError("Cursor queries require the default _id ascending sort");
481
- }
482
- const filter = createCursorFilter(this.effectiveFilter(), after);
483
- return new ModelCursor(() => {
484
- let cursor = this.collection.find(filter).sort({ _id: 1 }).limit(this.limitCount + 1);
485
- const projection = projectionFor(this.fields, this.hiddenFields, this.selectedFields, this.shownFields);
486
- if (projection) cursor = cursor.project(projection);
487
- return cursor;
488
- }, this.limitCount, (documents) => this.population.apply(documents, this.populateSpecs));
556
+ return createCursorPage(this.executionContext(), after);
489
557
  }
490
558
  execute() {
491
- return this.createQueryCursor().toArray().then((documents) => this.population.apply(documents, this.populateSpecs));
492
- }
493
- async first() {
494
- const documents = await this.createQueryCursor().limit(1).toArray();
495
- return (await this.population.apply(documents, this.populateSpecs))[0] ?? null;
496
- }
497
- createQueryCursor() {
498
- let cursor = this.collection.find(this.effectiveFilter());
499
- if (this.sortSpec) cursor = cursor.sort(this.sortSpec);
500
- if (this.skipCount !== void 0) cursor = cursor.skip(this.skipCount);
501
- if (this.limitCount !== void 0) cursor = cursor.limit(this.limitCount);
502
- const projection = projectionFor(this.fields, this.hiddenFields, this.selectedFields, this.shownFields);
503
- if (projection) cursor = cursor.project(projection);
504
- return cursor;
559
+ return executeQuery(this.executionContext());
560
+ }
561
+ first() {
562
+ return executeFirst(this.executionContext());
505
563
  }
506
564
  then(onfulfilled, onrejected) {
507
565
  return this.execute().then(onfulfilled, onrejected);
@@ -629,7 +687,7 @@ var Db = class {
629
687
  constructor(options) {
630
688
  this.options = options;
631
689
  this.client = new MongoClient(options.uri, options.clientOptions);
632
- for (const [name, schema] of Object.entries(options.schema ?? {})) {
690
+ for (const [name, schema] of Object.entries(options.schemas ?? {})) {
633
691
  this.registerSchema(schema, name);
634
692
  let model;
635
693
  Object.defineProperty(this, name, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongorm/orm",
3
- "version": "0.1.1-beta.4",
3
+ "version": "0.1.1",
4
4
  "description": "A TypeScript-first MongoDB ORM for applications that want strong types without hiding MongoDB",
5
5
  "keywords": [
6
6
  "mongodb",