typeorm-query-scopes 0.1.0-beta.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.
- package/LICENSE +21 -0
- package/README.md +487 -0
- package/dist/decorators.d.ts +28 -0
- package/dist/decorators.js +41 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +23 -0
- package/dist/metadata.d.ts +7 -0
- package/dist/metadata.js +24 -0
- package/dist/scope-merger.d.ts +8 -0
- package/dist/scope-merger.js +68 -0
- package/dist/scoped-repository.d.ts +54 -0
- package/dist/scoped-repository.js +129 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.js +2 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# TypeORM Scopes
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/typeorm-query-scopes)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://typeorm.io/)
|
|
7
|
+
|
|
8
|
+
> ⚠️ **Beta Release**: This is version 0.1.0-beta.1. The API is stable but may change based on community feedback. Please report any issues on GitHub.
|
|
9
|
+
|
|
10
|
+
Sequelize-like scopes for TypeORM entities. Define reusable query filters using decorators and apply them easily to your queries.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- 🎯 **Reusable Queries** - Define once, use everywhere
|
|
15
|
+
- 🔒 **Type Safe** - Full TypeScript support with autocomplete
|
|
16
|
+
- 🎨 **Clean Code** - Decorator-based, declarative syntax
|
|
17
|
+
- ⚡ **Zero Overhead** - No performance penalty
|
|
18
|
+
- 🔄 **Composable** - Combine multiple scopes intelligently
|
|
19
|
+
- 📦 **Easy Migration** - Familiar API for Sequelize users
|
|
20
|
+
- ✨ **Type-Safe Scope Names** - IDE autocomplete and compile-time validation
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install typeorm-query-scopes@beta
|
|
26
|
+
# or
|
|
27
|
+
yarn add typeorm-query-scopes@beta
|
|
28
|
+
# or
|
|
29
|
+
pnpm add typeorm-query-scopes@beta
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Make sure you have TypeORM installed as a peer dependency:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install typeorm reflect-metadata
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Enable decorators in your `tsconfig.json`:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"compilerOptions": {
|
|
43
|
+
"experimentalDecorators": true,
|
|
44
|
+
"emitDecoratorMetadata": true
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- 🎯 **Decorator-based scope definitions** - Define scopes directly on your entities
|
|
52
|
+
- 🔄 **Default scopes** - Automatically applied to all queries
|
|
53
|
+
- 🔗 **Scope merging** - Combine multiple scopes intelligently
|
|
54
|
+
- 📦 **Function scopes** - Dynamic scopes with parameters
|
|
55
|
+
- 🎨 **TypeScript support** - Full type safety
|
|
56
|
+
- ⚡ **Easy to use** - Similar API to Sequelize scopes
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
👉 **[Quick Start Guide](./QUICK_START.md)** - Get started in 5 minutes!
|
|
61
|
+
|
|
62
|
+
### 1. Define Scopes on Your Entity
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
|
66
|
+
import { Scopes, DefaultScope } from 'typeorm-query-scopes';
|
|
67
|
+
|
|
68
|
+
@DefaultScope<User>({
|
|
69
|
+
where: { isActive: true }
|
|
70
|
+
})
|
|
71
|
+
@Scopes<User>({
|
|
72
|
+
// Simple scope with where condition
|
|
73
|
+
verified: {
|
|
74
|
+
where: { isVerified: true }
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Scope with relations
|
|
78
|
+
withPosts: {
|
|
79
|
+
relations: { posts: true }
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// Scope with ordering
|
|
83
|
+
newest: {
|
|
84
|
+
order: { createdAt: 'DESC' }
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// Function scope with parameters
|
|
88
|
+
byRole: (role: string) => ({
|
|
89
|
+
where: { role }
|
|
90
|
+
}),
|
|
91
|
+
|
|
92
|
+
// Complex scope
|
|
93
|
+
premium: {
|
|
94
|
+
where: {
|
|
95
|
+
subscriptionType: 'premium',
|
|
96
|
+
subscriptionExpiry: MoreThan(new Date())
|
|
97
|
+
},
|
|
98
|
+
relations: { subscription: true }
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
@Entity()
|
|
102
|
+
export class User {
|
|
103
|
+
@PrimaryGeneratedColumn()
|
|
104
|
+
id: number;
|
|
105
|
+
|
|
106
|
+
@Column()
|
|
107
|
+
email: string;
|
|
108
|
+
|
|
109
|
+
@Column()
|
|
110
|
+
role: string;
|
|
111
|
+
|
|
112
|
+
@Column({ default: true })
|
|
113
|
+
isActive: boolean;
|
|
114
|
+
|
|
115
|
+
@Column({ default: false })
|
|
116
|
+
isVerified: boolean;
|
|
117
|
+
|
|
118
|
+
@Column()
|
|
119
|
+
createdAt: Date;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> 💡 **Type-Safe Scopes**: By adding the second generic parameter with scope names, you get IDE autocomplete and compile-time validation when calling `.scope("scopeName")`!
|
|
124
|
+
|
|
125
|
+
### 2. Use Scopes in Your Queries
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { getScopedRepository } from 'typeorm-query-scopes';
|
|
129
|
+
import { dataSource } from './data-source';
|
|
130
|
+
import { User } from './entities/User';
|
|
131
|
+
|
|
132
|
+
// Create a scoped repository
|
|
133
|
+
const userRepo = getScopedRepository(User, dataSource);
|
|
134
|
+
|
|
135
|
+
// Apply single scope
|
|
136
|
+
const verifiedUsers = await userRepo.scope('verified').find();
|
|
137
|
+
|
|
138
|
+
// Apply multiple scopes
|
|
139
|
+
const verifiedWithPosts = await userRepo
|
|
140
|
+
.scope('verified', 'withPosts')
|
|
141
|
+
.find();
|
|
142
|
+
|
|
143
|
+
// Apply function scope with parameters
|
|
144
|
+
const admins = await userRepo
|
|
145
|
+
.scope({ method: ['byRole', 'admin'] })
|
|
146
|
+
.find();
|
|
147
|
+
|
|
148
|
+
// Combine scopes and additional options
|
|
149
|
+
const recentAdmins = await userRepo
|
|
150
|
+
.scope('newest', { method: ['byRole', 'admin'] })
|
|
151
|
+
.find({ take: 10 });
|
|
152
|
+
|
|
153
|
+
// Remove default scope
|
|
154
|
+
const allUsers = await userRepo.unscoped().find();
|
|
155
|
+
|
|
156
|
+
// Apply default scope explicitly with other scopes
|
|
157
|
+
const activeVerified = await userRepo
|
|
158
|
+
.scope('defaultScope', 'verified')
|
|
159
|
+
.find();
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## API Reference
|
|
163
|
+
|
|
164
|
+
### Type-Safe Scope Names
|
|
165
|
+
|
|
166
|
+
For better IDE support and compile-time safety, you can define scope names as types:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
@Scopes<User, {
|
|
170
|
+
active: any;
|
|
171
|
+
verified: any;
|
|
172
|
+
byRole: any;
|
|
173
|
+
}>({
|
|
174
|
+
active: { where: { isActive: true } },
|
|
175
|
+
verified: { where: { isVerified: true } },
|
|
176
|
+
byRole: (role: string) => ({ where: { role } })
|
|
177
|
+
})
|
|
178
|
+
@Entity()
|
|
179
|
+
class User { ... }
|
|
180
|
+
|
|
181
|
+
// Now TypeScript knows the available scope names!
|
|
182
|
+
const repo = getScopedRepository(User, dataSource);
|
|
183
|
+
|
|
184
|
+
// ✅ TypeScript autocompletes: 'active' | 'verified' | 'byRole'
|
|
185
|
+
repo.scope('active') // IDE shows autocomplete!
|
|
186
|
+
|
|
187
|
+
// ❌ TypeScript error: 'invalid' is not a valid scope name
|
|
188
|
+
repo.scope('invalid') // Compile error!
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Benefits:**
|
|
192
|
+
- IDE autocomplete for scope names
|
|
193
|
+
- Compile-time error checking
|
|
194
|
+
- Better refactoring support
|
|
195
|
+
- Self-documenting code
|
|
196
|
+
|
|
197
|
+
### Decorators
|
|
198
|
+
|
|
199
|
+
#### `@Scopes<Entity>(scopes)`
|
|
200
|
+
|
|
201
|
+
Define named scopes on an entity.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
@Scopes<User>({
|
|
205
|
+
scopeName: { where: { field: value } },
|
|
206
|
+
dynamicScope: (param) => ({ where: { field: param } })
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### `@DefaultScope<Entity>(scope)`
|
|
211
|
+
|
|
212
|
+
Define a default scope that's automatically applied to all queries.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
@DefaultScope<User>({
|
|
216
|
+
where: { isActive: true }
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### ScopedRepository Methods
|
|
221
|
+
|
|
222
|
+
#### `scope(...scopeNames)`
|
|
223
|
+
|
|
224
|
+
Apply one or more scopes to the repository.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// Single scope
|
|
228
|
+
repo.scope('active')
|
|
229
|
+
|
|
230
|
+
// Multiple scopes
|
|
231
|
+
repo.scope('active', 'verified')
|
|
232
|
+
|
|
233
|
+
// Function scope with parameters
|
|
234
|
+
repo.scope({ method: ['byRole', 'admin'] })
|
|
235
|
+
|
|
236
|
+
// Include default scope explicitly
|
|
237
|
+
repo.scope('defaultScope', 'verified')
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### `unscoped()`
|
|
241
|
+
|
|
242
|
+
Remove all scopes including the default scope.
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
repo.unscoped().find()
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### `find(options?)`
|
|
249
|
+
|
|
250
|
+
Find entities with applied scopes.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
await repo.scope('active').find({ take: 10 })
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### `findOne(options)`
|
|
257
|
+
|
|
258
|
+
Find one entity with applied scopes.
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
await repo.scope('active').findOne({ where: { id: 1 } })
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### `findOneBy(where)`
|
|
265
|
+
|
|
266
|
+
Find one entity by conditions with applied scopes.
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
await repo.scope('active').findOneBy({ email: 'user@example.com' })
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### `count(options?)`
|
|
273
|
+
|
|
274
|
+
Count entities with applied scopes.
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
await repo.scope('active').count()
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### `findAndCount(options?)`
|
|
281
|
+
|
|
282
|
+
Find and count entities with applied scopes.
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const [users, total] = await repo.scope('active').findAndCount({ take: 10 })
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Scope Options
|
|
289
|
+
|
|
290
|
+
Scopes support the following TypeORM find options:
|
|
291
|
+
|
|
292
|
+
- `where` - Filter conditions (merged with AND logic)
|
|
293
|
+
- `relations` - Relations to load
|
|
294
|
+
- `order` - Sorting options
|
|
295
|
+
- `select` - Fields to select
|
|
296
|
+
- `skip` - Number of records to skip
|
|
297
|
+
- `take` - Number of records to take
|
|
298
|
+
- `cache` - Query caching options
|
|
299
|
+
|
|
300
|
+
## Scope Merging
|
|
301
|
+
|
|
302
|
+
When multiple scopes are applied, they are merged intelligently:
|
|
303
|
+
|
|
304
|
+
- **where**: Merged using AND logic
|
|
305
|
+
- **relations**: Combined (all relations loaded)
|
|
306
|
+
- **order**: Later scopes override earlier ones
|
|
307
|
+
- **select**: Combined (union of all fields)
|
|
308
|
+
- **skip/take/cache**: Last scope wins
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
@Scopes<User>({
|
|
312
|
+
scope1: {
|
|
313
|
+
where: { isActive: true },
|
|
314
|
+
order: { createdAt: 'DESC' },
|
|
315
|
+
take: 10
|
|
316
|
+
},
|
|
317
|
+
scope2: {
|
|
318
|
+
where: { isVerified: true },
|
|
319
|
+
take: 20
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
// Applying both scopes results in:
|
|
324
|
+
// where: { isActive: true, isVerified: true }
|
|
325
|
+
// order: { createdAt: 'DESC' }
|
|
326
|
+
// take: 20 (scope2 overrides scope1)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Advanced Examples
|
|
330
|
+
|
|
331
|
+
### Scope with Complex Conditions
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { MoreThan, LessThan, In } from 'typeorm';
|
|
335
|
+
|
|
336
|
+
@Scopes<Product>({
|
|
337
|
+
inStock: {
|
|
338
|
+
where: { quantity: MoreThan(0) }
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
inPriceRange: (min: number, max: number) => ({
|
|
342
|
+
where: {
|
|
343
|
+
price: MoreThan(min),
|
|
344
|
+
price: LessThan(max)
|
|
345
|
+
}
|
|
346
|
+
}),
|
|
347
|
+
|
|
348
|
+
byCategories: (categories: string[]) => ({
|
|
349
|
+
where: { category: In(categories) }
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Scope with Nested Relations
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
@Scopes<Post>({
|
|
358
|
+
withAuthorAndComments: {
|
|
359
|
+
relations: {
|
|
360
|
+
author: true,
|
|
361
|
+
comments: {
|
|
362
|
+
user: true
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
published: {
|
|
368
|
+
where: {
|
|
369
|
+
status: 'published',
|
|
370
|
+
publishedAt: LessThan(new Date())
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
})
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Reusable Scope Repository
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// Create a service with scoped repository
|
|
380
|
+
export class UserService {
|
|
381
|
+
private userRepo: ScopedRepository<User>;
|
|
382
|
+
|
|
383
|
+
constructor(private dataSource: DataSource) {
|
|
384
|
+
this.userRepo = getScopedRepository(User, dataSource);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async getActiveUsers() {
|
|
388
|
+
return this.userRepo.scope('active').find();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async getAdmins() {
|
|
392
|
+
return this.userRepo
|
|
393
|
+
.scope({ method: ['byRole', 'admin'] })
|
|
394
|
+
.find();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async getAllUsersIncludingInactive() {
|
|
398
|
+
return this.userRepo.unscoped().find();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Comparison with Sequelize
|
|
404
|
+
|
|
405
|
+
This package brings Sequelize-style scopes to TypeORM:
|
|
406
|
+
|
|
407
|
+
| Feature | Sequelize | TypeORM Scopes |
|
|
408
|
+
|---------|-----------|----------------|
|
|
409
|
+
| Default scope | ✅ | ✅ |
|
|
410
|
+
| Named scopes | ✅ | ✅ |
|
|
411
|
+
| Function scopes | ✅ | ✅ |
|
|
412
|
+
| Scope merging | ✅ | ✅ |
|
|
413
|
+
| Unscoped queries | ✅ | ✅ |
|
|
414
|
+
| Decorator-based | ❌ | ✅ |
|
|
415
|
+
|
|
416
|
+
## Documentation
|
|
417
|
+
|
|
418
|
+
- [Type-Safe Scopes Guide](./TYPE_SAFE_SCOPES.md) - Complete guide to type-safe scope names
|
|
419
|
+
- [Migration from Sequelize](./MIGRATION_FROM_SEQUELIZE.md) - Guide for migrating from Sequelize scopes
|
|
420
|
+
- [Basic Usage Example](./examples/basic-usage.ts) - Simple examples to get started
|
|
421
|
+
- [Advanced Usage Example](./examples/advanced-usage.ts) - Complex scope patterns
|
|
422
|
+
- [Type-Safe Scopes Example](./examples/type-safe-scopes.ts) - Type-safe scope demonstration
|
|
423
|
+
- [Real-world Example](./examples/real-world-example.ts) - Complete blog application example
|
|
424
|
+
- [Contributing Guide](./CONTRIBUTING.md) - How to contribute to the project
|
|
425
|
+
- [Changelog](./CHANGELOG.md) - Version history and changes
|
|
426
|
+
|
|
427
|
+
## Roadmap
|
|
428
|
+
|
|
429
|
+
Future enhancements being considered:
|
|
430
|
+
|
|
431
|
+
- [ ] Support for `update()` and `delete()` operations with scopes
|
|
432
|
+
- [ ] Scope inheritance for entity inheritance
|
|
433
|
+
- [ ] Query builder integration
|
|
434
|
+
- [ ] Performance optimizations
|
|
435
|
+
- [ ] Additional scope merging strategies
|
|
436
|
+
- [ ] Scope validation and debugging tools
|
|
437
|
+
|
|
438
|
+
## FAQ
|
|
439
|
+
|
|
440
|
+
### Q: Can I use this with NestJS?
|
|
441
|
+
|
|
442
|
+
Yes! TypeORM Scopes works perfectly with NestJS. Just inject the DataSource and create scoped repositories:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
@Injectable()
|
|
446
|
+
export class UserService {
|
|
447
|
+
private userRepo: ScopedRepository<User>;
|
|
448
|
+
|
|
449
|
+
constructor(private dataSource: DataSource) {
|
|
450
|
+
this.userRepo = getScopedRepository(User, dataSource);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async getActiveUsers() {
|
|
454
|
+
return this.userRepo.scope('active').find();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Q: Does this work with MongoDB?
|
|
460
|
+
|
|
461
|
+
Yes, TypeORM Scopes works with any database supported by TypeORM, including MongoDB.
|
|
462
|
+
|
|
463
|
+
### Q: Can I combine scopes with QueryBuilder?
|
|
464
|
+
|
|
465
|
+
Currently, scopes work with the repository pattern. QueryBuilder integration is planned for a future release.
|
|
466
|
+
|
|
467
|
+
### Q: How does performance compare to regular TypeORM queries?
|
|
468
|
+
|
|
469
|
+
Scopes add minimal overhead - they simply merge options before executing the query. The actual database query performance is identical to regular TypeORM queries.
|
|
470
|
+
|
|
471
|
+
## License
|
|
472
|
+
|
|
473
|
+
MIT - see [LICENSE](./LICENSE) file for details
|
|
474
|
+
|
|
475
|
+
## Contributing
|
|
476
|
+
|
|
477
|
+
Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
478
|
+
|
|
479
|
+
## Support
|
|
480
|
+
|
|
481
|
+
- 📖 [Documentation](./README.md)
|
|
482
|
+
- 🐛 [Issue Tracker](https://github.com/yourusername/typeorm-query-scopes/issues)
|
|
483
|
+
- 💬 [Discussions](https://github.com/yourusername/typeorm-query-scopes/discussions)
|
|
484
|
+
|
|
485
|
+
## Acknowledgments
|
|
486
|
+
|
|
487
|
+
Inspired by [Sequelize scopes](https://sequelize.org/docs/v7/other-topics/scopes/) - bringing the same powerful pattern to TypeORM.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ScopeDefinition, ScopeOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Decorator to define scopes on an entity with type-safe scope names
|
|
4
|
+
* @example
|
|
5
|
+
* // Without type-safe names:
|
|
6
|
+
* @Scopes<User>({
|
|
7
|
+
* active: { where: { isActive: true } }
|
|
8
|
+
* })
|
|
9
|
+
*
|
|
10
|
+
* // With type-safe names:
|
|
11
|
+
* @Scopes<User, { active: any; verified: any }>({
|
|
12
|
+
* active: { where: { isActive: true } },
|
|
13
|
+
* verified: { where: { isVerified: true } }
|
|
14
|
+
* })
|
|
15
|
+
* @Entity()
|
|
16
|
+
* class User { ... }
|
|
17
|
+
*/
|
|
18
|
+
export declare function Scopes<T, S extends Record<string, ScopeDefinition<T>> = Record<string, ScopeDefinition<T>>>(scopes: S): <C extends new (...args: any[]) => T>(target: C) => C & {
|
|
19
|
+
__scopeNames?: keyof S;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Decorator to define a default scope on an entity
|
|
23
|
+
* @example
|
|
24
|
+
* @DefaultScope<User>({ where: { isActive: true } })
|
|
25
|
+
* @Entity()
|
|
26
|
+
* class User { ... }
|
|
27
|
+
*/
|
|
28
|
+
export declare function DefaultScope<T>(scope: ScopeOptions<T>): (target: Function) => void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Scopes = Scopes;
|
|
4
|
+
exports.DefaultScope = DefaultScope;
|
|
5
|
+
const metadata_1 = require("./metadata");
|
|
6
|
+
/**
|
|
7
|
+
* Decorator to define scopes on an entity with type-safe scope names
|
|
8
|
+
* @example
|
|
9
|
+
* // Without type-safe names:
|
|
10
|
+
* @Scopes<User>({
|
|
11
|
+
* active: { where: { isActive: true } }
|
|
12
|
+
* })
|
|
13
|
+
*
|
|
14
|
+
* // With type-safe names:
|
|
15
|
+
* @Scopes<User, { active: any; verified: any }>({
|
|
16
|
+
* active: { where: { isActive: true } },
|
|
17
|
+
* verified: { where: { isVerified: true } }
|
|
18
|
+
* })
|
|
19
|
+
* @Entity()
|
|
20
|
+
* class User { ... }
|
|
21
|
+
*/
|
|
22
|
+
function Scopes(scopes) {
|
|
23
|
+
return function (target) {
|
|
24
|
+
Object.entries(scopes).forEach(([name, scope]) => {
|
|
25
|
+
metadata_1.ScopeMetadataStorage.addScope(target, name, scope);
|
|
26
|
+
});
|
|
27
|
+
return target;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Decorator to define a default scope on an entity
|
|
32
|
+
* @example
|
|
33
|
+
* @DefaultScope<User>({ where: { isActive: true } })
|
|
34
|
+
* @Entity()
|
|
35
|
+
* class User { ... }
|
|
36
|
+
*/
|
|
37
|
+
function DefaultScope(scope) {
|
|
38
|
+
return function (target) {
|
|
39
|
+
metadata_1.ScopeMetadataStorage.setDefaultScope(target, scope);
|
|
40
|
+
};
|
|
41
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { Scopes, DefaultScope } from './decorators';
|
|
2
|
+
export { ScopedRepository } from './scoped-repository';
|
|
3
|
+
export { ScopeMetadataStorage } from './metadata';
|
|
4
|
+
export { ScopeMerger } from './scope-merger';
|
|
5
|
+
export type { ScopeOptions, ScopeFunction, ScopeDefinition, ScopeMetadata, ScopeName, ExtractScopeNames } from './types';
|
|
6
|
+
import { DataSource, ObjectLiteral } from 'typeorm';
|
|
7
|
+
import { ScopedRepository } from './scoped-repository';
|
|
8
|
+
/**
|
|
9
|
+
* Create a scoped repository for an entity
|
|
10
|
+
* @example
|
|
11
|
+
* const userRepo = getScopedRepository(User, dataSource);
|
|
12
|
+
* const activeUsers = await userRepo.scope('active').find();
|
|
13
|
+
*/
|
|
14
|
+
export declare function getScopedRepository<Entity extends ObjectLiteral, EntityClass extends new (...args: any[]) => Entity = any>(entity: EntityClass, dataSource: DataSource): ScopedRepository<Entity, EntityClass>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScopeMerger = exports.ScopeMetadataStorage = exports.ScopedRepository = exports.DefaultScope = exports.Scopes = void 0;
|
|
4
|
+
exports.getScopedRepository = getScopedRepository;
|
|
5
|
+
var decorators_1 = require("./decorators");
|
|
6
|
+
Object.defineProperty(exports, "Scopes", { enumerable: true, get: function () { return decorators_1.Scopes; } });
|
|
7
|
+
Object.defineProperty(exports, "DefaultScope", { enumerable: true, get: function () { return decorators_1.DefaultScope; } });
|
|
8
|
+
var scoped_repository_1 = require("./scoped-repository");
|
|
9
|
+
Object.defineProperty(exports, "ScopedRepository", { enumerable: true, get: function () { return scoped_repository_1.ScopedRepository; } });
|
|
10
|
+
var metadata_1 = require("./metadata");
|
|
11
|
+
Object.defineProperty(exports, "ScopeMetadataStorage", { enumerable: true, get: function () { return metadata_1.ScopeMetadataStorage; } });
|
|
12
|
+
var scope_merger_1 = require("./scope-merger");
|
|
13
|
+
Object.defineProperty(exports, "ScopeMerger", { enumerable: true, get: function () { return scope_merger_1.ScopeMerger; } });
|
|
14
|
+
const scoped_repository_2 = require("./scoped-repository");
|
|
15
|
+
/**
|
|
16
|
+
* Create a scoped repository for an entity
|
|
17
|
+
* @example
|
|
18
|
+
* const userRepo = getScopedRepository(User, dataSource);
|
|
19
|
+
* const activeUsers = await userRepo.scope('active').find();
|
|
20
|
+
*/
|
|
21
|
+
function getScopedRepository(entity, dataSource) {
|
|
22
|
+
return new scoped_repository_2.ScopedRepository(entity, dataSource);
|
|
23
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ScopeMetadata } from './types';
|
|
2
|
+
export declare class ScopeMetadataStorage {
|
|
3
|
+
private static storage;
|
|
4
|
+
static getMetadata<T>(target: Function): ScopeMetadata<T>;
|
|
5
|
+
static setDefaultScope<T>(target: Function, scope: any): void;
|
|
6
|
+
static addScope<T>(target: Function, name: string, scope: any): void;
|
|
7
|
+
}
|
package/dist/metadata.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScopeMetadataStorage = void 0;
|
|
4
|
+
const SCOPE_METADATA_KEY = Symbol('typeorm:scopes');
|
|
5
|
+
class ScopeMetadataStorage {
|
|
6
|
+
static getMetadata(target) {
|
|
7
|
+
if (!this.storage.has(target)) {
|
|
8
|
+
this.storage.set(target, {
|
|
9
|
+
scopes: new Map(),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return this.storage.get(target);
|
|
13
|
+
}
|
|
14
|
+
static setDefaultScope(target, scope) {
|
|
15
|
+
const metadata = this.getMetadata(target);
|
|
16
|
+
metadata.defaultScope = scope;
|
|
17
|
+
}
|
|
18
|
+
static addScope(target, name, scope) {
|
|
19
|
+
const metadata = this.getMetadata(target);
|
|
20
|
+
metadata.scopes.set(name, scope);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.ScopeMetadataStorage = ScopeMetadataStorage;
|
|
24
|
+
ScopeMetadataStorage.storage = new Map();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ScopeOptions } from './types';
|
|
2
|
+
export declare class ScopeMerger {
|
|
3
|
+
/**
|
|
4
|
+
* Merges multiple scope options into a single options object
|
|
5
|
+
* Similar to Sequelize's scope merging behavior
|
|
6
|
+
*/
|
|
7
|
+
static merge<T>(...scopes: ScopeOptions<T>[]): ScopeOptions<T>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScopeMerger = void 0;
|
|
4
|
+
class ScopeMerger {
|
|
5
|
+
/**
|
|
6
|
+
* Merges multiple scope options into a single options object
|
|
7
|
+
* Similar to Sequelize's scope merging behavior
|
|
8
|
+
*/
|
|
9
|
+
static merge(...scopes) {
|
|
10
|
+
const result = {};
|
|
11
|
+
for (const scope of scopes) {
|
|
12
|
+
// Merge where conditions using AND logic
|
|
13
|
+
if (scope.where) {
|
|
14
|
+
if (!result.where) {
|
|
15
|
+
result.where = Array.isArray(scope.where) ? [...scope.where] : { ...scope.where };
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
// Merge where conditions
|
|
19
|
+
if (Array.isArray(result.where) && Array.isArray(scope.where)) {
|
|
20
|
+
result.where = [...result.where, ...scope.where];
|
|
21
|
+
}
|
|
22
|
+
else if (Array.isArray(result.where)) {
|
|
23
|
+
result.where = [...result.where, scope.where];
|
|
24
|
+
}
|
|
25
|
+
else if (Array.isArray(scope.where)) {
|
|
26
|
+
result.where = [result.where, ...scope.where];
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
result.where = { ...result.where, ...scope.where };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Merge relations
|
|
34
|
+
if (scope.relations) {
|
|
35
|
+
result.relations = {
|
|
36
|
+
...(result.relations || {}),
|
|
37
|
+
...scope.relations,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Merge order (later scopes override)
|
|
41
|
+
if (scope.order) {
|
|
42
|
+
result.order = {
|
|
43
|
+
...(result.order || {}),
|
|
44
|
+
...scope.order,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Merge select (combine unique fields)
|
|
48
|
+
if (scope.select) {
|
|
49
|
+
if (!result.select) {
|
|
50
|
+
result.select = [...scope.select];
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const combined = new Set([...result.select, ...scope.select]);
|
|
54
|
+
result.select = Array.from(combined);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Override scalar values (last scope wins)
|
|
58
|
+
if (scope.skip !== undefined)
|
|
59
|
+
result.skip = scope.skip;
|
|
60
|
+
if (scope.take !== undefined)
|
|
61
|
+
result.take = scope.take;
|
|
62
|
+
if (scope.cache !== undefined)
|
|
63
|
+
result.cache = scope.cache;
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.ScopeMerger = ScopeMerger;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { FindManyOptions, FindOneOptions, ObjectLiteral, EntityTarget, DataSource } from 'typeorm';
|
|
2
|
+
type ExtractScopeNames<T> = T extends {
|
|
3
|
+
__scopeNames?: infer S;
|
|
4
|
+
} ? S : string;
|
|
5
|
+
export declare class ScopedRepository<Entity extends ObjectLiteral, EntityClass = any> {
|
|
6
|
+
private target;
|
|
7
|
+
private dataSource;
|
|
8
|
+
private appliedScopes;
|
|
9
|
+
private includeDefaultScope;
|
|
10
|
+
constructor(target: EntityTarget<Entity>, dataSource: DataSource);
|
|
11
|
+
private get repository();
|
|
12
|
+
/**
|
|
13
|
+
* Apply one or more scopes to the repository
|
|
14
|
+
* @example
|
|
15
|
+
* userRepo.scope('active', 'withPosts').find()
|
|
16
|
+
* userRepo.scope({ method: ['byRole', 'admin'] }).find()
|
|
17
|
+
*/
|
|
18
|
+
scope(...scopeNames: (ExtractScopeNames<EntityClass> | {
|
|
19
|
+
method: [ExtractScopeNames<EntityClass>, ...any[]];
|
|
20
|
+
} | null)[]): ScopedRepository<Entity, EntityClass>;
|
|
21
|
+
/**
|
|
22
|
+
* Remove the default scope
|
|
23
|
+
* @example
|
|
24
|
+
* userRepo.unscoped().find()
|
|
25
|
+
*/
|
|
26
|
+
unscoped(): ScopedRepository<Entity, EntityClass>;
|
|
27
|
+
/**
|
|
28
|
+
* Get merged find options with all applied scopes
|
|
29
|
+
*/
|
|
30
|
+
private getMergedOptions;
|
|
31
|
+
/**
|
|
32
|
+
* Find entities with applied scopes
|
|
33
|
+
*/
|
|
34
|
+
find(options?: FindManyOptions<Entity>): Promise<Entity[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Find one entity with applied scopes
|
|
37
|
+
*/
|
|
38
|
+
findOne(options: FindOneOptions<Entity>): Promise<Entity | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Find one entity by ID with applied scopes
|
|
41
|
+
*/
|
|
42
|
+
findOneBy(where: any): Promise<Entity | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Count entities with applied scopes
|
|
45
|
+
*/
|
|
46
|
+
count(options?: FindManyOptions<Entity>): Promise<number>;
|
|
47
|
+
/**
|
|
48
|
+
* Find and count entities with applied scopes
|
|
49
|
+
*/
|
|
50
|
+
findAndCount(options?: FindManyOptions<Entity>): Promise<[Entity[], number]>;
|
|
51
|
+
private clone;
|
|
52
|
+
private getEntityConstructor;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScopedRepository = void 0;
|
|
4
|
+
const metadata_1 = require("./metadata");
|
|
5
|
+
const scope_merger_1 = require("./scope-merger");
|
|
6
|
+
class ScopedRepository {
|
|
7
|
+
constructor(target, dataSource) {
|
|
8
|
+
this.target = target;
|
|
9
|
+
this.dataSource = dataSource;
|
|
10
|
+
this.appliedScopes = [];
|
|
11
|
+
this.includeDefaultScope = true;
|
|
12
|
+
}
|
|
13
|
+
get repository() {
|
|
14
|
+
return this.dataSource.getRepository(this.target);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Apply one or more scopes to the repository
|
|
18
|
+
* @example
|
|
19
|
+
* userRepo.scope('active', 'withPosts').find()
|
|
20
|
+
* userRepo.scope({ method: ['byRole', 'admin'] }).find()
|
|
21
|
+
*/
|
|
22
|
+
scope(...scopeNames) {
|
|
23
|
+
const cloned = this.clone();
|
|
24
|
+
const metadata = metadata_1.ScopeMetadataStorage.getMetadata(this.getEntityConstructor());
|
|
25
|
+
for (const scopeName of scopeNames) {
|
|
26
|
+
if (scopeName === null || scopeName === 'defaultScope') {
|
|
27
|
+
cloned.includeDefaultScope = true;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (typeof scopeName === 'string') {
|
|
31
|
+
const scopeDef = metadata.scopes.get(scopeName);
|
|
32
|
+
if (!scopeDef) {
|
|
33
|
+
throw new Error(`Scope "${scopeName}" not found on entity`);
|
|
34
|
+
}
|
|
35
|
+
const scopeOptions = typeof scopeDef === 'function' ? scopeDef() : scopeDef;
|
|
36
|
+
cloned.appliedScopes.push(scopeOptions);
|
|
37
|
+
}
|
|
38
|
+
else if (typeof scopeName === 'object' && 'method' in scopeName && scopeName.method) {
|
|
39
|
+
const [name, ...args] = scopeName.method;
|
|
40
|
+
const scopeDef = metadata.scopes.get(name);
|
|
41
|
+
if (!scopeDef) {
|
|
42
|
+
throw new Error(`Scope "${String(name)}" not found on entity`);
|
|
43
|
+
}
|
|
44
|
+
if (typeof scopeDef !== 'function') {
|
|
45
|
+
throw new Error(`Scope "${String(name)}" is not a function`);
|
|
46
|
+
}
|
|
47
|
+
const scopeOptions = scopeDef(...args);
|
|
48
|
+
cloned.appliedScopes.push(scopeOptions);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return cloned;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Remove the default scope
|
|
55
|
+
* @example
|
|
56
|
+
* userRepo.unscoped().find()
|
|
57
|
+
*/
|
|
58
|
+
unscoped() {
|
|
59
|
+
const cloned = this.clone();
|
|
60
|
+
cloned.includeDefaultScope = false;
|
|
61
|
+
cloned.appliedScopes = [];
|
|
62
|
+
return cloned;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get merged find options with all applied scopes
|
|
66
|
+
*/
|
|
67
|
+
getMergedOptions(options = {}) {
|
|
68
|
+
const scopesToApply = [];
|
|
69
|
+
// Add default scope if enabled
|
|
70
|
+
if (this.includeDefaultScope) {
|
|
71
|
+
const metadata = metadata_1.ScopeMetadataStorage.getMetadata(this.getEntityConstructor());
|
|
72
|
+
if (metadata.defaultScope) {
|
|
73
|
+
scopesToApply.push(metadata.defaultScope);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Add applied scopes
|
|
77
|
+
scopesToApply.push(...this.appliedScopes);
|
|
78
|
+
// Add user-provided options
|
|
79
|
+
scopesToApply.push(options);
|
|
80
|
+
return scope_merger_1.ScopeMerger.merge(...scopesToApply);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Find entities with applied scopes
|
|
84
|
+
*/
|
|
85
|
+
async find(options) {
|
|
86
|
+
const mergedOptions = this.getMergedOptions(options);
|
|
87
|
+
return this.repository.find(mergedOptions);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Find one entity with applied scopes
|
|
91
|
+
*/
|
|
92
|
+
async findOne(options) {
|
|
93
|
+
const mergedOptions = this.getMergedOptions(options);
|
|
94
|
+
return this.repository.findOne(mergedOptions);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Find one entity by ID with applied scopes
|
|
98
|
+
*/
|
|
99
|
+
async findOneBy(where) {
|
|
100
|
+
return this.findOne({ where });
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Count entities with applied scopes
|
|
104
|
+
*/
|
|
105
|
+
async count(options) {
|
|
106
|
+
const mergedOptions = this.getMergedOptions(options);
|
|
107
|
+
return this.repository.count(mergedOptions);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Find and count entities with applied scopes
|
|
111
|
+
*/
|
|
112
|
+
async findAndCount(options) {
|
|
113
|
+
const mergedOptions = this.getMergedOptions(options);
|
|
114
|
+
return this.repository.findAndCount(mergedOptions);
|
|
115
|
+
}
|
|
116
|
+
clone() {
|
|
117
|
+
const cloned = new ScopedRepository(this.target, this.dataSource);
|
|
118
|
+
cloned.appliedScopes = [...this.appliedScopes];
|
|
119
|
+
cloned.includeDefaultScope = this.includeDefaultScope;
|
|
120
|
+
return cloned;
|
|
121
|
+
}
|
|
122
|
+
getEntityConstructor() {
|
|
123
|
+
if (typeof this.target === 'function') {
|
|
124
|
+
return this.target;
|
|
125
|
+
}
|
|
126
|
+
throw new Error('Entity target must be a constructor function');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.ScopedRepository = ScopedRepository;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FindOptionsWhere, FindOptionsOrder, FindOptionsRelations } from 'typeorm';
|
|
2
|
+
export type ScopeOptions<T> = {
|
|
3
|
+
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[];
|
|
4
|
+
relations?: FindOptionsRelations<T>;
|
|
5
|
+
order?: FindOptionsOrder<T>;
|
|
6
|
+
skip?: number;
|
|
7
|
+
take?: number;
|
|
8
|
+
select?: (keyof T)[];
|
|
9
|
+
cache?: boolean | number;
|
|
10
|
+
};
|
|
11
|
+
export type ScopeFunction<T> = (...args: any[]) => ScopeOptions<T>;
|
|
12
|
+
export type ScopeDefinition<T> = ScopeOptions<T> | ScopeFunction<T>;
|
|
13
|
+
export interface ScopeMetadata<T = any> {
|
|
14
|
+
defaultScope?: ScopeOptions<T>;
|
|
15
|
+
scopes: Map<string, ScopeDefinition<T>>;
|
|
16
|
+
}
|
|
17
|
+
export type ScopeName<T> = T extends {
|
|
18
|
+
__scopeNames?: infer S;
|
|
19
|
+
} ? S : string;
|
|
20
|
+
export type ExtractScopeNames<T> = T extends Record<infer K, any> ? K : never;
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "typeorm-query-scopes",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"description": "Sequelize-like scopes for TypeORM entities - Define reusable query filters with decorators",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "npx ts-node test/integration.test.ts",
|
|
10
|
+
"demo": "npx ts-node examples/demo.ts",
|
|
11
|
+
"docs:dev": "vitepress dev docs",
|
|
12
|
+
"docs:build": "vitepress build docs",
|
|
13
|
+
"docs:preview": "vitepress preview docs",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"typeorm",
|
|
18
|
+
"scopes",
|
|
19
|
+
"orm",
|
|
20
|
+
"database",
|
|
21
|
+
"query",
|
|
22
|
+
"decorator",
|
|
23
|
+
"typescript",
|
|
24
|
+
"sequelize",
|
|
25
|
+
"repository",
|
|
26
|
+
"filter",
|
|
27
|
+
"reusable-queries",
|
|
28
|
+
"type-safe"
|
|
29
|
+
],
|
|
30
|
+
"author": "aramyounis",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/aramyounis/typeorm-query-scopes.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/aramyounis/typeorm-query-scopes/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/aramyounis/typeorm-query-scopes#readme",
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"typeorm": "^0.3.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^20.0.0",
|
|
45
|
+
"better-sqlite3": "^12.6.2",
|
|
46
|
+
"sqlite3": "^5.1.7",
|
|
47
|
+
"typeorm": "^0.3.0",
|
|
48
|
+
"typescript": "^5.0.0",
|
|
49
|
+
"vitepress": "^1.6.4",
|
|
50
|
+
"vue": "^3.5.29"
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"dist",
|
|
54
|
+
"README.md",
|
|
55
|
+
"LICENSE",
|
|
56
|
+
"CHANGELOG.md",
|
|
57
|
+
"TYPE_SAFE_SCOPES.md",
|
|
58
|
+
"MIGRATION_FROM_SEQUELIZE.md"
|
|
59
|
+
],
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=16.0.0"
|
|
62
|
+
}
|
|
63
|
+
}
|