Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ repo.register({ Payment, CardPayment });
- Per-model invalidation markers (`$Model`) to evict request caches without dropping entry caches.
- Centralized in-memory cache across processes with UDP-based clustering.
- Discovery protocol auto-syncs peer list for invalidations.
- Repository Context
- **NEW**: Repository-wide context accessible in transactions, models, and records.
- `get_context(key, defaultValue)` retrieves context values.
- `set_context(key, value)` stores context values.
- Transactions inherit parent context but modifications are isolated.
- Use cases: multi-tenancy, user context, feature flags, request metadata.
- Schema sync
- Create/update tables from model fields with `repo.sync()`.
- Migration-safe helpers for column replacement and index updates.
Expand Down
148 changes: 148 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,66 @@ Synchronizes database schema from model definitions. **Use only in development!*
await repo.sync({ force: true });
```

### `repo.get_context(key, defaultValue)`

Retrieves a context value from the repository.

**Parameters:**
- `key` (string): Context key to retrieve
- `defaultValue` (any, optional): Value to return if key doesn't exist

**Returns:** any - The context value or default value

**Example:**
```js
// Set context
repo.set_context('tenant_id', 'tenant-123');
repo.set_context('user_role', 'admin');

// Get context
const tenantId = repo.get_context('tenant_id'); // 'tenant-123'
const locale = repo.get_context('locale', 'en-US'); // Returns default 'en-US'
```

### `repo.set_context(key, value)`

Sets a context value in the repository.

**Parameters:**
- `key` (string): Context key
- `value` (any): Value to store

**Returns:** Repository instance (chainable)

**Example:**
```js
// Store multi-tenancy context
repo.set_context('tenant_id', 'tenant-123');

// Store user context
repo.set_context('current_user_id', 999);
repo.set_context('current_user_role', 'admin');

// Store feature flags
repo.set_context('feature_new_ui', true);

// Store request metadata
repo.set_context('request_id', 'req-abc123');
repo.set_context('request_timestamp', new Date());
```

**Use Cases:**
- Multi-tenancy: Store tenant ID for filtering queries
- User context: Track current user for audit trails
- Feature flags: Enable/disable features at runtime
- Request metadata: Store request ID, timestamp, locale
- Configuration: Runtime settings without global variables

**Transaction Behavior:**
- Transactions inherit parent context at creation
- Changes in transactions are isolated (don't affect parent)
- Context is accessible from models and records within the transaction

---

## Model - Query Methods
Expand Down Expand Up @@ -416,6 +476,50 @@ const count = await Users.query()
.first();
```

### `Model.get_context(key, defaultValue)`

Gets a context value from the repository.

**Parameters:**
- `key` (string): Context key to retrieve
- `defaultValue` (any, optional): Value to return if key doesn't exist

**Returns:** any - The context value or default value

**Example:**
```js
const Users = repo.get('Users');

// Get context from model
const tenantId = Users.get_context('tenant_id');

// Use in model methods
class Users {
static async findForCurrentTenant() {
const tenantId = this.get_context('tenant_id');
return this.where({ tenant_id: tenantId }).find();
}
}
```

### `Model.set_context(key, value)`

Sets a context value in the repository.

**Parameters:**
- `key` (string): Context key
- `value` (any): Value to store

**Returns:** Model instance (chainable)

**Example:**
```js
const Users = repo.get('Users');

// Set context from model
Users.set_context('last_query_time', new Date());
```

---

## Record - Instance Methods
Expand Down Expand Up @@ -463,6 +567,50 @@ await user.unlink();
console.log('User deleted');
```

### `record.get_context(key, defaultValue)`

Gets a context value from the repository.

**Parameters:**
- `key` (string): Context key to retrieve
- `defaultValue` (any, optional): Value to return if key doesn't exist

**Returns:** any - The context value or default value

**Example:**
```js
const user = await Users.findById(1);

// Get context from record
const tenantId = user.get_context('tenant_id');

// Use in hooks
class Users {
async pre_create() {
const currentUserId = this.get_context('current_user_id');
this.created_by = currentUserId;
}
}
```

### `record.set_context(key, value)`

Sets a context value in the repository.

**Parameters:**
- `key` (string): Context key
- `value` (any): Value to store

**Returns:** Record instance (chainable)

**Example:**
```js
const user = await Users.findById(1);

// Set context from record
user.set_context('last_accessed_user_id', user.id);
```

---

## Record - Relation Methods
Expand Down
File renamed without changes.
112 changes: 112 additions & 0 deletions docs/cookbook/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
id: authentication
title: Authentication
keywords: [authentication, password, bcrypt, session, login, security]
---

# Authentication

## Password Hashing (with bcrypt)

```js
// ✅ User model with password hashing
class Users {
static _name = 'Users';
static fields = {
id: 'primary',
email: { type: 'string', unique: true, required: true },
password_hash: 'string'
};

// Set password with hashing
async setPassword(password) {
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 10);
await this.write({ password_hash: hash });
}

// Verify password
async verifyPassword(password) {
const bcrypt = require('bcrypt');
return bcrypt.compare(password, this.password_hash);
}

// Static method for authentication
static async authenticate(email, password) {
const user = await this.where({ email }).first();
if (!user) return null;

const valid = await user.verifyPassword(password);
return valid ? user : null;
}
}

// Usage
const Users = repo.get('Users');

// Register
const user = await Users.create({ email: 'john@example.com' });
await user.setPassword('secretpassword');

// Login
const user = await Users.authenticate('john@example.com', 'secretpassword');
if (!user) {
throw new Error('Invalid credentials');
}
```

## Session Management

```js
// ✅ Session model
class Sessions {
static _name = 'Sessions';
static fields = {
id: 'primary',
user_id: { type: 'many-to-one', model: 'Users' },
token: { type: 'string', unique: true },
expires_at: 'datetime',
created_at: { type: 'datetime', default: () => new Date() }
};

// Check if session is valid
get isValid() {
return this.expires_at > new Date();
}

// Create new session
static async createForUser(userId, ttlSeconds = 86400) {
const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');
const expiresAt = new Date(Date.now() + ttlSeconds * 1000);

return this.create({
user_id: userId,
token,
expires_at: expiresAt
});
}

// Find by token
static async findByToken(token) {
const session = await this.where({ token }).first();
if (!session || !session.isValid) {
return null;
}
return session;
}
}

// Usage
const Sessions = repo.get('Sessions');

// Create session
const session = await Sessions.createForUser(user.id);
console.log('Session token:', session.token);

// Validate session
const session = await Sessions.findByToken(token);
if (!session) {
throw new Error('Invalid or expired session');
}
```
Loading