@shipfox/api-runners 11.0.0 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +36 -0
- package/dist/db/job-executions.d.ts +10 -5
- package/dist/db/job-executions.d.ts.map +1 -1
- package/dist/db/job-executions.js +59 -23
- package/dist/db/job-executions.js.map +1 -1
- package/dist/db/rate-limits.d.ts +6 -15
- package/dist/db/rate-limits.d.ts.map +1 -1
- package/dist/db/rate-limits.js +17 -18
- package/dist/db/rate-limits.js.map +1 -1
- package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
- package/dist/presentation/routes/rate-limit.js +33 -54
- package/dist/presentation/routes/rate-limit.js.map +1 -1
- package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.js +3 -3
- package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/db/job-executions.test.ts +270 -22
- package/src/db/job-executions.ts +101 -32
- package/src/db/rate-limits.ts +30 -42
- package/src/presentation/routes/heartbeat.test.ts +3 -3
- package/src/presentation/routes/rate-limit.ts +33 -64
- package/src/presentation/subscribers/on-workflows-job-execution-timed-out.test.ts +16 -0
- package/src/presentation/subscribers/on-workflows-job-execution-timed-out.ts +3 -3
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,65 +1,44 @@
|
|
|
1
1
|
import { requireProvisionerContext } from '@shipfox/api-auth-context';
|
|
2
2
|
import { ClientError } from '@shipfox/node-fastify';
|
|
3
|
+
import { enforceRateLimit as enforceSharedRateLimit } from '@shipfox/node-rate-limit';
|
|
3
4
|
import { config } from '#config.js';
|
|
4
|
-
import { checkRunnersRateLimit
|
|
5
|
+
import { checkRunnersRateLimit } from '#core/rate-limit.js';
|
|
5
6
|
import { getRunnerContext } from '#presentation/auth/index.js';
|
|
6
7
|
function routeName(request) {
|
|
7
8
|
return request.routeOptions.url ?? request.url.split('?')[0] ?? 'unknown';
|
|
8
9
|
}
|
|
9
10
|
async function enforceRateLimit(params) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
if (error instanceof RunnersRateLimitUnavailableError) {
|
|
43
|
-
params.request.log.error({
|
|
44
|
-
action: error.action,
|
|
45
|
-
scope: error.scope,
|
|
46
|
-
route: routeName(params.request),
|
|
47
|
-
identifierHmacPrefix: error.identifierHmacPrefix,
|
|
48
|
-
err: error
|
|
49
|
-
}, 'Runners rate limiter unavailable');
|
|
50
|
-
throw new ClientError('Runners rate limiter unavailable', 'runners-rate-limit-unavailable', {
|
|
51
|
-
status: 503,
|
|
52
|
-
data: {
|
|
53
|
-
action: error.action,
|
|
54
|
-
scope: error.scope,
|
|
55
|
-
route: routeName(params.request),
|
|
56
|
-
identifierHmacPrefix: error.identifierHmacPrefix
|
|
57
|
-
},
|
|
58
|
-
cause: error
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
11
|
+
await enforceSharedRateLimit({
|
|
12
|
+
request: params.request,
|
|
13
|
+
reply: params.reply,
|
|
14
|
+
check: ()=>checkRunnersRateLimit({
|
|
15
|
+
action: params.action,
|
|
16
|
+
scope: params.scope,
|
|
17
|
+
identifier: params.identifier,
|
|
18
|
+
limit: params.limit,
|
|
19
|
+
windowSeconds: params.windowSeconds
|
|
20
|
+
}),
|
|
21
|
+
route: routeName(params.request),
|
|
22
|
+
unavailableCode: 'runners-rate-limit-unavailable',
|
|
23
|
+
unavailableMessage: 'Runners rate limiter unavailable',
|
|
24
|
+
setRetryAfter: (reply, retryAfterSeconds)=>{
|
|
25
|
+
reply.header('Retry-After', String(retryAfterSeconds));
|
|
26
|
+
},
|
|
27
|
+
logWarn: (request, context)=>{
|
|
28
|
+
request.log.warn(context, 'Runners rate limit blocked request');
|
|
29
|
+
},
|
|
30
|
+
logError: (request, context)=>{
|
|
31
|
+
request.log.error(context, 'Runners rate limiter unavailable');
|
|
32
|
+
},
|
|
33
|
+
createClientError: (presentation, cause)=>new ClientError(presentation.message, presentation.code, {
|
|
34
|
+
status: presentation.status,
|
|
35
|
+
...presentation.details ? {
|
|
36
|
+
details: presentation.details
|
|
37
|
+
} : {},
|
|
38
|
+
data: presentation.data,
|
|
39
|
+
cause
|
|
40
|
+
})
|
|
41
|
+
});
|
|
63
42
|
}
|
|
64
43
|
export function createProvisionerMintRateLimitPreHandler() {
|
|
65
44
|
return async (request, reply)=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/rate-limit.ts"],"sourcesContent":["import {requireProvisionerContext} from '@shipfox/api-auth-context';\nimport {ClientError, type FastifyReply, type FastifyRequest} from '@shipfox/node-fastify';\nimport {config} from '#config.js';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/rate-limit.ts"],"sourcesContent":["import {requireProvisionerContext} from '@shipfox/api-auth-context';\nimport {ClientError, type FastifyReply, type FastifyRequest} from '@shipfox/node-fastify';\nimport {enforceRateLimit as enforceSharedRateLimit} from '@shipfox/node-rate-limit';\nimport {config} from '#config.js';\nimport {checkRunnersRateLimit} from '#core/rate-limit.js';\nimport {getRunnerContext} from '#presentation/auth/index.js';\n\nfunction routeName(request: FastifyRequest): string {\n return request.routeOptions.url ?? request.url.split('?')[0] ?? 'unknown';\n}\n\nasync function enforceRateLimit(params: {\n request: FastifyRequest;\n reply: FastifyReply;\n action: 'provisioner-mint' | 'ephemeral-register';\n scope: 'provisioner' | 'ephemeral-token';\n identifier: string;\n limit: number;\n windowSeconds: number;\n}): Promise<void> {\n await enforceSharedRateLimit({\n request: params.request,\n reply: params.reply,\n check: () =>\n checkRunnersRateLimit({\n action: params.action,\n scope: params.scope,\n identifier: params.identifier,\n limit: params.limit,\n windowSeconds: params.windowSeconds,\n }),\n route: routeName(params.request),\n unavailableCode: 'runners-rate-limit-unavailable',\n unavailableMessage: 'Runners rate limiter unavailable',\n setRetryAfter: (reply, retryAfterSeconds) => {\n reply.header('Retry-After', String(retryAfterSeconds));\n },\n logWarn: (request, context) => {\n request.log.warn(context, 'Runners rate limit blocked request');\n },\n logError: (request, context) => {\n request.log.error(context, 'Runners rate limiter unavailable');\n },\n createClientError: (presentation, cause) =>\n new ClientError(presentation.message, presentation.code, {\n status: presentation.status,\n ...(presentation.details ? {details: presentation.details} : {}),\n data: presentation.data,\n cause,\n }),\n });\n}\n\nexport function createProvisionerMintRateLimitPreHandler() {\n return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const {provisionerTokenId} = requireProvisionerContext(request);\n await enforceRateLimit({\n request,\n reply,\n action: 'provisioner-mint',\n scope: 'provisioner',\n identifier: provisionerTokenId,\n limit: config.PROVISIONER_MINT_RATE_LIMIT_MAX_REQUESTS,\n windowSeconds: config.PROVISIONER_MINT_RATE_LIMIT_WINDOW_SECONDS,\n });\n };\n}\n\nexport function createEphemeralRegisterRateLimitPreHandler() {\n return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n const runner = getRunnerContext(request);\n if (runner.kind !== 'ephemeral') return;\n\n await enforceRateLimit({\n request,\n reply,\n action: 'ephemeral-register',\n scope: 'ephemeral-token',\n identifier: runner.ephemeralTokenId,\n limit: config.EPHEMERAL_REGISTER_RATE_LIMIT_MAX_REQUESTS,\n windowSeconds: config.EPHEMERAL_REGISTER_RATE_LIMIT_WINDOW_SECONDS,\n });\n };\n}\n"],"names":["requireProvisionerContext","ClientError","enforceRateLimit","enforceSharedRateLimit","config","checkRunnersRateLimit","getRunnerContext","routeName","request","routeOptions","url","split","params","reply","check","action","scope","identifier","limit","windowSeconds","route","unavailableCode","unavailableMessage","setRetryAfter","retryAfterSeconds","header","String","logWarn","context","log","warn","logError","error","createClientError","presentation","cause","message","code","status","details","data","createProvisionerMintRateLimitPreHandler","provisionerTokenId","PROVISIONER_MINT_RATE_LIMIT_MAX_REQUESTS","PROVISIONER_MINT_RATE_LIMIT_WINDOW_SECONDS","createEphemeralRegisterRateLimitPreHandler","runner","kind","ephemeralTokenId","EPHEMERAL_REGISTER_RATE_LIMIT_MAX_REQUESTS","EPHEMERAL_REGISTER_RATE_LIMIT_WINDOW_SECONDS"],"mappings":"AAAA,SAAQA,yBAAyB,QAAO,4BAA4B;AACpE,SAAQC,WAAW,QAA+C,wBAAwB;AAC1F,SAAQC,oBAAoBC,sBAAsB,QAAO,2BAA2B;AACpF,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,qBAAqB,QAAO,sBAAsB;AAC1D,SAAQC,gBAAgB,QAAO,8BAA8B;AAE7D,SAASC,UAAUC,OAAuB;IACxC,OAAOA,QAAQC,YAAY,CAACC,GAAG,IAAIF,QAAQE,GAAG,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI;AAClE;AAEA,eAAeT,iBAAiBU,MAQ/B;IACC,MAAMT,uBAAuB;QAC3BK,SAASI,OAAOJ,OAAO;QACvBK,OAAOD,OAAOC,KAAK;QACnBC,OAAO,IACLT,sBAAsB;gBACpBU,QAAQH,OAAOG,MAAM;gBACrBC,OAAOJ,OAAOI,KAAK;gBACnBC,YAAYL,OAAOK,UAAU;gBAC7BC,OAAON,OAAOM,KAAK;gBACnBC,eAAeP,OAAOO,aAAa;YACrC;QACFC,OAAOb,UAAUK,OAAOJ,OAAO;QAC/Ba,iBAAiB;QACjBC,oBAAoB;QACpBC,eAAe,CAACV,OAAOW;YACrBX,MAAMY,MAAM,CAAC,eAAeC,OAAOF;QACrC;QACAG,SAAS,CAACnB,SAASoB;YACjBpB,QAAQqB,GAAG,CAACC,IAAI,CAACF,SAAS;QAC5B;QACAG,UAAU,CAACvB,SAASoB;YAClBpB,QAAQqB,GAAG,CAACG,KAAK,CAACJ,SAAS;QAC7B;QACAK,mBAAmB,CAACC,cAAcC,QAChC,IAAIlC,YAAYiC,aAAaE,OAAO,EAAEF,aAAaG,IAAI,EAAE;gBACvDC,QAAQJ,aAAaI,MAAM;gBAC3B,GAAIJ,aAAaK,OAAO,GAAG;oBAACA,SAASL,aAAaK,OAAO;gBAAA,IAAI,CAAC,CAAC;gBAC/DC,MAAMN,aAAaM,IAAI;gBACvBL;YACF;IACJ;AACF;AAEA,OAAO,SAASM;IACd,OAAO,OAAOjC,SAAyBK;QACrC,MAAM,EAAC6B,kBAAkB,EAAC,GAAG1C,0BAA0BQ;QACvD,MAAMN,iBAAiB;YACrBM;YACAK;YACAE,QAAQ;YACRC,OAAO;YACPC,YAAYyB;YACZxB,OAAOd,OAAOuC,wCAAwC;YACtDxB,eAAef,OAAOwC,0CAA0C;QAClE;IACF;AACF;AAEA,OAAO,SAASC;IACd,OAAO,OAAOrC,SAAyBK;QACrC,MAAMiC,SAASxC,iBAAiBE;QAChC,IAAIsC,OAAOC,IAAI,KAAK,aAAa;QAEjC,MAAM7C,iBAAiB;YACrBM;YACAK;YACAE,QAAQ;YACRC,OAAO;YACPC,YAAY6B,OAAOE,gBAAgB;YACnC9B,OAAOd,OAAO6C,0CAA0C;YACxD9B,eAAef,OAAO8C,4CAA4C;QACpE;IACF;AACF"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { logger } from '@shipfox/node-opentelemetry';
|
|
2
|
-
import {
|
|
2
|
+
import { reconcileTerminalJobExecution } from '#db/job-executions.js';
|
|
3
3
|
export async function onWorkflowsJobExecutionTimedOut(payload) {
|
|
4
4
|
logger().info({
|
|
5
5
|
jobId: payload.jobId,
|
|
6
6
|
jobExecutionId: payload.jobExecutionId,
|
|
7
7
|
workflowRunAttemptId: payload.workflowRunAttemptId
|
|
8
|
-
}, '
|
|
9
|
-
await
|
|
8
|
+
}, 'Reconciling runner state for timed-out job execution');
|
|
9
|
+
await reconcileTerminalJobExecution({
|
|
10
10
|
jobExecutionId: payload.jobExecutionId
|
|
11
11
|
});
|
|
12
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/subscribers/on-workflows-job-execution-timed-out.ts"],"sourcesContent":["import type {WorkflowsJobExecutionTimedOutEventDto} from '@shipfox/api-workflows-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/subscribers/on-workflows-job-execution-timed-out.ts"],"sourcesContent":["import type {WorkflowsJobExecutionTimedOutEventDto} from '@shipfox/api-workflows-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {reconcileTerminalJobExecution} from '#db/job-executions.js';\n\nexport async function onWorkflowsJobExecutionTimedOut(\n payload: WorkflowsJobExecutionTimedOutEventDto,\n): Promise<void> {\n logger().info(\n {\n jobId: payload.jobId,\n jobExecutionId: payload.jobExecutionId,\n workflowRunAttemptId: payload.workflowRunAttemptId,\n },\n 'Reconciling runner state for timed-out job execution',\n );\n await reconcileTerminalJobExecution({jobExecutionId: payload.jobExecutionId});\n}\n"],"names":["logger","reconcileTerminalJobExecution","onWorkflowsJobExecutionTimedOut","payload","info","jobId","jobExecutionId","workflowRunAttemptId"],"mappings":"AACA,SAAQA,MAAM,QAAO,8BAA8B;AACnD,SAAQC,6BAA6B,QAAO,wBAAwB;AAEpE,OAAO,eAAeC,gCACpBC,OAA8C;IAE9CH,SAASI,IAAI,CACX;QACEC,OAAOF,QAAQE,KAAK;QACpBC,gBAAgBH,QAAQG,cAAc;QACtCC,sBAAsBJ,QAAQI,oBAAoB;IACpD,GACA;IAEF,MAAMN,8BAA8B;QAACK,gBAAgBH,QAAQG,cAAc;IAAA;AAC7E"}
|