@mongorm/orm 0.1.1-beta.3 → 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 -185
  2. package/dist/index.mjs +166 -105
  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,72 +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<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 ModelFilter<Shape extends SchemaShape> = ModelFilterForDocument<StoredDocument<Shape>, Infer<Schema<Shape>>>;
136
- type SortDirection = 'asc' | 'desc';
137
- type ModelSort<Shape extends SchemaShape> = Partial<Record<Extract<keyof Infer<Schema<Shape>>, string>, SortDirection>>;
138
- type ModelDocument<Shape extends SchemaShape> = Infer<Schema<Shape>>;
139
- type HiddenKey<Shape extends SchemaShape> = { [Key in keyof Shape]: Shape[Key] extends {
140
- readonly __hidden: true;
141
- } ? Key : never; }[keyof Shape];
142
- type HiddenDocumentKey<Shape extends SchemaShape> = Extract<HiddenKey<Shape>, keyof ModelDocument<Shape>> & string;
143
- 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;
144
- 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>];
145
- type CursorMethod<Shape extends SchemaShape, Result extends object, Ready extends boolean> = Ready extends true ? (after?: ObjectId$1) => ModelCursor<Shape, Result> : undefined;
146
- type SelectableKey<Shape extends SchemaShape> = Exclude<Extract<keyof ModelDocument<Shape>, string>, '_id' | HiddenDocumentKey<Shape>> | NestedSelectableKey<Shape>;
147
- 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;
148
- type UnionToIntersection<Value> = (Value extends unknown ? (input: Value) => void : never) extends ((input: infer Intersection) => void) ? Intersection : never;
149
- type Simplify<Value> = { [Key in keyof Value]: Value[Key]; };
150
- type VisibleDocument<Shape extends SchemaShape> = Omit<ModelDocument<Shape>, Extract<HiddenKey<Shape>, keyof ModelDocument<Shape>>>;
151
- 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>>>>;
152
- type RelationTarget<Relation> = Relation extends {
153
- resolve: () => infer Target;
154
- } ? Target : never;
155
- type RelationDocument<Relation> = RelationTarget<Relation> extends Schema<infer TargetShape, any> ? Infer<Schema<TargetShape>> : never;
156
- type RelationMapOf$1<Relation> = RelationTarget<Relation> extends {
157
- readonly relationMap: infer TargetRelations;
158
- } ? TargetRelations extends SchemaRelationMap ? TargetRelations : {} : {};
159
- type ScopeName<Scopes> = Extract<keyof Scopes, string>;
160
- type PopulationMode = 'none' | 'populate' | 'scope';
161
- type RelationSelect<Relation> = RelationTarget<Relation> extends Schema<infer TargetShape, any> ? Exclude<SelectableKey<TargetShape>, '_id'> : never;
162
- type PopulateSpec<Relations extends SchemaRelationMap> = { [Name in Extract<keyof Relations, string>]: {
163
- ref: Name;
164
- select?: readonly RelationSelect<Relations[Name]>[];
165
- populate?: PopulateSpecs<RelationMapOf$1<Relations[Name]>>;
166
- }; }[Extract<keyof Relations, string>];
167
- type PopulateSpecs<Relations extends SchemaRelationMap> = readonly PopulateSpec<Relations>[];
168
- 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; };
169
- type PopulatedRelation<Relation, Spec> = Spec extends {
170
- populate: infer Nested extends PopulateSpecs<RelationMapOf$1<Relation>>;
171
- } ? RelationDocument<Relation> extends (infer PopulatedDocument extends object) ? PopulatedResult<PopulatedDocument, RelationMapOf$1<Relation>, Nested> : never : RelationDocument<Relation>;
172
- //#endregion
173
- //#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>>>;
174
308
  /** A typed, awaitable MongoDB find query. */
