@mongorm/orm 0.1.1-beta.1 → 0.1.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +414 -0
- package/dist/index.mjs +676 -0
- package/package.json +5 -1
- package/.env.example +0 -2
- package/bumpp.config.ts +0 -7
- package/src/api.ts +0 -47
- package/src/connection/database.ts +0 -145
- package/src/index.ts +0 -50
- package/src/model/model.ts +0 -212
- package/src/model/soft-delete.ts +0 -15
- package/src/query/cursor.ts +0 -36
- package/src/query/many-query.ts +0 -338
- package/src/query/query.ts +0 -14
- package/src/query/runtime.ts +0 -140
- package/src/query/types.ts +0 -134
- package/src/relations/definitions.ts +0 -136
- package/src/relations/registry.ts +0 -144
- package/src/schema/contracts.ts +0 -7
- package/src/schema/index.ts +0 -22
- package/src/schema/inference.ts +0 -17
- package/src/schema/scalars.ts +0 -110
- package/src/schema/schema.ts +0 -234
- package/src/validation/errors.ts +0 -39
- package/tests/connection/database.test.ts +0 -28
- package/tests/env.ts +0 -14
- package/tests/model/bulk.test.ts +0 -40
- package/tests/model/crud.integration.test.ts +0 -160
- package/tests/model/lifecycle.integration.test.ts +0 -44
- package/tests/query/runtime.test.ts +0 -59
- package/tests/relations/definitions.test.ts +0 -36
- package/tests/schema/core.test.ts +0 -45
- package/tests/schema/inference.test-d.ts +0 -232
- package/tests/schema/options.test.ts +0 -55
- package/tsconfig.json +0 -13
- package/tsdown.config.ts +0 -8
- package/vitest.config.ts +0 -8
package/src/api.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
import { createRef } from './relations/definitions.js';
|
|
4
|
-
import type { RefField, SchemaLike } from './relations/definitions.js';
|
|
5
|
-
import { createSchemaRegistry } from './relations/registry.js';
|
|
6
|
-
import type { SchemaShape } from './schema/contracts.js';
|
|
7
|
-
import { objectId, withZodNamespace } from './schema/scalars.js';
|
|
8
|
-
import { Schema } from './schema/schema.js';
|
|
9
|
-
|
|
10
|
-
type ZodConstructorKey = {
|
|
11
|
-
[Key in keyof typeof z]: Key extends string
|
|
12
|
-
? Key extends Lowercase<Key>
|
|
13
|
-
? (typeof z)[Key] extends (...args: any[]) => any
|
|
14
|
-
? Key
|
|
15
|
-
: never
|
|
16
|
-
: never
|
|
17
|
-
: never;
|
|
18
|
-
}[keyof typeof z];
|
|
19
|
-
type ZodConstructors = Pick<typeof z, ZodConstructorKey>;
|
|
20
|
-
|
|
21
|
-
/** The public schema-construction API. */
|
|
22
|
-
export type OrmApi = ZodConstructors & {
|
|
23
|
-
/** Define a typed object schema. */
|
|
24
|
-
schema<Shape extends SchemaShape>(shape: Shape): Schema<Shape>;
|
|
25
|
-
/** Build a registry of named schemas and their relation graph. */
|
|
26
|
-
defineSchemas<const Registry extends Record<string, SchemaLike>>(
|
|
27
|
-
registry: Registry,
|
|
28
|
-
): ReturnType<typeof createSchemaRegistry<Registry>>;
|
|
29
|
-
/** Create a MongoDB ObjectId field. */
|
|
30
|
-
objectId: typeof objectId;
|
|
31
|
-
/** Create a string ID field linked to another schema. */
|
|
32
|
-
ref<Target extends SchemaLike>(resolve: () => Target): RefField<Target>;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/** The ORM schema API with the complete native Zod namespace. */
|
|
36
|
-
const zodConstructors = Object.fromEntries(
|
|
37
|
-
Object.entries(z).filter(
|
|
38
|
-
([name, value]) => name === name.toLowerCase() && typeof value === 'function',
|
|
39
|
-
),
|
|
40
|
-
) as ZodConstructors;
|
|
41
|
-
|
|
42
|
-
export const orm: OrmApi = Object.assign({}, withZodNamespace(zodConstructors), {
|
|
43
|
-
schema: <Shape extends SchemaShape>(shape: Shape) => new Schema(shape),
|
|
44
|
-
defineSchemas: createSchemaRegistry,
|
|
45
|
-
objectId,
|
|
46
|
-
ref: createRef,
|
|
47
|
-
});
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MongoClient,
|
|
3
|
-
type Collection,
|
|
4
|
-
type Document,
|
|
5
|
-
type IndexDescription,
|
|
6
|
-
type MongoClientOptions,
|
|
7
|
-
type Db as MongoDatabase,
|
|
8
|
-
} from 'mongodb';
|
|
9
|
-
|
|
10
|
-
import { Model } from '../model/model.js';
|
|
11
|
-
import type {
|
|
12
|
-
Schema,
|
|
13
|
-
SchemaOptions,
|
|
14
|
-
SchemaLike,
|
|
15
|
-
SchemaShape,
|
|
16
|
-
SchemaRelationMap,
|
|
17
|
-
ScopeDefinitions,
|
|
18
|
-
} from '../schema/index.js';
|
|
19
|
-
import { DatabaseNotConnectedError } from '../validation/errors.js';
|
|
20
|
-
|
|
21
|
-
export type SchemaRegistry = Record<string, SchemaLike>;
|
|
22
|
-
|
|
23
|
-
/** Configuration for a MongoDB connection. */
|
|
24
|
-
export interface DbOptions<Registry extends SchemaRegistry = SchemaRegistry> {
|
|
25
|
-
/** MongoDB connection string. */
|
|
26
|
-
uri: string;
|
|
27
|
-
/** Logical database name. */
|
|
28
|
-
database: string;
|
|
29
|
-
/** Optional native MongoDB client options. */
|
|
30
|
-
clientOptions?: MongoClientOptions;
|
|
31
|
-
/** Schemas registered as plural database model properties. */
|
|
32
|
-
schema?: Registry;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
type ModelForSchema<SchemaType> =
|
|
36
|
-
SchemaType extends Schema<infer Shape, infer Relations, infer Scopes, infer Options>
|
|
37
|
-
? Model<Shape, Relations, Scopes, Options>
|
|
38
|
-
: never;
|
|
39
|
-
|
|
40
|
-
export type DatabaseModels<Registry extends SchemaRegistry> = {
|
|
41
|
-
readonly [Name in keyof Registry]: ModelForSchema<Registry[Name]>;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
type RegistryOfBuilder<Builder extends { readonly __registry?: SchemaRegistry }> = NonNullable<
|
|
45
|
-
Builder['__registry']
|
|
46
|
-
>;
|
|
47
|
-
|
|
48
|
-
/** Owns a MongoDB client and creates schema-bound models. */
|
|
49
|
-
export class Db<Registry extends SchemaRegistry = SchemaRegistry> {
|
|
50
|
-
private readonly client: MongoClient;
|
|
51
|
-
private database: MongoDatabase | null = null;
|
|
52
|
-
private readonly schemaCollections = new Map<SchemaLike, string>();
|
|
53
|
-
|
|
54
|
-
/** Create a disconnected database handle. */
|
|
55
|
-
constructor(private readonly options: DbOptions<Registry>) {
|
|
56
|
-
this.client = new MongoClient(options.uri, options.clientOptions);
|
|
57
|
-
for (const [name, schema] of Object.entries(options.schema ?? {})) {
|
|
58
|
-
this.registerSchema(schema, name);
|
|
59
|
-
let model: Model<SchemaShape, SchemaRelationMap, ScopeDefinitions> | undefined;
|
|
60
|
-
Object.defineProperty(this, name, {
|
|
61
|
-
configurable: false,
|
|
62
|
-
enumerable: true,
|
|
63
|
-
get: () => (model ??= this.model(name, schema)),
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Connect to MongoDB and select the configured database. */
|
|
69
|
-
async connect(): Promise<void> {
|
|
70
|
-
await this.client.connect();
|
|
71
|
-
this.database = this.client.db(this.options.database);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** Close the MongoDB client and release its resources. */
|
|
75
|
-
async disconnect(): Promise<void> {
|
|
76
|
-
await this.client.close();
|
|
77
|
-
this.database = null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Create a model bound to a MongoDB collection and schema. */
|
|
81
|
-
model<
|
|
82
|
-
Shape extends SchemaShape,
|
|
83
|
-
Relations extends SchemaRelationMap,
|
|
84
|
-
Scopes extends ScopeDefinitions,
|
|
85
|
-
Options extends SchemaOptions,
|
|
86
|
-
>(
|
|
87
|
-
name: string,
|
|
88
|
-
schema: Schema<Shape, Relations, Scopes, Options>,
|
|
89
|
-
): Model<Shape, Relations, Scopes, Options> {
|
|
90
|
-
this.registerSchema(schema, name);
|
|
91
|
-
return new Model(this, name, schema);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private registerSchema(schema: SchemaLike, name: string): void {
|
|
95
|
-
const registeredName = this.schemaCollections.get(schema);
|
|
96
|
-
if (registeredName && registeredName !== name) {
|
|
97
|
-
throw new Error(
|
|
98
|
-
`Schema is already registered with collection "${registeredName}" and cannot also use "${name}"`,
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
this.schemaCollections.set(schema, name);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Resolve a registered schema to its MongoDB collection. */
|
|
105
|
-
collectionFor(schema: SchemaLike): Collection<Document> {
|
|
106
|
-
const name = this.schemaCollections.get(schema);
|
|
107
|
-
if (!name) throw new Error('Schema is not registered with this database');
|
|
108
|
-
return this.native.collection<Document>(name);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Explicitly create all indexes declared by registered schemas. */
|
|
112
|
-
async sync(): Promise<Record<string, string[]>> {
|
|
113
|
-
const synchronized: Record<string, string[]> = {};
|
|
114
|
-
for (const [schema, name] of this.schemaCollections) {
|
|
115
|
-
const definitions = schema.indexDefinitions ?? [];
|
|
116
|
-
synchronized[name] = definitions.length
|
|
117
|
-
? await this.collectionFor(schema).createIndexes(
|
|
118
|
-
definitions.map(({ fields, options }) => ({
|
|
119
|
-
...options,
|
|
120
|
-
key: fields,
|
|
121
|
-
})) as IndexDescription[],
|
|
122
|
-
)
|
|
123
|
-
: [];
|
|
124
|
-
}
|
|
125
|
-
return synchronized;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** Return the selected database, failing if `connect()` was not called. */
|
|
129
|
-
get native(): MongoDatabase {
|
|
130
|
-
if (!this.database) throw new DatabaseNotConnectedError();
|
|
131
|
-
return this.database;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** Create a disconnected MongoDB database handle. */
|
|
136
|
-
export function createDatabase<const Registry extends SchemaRegistry>(
|
|
137
|
-
options: DbOptions<Registry> & { schema: Registry },
|
|
138
|
-
): Db<Registry> & DatabaseModels<Registry>;
|
|
139
|
-
export function createDatabase<const Builder extends { readonly __registry?: SchemaRegistry }>(
|
|
140
|
-
options: Omit<DbOptions<RegistryOfBuilder<Builder>>, 'schema'> & { schema: Builder },
|
|
141
|
-
): Db<RegistryOfBuilder<Builder>> & DatabaseModels<RegistryOfBuilder<Builder>>;
|
|
142
|
-
export function createDatabase(options: DbOptions): Db;
|
|
143
|
-
export function createDatabase(options: DbOptions): Db {
|
|
144
|
-
return new Db(options);
|
|
145
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export { ObjectId } from 'mongodb';
|
|
2
|
-
export { orm } from './api.js';
|
|
3
|
-
export type { OrmApi } from './api.js';
|
|
4
|
-
export { Schema } from './schema/index.js';
|
|
5
|
-
export type { Infer, InferInput, InferShape } from './schema/index.js';
|
|
6
|
-
export type { SchemaDefinition, SchemaShape } from './schema/index.js';
|
|
7
|
-
export type {
|
|
8
|
-
ManagedField,
|
|
9
|
-
SchemaIndex,
|
|
10
|
-
SchemaIndexFields,
|
|
11
|
-
SchemaOptions,
|
|
12
|
-
} from './schema/index.js';
|
|
13
|
-
export { hasSoftDelete } from './schema/index.js';
|
|
14
|
-
export type { SoftDeleteEnabled } from './schema/index.js';
|
|
15
|
-
export type {
|
|
16
|
-
RefDefinition,
|
|
17
|
-
RefField,
|
|
18
|
-
RelationMap,
|
|
19
|
-
SchemaLike,
|
|
20
|
-
SchemaRelation,
|
|
21
|
-
SchemaRelationMap,
|
|
22
|
-
} from './schema/index.js';
|
|
23
|
-
export { createDatabase, Db } from './connection/database.js';
|
|
24
|
-
export type {
|
|
25
|
-
RelationDefinitions,
|
|
26
|
-
SchemaRegistryBuilder,
|
|
27
|
-
ScopeDefinitionsBySchema,
|
|
28
|
-
} from './relations/registry.js';
|
|
29
|
-
export {
|
|
30
|
-
CursorQueryError,
|
|
31
|
-
DatabaseNotConnectedError,
|
|
32
|
-
EstimatedCountError,
|
|
33
|
-
InvalidQueryError,
|
|
34
|
-
OrmError,
|
|
35
|
-
} from './validation/errors.js';
|
|
36
|
-
export { Model } from './model/model.js';
|
|
37
|
-
export { ModelCursor, ModelQuery } from './query/query.js';
|
|
38
|
-
export type {
|
|
39
|
-
HiddenDocumentKey,
|
|
40
|
-
ModelFilter,
|
|
41
|
-
ModelSort,
|
|
42
|
-
PopulateSpec,
|
|
43
|
-
PopulateSpecs,
|
|
44
|
-
PopulatedResult,
|
|
45
|
-
SelectedDocument,
|
|
46
|
-
SelectableKey,
|
|
47
|
-
StoredDocument,
|
|
48
|
-
VisibleDocument,
|
|
49
|
-
} from './query/query.js';
|
|
50
|
-
export type { DatabaseModels, DbOptions, SchemaRegistry } from './connection/database.js';
|
package/src/model/model.ts
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ObjectId,
|
|
3
|
-
type Collection,
|
|
4
|
-
type DeleteResult,
|
|
5
|
-
type Filter as MongoFilter,
|
|
6
|
-
type OptionalUnlessRequiredId,
|
|
7
|
-
type UpdateFilter,
|
|
8
|
-
} from 'mongodb';
|
|
9
|
-
|
|
10
|
-
import type { Db } from '../connection/database.js';
|
|
11
|
-
import { ModelQuery } from '../query/query.js';
|
|
12
|
-
import type { ModelFilter, StoredDocument, VisibleDocument } from '../query/query.js';
|
|
13
|
-
import { hasSoftDelete } from '../schema/index.js';
|
|
14
|
-
import type {
|
|
15
|
-
Infer,
|
|
16
|
-
InferInput,
|
|
17
|
-
Schema,
|
|
18
|
-
SchemaOptions,
|
|
19
|
-
SoftDeleteEnabled,
|
|
20
|
-
SchemaRelationMap,
|
|
21
|
-
SchemaShape,
|
|
22
|
-
ScopeDefinitions,
|
|
23
|
-
} from '../schema/index.js';
|
|
24
|
-
import { applySoftDeleteFilter } from './soft-delete.js';
|
|
25
|
-
|
|
26
|
-
type CreateInput<Shape extends SchemaShape, Options extends SchemaOptions> = Omit<
|
|
27
|
-
InferInput<Schema<Shape, {}, {}, Options>>,
|
|
28
|
-
'_id'
|
|
29
|
-
>;
|
|
30
|
-
type UpdateInput<Shape extends SchemaShape, Options extends SchemaOptions> = Partial<
|
|
31
|
-
Omit<InferInput<Schema<Shape, {}, {}, Options>>, '_id'>
|
|
32
|
-
>;
|
|
33
|
-
type ModelDocument<
|
|
34
|
-
Shape extends SchemaShape,
|
|
35
|
-
Relations extends SchemaRelationMap,
|
|
36
|
-
Scopes extends ScopeDefinitions,
|
|
37
|
-
Options extends SchemaOptions,
|
|
38
|
-
> = Infer<Schema<Shape, Relations, Scopes, Options>>;
|
|
39
|
-
/** A MongoDB collection with CRUD operations derived from a schema. */
|
|
40
|
-
export class Model<
|
|
41
|
-
Shape extends SchemaShape,
|
|
42
|
-
Relations extends SchemaRelationMap = {},
|
|
43
|
-
Scopes extends ScopeDefinitions = {},
|
|
44
|
-
Options extends SchemaOptions = {},
|
|
45
|
-
> {
|
|
46
|
-
declare readonly bulk: {
|
|
47
|
-
create: (
|
|
48
|
-
inputs: readonly CreateInput<Shape, Options>[],
|
|
49
|
-
) => Promise<ModelDocument<Shape, Relations, Scopes, Options>[]>;
|
|
50
|
-
};
|
|
51
|
-
declare readonly restore: SoftDeleteEnabled<Options> extends true
|
|
52
|
-
? (
|
|
53
|
-
filter: ModelFilter<Shape>,
|
|
54
|
-
) => Promise<Infer<Schema<Shape, Relations, Scopes, Options>> | null>
|
|
55
|
-
: never;
|
|
56
|
-
declare readonly purge: SoftDeleteEnabled<Options> extends true
|
|
57
|
-
? (filter: ModelFilter<Shape>) => Promise<DeleteResult>
|
|
58
|
-
: never;
|
|
59
|
-
|
|
60
|
-
/** Create a model bound to a database collection and schema. */
|
|
61
|
-
constructor(
|
|
62
|
-
private readonly db: Db,
|
|
63
|
-
readonly name: string,
|
|
64
|
-
private readonly schema: Schema<Shape, Relations, Scopes, Options>,
|
|
65
|
-
) {
|
|
66
|
-
Object.defineProperty(this, 'bulk', {
|
|
67
|
-
configurable: false,
|
|
68
|
-
enumerable: false,
|
|
69
|
-
value: {
|
|
70
|
-
create: (inputs: readonly CreateInput<Shape, Options>[]) => this.bulkCreate(inputs),
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
if (hasSoftDelete(schema.optionsConfig)) {
|
|
74
|
-
Object.defineProperties(this, {
|
|
75
|
-
restore: {
|
|
76
|
-
configurable: false,
|
|
77
|
-
enumerable: false,
|
|
78
|
-
value: (filter: ModelFilter<Shape>) => this.restoreDocument(filter),
|
|
79
|
-
},
|
|
80
|
-
purge: {
|
|
81
|
-
configurable: false,
|
|
82
|
-
enumerable: false,
|
|
83
|
-
value: (filter: ModelFilter<Shape>) => this.purgeDocuments(filter),
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private get collection(): Collection<StoredDocument<Shape>> {
|
|
90
|
-
return this.db.native.collection<StoredDocument<Shape>>(this.name);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private activeFilter(filter: ModelFilter<Shape>): ModelFilter<Shape> {
|
|
94
|
-
return hasSoftDelete(this.schema.optionsConfig)
|
|
95
|
-
? applySoftDeleteFilter(filter, 'active')
|
|
96
|
-
: filter;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** Validate and insert one document, generating its ObjectId. */
|
|
100
|
-
async create(
|
|
101
|
-
input: CreateInput<Shape, Options>,
|
|
102
|
-
): Promise<ModelDocument<Shape, Relations, Scopes, Options>> {
|
|
103
|
-
const document = this.prepareDocument(input);
|
|
104
|
-
await this.collection.insertOne(
|
|
105
|
-
document as unknown as OptionalUnlessRequiredId<StoredDocument<Shape>>,
|
|
106
|
-
);
|
|
107
|
-
return document as ModelDocument<Shape, Relations, Scopes, Options>;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private prepareDocument(input: CreateInput<Shape, Options>): Record<string, unknown> {
|
|
111
|
-
const now = new Date();
|
|
112
|
-
const document: Record<string, unknown> = {
|
|
113
|
-
_id: new ObjectId(),
|
|
114
|
-
...this.schema.parse(input),
|
|
115
|
-
};
|
|
116
|
-
if (this.schema.optionsConfig.timestamps) {
|
|
117
|
-
document.createdAt = now;
|
|
118
|
-
document.updatedAt = now;
|
|
119
|
-
}
|
|
120
|
-
if (hasSoftDelete(this.schema.optionsConfig)) document.deletedAt = null;
|
|
121
|
-
return document;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private async bulkCreate(
|
|
125
|
-
inputs: readonly CreateInput<Shape, Options>[],
|
|
126
|
-
): Promise<ModelDocument<Shape, Relations, Scopes, Options>[]> {
|
|
127
|
-
if (inputs.length === 0) return [];
|
|
128
|
-
const documents = inputs.map((input) => this.prepareDocument(input));
|
|
129
|
-
await this.collection.insertMany(
|
|
130
|
-
documents as unknown as OptionalUnlessRequiredId<StoredDocument<Shape>>[],
|
|
131
|
-
);
|
|
132
|
-
return documents as ModelDocument<Shape, Relations, Scopes, Options>[];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** Build a query for all documents matching a MongoDB filter. */
|
|
136
|
-
find(
|
|
137
|
-
filter: ModelFilter<Shape> = {},
|
|
138
|
-
): ModelQuery<
|
|
139
|
-
Shape,
|
|
140
|
-
VisibleDocument<Shape>,
|
|
141
|
-
true,
|
|
142
|
-
Relations,
|
|
143
|
-
Scopes,
|
|
144
|
-
'none',
|
|
145
|
-
SoftDeleteEnabled<Options>
|
|
146
|
-
> {
|
|
147
|
-
return new ModelQuery(
|
|
148
|
-
this.collection,
|
|
149
|
-
filter,
|
|
150
|
-
this.schema.fields,
|
|
151
|
-
this.schema.hiddenFields,
|
|
152
|
-
this.db,
|
|
153
|
-
this.schema.relationMap,
|
|
154
|
-
this.schema.scopeMap,
|
|
155
|
-
hasSoftDelete(this.schema.optionsConfig),
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** Validate and apply a partial update to the first matching document. */
|
|
160
|
-
async update(
|
|
161
|
-
filter: ModelFilter<Shape>,
|
|
162
|
-
patch: UpdateInput<Shape, Options>,
|
|
163
|
-
): Promise<Infer<Schema<Shape, Relations, Scopes, Options>> | null> {
|
|
164
|
-
const parsedPatch = this.schema.parsePartial(patch) as Record<string, unknown>;
|
|
165
|
-
const managedFields = new Set(['createdAt', 'updatedAt', 'deletedAt']);
|
|
166
|
-
managedFields.forEach((field) => delete parsedPatch[field]);
|
|
167
|
-
if (this.schema.optionsConfig.timestamps) parsedPatch.updatedAt = new Date();
|
|
168
|
-
return (await this.collection.findOneAndUpdate(
|
|
169
|
-
this.activeFilter(filter) as MongoFilter<StoredDocument<Shape>>,
|
|
170
|
-
{ $set: parsedPatch } as unknown as UpdateFilter<StoredDocument<Shape>>,
|
|
171
|
-
{ returnDocument: 'after' },
|
|
172
|
-
)) as unknown as Infer<Schema<Shape, Relations, Scopes, Options>> | null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** Restore matching soft-deleted documents. */
|
|
176
|
-
private async restoreDocument(
|
|
177
|
-
filter: ModelFilter<Shape>,
|
|
178
|
-
): Promise<Infer<Schema<Shape, Relations, Scopes, Options>> | null> {
|
|
179
|
-
if (!hasSoftDelete(this.schema.optionsConfig)) {
|
|
180
|
-
throw new Error('Restore requires softdelete schema options');
|
|
181
|
-
}
|
|
182
|
-
const patch: Record<string, unknown> = { deletedAt: null };
|
|
183
|
-
if (this.schema.optionsConfig.timestamps) patch.updatedAt = new Date();
|
|
184
|
-
return (await this.collection.findOneAndUpdate(
|
|
185
|
-
applySoftDeleteFilter(filter, 'deleted') as MongoFilter<StoredDocument<Shape>>,
|
|
186
|
-
{ $set: patch } as UpdateFilter<StoredDocument<Shape>>,
|
|
187
|
-
{ returnDocument: 'after' },
|
|
188
|
-
)) as unknown as Infer<Schema<Shape, Relations, Scopes, Options>> | null;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/** Delete every document matching a MongoDB filter. */
|
|
192
|
-
async delete(filter: ModelFilter<Shape>): Promise<DeleteResult> {
|
|
193
|
-
if (!hasSoftDelete(this.schema.optionsConfig)) {
|
|
194
|
-
return this.collection.deleteMany(filter as MongoFilter<StoredDocument<Shape>>);
|
|
195
|
-
}
|
|
196
|
-
const patch: Record<string, unknown> = { deletedAt: new Date() };
|
|
197
|
-
if (this.schema.optionsConfig.timestamps) patch.updatedAt = new Date();
|
|
198
|
-
const result = await this.collection.updateMany(
|
|
199
|
-
applySoftDeleteFilter(filter, 'active') as MongoFilter<StoredDocument<Shape>>,
|
|
200
|
-
{ $set: patch } as UpdateFilter<StoredDocument<Shape>>,
|
|
201
|
-
);
|
|
202
|
-
return {
|
|
203
|
-
acknowledged: result.acknowledged,
|
|
204
|
-
deletedCount: result.modifiedCount,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/** Permanently delete matching documents, including soft-deleted documents. */
|
|
209
|
-
private purgeDocuments(filter: ModelFilter<Shape>): Promise<DeleteResult> {
|
|
210
|
-
return this.collection.deleteMany(filter as MongoFilter<StoredDocument<Shape>>);
|
|
211
|
-
}
|
|
212
|
-
}
|
package/src/model/soft-delete.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ModelFilter } from '../query/types.js';
|
|
2
|
-
import type { SchemaShape } from '../schema/contracts.js';
|
|
3
|
-
|
|
4
|
-
export type SoftDeleteMode = 'active' | 'deleted' | 'all';
|
|
5
|
-
export type DeletedQueryMode = 'only' | 'include';
|
|
6
|
-
|
|
7
|
-
export const applySoftDeleteFilter = <Shape extends SchemaShape>(
|
|
8
|
-
filter: ModelFilter<Shape>,
|
|
9
|
-
mode: SoftDeleteMode,
|
|
10
|
-
): ModelFilter<Shape> => {
|
|
11
|
-
if (mode === 'all') return filter;
|
|
12
|
-
|
|
13
|
-
const deletionFilter = mode === 'deleted' ? { deletedAt: { $ne: null } } : { deletedAt: null };
|
|
14
|
-
return { $and: [deletionFilter, filter] } as ModelFilter<Shape>;
|
|
15
|
-
};
|
package/src/query/cursor.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { ObjectId, type FindCursor, type WithId } from 'mongodb';
|
|
2
|
-
|
|
3
|
-
import type { SchemaShape } from '../schema/contracts.js';
|
|
4
|
-
import type { StoredDocument } from './types.js';
|
|
5
|
-
|
|
6
|
-
/** A lazy async iterable for one cursor-pagination page. */
|
|
7
|
-
export class ModelCursor<
|
|
8
|
-
Shape extends SchemaShape,
|
|
9
|
-
Result extends object,
|
|
10
|
-
> implements AsyncIterable<Result> {
|
|
11
|
-
next: ObjectId | null = null;
|
|
12
|
-
|
|
13
|
-
constructor(
|
|
14
|
-
private readonly open: () => FindCursor<WithId<StoredDocument<Shape>>>,
|
|
15
|
-
private readonly pageSize: number,
|
|
16
|
-
private readonly transform?: (documents: Result[]) => Promise<Result[]>,
|
|
17
|
-
) {}
|
|
18
|
-
|
|
19
|
-
async *[Symbol.asyncIterator](): AsyncGenerator<Result> {
|
|
20
|
-
const cursor = this.open();
|
|
21
|
-
try {
|
|
22
|
-
const documents: Result[] = [];
|
|
23
|
-
for (let index = 0; index < this.pageSize && (await cursor.hasNext()); index += 1) {
|
|
24
|
-
documents.push((await cursor.next()) as unknown as Result);
|
|
25
|
-
}
|
|
26
|
-
const transformed = this.transform ? await this.transform(documents) : documents;
|
|
27
|
-
for (const document of transformed) {
|
|
28
|
-
yield document;
|
|
29
|
-
}
|
|
30
|
-
const last = transformed.at(-1) as (Result & { _id: ObjectId }) | undefined;
|
|
31
|
-
this.next = (await cursor.hasNext()) && last ? last._id : null;
|
|
32
|
-
} finally {
|
|
33
|
-
await cursor.close();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|