perf(api-gateway): cache SRC endpoint claims
This commit is contained in:
@@ -76,12 +76,17 @@ export class TypertGatewayError extends Error {
|
||||
export class TypertGatewayService extends Service implements TypertGateway {
|
||||
static inject = ['typert']
|
||||
|
||||
private srcClaims: ReadonlySet<string> | undefined
|
||||
|
||||
/**
|
||||
* Register the Gateway against the active TypeRT registry.
|
||||
* @param ctx - owning Host Context with TypeRT registry access.
|
||||
*/
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'typertGateway')
|
||||
ctx.on('internal/service', () => {
|
||||
this.srcClaims = undefined
|
||||
})
|
||||
ctx.inject(['connection'], (connectionCtx) => {
|
||||
connectionCtx.connection.rpc.intercept(
|
||||
'/api',
|
||||
@@ -95,18 +100,26 @@ export class TypertGatewayService extends Service implements TypertGateway {
|
||||
private claimsEndpoint(endpoint: string): boolean {
|
||||
const segments = endpoint.split('/')
|
||||
if (segments.length !== 2 || segments[0] === '' || segments[1] === '') return false
|
||||
const [namespace, method] = segments as [string, string]
|
||||
if (this.ctx.typert.local.get(endpoint) !== undefined || this.ctx.typert.local.hasSeen(endpoint)) return true
|
||||
this.srcClaims ??= this.collectSrcClaims()
|
||||
return this.srcClaims.has(endpoint)
|
||||
}
|
||||
|
||||
private collectSrcClaims(): ReadonlySet<string> {
|
||||
const claims = new Set<string>()
|
||||
for (const [serviceKey, definition] of Object.entries(this.ctx.reflect.props)) {
|
||||
if (definition.type !== 'service') continue
|
||||
const receiver = this.ctx.get(serviceKey) as unknown
|
||||
if (!isObject(receiver)) continue
|
||||
const original = originalOf(receiver)
|
||||
const binding = Reflect.get(original, 'typertGateway') as unknown
|
||||
if (!isObject(binding) || Reflect.get(binding, 'namespace') !== namespace) continue
|
||||
if (remoteMethods(original).some(candidate => (candidate.exportName ?? candidate.method) === method)) return true
|
||||
if (!isObject(binding) || typeof Reflect.get(binding, 'namespace') !== 'string') continue
|
||||
const namespace = Reflect.get(binding, 'namespace') as string
|
||||
for (const candidate of remoteMethods(original)) {
|
||||
claims.add(endpointOf(namespace, candidate.exportName ?? candidate.method))
|
||||
}
|
||||
}
|
||||
return false
|
||||
return claims
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -314,6 +314,25 @@ class NoBindingService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
class ObservedClaimService extends Service {
|
||||
private readonly binding = bindTypeRTGateway(this, 'observedClaim', { namespace: 'observed-claim' })
|
||||
bindingReads = 0
|
||||
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'observedClaim')
|
||||
}
|
||||
|
||||
get typertGateway() {
|
||||
this.bindingReads += 1
|
||||
return this.binding
|
||||
}
|
||||
|
||||
@Remote
|
||||
run(value: string): string {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
class MissingMethodService extends Service {
|
||||
readonly typertGateway = bindTypeRTGateway(this, 'missingMethod', { namespace: 'missing-method' })
|
||||
|
||||
@@ -949,6 +968,36 @@ describe('TypertGatewayService', () => {
|
||||
expect(connection.handler).toBeUndefined()
|
||||
})
|
||||
|
||||
it('caches SRC ownership until the Cordis Service set changes', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(TypertRegistry)
|
||||
await ctx.plugin(FakeConnectionService)
|
||||
await ctx.plugin(TypertGatewayService)
|
||||
const observedFiber = ctx.plugin(ObservedClaimService)
|
||||
await observedFiber
|
||||
const connection = rawConnection(ctx)
|
||||
const observed = ctx.get('observedClaim') as unknown as ObservedClaimService & {
|
||||
[symbols.original]?: ObservedClaimService
|
||||
}
|
||||
const service = observed[symbols.original] ?? observed
|
||||
|
||||
expect(connection.matches?.('legacy/list')).toBe(false)
|
||||
expect(connection.matches?.('legacy/list')).toBe(false)
|
||||
expect(service.bindingReads).toBe(1)
|
||||
expect(connection.matches?.('observed-claim/run')).toBe(true)
|
||||
expect(connection.matches?.('observed-claim/run')).toBe(true)
|
||||
expect(service.bindingReads).toBe(1)
|
||||
|
||||
const unrelatedFiber = ctx.plugin(NoBindingService)
|
||||
await unrelatedFiber
|
||||
expect(connection.matches?.('legacy/list')).toBe(false)
|
||||
expect(service.bindingReads).toBe(2)
|
||||
|
||||
await observedFiber.dispose()
|
||||
expect(connection.matches?.('observed-claim/run')).toBe(false)
|
||||
await unrelatedFiber.dispose()
|
||||
})
|
||||
|
||||
it('dispatches claimed invocations through /api and leaves unclaimed endpoints to its fallback', async () => {
|
||||
const ctx = new Context().extend({ fixtureScope: 'http-caller' })
|
||||
const routes: WebRoute[] = []
|
||||
|
||||
Reference in New Issue
Block a user