175
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[]> {
176
310
  private readonly collection;
@@ -196,6 +330,7 @@ export declare class ModelQuery<Shape extends SchemaShape, Result extends object
196
330
  /** Include both active and soft-deleted documents in this query. */
197
331
  private deletedMode;
198
332
  private effectiveFilter;
333
+ private executionContext;
199
334
  /** Sort results by one or more schema fields. */
200
335
  sort(spec: ModelSort<Shape>): ModelQuery<Shape, Result, false, Relations, Scopes, Mode, SoftDelete>;
201
336
  /** Skip a non-negative number of matching documents. */
@@ -209,65 +344,18 @@ export declare class ModelQuery<Shape extends SchemaShape, Result extends object
209
344
  /** Populate declared one-way relations, including nested relation arrays. */
210
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>;
211
346
  /** Apply a named population scope. */
212
- 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>;
213
348
  /** Count matching documents, optionally using MongoDB's collection estimate. */
214
349
  count(estimate?: boolean): Promise<number>;
215
- /** Return one `_id`-ordered page and the cursor for the next page. */
350
+ /** Return one bounded `_id`-ordered page and its continuation cursor. */
216
351
  private createCursor;
217
352
  private execute;
218
353
  first(): Promise<Result | null>;
219
- private createQueryCursor;
220
354
  then<TResult1 = Result[], TResult2 = never>(onfulfilled?: ((value: Result[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
221
355
  }
222
356
  //#endregion
223
357
  //#region src/schema/schema.d.ts
224
- /** Built-in persistence behavior applied by a schema. */
225
- interface SchemaOptions {
226
- readonly timestamps?: boolean;
227
- readonly softdelete?: boolean;
228
- }
229
- type IndexFieldMap<Shape extends SchemaShape> = Record<Extract<keyof Shape, string>, IndexDirection>;
230
- /** A non-empty, autocomplete-friendly MongoDB index key definition. */
231
- type SchemaIndexFields<Shape extends SchemaShape> = { [Key in keyof IndexFieldMap<Shape>]: Pick<IndexFieldMap<Shape>, Key> & Partial<Omit<IndexFieldMap<Shape>, Key>>; }[keyof IndexFieldMap<Shape>];
232
- /** A schema-aware MongoDB partial-index filter. */
233
- type SchemaPartialFilter<Shape extends SchemaShape> = ModelFilter<Shape>;
234
- type SchemaIndexOptions<Shape extends SchemaShape> = Omit<IndexDescription, 'key' | 'partialFilterExpression'> & {
235
- /** Restrict indexed documents using schema-aware MongoDB filter operators. */
236
- readonly partialFilterExpression?: SchemaPartialFilter<Shape>;
237
- };
238
- type SchemaIndex<Shape extends SchemaShape> = {
239
- readonly fields: SchemaIndexFields<Shape>;
240
- readonly options?: SchemaIndexOptions<Shape>;
241
- };
242
- type ExactPartialFilter<Shape extends SchemaShape, Filter> = Filter & Record<Exclude<keyof Filter, keyof SchemaPartialFilter<Shape>>, never>;
243
- type ValidateIndexDefinition<Shape extends SchemaShape, Definition> = Definition extends {
244
- readonly options?: infer Options;
245
- } ? Definition & {
246
- readonly options?: Options extends {
247
- readonly partialFilterExpression?: infer Filter;
248
- } ? Options & {
249
- readonly partialFilterExpression?: ExactPartialFilter<Shape, Filter>;
250
- } : Options;
251
- } : Definition;
252
- type ValidateIndexDefinitions<Shape extends SchemaShape, Definitions extends readonly unknown[]> = { [Key in keyof Definitions]: ValidateIndexDefinition<Shape, Definitions[Key]>; };
253
- type SchemaIndexNames<Indexes extends readonly SchemaIndex<any>[]> = Extract<Indexes[number] extends (infer Index) ? Index extends {
254
- readonly options?: {
255
- readonly name?: infer Name;
256
- };
257
- } ? Name : never : never, string>;
258
- type TimestampShape = {
259
- createdAt: z.ZodDefault<z.ZodDate>;
260
- updatedAt: z.ZodDefault<z.ZodDate>;
261
- };
262
- type SoftDeleteShape = {
263
- deletedAt: z.ZodDefault<z.ZodNullable<z.ZodDate>>;
264
- };
265
- type ManagedShape<Options extends SchemaOptions> = (Options['timestamps'] extends true ? TimestampShape : {}) & (Options['softdelete'] extends true ? SoftDeleteShape : {});
266
- type ManagedField<Options extends SchemaOptions> = (Options['timestamps'] extends true ? 'createdAt' | 'updatedAt' : never) | (Options['softdelete'] extends true ? 'deletedAt' : never);
267
- type SoftDeleteEnabled<Options extends SchemaOptions> = Options['softdelete'] extends true ? true : false;
268
- export declare const hasSoftDelete: (options: SchemaOptions) => boolean;
269
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>>];
270
- type ScopeDefinitions = Record<string, readonly object[]>;
271
359
  /** A typed, runtime-validated schema definition. */
272
360
  export declare class Schema<Shape extends SchemaShape, Relations extends SchemaRelationMap = {}, Scopes extends ScopeDefinitions = {}, Options extends SchemaOptions = {}, Indexes extends readonly SchemaIndex<any>[] = []> {
273
361
  /** The underlying Zod object for advanced validation use cases. */
@@ -309,93 +397,52 @@ export declare class Schema<Shape extends SchemaShape, Relations extends SchemaR
309
397
  }
310
398
  //#endregion
311
399
  //#region src/relations/definitions.d.ts
312
- /** A schema-like target resolved lazily to support circular module imports. */
313
- type SchemaLike = Schema<SchemaShape, any, any, any, readonly SchemaIndex<any>[]>;
314
- /** 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. */
315
403
  interface RefDefinition<Target extends SchemaLike = SchemaLike> {
316
- /** Resolve the related schema after module initialization completes. */
317
404
  resolve: () => Target;
318
405
  }
319
- /** Metadata for a one-way relation between two MongoDB schemas. */
320
- 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 : {}> {
321
410
  readonly resolve: () => Target;
322
411
  readonly localField: LocalField;
323
412
  readonly foreignField: ForeignField;
413
+ /** Target relations are carried separately to avoid recursively wrapping its schema type. */
414
+ readonly __targetRelations?: TargetRelations;
324
415
  }
325
- /** Relation metadata attached to a schema. */
326
416
  type SchemaRelationMap = Record<string, SchemaRelation>;
327
- /** A relation target declaration accepted by Schema.relations(). */
417
+ /** Relation target declaration accepted by `Schema.relations()`. */
328
418
  type RelationInput<Target extends SchemaLike = SchemaLike> = (() => Target) | {
329
419
  target: () => Target;
330
420
  foreignField?: string;
331
421
  };
332
- /** Extract a relation target from a relation declaration. */
333
422
  type RelationInputTarget<Input> = Input extends (() => infer Target) ? Target : Input extends {
334
423
  target: () => infer Target;
335
424
  } ? Target : never;
336
- /** A string identifier field carrying a typed relation target. */
337
- type RefField<Target extends SchemaLike = SchemaLike> = z.ZodType<ObjectId$1> & {
338
- readonly __ref?: RefDefinition<Target>;
339
- optional(): OptionalRefField<Target>;
340
- nullable(): NullableRefField<Target>;
341
- nullish(): NullishRefField<Target>;
342
- };
343
- /** An optional relation field that retains its target metadata. */
344
- type OptionalRefField<Target extends SchemaLike = SchemaLike> = z.ZodOptional<RefField<Target>> & {
345
- readonly __ref?: RefDefinition<Target>;
346
- };
347
- /** A nullable relation field that retains its target metadata. */
348
- type NullableRefField<Target extends SchemaLike = SchemaLike> = z.ZodNullable<RefField<Target>> & {
349
- readonly __ref?: RefDefinition<Target>;
350
- };
351
- /** An optional and nullable relation field that retains its target metadata. */
352
- type NullishRefField<Target extends SchemaLike = SchemaLike> = z.ZodOptional<NullableRefField<Target>> & {
353
- readonly __ref?: RefDefinition<Target>;
354
- };
355
- type RelationDefinition<Field> = Field extends {
356
- readonly __ref?: infer Definition;
357
- } ? NonNullable<Definition> : Field extends z.ZodOptional<infer Inner> ? RelationDefinition<Inner> : Field extends z.ZodNullable<infer Inner> ? RelationDefinition<Inner> : never;
358
- /** The relation metadata inferred from a schema shape. */
359
- type RelationMap<Shape extends SchemaShape> = { [Key in keyof Shape as RelationDefinition<Shape[Key]> extends never ? never : Key]: RelationDefinition<Shape[Key]>; };
360
425
  //#endregion
361
- //#region src/relations/registry.d.ts
426
+ //#region src/relations/registry-types.d.ts
362
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`. */
363
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; };
364
- 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;
365
- 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; };
366
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]; };
367
- 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. */
368
434
  type ScopeDefinitionsBySchema<Registry extends Record<string, SchemaLike>> = { [Name in keyof Registry]?: Record<string, PopulateSpecs<RelationMapOf<Registry[Name]>>>; };
369
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. */
370
437
  type SchemaRegistryBuilder<Registry extends Record<string, SchemaLike>> = Registry & {
371
438
  readonly __registry?: Registry;
372
439
  defineRelations<const Definitions extends RelationDefinitions<Registry>>(definitions: Definitions): SchemaRegistryBuilder<RegistryWithRelations<Registry, Definitions>>;
373
440
  defineScopes<const Definitions extends ScopeDefinitionsBySchema<Registry>>(definitions: Definitions): SchemaRegistryBuilder<RegistryWithScopes<Registry, Definitions>>;
374
441
  };
375
- declare const createSchemaRegistry: <const Registry extends Record<string, SchemaLike>>(registry: Registry) => SchemaRegistryBuilder<Registry>;
376
442
  //#endregion
377
- //#region src/schema/scalars.d.ts
378
- declare module 'zod' {
379
- interface ZodType {
380
- hidden(): this & {
381
- readonly __hidden: true;
382
- };
383
- }
384
- }
385
- /** A schema field that can be marked as hidden from default query results. */
386
- type HiddenCapable<T extends z.ZodType> = Omit<T, 'optional' | 'nullable' | 'nullish'> & HiddenMethods<T>;
387
- /** A schema field marked as hidden from default query results. */
388
- type HiddenSchema<T extends z.ZodType> = Omit<T, 'optional' | 'nullable' | 'nullish'> & HiddenMethods<T> & {
389
- readonly __hidden: true;
390
- };
391
- type HiddenMethods<T extends z.ZodType> = {
392
- hidden(): HiddenSchema<T>;
393
- optional(): HiddenCapable<z.ZodOptional<T>>;
394
- nullable(): HiddenCapable<z.ZodNullable<T>>;
395
- nullish(): HiddenCapable<z.ZodOptional<z.ZodNullable<T>>>;
396
- };
397
- /** Create a MongoDB ObjectId schema. */
398
- declare const objectId: () => HiddenCapable<z.ZodInstanceOf<ObjectId$1>>;
443
+ //#region src/relations/registry.d.ts
444
+ /** Create a registry of named schemas with typed relation and scope builders. */
445
+ declare const createSchemaRegistry: <const Registry extends Record<string, SchemaLike>>(registry: Registry) => SchemaRegistryBuilder<Registry>;
399
446
  //#endregion
400
447
  //#region src/api.d.ts
401
448
  type ZodConstructorKey = { [Key in keyof typeof z]: Key extends string ? Key extends Lowercase<Key> ? (typeof z)[Key] extends ((...args: any[]) => any) ? Key : never : never : never; }[keyof typeof z];
@@ -444,4 +491,4 @@ export declare class EstimatedCountError extends InvalidQueryError {
444
491
  constructor();
445
492
  }
446
493
  //#endregion
447
- 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,10 +174,10 @@ 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: z.date().default(() => /* @__PURE__ */ new Date()),
175
- updatedAt: z.date().default(() => /* @__PURE__ */ new Date())
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: z.date().nullable().default(null) } : {}
180
+ ...options.softdelete ? { deletedAt: managedField(z.date().nullable().default(null), options) } : {}
178
181
  };
179
182
  return new Schema({
180
183
  ...this.definition.shape,
@@ -271,40 +274,8 @@ var EstimatedCountError = class extends InvalidQueryError {
271
274
  }
272
275
  };
273
276
  //#endregion
274
- //#region src/query/cursor.ts
275
- /** A lazy async iterable for one cursor-pagination page. */
276
- var ModelCursor = class {
277
- open;
278
- pageSize;
279
- transform;
280
- next = null;
281
- constructor(open, pageSize, transform) {
282
- this.open = open;
283
- this.pageSize = pageSize;
284
- this.transform = transform;
285
- }
286
- async *[Symbol.asyncIterator]() {
287
- const cursor = this.open();
288
- try {
289
- const documents = [];
290
- for (let index = 0; index < this.pageSize && await cursor.hasNext(); index += 1) documents.push(await cursor.next());
291
- const transformed = this.transform ? await this.transform(documents) : documents;
292
- for (const document of transformed) yield document;
293
- const last = transformed.at(-1);
294
- this.next = await cursor.hasNext() && last ? last._id : null;
295
- } finally {
296
- await cursor.close();
297
- }
298
- }
299
- };
300
- //#endregion
301
- //#region src/model/soft-delete.ts
302
- const applySoftDeleteFilter = (filter, mode) => {
303
- if (mode === "all") return filter;
304
- return { $and: [mode === "deleted" ? { deletedAt: { $ne: null } } : { deletedAt: null }, filter] };
305
- };
306
- //#endregion
307
- //#region src/query/runtime.ts
277
+ //#region src/query/projection/runtime.ts
278
+ /** Remove duplicate and ancestor-overlapping MongoDB projection paths. */
308
279
  const normalizeProjectionFields = (fields) => {
309
280
  const unique = new Set(fields);
310
281
  return [...unique].filter((field) => {
@@ -316,6 +287,7 @@ const normalizeProjectionFields = (fields) => {
316
287
  return true;
317
288
  });
318
289
  };
290
+ /** Build the MongoDB projection from schema visibility and query selections. */
319
291
  const projectionFor = (fields, hiddenFields, selectedFields, shownFields) => {
320
292
  const effectiveSelectedFields = selectedFields?.length ? selectedFields : void 0;
321
293
  if (!effectiveSelectedFields && hiddenFields.length === 0 && shownFields.length === 0) return;
@@ -323,25 +295,9 @@ const projectionFor = (fields, hiddenFields, selectedFields, shownFields) => {
323
295
  const visibleFields = effectiveSelectedFields ?? fields.filter((field) => !hidden.has(field));
324
296
  return Object.fromEntries(normalizeProjectionFields([...visibleFields, ...shownFields]).map((field) => [field, 1]));
325
297
  };
326
- var SoftDeleteState = class {
327
- enabled;
328
- mode = "active";
329
- constructor(enabled) {
330
- this.enabled = enabled;
331
- }
332
- includeDeleted() {
333
- this.mode = "all";
334
- }
335
- deleted(mode) {
336
- this.mode = mode === "include" ? "all" : "deleted";
337
- }
338
- isFiltered() {
339
- return this.enabled && this.mode !== "all";
340
- }
341
- effectiveFilter(filter) {
342
- return applySoftDeleteFilter(filter, this.mode);
343
- }
344
- };
298
+ //#endregion
299
+ //#region src/query/population/executor.ts
300
+ /** Executes already-validated population instructions against related collections. */
345
301
  var PopulationExecutor = class {
346
302
  db;
347
303
  relations;
@@ -372,9 +328,111 @@ var PopulationExecutor = class {
372
328
  document[spec.ref] = related;
373
329
  }
374
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. */
375
389
  const createCursorFilter = (filter, after) => after ? { $and: [filter, { _id: { $gt: after } }] } : filter;
376
390
  //#endregion
377
- //#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
378
436
  /** A typed, awaitable MongoDB find query. */
379
437
  var ModelQuery = class {
380
438
  collection;
@@ -420,6 +478,34 @@ var ModelQuery = class {
420
478
  effectiveFilter() {
421
479
  return this.softDelete.effectiveFilter(this.filterSpec);
422
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
+ }
423
509
  /** Sort results by one or more schema fields. */
424
510
  sort(spec) {
425
511
  this.sortSpec = spec;
@@ -462,43 +548,18 @@ var ModelQuery = class {
462
548
  return this;
463
549
  }
464
550
  /** Count matching documents, optionally using MongoDB's collection estimate. */
465
- async count(estimate = false) {
466
- if (estimate) {
467
- if (Object.keys(this.filterSpec).length > 0 || this.softDelete.isFiltered()) throw new EstimatedCountError();
468
- return this.collection.estimatedDocumentCount();
469
- }
470
- return this.collection.countDocuments(this.effectiveFilter());
551
+ count(estimate = false) {
552
+ return countQuery(this.executionContext(), estimate);
471
553
  }
472
- /** Return one `_id`-ordered page and the cursor for the next page. */
554
+ /** Return one bounded `_id`-ordered page and its continuation cursor. */
473
555
  createCursor(after) {
474
- if (this.limitCount === void 0 || this.limitCount === 0) throw new CursorQueryError("Cursor queries require a positive limit");
475
- if (this.skipCount !== void 0) throw new CursorQueryError("Cursor queries do not support skip");
476
- if (this.sortSpec) {
477
- if (Object.keys(this.sortSpec).length !== 1 || this.sortSpec._id !== "asc") throw new CursorQueryError("Cursor queries require the default _id ascending sort");
478
- }
479
- const filter = createCursorFilter(this.effectiveFilter(), after);
480
- return new ModelCursor(() => {
481
- let cursor = this.collection.find(filter).sort({ _id: 1 }).limit(this.limitCount + 1);
482
- const projection = projectionFor(this.fields, this.hiddenFields, this.selectedFields, this.shownFields);
483
- if (projection) cursor = cursor.project(projection);
484
- return cursor;
485
- }, this.limitCount, (documents) => this.population.apply(documents, this.populateSpecs));
556
+ return createCursorPage(this.executionContext(), after);
486
557
  }
487
558
  execute() {
488
- return this.createQueryCursor().toArray().then((documents) => this.population.apply(documents, this.populateSpecs));
489
- }
490
- async first() {
491
- const documents = await this.createQueryCursor().limit(1).toArray();
492
- return (await this.population.apply(documents, this.populateSpecs))[0] ?? null;
493
- }
494
- createQueryCursor() {
495
- let cursor = this.collection.find(this.effectiveFilter());
496
- if (this.sortSpec) cursor = cursor.sort(this.sortSpec);
497
- if (this.skipCount !== void 0) cursor = cursor.skip(this.skipCount);
498
- if (this.limitCount !== void 0) cursor = cursor.limit(this.limitCount);
499
- const projection = projectionFor(this.fields, this.hiddenFields, this.selectedFields, this.shownFields);
500
- if (projection) cursor = cursor.project(projection);
501
- return cursor;
559
+ return executeQuery(this.executionContext());
560
+ }
561
+ first() {
562
+ return executeFirst(this.executionContext());
502
563
  }
503
564
  then(onfulfilled, onrejected) {
504
565
  return this.execute().then(onfulfilled, onrejected);
@@ -626,7 +687,7 @@ var Db = class {
626
687
  constructor(options) {
627
688
  this.options = options;
628
689
  this.client = new MongoClient(options.uri, options.clientOptions);
629
- for (const [name, schema] of Object.entries(options.schema ?? {})) {
690
+ for (const [name, schema] of Object.entries(options.schemas ?? {})) {
630
691
  this.registerSchema(schema, name);
631
692
  let model;
632
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.3",
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",