Arbel 消防基地ORM
Arbel Firebase Orm 是一个 机构 ,可在NodeJS,浏览器, Cordova, PhoneGap, Ionic, React Industrial, InternationalScript, Expo和Electron平台运行 并可用于TypeScript和JavaScript(ES5,ES6,ES7,ES8).
Firebase ORM目前只支持活动记录模式.
一些Arbel Firebase Orm 特性 :
- 🚀 活动记录模式 - 面向对象的数据库互动
- 🔗 全面关系 - 一对一,一对一,一对一..
- 🏗️ 等级数据结构 - 巢集的复杂参考路径(
websites/:website_id/members(中文(简体) ) - 🛡️ 类型安全 - 配有编译时间验证的全类型脚本支持
- ⚡ 实时更新 - 与自动模型水分同步的活数据
- 🔍 高级查询 - 有文本搜索和索引功能的可链接查询
- 🔧 横线 - 相同的API在浏览器、节点和Firebase函数中工作
- 📊 业绩优化 - 懒散地装载、缓存和高效的关系处理
- 🛠️ 生命周期钩 - 拯救之前 拯救之后 毁灭之前 毁灭之后
- 📝 自动时间戳 - 在自动管理的字段创建并更新
还有...
页:1
关于全面的指南和实例,请访问 文档数字 :
开始
框架整合
- 角质 - 服务、部件和依赖性注射
- 下一个.js - SSR、SG、客户端和API路线
- 纳克特.js - Vue.js,得到安全部门改革/特别支助组的支助
- 反应 - 虎克、背景和国家管理
- Vue.js (英语) - 可构成数据和反应数据
- 节点.js - 快速API和后端服务
- Firebase 函数 - 服务器侧功能、触发器和API
核心特征
高级主题
QQ 快例
有了Firebase ORM,你的模型看起来像这样:
import { Field, BaseModel, Model } from "@arbel/firebase-orm";
// Simple model
@Model({
reference_path: "users",
path_id: "user_id"
})
export class User extends BaseModel {
@Field({ is_required: true })
public name!: string;
@Field({ is_required: true })
public email!: string;
@Field({ field_name: "created_at" })
public createdAt?: string;
}
// Complex hierarchical model
@Model({
reference_path: "websites/:website_id/members", // Nested structure
path_id: "member_id"
})
export class Member extends BaseModel {
@Field({ is_required: true })
public name!: string;
@Field({ field_name: "photo_url" })
public photoUrl!: string;
@Field({ is_required: false })
public role?: string;
}
你的域逻辑是这样的:
// Create a new user
const user = new User();
user.name = "John Doe";
user.email = "[email protected]";
user.createdAt = new Date().toISOString();
await user.save();
// Work with hierarchical data
const website = await Website.findOne('domain', '==', 'www.google.com');
const members = await website.getModel(Member).getAll(); // Use getModel() for nested collections
console.log(`${website.domain} has ${members.length} members`);
// Query users
const activeUsers = await User.query()
.where('isActive', '==', true)
.orderBy('createdAt', 'desc')
.limit(10)
.get();
// Real-time updates
const unsubscribe = User.onList((user) => {
console.log('User updated:', user.name);
});
// Relationships
const posts = await user.loadHasMany('posts');
console.log(`${user.name} has ${posts.length} posts`);
// Text search
const searchResults = await User.query()
.like('name', '%john%')
.get();
• 简化输入模式
Firebase ORM 提供了一个方便 init() 简化装入现有模型的方法:
// ✨ NEW: Simple one-liner to load existing data
const user = await User.init(userId);
if (user) {
console.log(user.name); // Data is already loaded!
}
// ✨ NEW: Load nested models with path parameters
const member = await Member.init(memberId, { website_id: websiteId });
if (member) {
console.log(member.name);
}
// Traditional pattern (still supported)
const user = new User();
await user.load(userId);
// Traditional pattern for nested models (still supported)
const member = new Member();
member.setPathParams('website_id', websiteId);
await member.load(memberId);
// NEW: Use initPath() for cleaner nested model queries
const members = await Member.initPath({
website_id: websiteId
}).getAll();
// For creating new instances, use the constructor
const newUser = new User();
newUser.name = "Jane Doe";
newUser.email = "[email protected]";
await newUser.save();
效益 init()数字 :
- * * 用于装入数据的一行而不是二行
- QQ 更直观 - "初始化用户此ID"
- 如果找不到, 返回无效( 容易处理错误)
- QQ 网巢收藏的简单路径参数
- * 与现有代码完全兼容
效益 initPath()数字 :
- QQ 在一个调用中设置多条路径参数
- QQ 链条直接使用查询方法( GetAll, 在哪里, 查询等)
- * 清洁、可读的巢集语法
- 适合具有复杂路径等级的模型
常规 ORM 函数
Firebase ORM现在支持其他流行框架使用的熟悉的ORM方法名称,使得来自不同ORM的开发者更容易:
// Static Methods (Class-level operations)
const users = await User.all(); // Alias for getAll()
const user = await User.first('email', '==', '[email protected]'); // Alias for findOne()
const newUser = await User.create({ // Create and save in one step
name: 'John Doe',
email: '[email protected]'
});
await User.update('status', '==', 'pending', {status: 'active'}); // Update matching docs
await User.destroy('status', '==', 'inactive'); // Remove matching docs
// Instance Methods (Object-level operations)
const user = new User();
user.name = 'John Doe';
await user.create(); // Alias for save()
await user.update({name: 'John Smith'}); // Update and save
await user.destroy(); // Alias for remove()
await user.delete(); // Alternative alias for remove()
可用方法
| 原始方法 | 别名方法 | 说明 |
|---|---|---|
Model.getAll() | Model.all() | 获取所有文档 |
Model.findOne() | Model.first() | 查找第一个匹配的文档 |
| 不详 | Model.create(data) | 创建并保存新实例 |
| 不详 | Model.update(field, op, value, data) | 更新匹配文档 |
| 不详 | Model.destroy(field, op, value) | 删除匹配文档 |
instance.save() | instance.create() | 保存实例( 化名) |
| 不详 | instance.update(data?) | 更新并保存实例 |
instance.remove() | instance.destroy() | 删除实例 |
instance.remove() | instance.delete() | 删除实例( 备选) |
所有原始方法仍可用于后向相容性.
QQ 快速启动
-
安装防火基地ORM
npm install @arbel/firebase-orm firebase moment --save -
初始化火库并配置全局设置
import { initializeApp } from 'firebase/app'; import { getFirestore } from 'firebase/firestore'; import { FirestoreOrmRepository } from '@arbel/firebase-orm'; const app = initializeApp(firebaseConfig); const firestore = getFirestore(app); FirestoreOrmRepository.initGlobalConnection(firestore); // Configure global settings for automatic field naming and path_id generation FirestoreOrmRepository.setGlobalConfig({ auto_lower_case_field_name: true, // cartItem → cart_item auto_path_id: true // User class → user_id }); -
创建您的第一个模型
@Model({ reference_path: 'users' }) // path_id auto-generated as 'user_id' export class User extends BaseModel { @Field({ is_required: true }) public firstName!: string; // Stored as 'first_name' in database @Field({ is_required: true }) public emailAddress!: string; // Stored as 'email_address' in database } -
开始建设!
const user = new User(); user.firstName = 'John'; user.emailAddress = '[email protected]'; await user.save();
以下例子显示了从原始代码库中添加的功能: 所有成员=等待成员。 getAll();
//让所有年龄 > 3和体重 > 30的成员加入 const list = 等待 service.query (). where ('age',''','3'). where ('quent','','30'). get ();
//让所有年龄 > 3或年龄 < 3的成员限制10 const list = 等待 service.query (.) where ('age',''','3'.). or Where ('age',''','3'). limit(10).get (;);
//让成员汤姆 ===============================================================================================================================================================================================================================================================;
// 听实时的汤姆数据变化 var 未订阅 = tom.on (() ) 做点什么吧 * ;
/ 实时获取所有列表 var 未订阅 = 成员.onList((成员)) {{ 做一些与电流 } // 当添加新的 meber 时实时获取所有列表 var 未订阅 = 成员.onList((成员)) {{ 做一些与电流 爱人,爱人,爱人 /或( 或) var 未订阅 = 成员.onModeList({
/**
* Listen to add new objects from now
*/
added?: CallableFunction;
/**
* Listen to removed objects
*/
removed? : CallableFunction
/**
* Listen to modify objects
*/
modified? : CallableFunction
/**
* Listen to init loading objects
*/
init? : CallableFunction
}
//杀死收听进程 未订阅( i) ;
## 📋 Best Practices
### Naming Conventions
Firebase ORM works best with consistent naming conventions:
**✅ Recommended:**
- **Collection names**: Use lowercase with underscores (`users`, `user_profiles`, `shopping_carts`)
- **Field names**: Use camelCase in TypeScript with auto conversion enabled (`firstName`, `emailAddress`, `cartItems`)
- **Model classes**: Use PascalCase (`User`, `UserProfile`, `ShoppingCart`)
**❌ Avoid:**
- Mixed case collection names (`userProfiles`, `ShoppingCarts`)
- Snake_case in TypeScript properties (use camelCase instead)
- Inconsistent naming patterns
### Global Configuration Setup
For new projects, enable global configuration for consistency:
```typescript
FirestoreOrmRepository.setGlobalConfig({
auto_lower_case_field_name: true, // Automatic camelCase → snake_case conversion
auto_path_id: true // Automatic path_id generation from class names
});
模型结构
// ✅ Good: Clean, consistent structure with global config
@Model({
reference_path: 'user_profiles' // Lowercase with underscores
})
export class UserProfile extends BaseModel {
@Field({ is_required: true })
public firstName!: string; // Auto-converted to first_name
@Field({ is_required: true })
public lastName!: string; // Auto-converted to last_name
@Field({
is_required: false,
field_name: 'avatar_url' // Explicit naming when needed
})
public profileImage?: string;
}
安装
-
安装 npm 包 :
npm install @arbel/firebase-orm firebase rxfire moment --save对于Firebase Admin SDK支持:
npm install @arbel/firebase-orm firebase-admin moment --save
类型脚本配置
另外,确保您使用 TypeScript 编译器版本 3.3 国家 或超过,
您已启用以下设置: tsconfig.json数字 :
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"strictPropertyInitialization" : false,
您可能需要启用 es6 输入 lib 编译器选项的段落,或安装 es6-shim 从 @types。 。 。 .
模块格式支持
库支持通用JS(CJS)和ECMAScript模块(ESM)两种格式:
-
对于普通JS环境(Node.js,旧捆绑器):
const { FirestoreOrmRepository } = require("@arbel/firebase-orm"); -
对于无害环境管理环境(现代捆绑机、带有无害环境管理的TypeScript、含有无害环境管理的Node.js):
import { FirestoreOrmRepository } from "@arbel/firebase-orm";
浏览器对节点.js条目点
浏览器应用(Angular、React、Vue等):
使用默认的切入点 。 此版本排除了Admin SDK功能并被优化为浏览器捆绑:
import { FirestoreOrmRepository } from "@arbel/firebase-orm";
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const app = initializeApp(firebaseConfig);
const firestore = getFirestore(app);
FirestoreOrmRepository.initGlobalConnection(firestore);
对于带有Firebase Admin SDK的Node.js应用程序:
使用 /admin 服务器侧功能的切入点 :
import { initializeAdminApp } from "@arbel/firebase-orm/admin";
import * as admin from 'firebase-admin';
const app = admin.initializeApp(adminConfig);
await initializeAdminApp(app);
重要:Admin SDK功能被移动到一个单独的切入点来防止浏览器构建包含Node.js依赖. 见 (原始内容存档于2017-09-25) (英语). Migaliation Guide.md 关于迁移细节.
快速启动
1. 创建全球联系
import * as app from "firebase";
import { FirestoreOrmRepository } from "@arbel/firebase-orm";
var firebaseApp = FirestoreOrmRepository.initializeApp(config);
关系
Firebase ORM通过装饰机为一对一,一对多,多对多的关系提供全面支持. 这使得您可以模拟复杂的数据关系,同时保持Firestore的NoSQL结构的灵活性.
建立关系
先决条件
在界定关系之前,确保您有:
- 导入关系装饰器数字 :
import { BelongsTo, HasOne, HasMany, BelongsToMany } from "@arbel/firebase-orm";
- 定义了您的模型 与适当的
@Model财务报告和审定财务报表@Field装饰器 - 计划您的数据库结构 具有清晰的外国关键模式
数据库结构考虑
在 Firebase ORM 中设计关系时:
- 外国密钥: 使用字符串字段存储引用其他收藏的文档ID
- 收藏名称:使用一致的命名模式(例如,
users, (中文(简体) ),posts, (中文(简体) ),user_roles(中文(简体) ) - 路径标识: 确保每个模型都有独特的
path_id字段 - 交汇表: 对于多种关系,为相交表创建单独的模型
关系类型概览
| 关系 | 何时使用 | 示例 |
|---|---|---|
@BelongsTo | 这个模型有一个外国键指向另一个 | 用户简介 – 用户 |
@HasOne | 另一种模式有外国钥匙指向这个 | 用户 – 用户配置 |
@HasMany | 另一种模式有多种记录指出这一点 | 员额 |
@BelongsToMany | 通过相交台进行多对多 | 用户 QQ 角色 |
一对一关系
一对一的关系将一个集合中的一对一记录与另一个集合中的一对一记录完全联系起来.
属于( 此模型有外国密钥)
使用 @BelongsTo 何时 此模型存储外国密钥 指着另一个模型.
数据库结构 :
users/
user-1: { name: "John Doe" }
user_profiles/
profile-1: { user_id: "user-1", bio: "Software developer" }
设置 :
import { Field, BaseModel, Model, BelongsTo } from "@arbel/firebase-orm";
// First, define the target model (User)
@Model({
reference_path: 'users',
path_id: 'user_id'
})
class User extends BaseModel {
@Field({ is_required: true })
public name!: string;
}
// Then, define the model with the foreign key (UserProfile)
@Model({
reference_path: 'user_profiles',
path_id: 'profile_id'
})
class UserProfile extends BaseModel {
@Field({ is_required: true, field_name: 'user_id' })
public userId!: string; // This is the foreign key
@Field({ is_required: false })
public bio?: string;
// Define the relationship
@BelongsTo({
model: User, // The target model class
localKey: 'userId' // The local field containing the foreign key
})
public user?: User; // Optional property to hold the loaded relationship
}
用法 :
// Load a profile and its related user
const profile = new UserProfile();
await profile.load('profile-1');
// Method 1: Load the relationship explicitly
const user = await profile.loadBelongsTo('user');
console.log(user.name); // "John Doe"
// Method 2: Access the loaded relationship (after loading)
await profile.loadWithRelationships(['user']);
console.log(profile.user?.name); // "John Doe"
HasOne( 其他模型有外键)
使用 @HasOne 何时 另一个型号有外国钥匙 指向这个模型.
数据库结构 :
users/
user-1: { name: "John Doe" }
user_profiles/
profile-1: { user_id: "user-1", bio: "Software developer" }
设置 :
@Model({
reference_path: 'users',
path_id: 'user_id'
})
class User extends BaseModel {
@Field({ is_required: true })
public name!: string;
// Define the relationship
@HasOne({
model: UserProfile, // The model that has the foreign key
foreignKey: 'user_id' // The field in UserProfile that references this User
})
public profile?: UserProfile;
}
用法 :
// Load a user and their profile
const user = new User();
await user.load('user-1');
// Load the related profile
const profile = await user.loadHasOne('profile');
console.log(profile.bio); // "Software developer"
一对一关系
一对多关系将一个记录与多个相关记录连接. 这在诸如用户、职位或产品类等亲子关系中很常见.
数据库结构 :
users/
user-1: { name: "John Doe" }
user-2: { name: "Jane Smith" }
posts/
post-1: { title: "First Post", author_id: "user-1" }
post-2: { title: "Second Post", author_id: "user-1" }
post-3: { title: "Jane's Post", author_id: "user-2" }
设置 :
// Define the "many" side (Post belongs to User)
@Model({
reference_path: 'posts',
path_id: 'post_id'
})
class Post extends BaseModel {
@Field({ is_required: true })
public title!: string;
@Field({ is_required: true, field_name: 'author_id' })
public authorId!: string; // Foreign key to User
// Each post belongs to one user
@BelongsTo({
model: User,
localKey: 'authorId'
})
public author?: User;
}
// Define the "one" side (User has many Posts)
@Model({
reference_path: 'users',
path_id: 'user_id'
})
class User extends BaseModel {
@Field({ is_required: true })
public name!: string;
// User has many posts
@HasMany({
model: Post, // The model that contains the foreign key
foreignKey: 'author_id' // The field in Post that references this User
})
public posts?: Post[];
}
用法 :
// Load a user and their posts
const user = new User();
await user.load('user-1');
// Load all posts by this user
const posts = await user.loadHasMany('posts');
console.log(posts.length); // 2
console.log(posts[0].title); // "First Post"
// Load a post and its author
const post = new Post();
await post.load('post-1');
const author = await post.loadBelongsTo('author');
console.log(author.name); // "John Doe"
// Load both relationships at once
await user.loadWithRelationships(['posts']);
await post.loadWithRelationships(['author']);
多种关系
多对多关系将从一个集合的多记录与另一个集合的多记录联系起来. 这需要 交叉表 (也叫"支点表")用于存储关系.
数据库结构 :
users/
user-1: { name: "John Doe" }
user-2: { name: "Jane Smith" }
roles/
role-1: { name: "Admin" }
role-2: { name: "Editor" }
role-3: { name: "Viewer" }
user_roles/ (junction table)
ur-1: { user_id: "user-1", role_id: "role-1" }
ur-2: { user_id: "user-1", role_id: "role-2" }
ur-3: { user_id: "user-2", role_id: "role-3" }
设置 :
// Step 1: Define the junction table model
@Model({
reference_path: 'user_roles',
path_id: 'user_role_id'
})
class UserRole extends BaseModel {
@Field({ is_required: true, field_name: 'user_id' })
public userId!: string;
@Field({ is_required: true, field_name: 'role_id' })
public roleId!: string;
// Optional: You can add relationships to the junction table too
@BelongsTo({ model: User, localKey: 'userId' })
public user?: User;
@BelongsTo({ model: Role, localKey: 'roleId' })
public role?: Role;
}
// Step 2: Define the Role model
@Model({
reference_path: 'roles',
path_id: 'role_id'
})
class Role extends BaseModel {
@Field({ is_required: true })
public name!: string;
// Many-to-many: Role belongs to many users
@BelongsToMany({
model: User, // The target model
through: UserRole, // The junction table model
thisKey: 'role_id', // Field in junction table that references this Role
otherKey: 'user_id' // Field in junction table that references the User
})
public users?: User[];
}
// Step 3: Define the User model
@Model({
reference_path: 'users',
path_id: 'user_id'
})
class User extends BaseModel {
@Field({ is_required: true })
public name!: string;
// Many-to-many: User belongs to many roles
@BelongsToMany({
model: Role, // The target model
through: UserRole, // The junction table model
thisKey: 'user_id', // Field in junction table that references this User
otherKey: 'role_id' // Field in junction table that references the Role
})
public roles?: Role[];
}
用法 :
// Load a user and their roles
const user = new User();
await user.load('user-1');
const roles = await user.loadBelongsToMany('roles');
console.log(roles.length); // 2
console.log(roles.map(r => r.name)); // ["Admin", "Editor"]
// Load a role and its users
const role = new Role();
await role.load('role-1');
const users = await role.loadBelongsToMany('users');
console.log(users.length); // 1
console.log(users[0].name); // "John Doe"
// Create new many-to-many relationships
const newUser = new User();
newUser.name = "Bob Wilson";
await newUser.save();
const adminRole = new Role();
await adminRole.load('role-1'); // Load existing Admin role
// Create the junction record
const userRole = new UserRole();
userRole.userId = newUser.getId();
userRole.roleId = adminRole.getId();
await userRole.save();
高级关系加载
正在装入多重关系
您可以同时装入所有关系 loadWithRelationships()数字 :
const user = new User();
await user.load('user-id');
// Load multiple relationships in one call
await user.loadWithRelationships(['profile', 'posts', 'roles']);
// Now you can access all loaded relationships
console.log(user.profile?.bio);
console.log(user.posts?.length);
console.log(user.roles?.map(r => r.name));
// Or load all relationships (if none specified, loads all defined relationships)
await user.loadWithRelationships();
关系加载方法
| 方法 | 说明 | 回返 |
|---|---|---|
loadBelongsTo(name) | 通过外国密钥装入单个相关模型 | Promise<T & BaseModel> |
loadHasOne(name) | 装入单个相关模型( 反向外键) | Promise<T & BaseModel> |
loadHasMany(name) | 装入多个相关模型 | Promise<Array<T & BaseModel>> |
loadBelongsToMany(name) | 装入多种关系 | Promise<Array<T & BaseModel>> |
loadWithRelationships(names?) | 立即装入多个关系 | Promise<this> |
处理错误
try {
const user = new User();
await user.load('user-id');
const posts = await user.loadHasMany('posts');
console.log(`User has ${posts.length} posts`);
} catch (error) {
if (error.message.includes('Relationship not found')) {
console.log('Relationship not defined on model');
} else if (error.message.includes('not found')) {
console.log('Related record not found');
} else {
console.error('Unexpected error:', error);
}
}
最佳做法和共同模式
1. 一致命名公约
// Use consistent field naming patterns
@Field({ field_name: 'user_id' }) // Foreign key fields
public userId!: string;
@Field({ field_name: 'created_at' }) // Timestamp fields
public createdAt!: string;
// Use descriptive relationship names
@HasMany({ model: Post, foreignKey: 'author_id' })
public posts?: Post[]; // Clear what this represents
@BelongsTo({ model: User, localKey: 'authorId' })
public author?: User; // Singular for one-to-one/many-to-one
2. 模式初始化顺序
// Define models in dependency order to avoid circular references
// 1. Base models first (no dependencies)
class User extends BaseModel { /* ... */ }
class Role extends BaseModel { /* ... */ }
// 2. Junction tables next
class UserRole extends BaseModel { /* ... */ }
// 3. Models with relationships last
class Post extends BaseModel {
@BelongsTo({ model: User, localKey: 'authorId' })
public author?: User;
}
3. 业绩考量
// Load relationships only when needed
const users = await User.getAll();
// Avoid N+1 queries - batch load relationships
for (const user of users) {
await user.loadWithRelationships(['posts']); // Load all at once
}
// Or use specific loading methods
const activePosts = await user.loadHasMany('posts').then(posts =>
posts.filter(p => p.status === 'active')
);
4. 数据完整性
// Always validate foreign keys before saving
class Post extends BaseModel {
async save() {
// Validate author exists before saving
if (this.authorId) {
const author = new User();
try {
await author.load(this.authorId);
} catch (error) {
throw new Error(`Invalid author_id: ${this.authorId}`);
}
}
return super.save();
}
}
解决共同问题
问题: "没有找到关系"
// ❌ Wrong: Relationship name doesn't match decorator property
@HasMany({ model: Post, foreignKey: 'author_id' })
public userPosts?: Post[]; // Property name is 'userPosts'
await user.loadHasMany('posts'); // ❌ Looking for 'posts' but property is 'userPosts'
// ✅ Correct: Relationship name matches property name
await user.loadHasMany('userPosts'); // ✅ Matches property name
问题: "没有找到记录"
// Check if the foreign key relationship is correct
const profile = new UserProfile();
profile.userId = 'wrong-user-id'; // ❌ Non-existent user ID
try {
const user = await profile.loadBelongsTo('user');
} catch (error) {
console.log('User not found for profile'); // Handle gracefully
}
问题:循环依赖
// ❌ Avoid circular imports - define models in separate files
// user.model.ts
import { Post } from './post.model'; // ❌ Circular if Post imports User
// ✅ Use forward references or organize dependencies properly
// base-models.ts - Define all base models
// relationship-models.ts - Add relationships after base definitions
完整示例:博客系统
以下是一个完整的例子, 说明如何建立一个有关系的博客系统:
import { Field, BaseModel, Model, BelongsTo, HasOne, HasMany, BelongsToMany } from "@arbel/firebase-orm";
// 1. Base Models (no dependencies)
@Model({
reference_path: 'users',
path_id: 'user_id'
})
export class User extends BaseModel {
@Field({ is_required: true })
public name!: string;
@Field({ is_required: true })
public email!: string;
@Field({ is_required: false, field_name: 'created_at' })
public createdAt?: string;
// Relationships
@HasOne({ model: UserProfile, foreignKey: 'user_id' })
public profile?: UserProfile;
@HasMany({ model: Post, foreignKey: 'author_id' })
public posts?: Post[];
@HasMany({ model: Comment, foreignKey: 'user_id' })
public comments?: Comment[];
@BelongsToMany({
model: Tag,
through: UserTag,
thisKey: 'user_id',
otherKey: 'tag_id'
})
public followedTags?: Tag[];
}
@Model({
reference_path: 'tags',
path_id: 'tag_id'
})
export class Tag extends BaseModel {
@Field({ is_required: true })
public name!: string;
@Field({ is_required: false })
public description?: string;
// Relationships
@BelongsToMany({
model: Post,
through: PostTag,
thisKey: 'tag_id',
otherKey: 'post_id'
})
public posts?: Post[];
@BelongsToMany({
model: User,
through: UserTag,
thisKey: 'tag_id',
otherKey: 'user_id'
})
public followers?: User[];
}
// 2. Junction Tables
@Model({
reference_path: 'post_tags',
path_id: 'post_tag_id'
})
export class PostTag extends BaseModel {
@Field({ is_required: true, field_name: 'post_id' })
public postId!: string;
@Field({ is_required: true, field_name: 'tag_id' })
public tagId!: string;
}
@Model({
reference_path: 'user_tags',
path_id: 'user_tag_id'
})
export class UserTag extends BaseModel {
@Field({ is_required: true, field_name: 'user_id' })
public userId!: string;
@Field({ is_required: true, field_name: 'tag_id' })
public tagId!: string;
}
// 3. Dependent Models
@Model({
reference_path: 'user_profiles',
path_id: 'profile_id'
})
export class UserProfile extends BaseModel {
@Field({ is_required: true, field_name: 'user_id' })
public userId!: string;
@Field({ is_required: false })
public bio?: string;
@Field({ is_required: false, field_name: 'avatar_url' })
public avatarUrl?: string;
@Field({ is_required: false })
public website?: string;
// Relationships
@BelongsTo({ model: User, localKey: 'userId' })
public user?: User;
}
@Model({
reference_path: 'posts',
path_id: 'post_id'
})
export class Post extends BaseModel {
@Field({ is_required: true })
public title!: string;
@Field({ is_required: true })
public content!: string;
@Field({ is_required: true, field_name: 'author_id' })
public authorId!: string;
@Field({ is_required: false, field_name: 'published_at' })
public publishedAt?: string;
@Field({ is_required: false })
public status?: 'draft' | 'published' | 'archived';
// Relationships
@BelongsTo({ model: User, localKey: 'authorId' })
public author?: User;
@HasMany({ model: Comment, foreignKey: 'post_id' })
public comments?: Comment[];
@BelongsToMany({
model: Tag,
through: PostTag,
thisKey: 'post_id',
otherKey: 'tag_id'
})
public tags?: Tag[];
}
@Model({
reference_path: 'comments',
path_id: 'comment_id'
})
export class Comment extends BaseModel {
@Field({ is_required: true })
public content!: string;
@Field({ is_required: true, field_name: 'post_id' })
public postId!: string;
@Field({ is_required: true, field_name: 'user_id' })
public userId!: string;
@Field({ is_required: false, field_name: 'parent_id' })
public parentId?: string; // For nested comments
@Field({ is_required: false, field_name: 'created_at' })
public createdAt?: string;
// Relationships
@BelongsTo({ model: Post, localKey: 'postId' })
public post?: Post;
@BelongsTo({ model: User, localKey: 'userId' })
public user?: User;
@BelongsTo({ model: Comment, localKey: 'parentId' })
public parent?: Comment;
@HasMany({ model: Comment, foreignKey: 'parent_id' })
public replies?: Comment[];
}
// Usage Examples:
// Create a new blog post with relationships
async function createBlogPost() {
// 1. Create or load the author
const author = new User();
author.name = "John Doe";
author.email = "[email protected]";
author.createdAt = new Date().toISOString();
await author.save();
// 2. Create the post
const post = new Post();
post.title = "Getting Started with Firebase ORM";
post.content = "This is a comprehensive guide...";
post.authorId = author.getId();
post.status = 'published';
post.publishedAt = new Date().toISOString();
await post.save();
// 3. Create some tags
const jsTag = new Tag();
jsTag.name = "JavaScript";
jsTag.description = "JavaScript programming language";
await jsTag.save();
const ormTag = new Tag();
ormTag.name = "ORM";
ormTag.description = "Object-Relational Mapping";
await ormTag.save();
// 4. Associate tags with the post
const postTag1 = new PostTag();
postTag1.postId = post.getId();
postTag1.tagId = jsTag.getId();
await postTag1.save();
const postTag2 = new PostTag();
postTag2.postId = post.getId();
postTag2.tagId = ormTag.getId();
await postTag2.save();
return { post, author, tags: [jsTag, ormTag] };
}
// Load a complete blog post with all relationships
async function loadBlogPostWithRelationships(postId: string) {
const post = new Post();
await post.load(postId);
// Load all relationships
await post.loadWithRelationships(['author', 'comments', 'tags']);
// Access the loaded data
console.log(`Post: ${post.title}`);
console.log(`Author: ${post.author?.name}`);
console.log(`Tags: ${post.tags?.map(t => t.name).join(', ')}`);
console.log(`Comments: ${post.comments?.length} comments`);
// Load nested relationships for comments
if (post.comments) {
for (const comment of post.comments) {
await comment.loadWithRelationships(['user', 'replies']);
console.log(`Comment by ${comment.user?.name}: ${comment.content}`);
if (comment.replies?.length) {
for (const reply of comment.replies) {
await reply.loadBelongsTo('user');
console.log(` Reply by ${reply.user?.name}: ${reply.content}`);
}
}
}
}
return post;
}
// Find posts by tag
async function findPostsByTag(tagName: string) {
// Find the tag
const tag = await Tag.findOne('name', '==', tagName);
if (!tag) {
throw new Error(`Tag '${tagName}' not found`);
}
// Load posts with this tag
const posts = await tag.loadBelongsToMany('posts');
// Load authors for all posts
for (const post of posts) {
await post.loadBelongsTo('author');
}
return posts;
}
// Get user's blog activity
async function getUserBlogActivity(userId: string) {
const user = new User();
await user.load(userId);
// Load all user's blog-related data
await user.loadWithRelationships(['profile', 'posts', 'comments', 'followedTags']);
const activity = {
user: {
name: user.name,
email: user.email,
bio: user.profile?.bio,
avatar: user.profile?.avatarUrl
},
stats: {
postsCount: user.posts?.length || 0,
commentsCount: user.comments?.length || 0,
followedTagsCount: user.followedTags?.length || 0
},
recentPosts: user.posts?.slice(0, 5).map(p => ({
title: p.title,
status: p.status,
publishedAt: p.publishedAt
})),
followedTags: user.followedTags?.map(t => t.name)
};
return activity;
}
遗产关系方法
对于后向相容性,原始的关系方法仍然可用:
// Load one related model (assumes foreign key pattern)
const relatedModel = await model.getOneRel(RelatedModel);
// Load many related models (assumes foreign key pattern)
const relatedModels = await model.getManyRel(RelatedModel);
说明: 这些遗留方法使用基于模型名的自动外钥检测,可能无法可靠地处理复杂的关系. 建议对所有新项目采用以装饰者为基础的新方法.
1. 启动消防基地管理SDK
import * as admin from "firebase-admin";
import { FirestoreOrmRepository } from "@arbel/firebase-orm";
// Initialize Firebase Admin with your credentials
const adminApp = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://your-project-id.firebaseio.com",
storageBucket: "your-project-id.appspot.com"
});
// Initialize Firebase ORM with the Admin app
FirestoreOrmRepository.initializeAdminApp(adminApp);
// Initialize storage (optional)
const adminStorage = admin.storage();
FirestoreOrmRepository.initGlobalStorage(adminStorage);
使用消防基地管理员
1. 启动消防基地管理SDK
import * as admin from "firebase-admin";
import { FirestoreOrmRepository } from "@arbel/firebase-orm";
// Initialize Firebase Admin with your credentials
const adminApp = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://your-project-id.firebaseio.com",
storageBucket: "your-project-id.appspot.com"
});
// Initialize Firebase ORM with the Admin app
FirestoreOrmRepository.initializeAdminApp(adminApp);
// Initialize storage (optional)
const adminStorage = admin.storage();
FirestoreOrmRepository.initGlobalStorage(adminStorage);
2. 创建全球路径 id - (可选)
import { FirestoreOrmRepository } from "@arbel/firebase-orm";
FirestoreOrmRepository.initGlobalPath("user_id", 50);
3. 创建新对象
import { Member } from "model/member";
const member = new Member();
member.name = "Timber";
member.photoUrl = "https://www.example.com/image.png";
member.save();
数据库结构
- 仅使用装饰符的变量@ Field 将保存在数据库中
- 每个模型必须包含需要独有的路径 id属性
- 引用 路径是数据附录内模型数据的路径
文本索引/ 如搜索
1. 增加国旗 is_text_indexing 转到 @ field 装饰器
import { Field, BaseModel, Model } from "@arbel/firebase-orm";
@Model({
reference_path: "websites/:website_id/members",
path_id: "member_id"
})
export class Member extends BaseModel {
@Field({
is_text_indexing: true
})
public name!: string;
@Field({
is_required: true
})
public age!: number;
@Field({
is_required: true
})
public weight!: number;
}
- 在变量中保存新值.
- 使用所需的运算符
//Get all members with age > 3 and weight > 30 and name conatin `Dav`
const list = await Member.query()
.where("age", ">", "3")
.where("weight", ">", "30")
.like("name", "%Dav%")
.get();
弹性搜索支持
1. 用 Write 触发器添加火基地功能
import * as functions from "firebase-functions";
import { Client } from "@elastic/elasticsearch";
const client = new Client({
cloud: {
id: "xxxxxxx",
username: "xxxxx",
password: "xxxxxxx"
}
});
export const elasticsearchProductsSync = functions.firestore
.document("products/{productId}")
.onWrite((snap, context) => {
const productId = context.params.productId;
const newData = snap.after.data();
// ...or the previous value before this update
const previousData = snap.before.data();
if (newData) {
newData.id = productId;
if (!previousData) {
printLog("create new product - ", productId);
client
.create({
index: "products",
type: "_doc",
id: productId,
body: newData
})
.catch(e => {
var error =
e.meta && e.meta.body && e.meta.body.error ? e.meta.body : e;
console.error("Elasticsearch error - ", error);
});
} else {
printLog("update product - ", productId);
client.transport
.request({
method: "POST",
path: "/products/_doc/" + productId,
body: newData
})
.catch(e => {
var error =
e.meta && e.meta.body && e.meta.body.error ? e.meta.body : e;
console.error("Elasticsearch error - ", error);
});
}
} else {
printLog("delete product - ", productId);
client
.delete({
index: "products",
type: "_doc",
id: productId
})
.catch(e => {
var error =
e.meta && e.meta.body && e.meta.body.error ? e.meta.body : e;
console.error("Elasticsearch error - ", error);
});
}
return true;
});
- 设置全局弹性搜索 url
FirestoreOrmRepository.initGlobalElasticsearchConnection(
"https://elasticsearch.com"
);
- 以 sql 获取数据
var result: any = await Product.elasticSql("WHERE qty > 0", 3);
//Total rows
var totalCount = await result.count();
var current = 0;
//Pagination
while (result.next) {
index++;
var result = await result.next();
}
- 或使用绑定的 sql
var result: any = await Product.elasticSql([
"WHERE name in (:options) and cost > :cost",
{
options: ["a", "b", "c"],
cost: 9
}
]);
//Total rows
var totalCount = await result.count();
var current = 0;
//Pagination
while (result.next) {
index++;
var result = await result.next();
}
储存支助
1. 安装火库储存连接
var firebaseApp: any = firebase.initializeApp(config.api.firebase);
var storage = firebaseApp.storage();
FirestoreOrmRepository.initGlobalStorage(storage);
- 获取想要的字段的存储引用
var product = new Product();
product.name = "test product";
var storageRef = product.getStorageFile("photoUrl");
- 上传文件
var product = new Product();
await product.getStorageFile("photoUrl").uploadFile(file);
product.save();
- 从字符串上传文件
var product = new Product();
await product.getStorageFile("photoUrl").uploadString(file,'base64');
product.save();
- 从url上传文件( 复制文件到存储)
var product = new Product();
await product.getStorageFile("photoUrl").uploadFromUrl(url);
product.save();
- 获取 Firebase 存储文件
var product = new Product();
var ref = product.getStorageFile("photoUrl").getRef();
- 跟踪进度
var product = new Product();
product.name = "test product";
var storageRef = product.getStorageFile("photoUrl");
await storageRef.uploadFromUrl(
"https://img.example.com/image.jpg",
function(snapshot: any) {
// Observe state change events such as progress, pause, and resume
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
switch (snapshot.state) {
case firebase.storage.TaskState.PAUSED: // or 'paused'
break;
case firebase.storage.TaskState.RUNNING: // or 'running'
break;
}
},
function(error: any) {
// Handle unsuccessful uploads
},
function(task: any) {
// Handle successful uploads on complete
// For instance, get the download URL: https://firebasestorage.googleapis.com/...
task.snapshot.ref.getDownloadURL().then(function(downloadURL: any) {
printLog("File available at", downloadURL);
});
}
);
await product.save();