@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/validation/errors.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/** Base error type emitted by the ORM. */
|
|
2
|
-
export class OrmError extends Error {
|
|
3
|
-
constructor(
|
|
4
|
-
message: string,
|
|
5
|
-
readonly code: string,
|
|
6
|
-
) {
|
|
7
|
-
super(message);
|
|
8
|
-
this.name = new.target.name;
|
|
9
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Raised when an operation requires a connected database. */
|
|
14
|
-
export class DatabaseNotConnectedError extends OrmError {
|
|
15
|
-
constructor() {
|
|
16
|
-
super('Database is not connected', 'DATABASE_NOT_CONNECTED');
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Raised when a query configuration is not supported. */
|
|
21
|
-
export class InvalidQueryError extends OrmError {
|
|
22
|
-
constructor(message: string, code = 'INVALID_QUERY') {
|
|
23
|
-
super(message, code);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Raised when cursor pagination options are incompatible. */
|
|
28
|
-
export class CursorQueryError extends InvalidQueryError {
|
|
29
|
-
constructor(message: string) {
|
|
30
|
-
super(message, 'CURSOR_QUERY_INVALID');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Raised when an estimated count is requested with a filter. */
|
|
35
|
-
export class EstimatedCountError extends InvalidQueryError {
|
|
36
|
-
constructor() {
|
|
37
|
-
super('Estimated query counts do not support filters', 'ESTIMATED_COUNT_FILTER_UNSUPPORTED');
|
|
38
|
-
}
|
|
39
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { createDatabase, orm } from '../../src/index.js';
|
|
4
|
-
|
|
5
|
-
describe('database connection', () => {
|
|
6
|
-
it('registers schemas as database models', () => {
|
|
7
|
-
const users = orm.schema({ name: orm.string() });
|
|
8
|
-
const db = createDatabase({
|
|
9
|
-
uri: 'mongodb://127.0.0.1:27017',
|
|
10
|
-
database: 'mongorm_registry_test',
|
|
11
|
-
schema: { users },
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
expect(db.users.name).toBe('users');
|
|
15
|
-
expect(db.users).toBe(db.users);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('rejects registering one schema to multiple collections', () => {
|
|
19
|
-
const users = orm.schema({ name: orm.string() });
|
|
20
|
-
const db = createDatabase({
|
|
21
|
-
uri: 'mongodb://127.0.0.1:27017',
|
|
22
|
-
database: 'mongorm_registry_test',
|
|
23
|
-
schema: { users },
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
expect(() => db.model('accounts', users)).toThrow('already registered with collection "users"');
|
|
27
|
-
});
|
|
28
|
-
});
|
package/tests/env.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import * as dotenv from 'dotenv';
|
|
2
|
-
import * as z from 'zod';
|
|
3
|
-
|
|
4
|
-
dotenv.config({
|
|
5
|
-
quiet: true,
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
const schema = z.object({
|
|
9
|
-
MONGODB_URI: z.string().min(1),
|
|
10
|
-
MONGODB_DATABASE: z.string().min(1),
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
/** Required MongoDB integration-test configuration. */
|
|
14
|
-
export const env = schema.parse(process.env);
|
package/tests/model/bulk.test.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import type { Db } from '../../src/connection/database.js';
|
|
4
|
-
import { orm } from '../../src/index.js';
|
|
5
|
-
import { Model } from '../../src/model/model.js';
|
|
6
|
-
|
|
7
|
-
describe('bulk model operations', () => {
|
|
8
|
-
it('validates inputs and inserts all prepared documents at once', async () => {
|
|
9
|
-
const insertMany = vi
|
|
10
|
-
.fn<(documents: unknown[]) => Promise<unknown>>()
|
|
11
|
-
.mockResolvedValue({ acknowledged: true, insertedCount: 2 });
|
|
12
|
-
const collection = { insertMany };
|
|
13
|
-
const database = {
|
|
14
|
-
native: { collection: () => collection },
|
|
15
|
-
} as unknown as Db;
|
|
16
|
-
const model = new Model(database, 'users', orm.schema({ name: orm.string() }));
|
|
17
|
-
|
|
18
|
-
const created = await model.bulk.create([{ name: 'Ada' }, { name: 'Alan' }]);
|
|
19
|
-
|
|
20
|
-
expect(insertMany).toHaveBeenCalledOnce();
|
|
21
|
-
expect(insertMany).toHaveBeenCalledWith(created);
|
|
22
|
-
expect(created).toHaveLength(2);
|
|
23
|
-
expect(created[0]._id).toBeDefined();
|
|
24
|
-
expect(created[1]._id).toBeDefined();
|
|
25
|
-
expect(created[0]._id).not.toEqual(created[1]._id);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('validates the complete batch before writing', async () => {
|
|
29
|
-
const insertMany = vi.fn<(documents: unknown[]) => Promise<unknown>>();
|
|
30
|
-
const database = {
|
|
31
|
-
native: { collection: () => ({ insertMany }) },
|
|
32
|
-
} as unknown as Db;
|
|
33
|
-
const model = new Model(database, 'users', orm.schema({ name: orm.string() }));
|
|
34
|
-
|
|
35
|
-
await expect(model.bulk.create([{ name: 'Ada' }, { name: 42 as never }])).rejects.toThrow(
|
|
36
|
-
/string/,
|
|
37
|
-
);
|
|
38
|
-
expect(insertMany).not.toHaveBeenCalled();
|
|
39
|
-
});
|
|
40
|
-
});
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { ObjectId, createDatabase, orm } from '../../src/index.js';
|
|
4
|
-
import { env } from '../env.js';
|
|
5
|
-
|
|
6
|
-
const runDatabaseTests = Boolean(env.MONGODB_URI);
|
|
7
|
-
|
|
8
|
-
const database = createDatabase({
|
|
9
|
-
uri: env.MONGODB_URI ?? 'mongodb://127.0.0.1:27017',
|
|
10
|
-
database: env.MONGODB_DATABASE,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const ticketSchema = orm.schema({
|
|
14
|
-
title: orm.string(),
|
|
15
|
-
status: orm.enum(['open', 'closed']),
|
|
16
|
-
priority: orm.number(),
|
|
17
|
-
owner: orm.objectId(),
|
|
18
|
-
secret: orm.string().hidden(),
|
|
19
|
-
});
|
|
20
|
-
const ownerSchema = orm.schema({ name: orm.string() });
|
|
21
|
-
const relatedTicketSchema = ticketSchema.relations({ owner: () => ownerSchema });
|
|
22
|
-
|
|
23
|
-
const owners = database.model('owners', ownerSchema);
|
|
24
|
-
const tickets = database.model('tickets', relatedTicketSchema);
|
|
25
|
-
|
|
26
|
-
describe.skipIf(!runDatabaseTests)('database CRUD', () => {
|
|
27
|
-
beforeAll(() => database.connect());
|
|
28
|
-
beforeEach(async () => {
|
|
29
|
-
await tickets.delete({});
|
|
30
|
-
await owners.delete({});
|
|
31
|
-
});
|
|
32
|
-
afterAll(() => database.disconnect());
|
|
33
|
-
|
|
34
|
-
it('creates, filters, finds, updates, and deletes typed documents', async () => {
|
|
35
|
-
const owner = await owners.create({ name: 'Ada' });
|
|
36
|
-
const otherOwner = await owners.create({ name: 'Alan' });
|
|
37
|
-
const first = await tickets.create({
|
|
38
|
-
title: 'Design API',
|
|
39
|
-
status: 'open',
|
|
40
|
-
priority: 1,
|
|
41
|
-
owner: owner._id,
|
|
42
|
-
secret: 'first-secret',
|
|
43
|
-
});
|
|
44
|
-
await tickets.create({
|
|
45
|
-
title: 'Implement API',
|
|
46
|
-
status: 'open',
|
|
47
|
-
priority: 2,
|
|
48
|
-
owner: owner._id,
|
|
49
|
-
secret: 'second-secret',
|
|
50
|
-
});
|
|
51
|
-
await tickets.create({
|
|
52
|
-
title: 'Document API',
|
|
53
|
-
status: 'closed',
|
|
54
|
-
priority: 3,
|
|
55
|
-
owner: otherOwner._id,
|
|
56
|
-
secret: 'third-secret',
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
expect(first._id).toBeInstanceOf(ObjectId);
|
|
60
|
-
expect((await tickets.find({}))[0]).not.toHaveProperty('secret');
|
|
61
|
-
expect((await tickets.find({}).show(['secret']))[0]).toHaveProperty('secret');
|
|
62
|
-
const defaultSelection = await tickets.find({}).select();
|
|
63
|
-
expect(defaultSelection[0]).not.toHaveProperty('secret');
|
|
64
|
-
expect(await tickets.find({ status: 'open', owner: owner._id })).toHaveLength(2);
|
|
65
|
-
expect(await tickets.find({ title: 'Missing ticket' }).first()).toBeNull();
|
|
66
|
-
const populated = await tickets
|
|
67
|
-
.find({ status: 'open' })
|
|
68
|
-
.populate([{ ref: 'owner', select: ['name'] }]);
|
|
69
|
-
expect(populated[0].owner?.name).toBe('Ada');
|
|
70
|
-
expect(populated[0].owner).toHaveProperty('_id');
|
|
71
|
-
const populatedCursor = tickets
|
|
72
|
-
.find({ status: 'open' })
|
|
73
|
-
.populate([{ ref: 'owner', select: ['name'] }])
|
|
74
|
-
.limit(1)
|
|
75
|
-
.cursor();
|
|
76
|
-
const cursorDocuments = [];
|
|
77
|
-
for await (const ticket of populatedCursor) cursorDocuments.push(ticket);
|
|
78
|
-
expect(cursorDocuments[0].owner?.name).toBe('Ada');
|
|
79
|
-
expect(await tickets.find()).toEqual(await tickets.find({}));
|
|
80
|
-
expect(await tickets.find({ status: 'open' }).count()).toBe(2);
|
|
81
|
-
expect(await tickets.find().count(true)).toBeGreaterThanOrEqual(3);
|
|
82
|
-
await expect(tickets.find({ status: 'open' }).count(true)).rejects.toThrow(
|
|
83
|
-
'do not support filters',
|
|
84
|
-
);
|
|
85
|
-
const firstPage = tickets.find({}).limit(2).cursor();
|
|
86
|
-
const firstResults = [];
|
|
87
|
-
for await (const ticket of firstPage) firstResults.push(ticket);
|
|
88
|
-
expect(firstResults).toHaveLength(2);
|
|
89
|
-
expect(firstPage.next).toBeInstanceOf(ObjectId);
|
|
90
|
-
const secondPage = await tickets
|
|
91
|
-
.find({})
|
|
92
|
-
.limit(2)
|
|
93
|
-
.cursor(firstPage.next ?? undefined);
|
|
94
|
-
const secondResults = [];
|
|
95
|
-
for await (const ticket of secondPage) secondResults.push(ticket);
|
|
96
|
-
expect(secondResults).toHaveLength(1);
|
|
97
|
-
expect(secondPage.next).toBeNull();
|
|
98
|
-
expect(new Set([...firstResults, ...secondResults].map((ticket) => ticket._id)).size).toBe(3);
|
|
99
|
-
const projectedPage = tickets.find({}).select(['title']).limit(2).cursor();
|
|
100
|
-
for await (const ticket of projectedPage) {
|
|
101
|
-
expect(ticket).toHaveProperty('title');
|
|
102
|
-
expect(ticket).not.toHaveProperty('secret');
|
|
103
|
-
expect(ticket).not.toHaveProperty('priority');
|
|
104
|
-
}
|
|
105
|
-
const sorted = await tickets.find({ status: 'open' }).sort({ priority: 'desc' });
|
|
106
|
-
expect(sorted.map((ticket) => ticket.priority)).toEqual([2, 1]);
|
|
107
|
-
const skipped = await tickets.find({ status: 'open' }).sort({ priority: 'desc' }).skip(1);
|
|
108
|
-
expect(skipped.map((ticket) => ticket.priority)).toEqual([1]);
|
|
109
|
-
expect(() => tickets.find({}).skip(-1)).toThrow('non-negative integer');
|
|
110
|
-
const limited = await tickets.find({ status: 'open' }).sort({ priority: 'desc' }).limit(1);
|
|
111
|
-
expect(limited.map((ticket) => ticket.priority)).toEqual([2]);
|
|
112
|
-
expect(() => tickets.find({}).limit(-1)).toThrow('non-negative integer');
|
|
113
|
-
const invalidCursorQuery = tickets.find({}).limit(3).sort({ priority: 'desc' }).skip(5) as any;
|
|
114
|
-
expect(() => invalidCursorQuery.cursor()).toThrow('Cursor queries do not support skip');
|
|
115
|
-
expect(() => tickets.find({}).cursor()).toThrow('Cursor queries require a positive limit');
|
|
116
|
-
expect(() => (tickets.find({}).limit(3).sort({ priority: 'desc' }).cursor as any)()).toThrow(
|
|
117
|
-
'default _id ascending sort',
|
|
118
|
-
);
|
|
119
|
-
const selected = await tickets.find({ status: 'open' }).select(['title', 'priority']);
|
|
120
|
-
expect(selected[0]).toMatchObject({ title: 'Design API', priority: 1 });
|
|
121
|
-
expect(Object.keys(selected[0])).toEqual(expect.arrayContaining(['_id', 'title', 'priority']));
|
|
122
|
-
const selectedOne = await tickets.find({ title: 'Design API' }).select(['title']).first();
|
|
123
|
-
expect(selectedOne).toMatchObject({ title: 'Design API' });
|
|
124
|
-
expect(selectedOne).not.toHaveProperty('priority');
|
|
125
|
-
expect((await tickets.find({ title: 'Design API' }).first())?._id).toEqual(first._id);
|
|
126
|
-
|
|
127
|
-
const updated = await tickets.update({ _id: first._id }, { status: 'closed' });
|
|
128
|
-
expect(updated).toMatchObject({ title: 'Design API', status: 'closed' });
|
|
129
|
-
|
|
130
|
-
const deleted = await tickets.delete({ owner: otherOwner._id });
|
|
131
|
-
expect(deleted.deletedCount).toBe(1);
|
|
132
|
-
expect(await tickets.find({})).toHaveLength(2);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('bulk creates validated documents with generated ids', async () => {
|
|
136
|
-
const owner = await owners.create({ name: 'Grace' });
|
|
137
|
-
const created = await tickets.bulk.create([
|
|
138
|
-
{
|
|
139
|
-
title: 'Bulk one',
|
|
140
|
-
status: 'open',
|
|
141
|
-
priority: 1,
|
|
142
|
-
owner: owner._id,
|
|
143
|
-
secret: 'first-secret',
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
title: 'Bulk two',
|
|
147
|
-
status: 'closed',
|
|
148
|
-
priority: 2,
|
|
149
|
-
owner: owner._id,
|
|
150
|
-
secret: 'second-secret',
|
|
151
|
-
},
|
|
152
|
-
]);
|
|
153
|
-
|
|
154
|
-
expect(created).toHaveLength(2);
|
|
155
|
-
expect(created[0]._id).toBeInstanceOf(ObjectId);
|
|
156
|
-
expect(created[0]._id).not.toEqual(created[1]._id);
|
|
157
|
-
expect(await tickets.find({})).toHaveLength(2);
|
|
158
|
-
expect((await tickets.find({}).show(['secret']))[1].secret).toBe('second-secret');
|
|
159
|
-
});
|
|
160
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { createDatabase, orm } from '../../src/index.js';
|
|
4
|
-
import { env } from '../env.js';
|
|
5
|
-
|
|
6
|
-
const runDatabaseTests = Boolean(env.MONGODB_URI);
|
|
7
|
-
const database = createDatabase({
|
|
8
|
-
uri: env.MONGODB_URI ?? 'mongodb://127.0.0.1:27017',
|
|
9
|
-
database: env.MONGODB_DATABASE,
|
|
10
|
-
});
|
|
11
|
-
const lifecycleSchema = orm
|
|
12
|
-
.schema({ name: orm.string() })
|
|
13
|
-
.options({ timestamps: true, softdelete: true });
|
|
14
|
-
const lifecycle = database.model('lifecycle', lifecycleSchema);
|
|
15
|
-
|
|
16
|
-
describe.skipIf(!runDatabaseTests)('model lifecycle', () => {
|
|
17
|
-
beforeAll(() => database.connect());
|
|
18
|
-
beforeEach(() => lifecycle.purge({}));
|
|
19
|
-
afterAll(() => database.disconnect());
|
|
20
|
-
|
|
21
|
-
it('manages timestamps and soft deletion', async () => {
|
|
22
|
-
const first = await lifecycle.create({ name: 'Ada' });
|
|
23
|
-
const second = await lifecycle.create({ name: 'Alan' });
|
|
24
|
-
|
|
25
|
-
expect(first.createdAt).toBeInstanceOf(Date);
|
|
26
|
-
expect(first.updatedAt).toBeInstanceOf(Date);
|
|
27
|
-
expect(first.deletedAt).toBeNull();
|
|
28
|
-
expect(await lifecycle.find({})).toHaveLength(2);
|
|
29
|
-
|
|
30
|
-
const deleted = await lifecycle.delete({ _id: first._id });
|
|
31
|
-
expect(deleted.deletedCount).toBe(1);
|
|
32
|
-
expect(await lifecycle.find({})).toHaveLength(1);
|
|
33
|
-
expect(await lifecycle.find({}).deleted('include')).toHaveLength(2);
|
|
34
|
-
expect((await lifecycle.find({}).deleted('only'))[0]._id).toEqual(first._id);
|
|
35
|
-
|
|
36
|
-
const restored = await lifecycle.restore({ _id: first._id });
|
|
37
|
-
expect(restored?.deletedAt).toBeNull();
|
|
38
|
-
expect(await lifecycle.find({})).toHaveLength(2);
|
|
39
|
-
|
|
40
|
-
const updated = await lifecycle.update({ _id: second._id }, { name: 'Alan Turing' });
|
|
41
|
-
expect(updated?.name).toBe('Alan Turing');
|
|
42
|
-
expect(updated?.updatedAt.getTime()).toBeGreaterThanOrEqual(second.updatedAt.getTime());
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { ObjectId, orm } from '../../src/index.js';
|
|
4
|
-
import { createCursorFilter, projectionFor, SoftDeleteState } from '../../src/query/runtime.js';
|
|
5
|
-
import type { ModelFilter } from '../../src/query/types.js';
|
|
6
|
-
|
|
7
|
-
const fields = {
|
|
8
|
-
name: orm.string(),
|
|
9
|
-
profile: orm.object({ city: orm.string(), country: orm.string() }),
|
|
10
|
-
secret: orm.string().hidden(),
|
|
11
|
-
};
|
|
12
|
-
type Shape = typeof fields;
|
|
13
|
-
|
|
14
|
-
describe('query runtime policies', () => {
|
|
15
|
-
it('builds visible projections when selection is empty', () => {
|
|
16
|
-
expect(projectionFor(['name', 'profile.city', 'secret'], ['secret'], [], [])).toEqual({
|
|
17
|
-
name: 1,
|
|
18
|
-
'profile.city': 1,
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('normalizes overlapping selected and shown fields', () => {
|
|
23
|
-
expect(
|
|
24
|
-
projectionFor(
|
|
25
|
-
['name', 'profile', 'profile.city', 'secret'],
|
|
26
|
-
['secret'],
|
|
27
|
-
['profile', 'profile.city'],
|
|
28
|
-
['secret'],
|
|
29
|
-
),
|
|
30
|
-
).toEqual({ profile: 1, secret: 1 });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('applies active, deleted, and all soft-delete filters', () => {
|
|
34
|
-
const filter = { name: 'Ada' } as ModelFilter<Shape>;
|
|
35
|
-
const state = new SoftDeleteState<Shape>(true);
|
|
36
|
-
|
|
37
|
-
expect(state.effectiveFilter(filter)).toEqual({
|
|
38
|
-
$and: [{ deletedAt: null }, filter],
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
state.deleted('only');
|
|
42
|
-
expect(state.effectiveFilter(filter)).toEqual({
|
|
43
|
-
$and: [{ deletedAt: { $ne: null } }, filter],
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
state.includeDeleted();
|
|
47
|
-
expect(state.effectiveFilter(filter)).toBe(filter);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('adds an id cursor boundary without changing the base filter', () => {
|
|
51
|
-
const filter = { name: 'Ada' } as ModelFilter<Shape>;
|
|
52
|
-
const after = new ObjectId();
|
|
53
|
-
|
|
54
|
-
expect(createCursorFilter(filter, after)).toEqual({
|
|
55
|
-
$and: [filter, { _id: { $gt: after } }],
|
|
56
|
-
});
|
|
57
|
-
expect(createCursorFilter(filter)).toBe(filter);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { ObjectId } from 'mongodb';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
|
|
4
|
-
import { orm } from '../../src/index.js';
|
|
5
|
-
|
|
6
|
-
describe('relations', () => {
|
|
7
|
-
it('collects lazy relation metadata without evaluating it during construction', () => {
|
|
8
|
-
const group = orm.schema({ name: orm.string() });
|
|
9
|
-
const user = orm.schema({
|
|
10
|
-
group: orm.ref(() => group).optional(),
|
|
11
|
-
nullableGroup: orm.ref(() => group).nullable(),
|
|
12
|
-
nullishGroup: orm.ref(() => group).nullish(),
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
expect(user.refs.group.resolve()).toBe(group);
|
|
16
|
-
const groupId = new ObjectId();
|
|
17
|
-
expect(user.parse({ group: groupId, nullableGroup: null, nullishGroup: undefined })).toEqual({
|
|
18
|
-
group: groupId,
|
|
19
|
-
nullableGroup: null,
|
|
20
|
-
nullishGroup: undefined,
|
|
21
|
-
});
|
|
22
|
-
expect(() => user.parse({ group: 'group-1' })).toThrow('Invalid input');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('declares one-way relations and population scopes', () => {
|
|
26
|
-
const group = orm.schema({ name: orm.string() });
|
|
27
|
-
const user = orm
|
|
28
|
-
.schema({ name: orm.string(), group: orm.objectId().optional() })
|
|
29
|
-
.relations({ group: () => group })
|
|
30
|
-
.scopes({ detail: [{ ref: 'group', select: ['name'] }] });
|
|
31
|
-
|
|
32
|
-
expect(user.relationMap.group.resolve()).toBe(group);
|
|
33
|
-
expect(user.relationMap.group.localField).toBe('group');
|
|
34
|
-
expect(user.scopeMap.detail).toEqual([{ ref: 'group', select: ['name'] }]);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { orm } from '../../src/index.js';
|
|
4
|
-
|
|
5
|
-
describe('schema', () => {
|
|
6
|
-
it('parses a schema definition', () => {
|
|
7
|
-
const user = orm.schema({ name: orm.string(), age: orm.number() });
|
|
8
|
-
|
|
9
|
-
expect(user.parse({ name: 'Ada', age: 36 })).toEqual({ name: 'Ada', age: 36 });
|
|
10
|
-
expect(user.parsePartial({ age: 37 })).toEqual({ age: 37 });
|
|
11
|
-
expect(() => user.parsePartial({ age: 'thirty-seven' })).toThrow('Invalid input');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('parses nested object fields', () => {
|
|
15
|
-
const profile = orm.schema({
|
|
16
|
-
details: orm.object({
|
|
17
|
-
website: orm.string().optional(),
|
|
18
|
-
location: orm.object({ city: orm.string() }),
|
|
19
|
-
}),
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
expect(
|
|
23
|
-
profile.parse({ details: { website: 'example.test', location: { city: 'London' } } }),
|
|
24
|
-
).toEqual({
|
|
25
|
-
details: { website: 'example.test', location: { city: 'London' } },
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('preserves hidden metadata through common Zod wrappers', () => {
|
|
30
|
-
const account = orm.schema({
|
|
31
|
-
email: orm.string().email(),
|
|
32
|
-
token: orm.string().optional().hidden(),
|
|
33
|
-
backupToken: orm.string().nullable().hidden(),
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
expect(account.hiddenFields).toEqual(['token', 'backupToken']);
|
|
37
|
-
expect(
|
|
38
|
-
account.parse({ email: 'ada@example.test', token: undefined, backupToken: null }),
|
|
39
|
-
).toEqual({
|
|
40
|
-
email: 'ada@example.test',
|
|
41
|
-
token: undefined,
|
|
42
|
-
backupToken: null,
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
});
|