- 数据库
- 后端
【免费下载链接】objection.js
An SQL-friendly ORM for Node.js
导读
Model静态方法是 objection.js(一款 SQL-friendly 的 Node.js ORM)中所有查询、事务、校验与元数据操作的入口。本文以 doc/api/model/static-methods.md 为骨架,逐一剖析query()、relatedQuery()、knex()、transaction()、八个静态钩子(hook)、bindKnex()/bindTransaction()、fromJson()/fromDatabaseJson()、fetchGraph()、traverseAsync()以及raw()/ref()/fn()/knexQuery()等方法的签名、参数、返回值与底层实现。读完本文,你将掌握如何用 objection.js 编写类型安全的 CRUD 查询、关联查询、多库绑定、事务控制与自定义校验,并能通过源码定位每个方法背后的真实调用链。
在 objection.js 中,"所有配置都通过 Model 类完成,没有全局配置或状态",这也是静态方法如此重要的原因——它们是每个模型的唯一事实来源。下文所有实现细节均可在 lib/model/Model.js 及其辅助模块中印证。
一、static query():模型查询的入口
方法签名与参数
const queryBuilder = Person.query(transactionOrKnex);| 参数 | 类型 | 说明 |
|---|---|---|
| transactionOrKnex | object | 可选的事务(transaction)或 knex 实例,用于指定事务甚至切换数据库;falsy 值(如undefined/null)会被忽略 |
返回值:QueryBuilder,一个可链式调用的查询构建器。
实现原理
源码 lib/model/Model.js#L492-L496 中的实现非常简洁:
static query(trx) { const query = this.QueryBuilder.forClass(this).transacting(trx); this.onCreateQuery(query); return query; }即:先通过QueryBuilder.forClass(this)为当前模型类创建一个专属查询构建器,再调用.transacting(trx)把传入的事务绑定进去(falsy 值会被忽略),最后触发可覆写的static onCreateQuery(builder)钩子。文档特别强调:所有查询构建器都经由该函数创建,包括实例方法$query、$relatedQuery以及静态的relatedQuery——因此你可以通过覆写query()或onCreateQuery统一修改每个查询的默认行为(例如注入软删除过滤、租户隔离条件)。
典型用法
1. 读取数据。复杂 WHERE 子句可通过子构建器组合:
// Get all rows. const people = await Person.query(); console.log('there are', people.length, 'people in the database'); // 生成:SELECT "persons".* FROM "persons" // WHERE ("firstName" = 'Jennifer' AND "age" < 30) OR ("firstName" = 'Mark' AND "age" > 30) const marksAndJennifers = await Person.query() .where(builder => { builder.where('firstName', 'Jennifer').where('age', '<', 30); }) .orWhere(builder => { builder.where('firstName', 'Mark').where('age', '>', 30); }); // 取子集并级联加载关联模型 const oldPeople = await Person.query() .where('age', '>', 60) .withGraphFetched('children.children.movies');2. 插入数据。insert会自动把传入的 POJO 转为模型实例并做 jsonSchema 校验:
const sylvester = await Person.query().insert({ firstName: 'Sylvester', lastName: 'Stallone' }); console.log(sylvester.fullName()); // --> 'Sylvester Stallone' // 批量插入:只有 PostgreSQL 会返回所有插入行的主键, // 其他数据库请改用 .knexQuery() 直接操作 knex。 const inserted = await Person.query().insert([ { firstName: 'Arnold', lastName: 'Schwarzenegger' }, { firstName: 'Sylvester', lastName: 'Stallone' } ]); console.log(inserted[0].fullName()); // --> 'Arnold Schwarzenegger'3. 更新数据。update与patch的唯一区别在于校验强度:update用模型完整的jsonSchema校验(含required),适合更新全部属性;patch忽略required规则,适合局部更新:
const numUpdatedRows = await Person.query() .update({ firstName: 'Jennifer', lastName: 'Lawrence', age: 35 }) .where('id', jennifer.id); // 假设 firstName / lastName 是 Person 的必填属性,下面这行会抛错 await Person.query().update({ age: 100 }); // 而 patch 不会抛错 await Person.query().patch({ age: 100 });4. 删除数据,可自由链式拼接 knex 方法:
await Person.query() .delete() .where('age', '>', 90);二、static relatedQuery():关联查询的利器
方法签名与参数
const queryBuilder = Person.relatedQuery(relationName, transactionOrKnex);| 参数 | 类型 | 说明 |
|---|---|---|
| relationName | string | 要查询的关联名称(定义于relationMappings) |
| transactionOrKnex | object | 可选的事务或 knex 实例;falsy 值被忽略 |
返回值:QueryBuilder。
实现原理
源码 lib/model/Model.js#L770-L817 中的relatedQuery()工厂函数揭示了完整机制:它先通过modelClass.getRelation(relationName)取得 Relation 对象,再用关联的目标模型类创建查询构建器,并通过findOperationFactory/insertOperationFactory/updateOperationFactory/deleteOperationFactory/relateOperationFactory/unrelateOperationFactory为每种操作注入对应的关联操作(如relation.find(builder, owner))。alwaysReturnArray为true表示静态relatedQuery始终返回数组,而实例方法$relatedQuery则为false。
基础用法
relatedQuery必须配合.for(...)指定归属对象(见 doc/api/query-builder/other-methods.md):
// 取 personId = 1 的所有 pets(pets 是 relationMappings 中定义的关联名) const personId = 1; const pets = await Person.relatedQuery('pets').for(personId);select "animals".* from "animals" where "animals"."ownerId" = 1可以链式追加任何查询方法,例如只取狗并按名字排序:
const dogs = await Person.relatedQuery('pets') .for(1) .where('species', 'dog') .orderBy('name');select "animals".* from "animals" where "species" = 'dog' and "animals"."ownerId" = 1 order by "name" asc进阶用法
多对象查询:给.for()传 ID 数组,生成IN条件:
const dogs = await Person.relatedQuery('pets') .for([1, 2]) .where('species', 'dog') .orderBy('name');select "animals".* from "animals" where "species" = 'dog' and "animals"."ownerId" in (1, 2) order by "name" asc子查询作为.for()参数:传入一个未执行(不await)的查询构建器,它会内联为子查询:
const jennifers = Person.query().where('name', 'Jennifer'); // 不执行 const allDogsOfAllJennifers = await Person.relatedQuery('pets') .for(jennifers) .where('species', 'dog') .orderBy('name');select "animals".* from "animals" where "species" = 'dog' and "animals"."ownerId" in ( select "persons"."id" from "persons" where "name" = 'Jennifer' ) order by "name" asc配合 relate / unrelate / delete:多对多关联movies的关联与解除同样支持:
// 把 id=200 的电影关联到 id=100 的人 await Person.relatedQuery('movies').for(100).relate(200);insert into "persons_movies" ("personId", "movieId") values (100, 200)作为子查询嵌入主查询(省略.for()):可用于统计关联数量、求极值并注入 SELECT:
const people = await Person.query().select([ 'persons.*', Person.relatedQuery('pets') .count() .where('species', 'dog') .as('dogCount'), Person.relatedQuery('movies') .max('createdAt') .as('mostRecentMovieDate') ]);与whereExists组合,查找"至少拥有一个关联项"的模型:
const peopleThatHavePets = await Person.query().whereExists( Person.relatedQuery('pets') );select "persons".* from "persons" where exists ( select "pets".* from "animals" as "pets" where "pets"."ownerId" = "persons"."id" )更多示例见 doc/guide/query-examples.md 与 doc/recipes/relation-subqueries.md。
三、static knex()与static transaction():连接与事务
knex():获取/设置连接
Model.knex(knex)为模型类设置 knex 实例,Person.knex()读取当前实例。子类自动继承父类连接,因此可以这样设置全局连接——即使子类已创建也依然生效:
const knex = require('knex')({ client: 'sqlite3', connection: { filename: 'database.db' } }); Model.knex(knex);读取:
const knex = Person.knex();源码 lib/model/Model.js#L515-L521 的实现是:有参数时通过defineNonEnumerableProperty写入$$knex,无参数时读取。多数据库场景下,官方建议要么把 knex 实例逐查询传入,要么使用下文bindKnex()。
transaction():事务快捷方式
const result = await Person.transaction(callback); const result = await Person.transaction(trxOrKnex, callback);| 参数 | 类型 | 说明 |
|---|---|---|
| callback | function | 事务回调,接收trx作为第一个参数 |
| trxOrKnex | knex 或 Transaction | 可选的既有事务或 knex 实例 |
它等价于Person.knex().transaction(callback)(见 lib/model/Model.js#L523-L530)。回调内所有查询必须显式传trx,保证原子提交/回滚:
try { const scrappy = await Person.transaction(async trx => { const jennifer = await Person.query(trx).insert({ firstName: 'Jennifer', lastName: 'Lawrence' }); const scrappy = await jennifer .$relatedQuery('pets', trx) .insert({ name: 'Scrappy' }); return scrappy; }); console.log('Great success! Both Jennifer and Scrappy were inserted'); } catch (err) { console.log('Something went wrong. Neither Jennifer nor Scrappy were inserted'); }startTransaction():手动控制事务生命周期
const trx = await Person.startTransaction(trxOrKnex);等价于objection.transaction.start(Model1.knex())(源码 lib/transaction.js#L64-L88)。它只负责创建并解析出trx,提交/回滚由你手动控制:
const trx = await Person.startTransaction(); try { await Person.query(trx).insert(person1); await Person.query(trx).insert(person2); await Person.query(trx).patch(person3).where('id', person3.id); await trx.commit(); } catch (err) { await trx.rollback(); throw err; }完整的全局transaction()函数(支持传入多个模型类自动绑定事务)见 lib/transaction.js,事务指南见 doc/guide/transactions.md。
四、八个静态钩子(hook):before/after 生命周期
objection.js 为查询的四大操作(find、insert、update、delete)各提供一对静态钩子,均接收 StaticHookArguments 类型的args:
| 钩子 | 触发时机 | 返回值 |
|---|---|---|
beforeFind(args) | find 查询执行前 | 任意值,被忽略 |
afterFind(args) | find 查询执行后 | 非undefined时作为查询返回值 |
beforeInsert(args) | insert 查询执行前 | 任意值,被忽略 |
afterInsert(args) | insert 查询执行后 | 非undefined时作为查询返回值 |
beforeUpdate(args) | update 与 patch 查询执行前 | 任意值,被忽略 |
afterUpdate(args) | update 与 patch 查询执行后 | 非undefined时作为查询返回值 |
beforeDelete(args) | delete 查询执行前 | 任意值,被忽略 |
afterDelete(args) | delete 查询执行后 | 非undefined时作为查询返回值 |
定义方式如下(以 beforeFind 为例,其余七个写法完全一致):
class Person extends Model { static beforeFind(args) {} }StaticHookArguments在 lib/queryBuilder/StaticHookArguments.js 中实现,通常包含builder(当前查询构建器)与(after 钩子中的)result。以 find 为例,lib/queryBuilder/operations/FindOperation.js 在查询执行前调用builder.modelClass().beforeFind(args),执行后调用afterFind(args)并允许其返回值覆盖结果;insert 的对应逻辑见 lib/queryBuilder/operations/InsertOperation.js,update/patch 见 lib/queryBuilder/operations/UpdateOperation.js,delete 见 lib/queryBuilder/operations/DeleteOperation.js。注意这些静态钩子与实例级$beforeInsert/$afterInsert等钩子(见 lib/model/Model.js#L159-L185)是两套独立的机制,静态钩子面向"类"作用于所有查询,详见 doc/guide/hooks.md。
五、static bindKnex()与static bindTransaction():多库与事务绑定
bindKnex():绑定模型到指定连接
const BoundPerson = Person.bindKnex(transactionOrKnex);| 参数 | 类型 | 说明 |
|---|---|---|
| transactionOrKnex | object | 要绑定的 knex 实例或事务 |
返回值:一个匿名模型子类构造器(Constructor<? extends Model>)。典型场景是多租户系统中把同一模型类绑定到多个数据库,详见 doc/recipes/multitenancy-using-multiple-databases.md。
const knex1 = require('knex')({ client: 'sqlite3', connection: { filename: 'database1.db' } }); const knex2 = require('knex')({ client: 'sqlite3', connection: { filename: 'database2.db' } }); SomeModel.knex(null); const BoundModel1 = SomeModel.bindKnex(knex1); const BoundModel2 = SomeModel.bindKnex(knex2); // 抛出异常:knex 实例为 null await SomeModel.query(); // 正常工作 const models = await BoundModel1.query(); console.log(models[0] instanceof SomeModel); // --> true console.log(models[0] instanceof BoundModel1); // --> true const models = await BoundModel2.query(); console.log(models[0] instanceof SomeModel); // --> true console.log(models[0] instanceof BoundModel2); // --> true实现层面,lib/model/modelBindKnex.js 通过inheritModel(modelClass)创建匿名子类、复制隐藏属性、调用BoundModelClass.knex(knex),并把绑定结果缓存在knex.$$objection.boundModels(以modelClass.uniqueTag()为键);同时bindRelations会为绑定类重设所有关联的 knex,保证BoundModel1的关联查询也走knex1。
bindTransaction():事务绑定别名
bindTransaction是bindKnex的别名(源码 lib/model/Model.js#L567-L569)。它常用于"把模型绑定到事务"这一模式,下面两种写法 100% 等价:
const { transaction } = require('objection'); const Person = require('./models/Person'); await transaction(Person.knex(), async trx => { const TransactingPerson = Person.bindTransaction(trx); await TransactingPerson.query().insert({ firstName: 'Jennifer' }); return TransactingPerson.query() .patch({ lastName: 'Lawrence' }) .where('id', jennifer.id); });const { transaction } = require('objection'); const Person = require('./models/Person'); await transaction(Person, async TransactingPerson => { await TransactingPerson.query().insert({ firstName: 'Jennifer' }); return TransactingPerson.query() .patch({ lastName: 'Lawrence' }) .where('id', jennifer.id); });第二种写法中,全局transaction()会自动把所有传入的模型类bindTransaction(trx)再传给回调(见 lib/transaction.js#L48-L60),因此回调第一参数直接就是绑定过事务的模型类。事务绑定模式的详细说明见 doc/guide/transactions.md。
六、static fromJson()与static fromDatabaseJson():模型实例化
fromJson():从业务 JSON 创建实例
const person = Person.fromJson(json, opt);| 参数 | 类型 | 说明 |
|---|---|---|
| json | Object | 用于创建模型的 JSON 对象 |
| opt | ModelOptions | 选项,例如{ skipValidation: true } |
流程(见 lib/model/Model.js#L262-L266):new this()创建实例后调用model.$setJson(json, options)。如果模型定义了jsonSchema,输入会先通过校验(失败抛出异常),随后 JSON 会经过$parseJson钩子再落到实例上。这与数据库读取是两条路径,模型数据生命周期详见 doc/api/model/overview.md。
const jennifer = Person.fromJson({ firstName: 'Jennifer' }); // 跳过校验 const jennifer = Person.fromJson( { firstName: 'Jennifer' }, { skipValidation: true } );fromDatabaseJson():从数据库行创建实例
const person = Person.fromDatabaseJson(row);| 参数 | 类型 | 说明 |
|---|---|---|
| row | Object | 数据库返回的一行数据 |
与fromJson不同(见 lib/model/Model.js#L268-L272):不做任何校验,输入按数据库格式处理(列名 → 属性名的映射、JSON 属性解析等,见 lib/model/modelColPropMap.js)。读取数据库时 objection.js 内部正是用它创建实例,因此数据库里的脏数据不会被校验拦截。
七、static modifierNotFound():兜底处理未知 modifier
class BaseModel extends Model { static modifierNotFound(builder, modifier) { const { properties } = this.jsonSchema; if (properties && modifier in properties) { builder.select(modifier); } else { super.modifierNotFound(builder, modifier); } } }| 参数 | 类型 | 说明 |
|---|---|---|
| builder | QueryBuilder | 要应用 modifier 的查询构建器 |
| modifier | string | 未知 modifier 的名称 |
当某处使用了未注册的 modifier(例如withGraphFetched中的过滤函数、.modify())时,该钩子会被调用。默认实现直接抛ModifierNotFoundError(见 lib/model/Model.js#L329-L331 与 lib/model/ModifierNotFoundError.js)。覆写后既可借助builder自行处理(如上例把 jsonSchema 中的属性名当作可 select 的列),也可调用super.modifierNotFound(builder, modifier)保留默认抛错行为。
八、static createValidator():自定义校验器
class BaseModel extends Model { static createValidator() { return new MyCustomValidator(); } }该方法每个模型类只调用一次(由getValidator()配合cachedGet缓存,见 lib/model/Model.js#L365-L367 与 lib/model/Model.js#L819-L829),返回负责全部校验工作的 Validator 实例。自定义校验器不一定要基于 jsonSchema,只要实现Validator接口即可;也可以多个模型共享同一个实例:
const validator = new MyCustomValidator(); class BaseModel extends Model { static createValidator() { return validator; } }默认实现(源码 lib/model/Model.js#L318-L327)返回AjvValidator,其构造选项如下(对应 lib/model/AjvValidator.js):
const AjvValidator = require('objection').AjvValidator; class Model { static createValidator() { return new AjvValidator({ onCreateAjv: ajv => { // 在这里修改 Ajv 实例,例如添加自定义关键字 }, options: { allErrors: true, // 收集所有错误而非首个 validateSchema: false, // 不校验 schema 本身 ownProperties: true, v5: true // 启用草案 v5 特性 } }); } }自定义校验的完整示例见 doc/recipes/custom-validation.md,AjvValidator 单元测试见 tests/unit/model/AjvValidator.js。
九、错误工厂:createNotFoundError()与createValidationError()
createNotFoundError()
class BaseModel extends Model { static createNotFoundError(queryContext, props) { return new MyCustomNotFoundError({ ...props, modelClass: this }); } }| 参数 | 类型 | 说明 |
|---|---|---|
| queryContext | Object | 产生空结果的查询上下文对象(见.context()) |
| props | any | 传给错误类构造器的数据 |
返回值:Error,默认是 NotFoundError。该错误由throwIfNotFound()抛出(见 doc/api/query-builder/other-methods.md)。默认实现:
class Model { static createNotFoundError(queryContext, props) { return new this.NotFoundError({ ...props, modelClass: this }); } }createValidationError()
class BaseModel extends Model { static createValidationError({ type, message, data }) { return new MyCustomValidationError({ type, message, data, modelClass: this }); } }返回值:Error,默认是 ValidationError(源码见 lib/model/Model.js#L337-L339)。校验失败时由该工厂创建错误;其产物无需实现任何固定接口,因为 objection.js 只抛出这些错误而从不捕获它们。自定义错误类型的完整实践见 doc/recipes/error-handling.md。
十、static fetchGraph():为已加载模型拉取关联
const queryBuilder = Person.fetchGraph(models, expression, options);| 参数 | 类型 | 说明 |
|---|---|---|
| models | Array<Model | Object> | 模型实例或 POJO,支持数组、单个实例/单个 POJO |
| expression | string | RelationExpression | 关联表达式 |
| options | FetchGraphOptions | 可选选项 |
返回值:QueryBuilder。它是对"先查模型、再补关联"场景的封装,与withGraphFetched共享同一套关联表达式语法:
const people = await Person.fetchGraph([person1, person2], 'children.pets'); const person1 = people[0]; const person2 = people[1];表达式中的圆括号可以给每个关联指定 modifier 过滤函数:
const people = await Person.fetchGraph( [person1, person2], ` children(orderByAge).[ pets(onlyDogs, orderByName), movies ] ` ).modifiers({ orderByAge(builder) { builder.orderBy('age'); }, orderByName(builder) { builder.orderBy('name'); }, onlyDogs(builder) { builder.where('species', 'dog'); } });实现层面(lib/model/Model.js#L632-L638)会先ensureModelArray把 POJO 转成模型实例,然后.withGraphFetched(expression, options)执行关联加载,最后.runAfter保证输入是数组时返回数组、单个对象时返回单个对象。更多关联表达式语法见 doc/api/query-builder/eager-methods.md。
十一、static traverse()/static traverseAsync():遍历关联树
以异步版本为例,它递归遍历一个(或一组)模型实例的整个关联树,对每个模型(包括输入模型本身)调用回调;回调参数为(model, parentModel, relationName)——后两个参数在模型不属于任何关联时为undefined。可选的第一个参数filterConstructor用于只处理特定类别的模型。
const models = await SomeModel.query(); // 两种等价调用方式 await Model.traverseAsync(models, async (model, parentModel, relationName) => { await doSomething(model); }); const persons = await Person.query(); Model.traverseAsync( Person, persons, async (person, parentModel, relationName) => { await doSomethingWithPerson(person); } ); // 单个模型实例也可以 const person = await Person.query(); await Person.traverseAsync(person, async (model, parentModel, relationName) => { await doSomething(model); });源码 lib/model/Model.js#L640-L676 通过visitModels(见 lib/model/modelVisitor.js)遍历,异步版本用promiseMap并发执行回调(并发度由getConcurrency(knex)决定,默认 4,MSSQL 为 1,见 lib/model/Model.js#L381-L402)。
十二、元数据与名称映射:getRelations()、fetchTableMetadata()、tableMetadata()
getRelations()
const relations = Person.getRelations();返回relationMappings中每个关联对应的 Relation 实例集合(键为关联名,值为 Relation 对象)。实现见 lib/model/Model.js#L462-L470,主要供插件开发者与通用工具使用。
fetchTableMetadata() / tableMetadata()
const metadata = await Person.fetchTableMetadata(opt); const metadata = Person.tableMetadata(opt);fetchTableMetadata(异步)获取并缓存表元数据;大多数场景 objection.js 并不需要它,但withGraphJoined等方法会用到。它按需触发、结果缓存在$$tableMetadataMap 中(lib/model/modelTableMetadata.js),因此首次需要元数据的查询性能可能不稳定——如果在意,可在应用启动时对所有模型预热调用一次。若你覆写了tableMetadata()返回自定义对象,fetchTableMetadata将直接Promise.resolve(this.tableMetadata())而不发数据库查询(底层默认通过.columnInfo()取列名,见 lib/model/modelTableMetadata.js#L44-L63)。
tableMetadata(同步)直接读缓存,可覆写以跳过数据库探测,例如利用 jsonSchema 的属性信息:
class Person extends Model { static tableMetadata() { return { columns: Object.keys(this.jsonSchema.properties) }; } }columnNameToPropertyName() / propertyNameToColumnName()
这两个方法把列名与属性名互相转换,转换过程会经过columnNameMappers以及$parseDatabaseJson/$formatDatabaseJson钩子(实现见 lib/model/modelColPropMap.js,并带有基于cachedGet的缓存,见 lib/model/Model.js#L408-L430)。若模型配置了snakeCaseMappers():
const propName = Person.columnNameToPropertyName('foo_bar'); console.log(propName); // --> 'fooBar' const columnName = Person.propertyNameToColumnName('fooBar'); console.log(columnName); // --> 'foo_bar'十三、SQL 工具方法:raw()、ref()、fn()、knexQuery()
| 方法 | 等价于 | 说明 |
|---|---|---|
Person.raw(...args) | Person.knex().raw(...args) | 执行原始 SQL |
Person.ref(name) | 绑定模型类的 ReferenceBuilder | 引用列时自动带上正确的表名(含别名) |
Person.fn | Person.knex().fn | knex 的 SQL 函数构造器 |
Person.knexQuery() | Person.knex().table(Person.tableName) | 直接以该模型对应的表构建 knex 查询 |
源码实现见 lib/model/Model.js#L537-L552。ref的关键价值是引用自动带表名,即使表被起了别名也能正确解析:
const { ref } = Person; await Person.query().where(ref('firstName'), 'Jennifer');select "persons".* from "persons" where "persons"."firstName" = 'Jennifer'const { ref } = Person; await Person.query() .alias('p') .where(ref('firstName'), 'Jennifer');select "p".* from "persons" as "p" where "p"."firstName" = 'Jennifer'以下两种写法完全等价:
const { ref } = Person; await Person.query().where(ref('firstName'), 'Jennifer');await Person.query().where(Person.ref('firstName'), 'Jennifer');总结
objection.js 的模型静态方法构成了完整的"模型能力面":query()/relatedQuery()是所有数据访问的统一入口,knex()/transaction()/startTransaction()/bindKnex()/bindTransaction()覆盖连接、事务与多库绑定,八个静态钩子与createValidator()/createNotFoundError()/createValidationError()提供了可插拔的生命周期与错误/校验定制点,fromJson()/fromDatabaseJson()定义了数据进出模型的边界,而fetchGraph()/traverseAsync()/getRelations()/fetchTableMetadata()则服务于关联加载、批量处理与元数据场景。理解这些方法在 lib/model/Model.js 中的实现,你将能更自信地定制模型行为、排查查询问题,并把 objection.js 的能力发挥到极致。
- 数据库
- 后端
【免费下载链接】objection.js
An SQL-friendly ORM for Node.js
相关推荐
Eino流式处理4大范式详解:Invoke、Stream、Collect与Transform实战指南
Eino流式处理4大范式详解:Invoke、Stream、Collect与Transform实战指南 Eino 是 Go 语言编写的 LLM 应用开发框架,其流
人工智能大模型AI AgentRAGBetterScroll 2.x 实例 API 完全指南:属性、方法、事件与钩子体系详解
BetterScroll 2.x 实例 API 完全指南:属性、方法、事件与钩子体系详解 BetterScroll 2.x 是一个基于原生 JavaScript
前端UI组件KMS_VL_ALL_AIO 激活教程:10分钟用这款免费开源KMS工具一键激活Windows与Office并长期保持有效
KMS_VL_ALL_AIO 激活教程:10分钟用这款免费开源KMS工具一键激活Windows与Office并长期保持有效 如果你也经历过这样的场景——辛苦找到
数据库后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考