@rpal/orm 1.0.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 paljs
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.
@@ -0,0 +1,125 @@
1
+ import 'reflect-metadata';
2
+ import { Scope, DEFAULT_SCOPE } from './scope';
3
+ import { InjectionToken } from './token';
4
+ import { getInjectableMetadata, getConstructorParams } from './decorators';
5
+ export class PalCircularDependencyError extends Error {
6
+ constructor(path) {
7
+ super(`Circular dependency detected: ${path}`);
8
+ this.name = 'PalCircularDependencyError';
9
+ }
10
+ }
11
+ export class PalDependencyNotFoundError extends Error {
12
+ constructor(token, index, target) {
13
+ super(`Cannot resolve dependency "${getTokenName(token)}" at index ${index} in ${target.name || 'Unknown'} constructor`);
14
+ this.name = 'PalDependencyNotFoundError';
15
+ }
16
+ }
17
+ function getTokenName(token) {
18
+ if (token instanceof InjectionToken)
19
+ return token.name;
20
+ if (typeof token === 'string')
21
+ return token;
22
+ if (typeof token === 'symbol')
23
+ return String(token);
24
+ return token.name || 'Unknown';
25
+ }
26
+ export class PalContainer {
27
+ constructor(parent) {
28
+ this.registrations = new Map();
29
+ this.resolutionStack = [];
30
+ this.singletonCache = new Map();
31
+ this.parent = parent;
32
+ }
33
+ provide(token, provider) {
34
+ const reg = this.createRegistration(token, provider);
35
+ this.registrations.set(token, reg);
36
+ return this;
37
+ }
38
+ createRegistration(token, provider) {
39
+ const scope = provider.scope ?? DEFAULT_SCOPE;
40
+ if ('useValue' in provider) {
41
+ return { token, factory: () => provider.useValue, scope: Scope.Singleton, instance: provider.useValue };
42
+ }
43
+ if ('useClass' in provider) {
44
+ return { token, factory: (ctx) => this.resolveClass(provider.useClass, ctx), scope };
45
+ }
46
+ if ('useFactory' in provider) {
47
+ return { token, factory: (ctx) => provider.useFactory(ctx), scope };
48
+ }
49
+ throw new Error('Provider must have useValue, useClass, or useFactory');
50
+ }
51
+ get(token) {
52
+ if (this.resolutionStack.includes(token)) {
53
+ const path = [...this.resolutionStack, token].map(getTokenName).join(' -> ');
54
+ throw new PalCircularDependencyError(path);
55
+ }
56
+ const registration = this.findRegistration(token);
57
+ if (!registration) {
58
+ if (token instanceof Function && getInjectableMetadata(token)) {
59
+ return this.resolveClass(token, this);
60
+ }
61
+ throw new PalDependencyNotFoundError(token, 0, token);
62
+ }
63
+ return this.resolve(registration);
64
+ }
65
+ has(token) {
66
+ return this.findRegistration(token) !== undefined;
67
+ }
68
+ createChild() {
69
+ return new PalContainer(this);
70
+ }
71
+ override(token, provider) {
72
+ this.registrations.set(token, this.createRegistration(token, provider));
73
+ return this;
74
+ }
75
+ reset() {
76
+ this.registrations.clear();
77
+ this.singletonCache.clear();
78
+ }
79
+ findRegistration(token) {
80
+ return this.registrations.get(token) ?? this.parent?.findRegistration(token);
81
+ }
82
+ resolve(registration) {
83
+ if (registration.scope === Scope.Singleton && registration.instance !== undefined) {
84
+ return registration.instance;
85
+ }
86
+ if (registration.scope === Scope.Singleton) {
87
+ const instance = registration.factory(this);
88
+ registration.instance = instance;
89
+ return instance;
90
+ }
91
+ return registration.factory(this);
92
+ }
93
+ resolveClass(klass, ctx) {
94
+ this.resolutionStack.push(klass);
95
+ try {
96
+ const paramMeta = getConstructorParams(klass);
97
+ const args = paramMeta.map((param) => {
98
+ if (param.token && ctx.has(param.token)) {
99
+ return param.optional && !ctx.has(param.token) ? undefined : ctx.get(param.token);
100
+ }
101
+ if (ctx.has(param.type)) {
102
+ return ctx.get(param.type);
103
+ }
104
+ if (param.type === Object)
105
+ return undefined;
106
+ try {
107
+ return this.resolveClass(param.type, ctx);
108
+ }
109
+ catch {
110
+ return undefined;
111
+ }
112
+ });
113
+ return new klass(...args);
114
+ }
115
+ finally {
116
+ this.resolutionStack.pop();
117
+ }
118
+ }
119
+ }
120
+ export function createPal(parent) {
121
+ return new PalContainer(parent);
122
+ }
123
+ export const Pal = {
124
+ create: createPal,
125
+ };
@@ -0,0 +1,59 @@
1
+ import 'reflect-metadata';
2
+ import { DEFAULT_SCOPE } from './scope';
3
+ import { INJECTABLE_METADATA_KEY, INJECTABLE_CONSTRUCTOR_KEY } from './interface';
4
+ const paramsCache = new WeakMap();
5
+ export function Injectable(options) {
6
+ return (target) => {
7
+ Reflect.defineMetadata(INJECTABLE_METADATA_KEY, {
8
+ scope: options?.scope ?? DEFAULT_SCOPE,
9
+ token: options?.token,
10
+ }, target);
11
+ };
12
+ }
13
+ export function Inject(token, type, opts = {}) {
14
+ return (target, _key, index) => {
15
+ const inferredType = type || getParamType(target, index);
16
+ if (!inferredType) {
17
+ console.warn(`@Inject: Could not determine type for parameter ${index}. Provide it explicitly.`);
18
+ }
19
+ storeParamMeta(target, index, { type: inferredType, token, optional: opts.optional });
20
+ };
21
+ }
22
+ export function Optional() {
23
+ return (target, _key, index) => {
24
+ const type = getParamType(target, index);
25
+ const existing = getParamMeta(target, index);
26
+ storeParamMeta(target, index, { type, optional: true, token: existing?.token });
27
+ };
28
+ }
29
+ function getParamType(target, index) {
30
+ const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
31
+ return paramTypes[index];
32
+ }
33
+ function getParamMeta(target, index) {
34
+ const params = getConstructorParams(target);
35
+ return params[index];
36
+ }
37
+ function storeParamMeta(target, index, meta) {
38
+ const params = Reflect.getOwnMetadata(INJECTABLE_CONSTRUCTOR_KEY, target) || [];
39
+ params[index] = meta;
40
+ Reflect.defineMetadata(INJECTABLE_CONSTRUCTOR_KEY, params, target);
41
+ }
42
+ export function getInjectableMetadata(target) {
43
+ return Reflect.getOwnMetadata(INJECTABLE_METADATA_KEY, target);
44
+ }
45
+ export function getConstructorParams(target) {
46
+ const cached = paramsCache.get(target);
47
+ if (cached)
48
+ return cached;
49
+ const params = Reflect.getOwnMetadata(INJECTABLE_CONSTRUCTOR_KEY, target) || [];
50
+ if (!params.length) {
51
+ const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
52
+ return paramTypes.map((type, index) => ({ type }));
53
+ }
54
+ paramsCache.set(target, params);
55
+ return params;
56
+ }
57
+ export function getInjectionMetadata(target) {
58
+ return getConstructorParams(target).map(p => ({ token: p.token || p.type, optional: p.optional }));
59
+ }
@@ -0,0 +1,14 @@
1
+ export * from './scope';
2
+ export * from './token';
3
+ export * from './decorators';
4
+ export * from './interface';
5
+ export * from './container';
6
+ export * from './test-container';
7
+ import { Pal, PalContainer, createPal, PalCircularDependencyError, PalDependencyNotFoundError } from './container';
8
+ import { PalTestContainer } from './test-container';
9
+ export { Pal as default };
10
+ export const Container = PalContainer;
11
+ export const TestContainer = PalTestContainer;
12
+ export const createContainer = createPal;
13
+ export const CircularDependencyError = PalCircularDependencyError;
14
+ export const DependencyNotFoundError = PalDependencyNotFoundError;
@@ -0,0 +1,3 @@
1
+ export const INJECTABLE_METADATA_KEY = Symbol('injectable:metadata');
2
+ export const INJECT_METADATA_KEY = Symbol('inject:metadata');
3
+ export const INJECTABLE_CONSTRUCTOR_KEY = Symbol('injectable:constructor');
@@ -0,0 +1,6 @@
1
+ export const Scope = {
2
+ Singleton: 'singleton',
3
+ Transient: 'transient',
4
+ Scoped: 'scoped',
5
+ };
6
+ export const DEFAULT_SCOPE = Scope.Singleton;
@@ -0,0 +1,52 @@
1
+ import { PalContainer } from './container';
2
+ export class PalTestContainer extends PalContainer {
3
+ constructor() {
4
+ super(...arguments);
5
+ this.snapshots = [];
6
+ }
7
+ static create() {
8
+ return new PalTestContainer();
9
+ }
10
+ snapshot() {
11
+ this.snapshots.push({
12
+ registrations: new Map(this.getRegistrations()),
13
+ singletons: new Map(this.getSingletons()),
14
+ });
15
+ }
16
+ restore() {
17
+ const snap = this.snapshots.pop();
18
+ if (!snap)
19
+ return;
20
+ this.restoreFrom(snap);
21
+ }
22
+ reset() {
23
+ super.reset();
24
+ this.snapshots = [];
25
+ }
26
+ clearSnapshots() {
27
+ this.snapshots = [];
28
+ }
29
+ mock(token, mockValue) {
30
+ const mock = new Proxy({}, {
31
+ get(_, prop) {
32
+ if (prop === 'then' || prop === 'catch')
33
+ return undefined;
34
+ if (typeof prop === 'symbol')
35
+ return undefined;
36
+ return mockValue?.[prop] ?? (() => mock);
37
+ },
38
+ });
39
+ this.override(token, { useValue: mock });
40
+ return mock;
41
+ }
42
+ getRegistrations() {
43
+ return this.registrations;
44
+ }
45
+ getSingletons() {
46
+ return this.singletonCache;
47
+ }
48
+ restoreFrom(snap) {
49
+ this.registrations = snap.registrations;
50
+ this.singletonCache = snap.singletons;
51
+ }
52
+ }
@@ -0,0 +1,6 @@
1
+ export class InjectionToken {
2
+ constructor(name) {
3
+ this.name = name;
4
+ this.toString = () => `InjectionToken(${name})`;
5
+ }
6
+ }
package/dist/demo.js ADDED
@@ -0,0 +1,113 @@
1
+ import 'reflect-metadata';
2
+ import { Pal } from '@paljs/core';
3
+ import { UserRepositoryToken, registerRepositories, } from './index';
4
+ console.log('═══════════════════════════════════════════════════');
5
+ console.log(' @paljs/orm Demo ');
6
+ console.log('═══════════════════════════════════════════════════\n');
7
+ async function main() {
8
+ // ─────────────────────────────────────────────────────────────
9
+ // SETUP: Create container and register repositories
10
+ // ─────────────────────────────────────────────────────────────
11
+ console.log('🔧 Setting up DI container with repositories...\n');
12
+ const container = Pal.create();
13
+ registerRepositories(container);
14
+ const userRepo = container.get(UserRepositoryToken);
15
+ // ─────────────────────────────────────────────────────────────
16
+ // CREATE: Add some users
17
+ // ─────────────────────────────────────────────────────────────
18
+ console.log('📝 CREATE Users:');
19
+ const user1 = await userRepo.create({
20
+ name: 'John Doe',
21
+ email: 'john@example.com',
22
+ password: 'secret123',
23
+ role: 'admin',
24
+ status: 'active',
25
+ });
26
+ console.log('Created:', user1.name, '-', user1.email);
27
+ const user2 = await userRepo.create({
28
+ name: 'Jane Smith',
29
+ email: 'jane@example.com',
30
+ role: 'user',
31
+ status: 'active',
32
+ });
33
+ console.log('Created:', user2.name, '-', user2.email);
34
+ const user3 = await userRepo.create({
35
+ name: 'Bob Wilson',
36
+ email: 'bob@example.com',
37
+ role: 'guest',
38
+ status: 'pending',
39
+ });
40
+ console.log('Created:', user3.name, '-', user3.email);
41
+ // ─────────────────────────────────────────────────────────────
42
+ // READ: Query users
43
+ // ─────────────────────────────────────────────────────────────
44
+ console.log('\n📖 READ Users:');
45
+ const foundUser = await userRepo.findById(user1.id);
46
+ console.log('Find by ID:', foundUser?.name);
47
+ const byEmail = await userRepo.findByEmail('jane@example.com');
48
+ console.log('Find by email:', byEmail?.name);
49
+ console.log('Admins:', (await userRepo.findByRole('admin')).map((u) => u.name));
50
+ console.log('Users:', (await userRepo.findByRole('user')).map((u) => u.name));
51
+ console.log('Guests:', (await userRepo.findByRole('guest')).map((u) => u.name));
52
+ console.log('Active users:', (await userRepo.findActive()).map((u) => u.name));
53
+ console.log('\nPagination example:');
54
+ const page1 = await userRepo.findMany({ pagination: { skip: 0, take: 2 } });
55
+ console.log('Page 1 (2 items):', page1.data.map((u) => u.name));
56
+ console.log('Total count:', page1.total);
57
+ // ─────────────────────────────────────────────────────────────
58
+ // UPDATE: Modify users
59
+ // ─────────────────────────────────────────────────────────────
60
+ console.log('\n✏️ UPDATE Users:');
61
+ const updated = await userRepo.update(user3.id, {
62
+ status: 'active',
63
+ role: 'user'
64
+ });
65
+ console.log('Updated Bob:', updated?.name, '- Status:', updated?.status, '- Role:', updated?.role);
66
+ // ─────────────────────────────────────────────────────────────
67
+ // DELETE: Remove users
68
+ // ─────────────────────────────────────────────────────────────
69
+ console.log('\n🗑️ DELETE Users:');
70
+ const deleted = await userRepo.delete(user2.id);
71
+ console.log('Deleted Jane:', deleted ? 'YES' : 'NO');
72
+ const jane = await userRepo.findByEmail('jane@example.com');
73
+ console.log('Jane still exists:', jane ? 'YES (bug!)' : 'NO (correct)');
74
+ // ─────────────────────────────────────────────────────────────
75
+ // UTILITY: Count and exists
76
+ // ─────────────────────────────────────────────────────────────
77
+ console.log('\n📊 UTILITY Methods:');
78
+ console.log('Total users:', await userRepo.count());
79
+ console.log('Active users:', await userRepo.count({ status: 'active' }));
80
+ console.log('Email exists (john@example.com):', await userRepo.emailExists('john@example.com'));
81
+ console.log('Email exists (jane@example.com):', await userRepo.emailExists('jane@example.com'));
82
+ // ─────────────────────────────────────────────────────────────
83
+ // ADVANCED: Upsert
84
+ // ─────────────────────────────────────────────────────────────
85
+ console.log('\n🔄 UPSERT (Insert or Update):');
86
+ const newUserData = {
87
+ name: 'New User',
88
+ email: 'newuser@example.com',
89
+ role: 'user',
90
+ status: 'active',
91
+ };
92
+ await userRepo.upsert('custom-id', newUserData, { name: 'Updated User' });
93
+ const upserted = await userRepo.findById('custom-id');
94
+ console.log('Upserted:', upserted?.name, '- ID:', upserted?.id);
95
+ await userRepo.upsert('custom-id', newUserData, { name: 'Updated via Upsert' });
96
+ const updatedViaUpsert = await userRepo.findById('custom-id');
97
+ console.log('Updated via upsert:', updatedViaUpsert?.name);
98
+ // ─────────────────────────────────────────────────────────────
99
+ // Summary
100
+ // ─────────────────────────────────────────────────────────────
101
+ console.log('\n' + '═'.repeat(50));
102
+ console.log('✅ @paljs/orm Demo Complete!');
103
+ console.log('═'.repeat(50));
104
+ console.log('\nKey Concepts Demonstrated:');
105
+ console.log(' • Repository Pattern for data access');
106
+ console.log(' • BaseRepository with common CRUD operations');
107
+ console.log(' • Custom repository extensions (UserRepository)');
108
+ console.log(' • Pagination and sorting support');
109
+ console.log(' • Upsert operations');
110
+ console.log(' • DI integration with @paljs/core');
111
+ console.log('\n');
112
+ }
113
+ main().catch(console.error);
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ import 'reflect-metadata';
2
+ export * from './types';
3
+ export * from './repository';
4
+ export * from './user.repository';
5
+ import { InjectionToken, Scope } from '@paljs/core';
6
+ import { UserRepository } from './user.repository';
7
+ export const UserRepositoryToken = new InjectionToken('user-repository');
8
+ export const OrmConfigToken = new InjectionToken('orm-config');
9
+ export function registerRepositories(container) {
10
+ container.provide(UserRepositoryToken, {
11
+ useClass: UserRepository,
12
+ scope: Scope.Singleton,
13
+ });
14
+ }
@@ -0,0 +1,113 @@
1
+ import 'reflect-metadata';
2
+ import { Pal } from '@paljs/core';
3
+ import { UserRepositoryToken, registerRepositories, } from './index';
4
+ console.log('═══════════════════════════════════════════════════');
5
+ console.log(' @paljs/orm Demo ');
6
+ console.log('═══════════════════════════════════════════════════\n');
7
+ async function main() {
8
+ // ─────────────────────────────────────────────────────────────
9
+ // SETUP: Create container and register repositories
10
+ // ─────────────────────────────────────────────────────────────
11
+ console.log('🔧 Setting up DI container with repositories...\n');
12
+ const container = Pal.create();
13
+ registerRepositories(container);
14
+ const userRepo = container.get(UserRepositoryToken);
15
+ // ─────────────────────────────────────────────────────────────
16
+ // CREATE: Add some users
17
+ // ─────────────────────────────────────────────────────────────
18
+ console.log('📝 CREATE Users:');
19
+ const user1 = await userRepo.create({
20
+ name: 'John Doe',
21
+ email: 'john@example.com',
22
+ password: 'secret123',
23
+ role: 'admin',
24
+ status: 'active',
25
+ });
26
+ console.log('Created:', user1.name, '-', user1.email);
27
+ const user2 = await userRepo.create({
28
+ name: 'Jane Smith',
29
+ email: 'jane@example.com',
30
+ role: 'user',
31
+ status: 'active',
32
+ });
33
+ console.log('Created:', user2.name, '-', user2.email);
34
+ const user3 = await userRepo.create({
35
+ name: 'Bob Wilson',
36
+ email: 'bob@example.com',
37
+ role: 'guest',
38
+ status: 'pending',
39
+ });
40
+ console.log('Created:', user3.name, '-', user3.email);
41
+ // ─────────────────────────────────────────────────────────────
42
+ // READ: Query users
43
+ // ─────────────────────────────────────────────────────────────
44
+ console.log('\n📖 READ Users:');
45
+ const foundUser = await userRepo.findById(user1.id);
46
+ console.log('Find by ID:', foundUser?.name);
47
+ const byEmail = await userRepo.findByEmail('jane@example.com');
48
+ console.log('Find by email:', byEmail?.name);
49
+ console.log('Admins:', (await userRepo.findByRole('admin')).map((u) => u.name));
50
+ console.log('Users:', (await userRepo.findByRole('user')).map((u) => u.name));
51
+ console.log('Guests:', (await userRepo.findByRole('guest')).map((u) => u.name));
52
+ console.log('Active users:', (await userRepo.findActive()).map((u) => u.name));
53
+ console.log('\nPagination example:');
54
+ const page1 = await userRepo.findMany({ pagination: { skip: 0, take: 2 } });
55
+ console.log('Page 1 (2 items):', page1.data.map((u) => u.name));
56
+ console.log('Total count:', page1.total);
57
+ // ─────────────────────────────────────────────────────────────
58
+ // UPDATE: Modify users
59
+ // ─────────────────────────────────────────────────────────────
60
+ console.log('\n✏️ UPDATE Users:');
61
+ const updated = await userRepo.update(user3.id, {
62
+ status: 'active',
63
+ role: 'user'
64
+ });
65
+ console.log('Updated Bob:', updated?.name, '- Status:', updated?.status, '- Role:', updated?.role);
66
+ // ─────────────────────────────────────────────────────────────
67
+ // DELETE: Remove users
68
+ // ─────────────────────────────────────────────────────────────
69
+ console.log('\n🗑️ DELETE Users:');
70
+ const deleted = await userRepo.delete(user2.id);
71
+ console.log('Deleted Jane:', deleted ? 'YES' : 'NO');
72
+ const jane = await userRepo.findByEmail('jane@example.com');
73
+ console.log('Jane still exists:', jane ? 'YES (bug!)' : 'NO (correct)');
74
+ // ─────────────────────────────────────────────────────────────
75
+ // UTILITY: Count and exists
76
+ // ─────────────────────────────────────────────────────────────
77
+ console.log('\n📊 UTILITY Methods:');
78
+ console.log('Total users:', await userRepo.count());
79
+ console.log('Active users:', await userRepo.count({ status: 'active' }));
80
+ console.log('Email exists (john@example.com):', await userRepo.emailExists('john@example.com'));
81
+ console.log('Email exists (jane@example.com):', await userRepo.emailExists('jane@example.com'));
82
+ // ─────────────────────────────────────────────────────────────
83
+ // ADVANCED: Upsert
84
+ // ─────────────────────────────────────────────────────────────
85
+ console.log('\n🔄 UPSERT (Insert or Update):');
86
+ const newUserData = {
87
+ name: 'New User',
88
+ email: 'newuser@example.com',
89
+ role: 'user',
90
+ status: 'active',
91
+ };
92
+ await userRepo.upsert('custom-id', newUserData, { name: 'Updated User' });
93
+ const upserted = await userRepo.findById('custom-id');
94
+ console.log('Upserted:', upserted?.name, '- ID:', upserted?.id);
95
+ await userRepo.upsert('custom-id', newUserData, { name: 'Updated via Upsert' });
96
+ const updatedViaUpsert = await userRepo.findById('custom-id');
97
+ console.log('Updated via upsert:', updatedViaUpsert?.name);
98
+ // ─────────────────────────────────────────────────────────────
99
+ // Summary
100
+ // ─────────────────────────────────────────────────────────────
101
+ console.log('\n' + '═'.repeat(50));
102
+ console.log('✅ @paljs/orm Demo Complete!');
103
+ console.log('═'.repeat(50));
104
+ console.log('\nKey Concepts Demonstrated:');
105
+ console.log(' • Repository Pattern for data access');
106
+ console.log(' • BaseRepository with common CRUD operations');
107
+ console.log(' • Custom repository extensions (UserRepository)');
108
+ console.log(' • Pagination and sorting support');
109
+ console.log(' • Upsert operations');
110
+ console.log(' • DI integration with @paljs/core');
111
+ console.log('\n');
112
+ }
113
+ main().catch(console.error);
@@ -0,0 +1,14 @@
1
+ import 'reflect-metadata';
2
+ export * from './types';
3
+ export * from './repository';
4
+ export * from './user.repository';
5
+ import { InjectionToken, Scope } from '@paljs/core';
6
+ import { UserRepository } from './user.repository';
7
+ export const UserRepositoryToken = new InjectionToken('user-repository');
8
+ export const OrmConfigToken = new InjectionToken('orm-config');
9
+ export function registerRepositories(container) {
10
+ container.provide(UserRepositoryToken, {
11
+ useClass: UserRepository,
12
+ scope: Scope.Singleton,
13
+ });
14
+ }
@@ -0,0 +1,114 @@
1
+ export class BaseRepository {
2
+ constructor() {
3
+ this.collection = new Map();
4
+ }
5
+ async findById(id) {
6
+ return this.collection.get(id) || null;
7
+ }
8
+ async findOne(where) {
9
+ for (const item of this.collection.values()) {
10
+ if (this.matches(item, where))
11
+ return item;
12
+ }
13
+ return null;
14
+ }
15
+ async findMany(options) {
16
+ let results = Array.from(this.collection.values());
17
+ if (options?.where) {
18
+ results = results.filter(item => this.matches(item, options.where));
19
+ }
20
+ if (options?.sort) {
21
+ results = this.sort(results, options.sort);
22
+ }
23
+ const total = results.length;
24
+ const skip = options?.pagination?.skip ?? options?.pagination?.offset ?? 0;
25
+ const take = options?.pagination?.take ?? options?.pagination?.limit ?? total;
26
+ results = results.slice(skip, skip + take);
27
+ return { data: results, total, skip, take };
28
+ }
29
+ async findAll(where) {
30
+ if (!where)
31
+ return Array.from(this.collection.values());
32
+ return Array.from(this.collection.values()).filter(item => this.matches(item, where));
33
+ }
34
+ async create(data) {
35
+ const entity = this.toEntity(data);
36
+ this.collection.set(entity.id, entity);
37
+ return entity;
38
+ }
39
+ async createMany(data) {
40
+ return Promise.all(data.map(d => this.create(d)));
41
+ }
42
+ async update(id, data) {
43
+ const existing = this.collection.get(id);
44
+ if (!existing)
45
+ return null;
46
+ const updateData = this.toUpdateData(data);
47
+ const filteredUpdate = {};
48
+ for (const [key, value] of Object.entries(updateData)) {
49
+ if (value !== undefined) {
50
+ filteredUpdate[key] = value;
51
+ }
52
+ }
53
+ const updated = {
54
+ ...existing,
55
+ ...filteredUpdate,
56
+ updatedAt: new Date(),
57
+ };
58
+ this.collection.set(id, updated);
59
+ return updated;
60
+ }
61
+ async upsert(id, createData, updateData) {
62
+ const existing = await this.findById(id);
63
+ if (existing) {
64
+ return (await this.update(id, updateData));
65
+ }
66
+ const entity = { ...createData };
67
+ const created = await this.create(entity);
68
+ this.collection.delete(created.id);
69
+ const withId = { ...created, id };
70
+ this.collection.set(id, withId);
71
+ return withId;
72
+ }
73
+ async delete(id) {
74
+ return this.collection.delete(id);
75
+ }
76
+ async deleteMany(ids) {
77
+ let count = 0;
78
+ for (const id of ids) {
79
+ if (this.collection.delete(id))
80
+ count++;
81
+ }
82
+ return count;
83
+ }
84
+ async count(where) {
85
+ if (!where)
86
+ return this.collection.size;
87
+ return Array.from(this.collection.values()).filter(item => this.matches(item, where)).length;
88
+ }
89
+ async exists(where) {
90
+ return (await this.findOne(where)) !== null;
91
+ }
92
+ matches(item, where) {
93
+ return Object.entries(where).every(([key, value]) => item[key] === value);
94
+ }
95
+ sort(items, sort) {
96
+ return [...items].sort((a, b) => {
97
+ const aVal = a[sort.field];
98
+ const bVal = b[sort.field];
99
+ if (aVal === bVal || aVal === undefined || bVal === undefined) {
100
+ if (aVal === undefined && bVal === undefined)
101
+ return 0;
102
+ return aVal === undefined ? 1 : -1;
103
+ }
104
+ const cmp = aVal < bVal ? -1 : 1;
105
+ return sort.order === 'desc' ? -cmp : cmp;
106
+ });
107
+ }
108
+ generateId() {
109
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
110
+ }
111
+ clear() {
112
+ this.collection.clear();
113
+ }
114
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import { BaseRepository } from './repository';
2
+ export class UserRepository extends BaseRepository {
3
+ toEntity(data) {
4
+ const now = new Date();
5
+ return {
6
+ id: this.generateId(),
7
+ name: data.name,
8
+ email: data.email,
9
+ password: data.password,
10
+ avatar: data.avatar,
11
+ status: data.status || 'pending',
12
+ role: data.role || 'user',
13
+ createdAt: now,
14
+ };
15
+ }
16
+ toUpdateData(data) {
17
+ return {
18
+ name: data.name,
19
+ email: data.email,
20
+ password: data.password,
21
+ avatar: data.avatar,
22
+ status: data.status,
23
+ role: data.role,
24
+ };
25
+ }
26
+ async findByEmail(email) {
27
+ return this.findOne({ email });
28
+ }
29
+ async findByRole(role) {
30
+ return this.findAll({ role });
31
+ }
32
+ async findActive() {
33
+ return this.findAll({ status: 'active' });
34
+ }
35
+ async emailExists(email, excludeId) {
36
+ const user = await this.findByEmail(email);
37
+ if (!user)
38
+ return false;
39
+ if (excludeId && user.id === excludeId)
40
+ return false;
41
+ return true;
42
+ }
43
+ }
@@ -0,0 +1,114 @@
1
+ export class BaseRepository {
2
+ constructor() {
3
+ this.collection = new Map();
4
+ }
5
+ async findById(id) {
6
+ return this.collection.get(id) || null;
7
+ }
8
+ async findOne(where) {
9
+ for (const item of this.collection.values()) {
10
+ if (this.matches(item, where))
11
+ return item;
12
+ }
13
+ return null;
14
+ }
15
+ async findMany(options) {
16
+ let results = Array.from(this.collection.values());
17
+ if (options?.where) {
18
+ results = results.filter(item => this.matches(item, options.where));
19
+ }
20
+ if (options?.sort) {
21
+ results = this.sort(results, options.sort);
22
+ }
23
+ const total = results.length;
24
+ const skip = options?.pagination?.skip ?? options?.pagination?.offset ?? 0;
25
+ const take = options?.pagination?.take ?? options?.pagination?.limit ?? total;
26
+ results = results.slice(skip, skip + take);
27
+ return { data: results, total, skip, take };
28
+ }
29
+ async findAll(where) {
30
+ if (!where)
31
+ return Array.from(this.collection.values());
32
+ return Array.from(this.collection.values()).filter(item => this.matches(item, where));
33
+ }
34
+ async create(data) {
35
+ const entity = this.toEntity(data);
36
+ this.collection.set(entity.id, entity);
37
+ return entity;
38
+ }
39
+ async createMany(data) {
40
+ return Promise.all(data.map(d => this.create(d)));
41
+ }
42
+ async update(id, data) {
43
+ const existing = this.collection.get(id);
44
+ if (!existing)
45
+ return null;
46
+ const updateData = this.toUpdateData(data);
47
+ const filteredUpdate = {};
48
+ for (const [key, value] of Object.entries(updateData)) {
49
+ if (value !== undefined) {
50
+ filteredUpdate[key] = value;
51
+ }
52
+ }
53
+ const updated = {
54
+ ...existing,
55
+ ...filteredUpdate,
56
+ updatedAt: new Date(),
57
+ };
58
+ this.collection.set(id, updated);
59
+ return updated;
60
+ }
61
+ async upsert(id, createData, updateData) {
62
+ const existing = await this.findById(id);
63
+ if (existing) {
64
+ return (await this.update(id, updateData));
65
+ }
66
+ const entity = { ...createData };
67
+ const created = await this.create(entity);
68
+ this.collection.delete(created.id);
69
+ const withId = { ...created, id };
70
+ this.collection.set(id, withId);
71
+ return withId;
72
+ }
73
+ async delete(id) {
74
+ return this.collection.delete(id);
75
+ }
76
+ async deleteMany(ids) {
77
+ let count = 0;
78
+ for (const id of ids) {
79
+ if (this.collection.delete(id))
80
+ count++;
81
+ }
82
+ return count;
83
+ }
84
+ async count(where) {
85
+ if (!where)
86
+ return this.collection.size;
87
+ return Array.from(this.collection.values()).filter(item => this.matches(item, where)).length;
88
+ }
89
+ async exists(where) {
90
+ return (await this.findOne(where)) !== null;
91
+ }
92
+ matches(item, where) {
93
+ return Object.entries(where).every(([key, value]) => item[key] === value);
94
+ }
95
+ sort(items, sort) {
96
+ return [...items].sort((a, b) => {
97
+ const aVal = a[sort.field];
98
+ const bVal = b[sort.field];
99
+ if (aVal === bVal || aVal === undefined || bVal === undefined) {
100
+ if (aVal === undefined && bVal === undefined)
101
+ return 0;
102
+ return aVal === undefined ? 1 : -1;
103
+ }
104
+ const cmp = aVal < bVal ? -1 : 1;
105
+ return sort.order === 'desc' ? -cmp : cmp;
106
+ });
107
+ }
108
+ generateId() {
109
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
110
+ }
111
+ clear() {
112
+ this.collection.clear();
113
+ }
114
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import { BaseRepository } from './repository';
2
+ export class UserRepository extends BaseRepository {
3
+ toEntity(data) {
4
+ const now = new Date();
5
+ return {
6
+ id: this.generateId(),
7
+ name: data.name,
8
+ email: data.email,
9
+ password: data.password,
10
+ avatar: data.avatar,
11
+ status: data.status || 'pending',
12
+ role: data.role || 'user',
13
+ createdAt: now,
14
+ };
15
+ }
16
+ toUpdateData(data) {
17
+ return {
18
+ name: data.name,
19
+ email: data.email,
20
+ password: data.password,
21
+ avatar: data.avatar,
22
+ status: data.status,
23
+ role: data.role,
24
+ };
25
+ }
26
+ async findByEmail(email) {
27
+ return this.findOne({ email });
28
+ }
29
+ async findByRole(role) {
30
+ return this.findAll({ role });
31
+ }
32
+ async findActive() {
33
+ return this.findAll({ status: 'active' });
34
+ }
35
+ async emailExists(email, excludeId) {
36
+ const user = await this.findByEmail(email);
37
+ if (!user)
38
+ return false;
39
+ if (excludeId && user.id === excludeId)
40
+ return false;
41
+ return true;
42
+ }
43
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@rpal/orm",
3
+ "version": "1.0.0",
4
+ "description": "ORM utilities and decorators for PalJS",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/paljs/paljs.git"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "dependencies": {
16
+ "@rpal/core": "^1.0.0"
17
+ },
18
+ "peerDependencies": {
19
+ "reflect-metadata": "^0.2.0"
20
+ },
21
+ "devDependencies": {
22
+ "tsx": "^4.0.0",
23
+ "vitest": "^3.0.0"
24
+ },
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "dev": "tsx src/demo.ts",
28
+ "test": "vitest run"
29
+ }
30
+ }