林克-服务器-实用
为应用提供一套具有林克-服务器后端的公用事业.
服务器
import { Server } from 'lincd-server-utils/lib/utils/Server';
从前端呼叫服务器 .
调用失败
默认情况下,一个无法使用非-2xx响应的呼叫会记录一个警告并解决 undefined。 。 。 .
拒绝,通过一个 CallConfig 与 rejectOnError: true 作为方法 :
import { Server, ServerCallError } from '@_linked/server-utils/utils/Server';
try {
const result = await Server.call(this, { method: 'selectQuery', rejectOnError: true }, query);
} catch (err) {
if (ServerCallError.is(err)) {
err.status; // e.g. 500 (provider threw) or 501 (no provider for the call)
err.message; // the server's {error} message, or the status text
}
}
随着选择进入,一个成功的呼叫仍然解决 任何提供者返回,包括 null 或者说 undefined。 。 。 .
在后门上, Server.call 直接调用本地服务器并遵循相同的规则 :
- 无供养. 一个没有提供者处理的电话解决了
undefined默认。 与rejectOnError它以501的地位拒绝. - 供养所掷. 一个总是拒绝的提供者方法。 与
rejectOnError拒绝是ServerCallError状态 500,原始错误保留为cause。 。 。 .
认证钩
认证软件包通过注册让服务器呼叫周围的会话存在 AuthHandler. 服务器使用器对符号一无所知: 处理器更改默认信头(Server.addDefaultHeaders 编号 : Server.removeDefaultHeaders)),而每个请求头都是在钩子运行后所建.
import { Server } from '@_linked/server-utils/utils/Server';
Server.setAuthHandler({
// before every request: e.g. refresh a token that has already expired
async beforeRequest(url, init) {
if (tokenExpired()) await refresh(); // refresh() calls Server.addDefaultHeaders(...)
},
// after a 401: resolve true to send the request again, once, with the current default headers
async onUnauthorized(url, response) {
return refresh();
},
});
- 打电话来. HTTP的每个电话:
Server.call, (中文(简体) ),Server.customPost财务报告和审定财务报表Server.callCustomShapeMethod。直接针对后端本地服务器的电话不使用。 有一个处理器;设置另一个替换它null将取去. init是一个预览. 要更改发送的内容, 请更改默认标题 .- 401号. 403表示来电者是已知的且不允许的;刷新无济于事,所以不会传递给
onUnauthorized。 。 。 . - 每个电话最多一次重试. 重试的回复是给打电话的人的:
onUnauthorized再来一次 如果在请求飞行时默认头部发生变化(同时刷新了另一通电话),则401在一次重审时无需询问. - 货币. 重试状态属于每个呼叫. 做同时刷新 分享一次往返是处理者的工作.
- 扔钩子 被登录,并作为“无变化” /“不重试”.
- 反应行动没有经过反复研究. 一个回答的提供者
200反应行动(例如:@_linked/auth是因为ENFORCE_SIGNIN)已经运行了,并且可能在决定来电者不签名之前写过一些东西,所以再次发送请求并不安全. 反应行动将持续到Server.registerActionHandler。 。 。 .
callCustomShapeMethod 发送默认信头, 但 Content-Type (身体经常是) FormData,它需要浏览器来设定边界。 信头传递给它胜利。 它的躯体又被重试送去,所以使用一个能成为躯体的躯体(一串, FormData页:1 Blob),不是溪流.
客户端可以调用哪些提供者的方法
提供者方法可以达到 /call/... 只有在它被宣布的时候。 类型脚本 private 财务报告和审定财务报表 protected 在运行时间不存在,所以宣言是决定的。 有三个宣言,全部在 @_linked/server-utils/utils/callable (没有服务器导入, 可以安全加载客户端):
@callable('public' | 'signed-in')使用实例方法,或declareCallable(Cls, {method: level})(原始内容存档于2018-09-21) (英语). In plain JS.'user'是一个已贬值的别名,其名称是:'signed-in',对待完全像它.@internal()使用实例方法,或declareInternal(Cls, ['method'])。它也用于您从另一个软件包导入的类.- 终不宣说.
当客户称这个方法时 每个人会做什么:
| 声明 | 超过 HTTP |
|---|---|
@callable('public') | 任何人都可以打电话, 签名与否. |
@callable('signed-in') | 没有会话, 服务器会回答 401 在方法体运行之前。 机体从调用上下文读取用户,从不读取其论据. |
| 未申报 | 取决于 server.rpcExposure 内 @_linked/server: 内 warn 模式(默认)它运行一次并记录一次 enforce 模式 501,似乎该方法不存在. |
@internal() | 从未以任何方式通过 HTTP 发送( )501),即使它也被宣布为可调用. 后端代码仍然使用 Server.call。 。 。 . |
为登录用户采取行动的方法必须宣布 @callable('signed-in') 并读取用户 requireSessionUser()。 。 。 . 'public' 允许任何人进入,而参数中的用户id是客户端发送的任意信息.
import { BackendProvider } from '@_linked/server-utils/utils/BackendProvider';
import { callable, internal } from '@_linked/server-utils/utils/callable';
import { requireSessionUser } from '@_linked/server-utils/utils/CallContext';
export default class NotesBackendProvider extends BackendProvider {
@callable('public')
getPublicNotes() {
return loadPublicNotes();
}
@callable('signed-in')
getMyNotes() {
const user = requireSessionUser(); // the session's user account, not an argument
return loadNotesOf(user);
}
@internal()
purgeNotes(olderThan: Date) {
// only reachable from backend code
}
}
requireSessionUser() 丢出一个 401 ServerCallError 在HTTP呼叫中,没有一个会话,所以它也是对一种方法的检查,有人忘记了它.
一些规则值得了解:
- 只能宣告实例方法;静态方法或错误的页码名称丢出一个
TypeError。 。 。 . - 一个子类,在不重新宣布的情况下,将宣布的最严格级别保留在它的阶级链上(
'signed-in'结束'public') (中文(简体) ).@internal()被覆盖所继承,并且胜过可调用的声明(带有警告). - 后端到后端的呼叫( N)
Server.call)跳过这些检查,包括内部方法.
无用户工作 : runAsSystem
每个HTTP呼叫都运行在带有其请求的呼叫上下文中,因此一个呼叫的用户遵循它所期待的或计划的一切. 未为该用户完成的工作,如从请求内开始的工作、计时器或队列消费者,必须包入 runAsSystem,或以该请求的用户身份运行 :
import { runAsSystem } from '@_linked/server-utils/utils/CallContext';
runAsSystem(() => startNightlyReindex(), 'nightly reindex');
在系统内 requireSessionUser() 抛出平原 Error (没有用户可以接受);在那里,一个用户的行为得到该用户的明确通过. getCallContext() 返回时 {kind: 'http', request, response} 或者说 {kind: 'system', reason}。 。 。 .
链接电子邮件
从后端的任何地方发送邮件 .
确保先安装电子邮件客户端, 如 lincd-zeptomail。 。 。 .
通过添加 lincd-zeptomail 到您的项目( in package.json), 它会自动用作默认的电子邮件客户端 .
在此之后, 您可以从后端的任何地方发送邮件 .
LinkedEmail.sendEmail({
...
});
设置默认提供者
如果您想要执行电子邮件客户端, 您可以使用 LinkedEmail.setDefaultProvider 设置默认的电子邮件客户端.
您可以在您的后端提供者的构造器中做到这一点 。 以下是一个例子:
import { LinkedEmail } from 'lincd-server-utils/lib/utils/LinkedEmail';
// import your email client from this package
import { MyMailClient } from './utils/MyMailClient';
import { BackendProvider } from 'lincd-server-utils/lib/utils/BackendProvider';
export class MyBackendProvider extends BackendProvider {
constructor(s) {
super(s);
LinkedEmail.setDefaultProvider(new MyMailClient());
}
}
共享状态和错误品牌
此软件包的一个模块可以在一次运行中两次得到评价(Vite's SSR srunder and Node under
linked start,或两个浏览器捆绑。 呼叫者设置的州 -- Server.setLocalServer, (中文(简体) ),
addDefaultHeaders, (中文(简体) ), setAuthHandler, (中文(简体) ), registerActionHandler, (中文(简体) ), LinkedEmail.setDefaultProvider, (中文(简体) ),
实时更新日志,请求数据——保存在 globalThis 与 sharedState 从
@_linked/core,所以每个副本都能看到它. ServerCallError 标记为:
isLinkedError(err, ServerCallError) 从 @_linked/core (或 ServerCallError.is(err)(没有)
instanceof。新的代码遵循相同的规则;参见
共享状态和错误品牌。 。 。 .