grafbase-hooks 0.4.1

An SDK to implement hooks for the Grafbase Gateway
Documentation
1
2
3
4
5
6
7
8
9
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
package component:grafbase;

world hooks {
    // Error thrown when accessing the headers. Headers names or values
    // must not contain any special characters.
    enum header-error {
        // the given header value is not valid
        invalid-header-value,
        // the given header name is not valid
        invalid-header-name,
    }

    // Error variant sent if failing to write to access log.
    variant log-error {
        // The log channel is over capacity. The data is returned to the caller.
        channel-full(list<u8>),
        // The channel is closed.
        channel-closed,
    }

    // A context object is available in all hooks during the whole request
    // lifecycle. It can be used to store custom data in one hook and make it
    // available in the hooks executed later in the request.
    //
    // This resource provides mutable access to the context and is available only
    // in the gateway request hook.
    resource context {
        // Fetches a context value with the given name, if existing.
        get: func(name: string) -> option<string>;
        // Stores a context value with the given name.
        set: func(name: string, value: string);
        // Deletes a context value with the given name. Returns the value
        // if existing.
        delete: func(name: string) -> option<string>;
    }

    // The context as a read-only object.
    resource shared-context {
        // Fetches a context value with the given name, if existing.
        get: func(name: string) -> option<string>;
        // Gets the current trace-id.
        trace-id: func() -> string;
    }

    // Provides access to the request headers. Available in a mutable form
    // only in the gateway request hook.
    resource headers {
        // Gets a header value with the given name.
        get: func(name: string) -> option<string>;
        // Sets the header value with the given name. Returns an error if the given name
        // is not a valid header name.
        set: func(name: string, value: string) -> result<_, header-error>;
        // Deletes a header value with the given name.
        delete: func(name: string) -> option<string>;
        // Return all headers as a list of tuples.
        entries: func() -> list<tuple<string, string>>;
    }

    // Defines an edge in a type
    record edge-definition {
        // The name of the type the edge is part of
        parent-type-name: string,
        // The name of the field of this edge
        field-name: string,
    }

    // Defines a node
    record node-definition {
        // The name of the type of this node
        type-name: string,
    }

    // Info about an executed HTTP request.
    record executed-http-request {
        // The request method.
        method: string,
        // The request URL.
        url: string,
        // The response status code.
        status-code: u16,
        // The outputs of executed on-operation-response hooks for every operation of the request.
        on-operation-response-outputs: list<list<u8>>,
    }

    // An error returned from a field.
    record field-error {
        // The number of errors.
        count: u64,
        // The returned data is null.
        data-is-null: bool,
    }

    // An error from a GraphQL request.
    record request-error {
        // The number of errors.
        count: u64,
    }

    // A status of a GraphQL operation.
    variant graphql-response-status {
        // Request was successful.
        success,
        // A field returned an error.
        field-error(field-error),
        // A request error.
        request-error(request-error),
        // The request was refused.
        refused-request,
    }

    // Info about an executed operation.
    record executed-operation {
        // The name of the operation, if present.
        name: option<string>,
        // The operation document in sanitized form.
        document: string,
        // The time taken in preparing.
        prepare-duration-ms: u64,
        // True, if the plan was taken from cache.
        cached-plan: bool,
        // Time in milliseconds spent executing the operation.
        duration-ms: u64,
        // The status of the operation.
        status: graphql-response-status,
        // If queried any subgraphs, the outputs of on-subgraph-response hooks.
        // Will be empty if no subgraphs were called.
        on-subgraph-response-outputs: list<list<u8>>,
    }

    // Information on a response
    record subgraph-response {
        // The milliseconds it took to connect to the host.
        connection-time-ms: u64,
        // The milliseconds it took for the host to respond with data.
        response-time-ms: u64,
        // The response status code
        status-code: u16
    }

    // Cache status of a subgraph call.
    enum cache-status {
        // All data fetched from cache.
        hit,
        // Some data fetched from cache.
        partial-hit,
        // Cache miss
        miss,
    }

    // Subgraph response variant.
    variant subgraph-request-execution-kind {
        // Internal server error in the gateway.
        internal-server-error,
        // Response prevented by subgraph request hook.
        hook-error,
        // HTTP request failed.
        request-error,
        // Request was rate-limited.
        rate-limited,
        // A response was received.
        response(subgraph-response),
    }

    // Info about an executed subgraph request.
    record executed-subgraph-request {
        // The name of the subgraph.
        subgraph-name: string,

        // The request method.
        method: string,

        // The subgraph URL.
        url: string,

        // The subgraph responses
        executions: list<subgraph-request-execution-kind>,

        // The cache status of the subgraph call.
        cache-status: cache-status,

        // The time in milliseconds taken for the whole operation.
        total-duration-ms: u64,

        // True, if the subgraph returned any errors.
        has-errors: bool,
    }

    // An HTTP error response.
    record error-response {
        // HTTP status code. Must be a valid status code. If not, the status code will be 500.
        status-code: u16,
        // List of GraphQL errors.
        errors: list<error>,
    }

    // An error response can be used to inject an error to the GraphQL response.
    record error {
        // Adds the given extensions to the response extensions. The first item in
        // the tuple is the extension key, and the second item is the extension value.
        // The extension value can be string-encoded JSON, which will be converted as
        // JSON in the response. It can also be just a string, which will be converted as
        // a JSON string in the response.
        extensions: list<tuple<string, list<u8>>>,
        // The error message.
        message: string,
    }

    // A HTTP client.
    resource http-client {
        // Executes a request and returns the response, yielding the current future until finished.
        execute: static func(request: http-request) -> result<http-response, http-error>;
        // Executes multiple requests in parallel, yielding the current future until all requests are done.
        execute-many: static func(requests: list<http-request>) -> list<result<http-response, http-error>>;
    }

    // A sender for the system access log.
    resource access-log {
        // Sends the data to the access log.
        send: static func(data: list<u8>) -> result<_, log-error>;
    }

    // A HTTP request.
    record http-request {
        // The HTTP method.
        method: http-method,
        // The URL to send the request to.
        url: string,
        // The headers to send with the request. Keys and values must be ASCII strings.
        headers: list<tuple<string, string>>,
        // The body of the request. If the body is set, the Content-Type header must be set.
        body: list<u8>,
        // The timeout in milliseconds for the request. If not set, no timeout is used.
        timeout-ms: option<u64>,
    }

    // The HTTP method.
    enum http-method {
        // The GET method requests a representation of the specified resource. Requests using GET should only retrieve data.
        get,
        // The POST method is used to submit an entity to the specified resource, often causing a change in state or side effects on the server.
        post,
        // The PUT method replaces all current representations of the target resource with the request payload.
        put,
        // The DELETE method deletes the specified resource.
        delete,
        // The PATCH method is used to apply partial modifications to a resource.
        patch,
        // The HEAD method asks for a response identical to that of a GET request, but without the response body.
        head,
        // The OPTIONS method is used to describe the communication options for the target resource.
        options,
        // The CONNECT method establishes a tunnel to the server identified by the target resource.
        connect,
        // The TRACE method performs a message loop-back test along the path to the target resource.
        trace,
    }

    // An HTTP response.
    record http-response {
        // The HTTP status code.
        status: u16,
        // The HTTP version.
        version: http-version,
        // The headers of the response.
        headers: list<tuple<string, string>>,
        // The body of the response.
        body: list<u8>,
    }

    // The HTTP version.
    enum http-version {
        // The HTTP/0.9 version.
        http09,
        // The HTTP/1.0 version.
        http10,
        // The HTTP/1.1 version.
        http11,
        // The HTTP/2.0 version.
        http20,
        // The HTTP/3.0 version.
        http30,
    }

    // An HTTP error.
    variant http-error {
        // The request timed out.
        timeout,
        // The request failed due to an error (invalid user data).
        request(string),
        // The request failed due to an error (server connection failed).
        connect(string),
    }

    // GraphQL HTTP request that will be sent to a subgraph. Allows inspecting and modifying the method, url and headers.
    resource subgraph-request {
        // The HTTP method.
        method: func() -> http-method;
        set-method: func(method: http-method);

        // The URL to send the request to.
        url: func() -> string;
        set-url: func(url: string) -> result<_, string>;

        // The headers to send with the request.
        headers: func() -> headers;
    }

    // The hook is called in the federated gateway just before authentication. It can be used
    // to read and modify the request headers. The context object is provided in a mutable form,
    // allowing storage for the subsequent hooks to read.
    //
    // If returning an error from the hook, the request processing is stopped and the given error
    // returned to the client.
    export on-gateway-request: func(context: context, url: string, headers: headers) -> result<_, error-response>;

    // The hook is called just before requesting a subgraph, after rate limiting is done. It can be used
    // to read and modify the subgraph request headers. If returning an error, the subgraph is not requested.
    export on-subgraph-request: func(
        context: shared-context,
        subgraph-name: string,
        request: subgraph-request
    ) -> result<_, error>;

    // The hook is called in the request cycle if the schema defines an authorization directive on
    // an edge, providing the arguments of the edge selected in the directive, the definition of the esge
    // and the metadata of the directive to the hook.
    //
    // The hook is run before fetching any data.
    //
    // The result, if an error, will stop the request execution and return an error back to the user.
    // Result of the edge will be null for an error response.
    export authorize-edge-pre-execution: func(
        context: shared-context,
        definition: edge-definition,
        arguments: string,
        metadata: string
    ) -> result<_, error>;

    // The hook is called in the request cycle if the schema defines an authorization directive to
    // a node, providing the definition of the node and the metadata of the directive to the hook.
    //
    // The hook is run before fetching any data.
    //
    // The result, if an error, will stop the request execution and return an error back to the user.
    // Result of the edge will be null for an error response.
    export authorize-node-pre-execution: func(
        context: shared-context,
        definition: node-definition,
        metadata: string
    ) -> result<_, error>;

    // The hook is called in the request cycle if the schema defines an authorization directive on
    // an edge with the fields argument, providing fields from the parent node. The hook gets the
    // parent type information, and a list of data with the defined fields of the parent for every
    // child loaded by the parent query.
    //
    // The hook is run after fetching the data.
    //
    // The result can be one of the following:
    //
    // - A list of one item, which dictates the result for every child loaded from the edge
    // - A list of many items, each one defining if the child should be shown or not
    //
    // Providing any other response will lead to the whole authorization hook failing and data not
    // returned to the user.
    //
    // The list item can either be an empty Ok, which returns the edge data to the client. Or the
    // item can be an error and the edge access is denied. The error data will be propagated to the
    // response errors.
    export authorize-parent-edge-post-execution: func(
        context: shared-context,
        definition: edge-definition,
        parents: list<string>,
        metadata: string
    ) -> list<result<_, error>>;

    // The hook is called in the request cycle if the schema defines an authorization directive on
    // an edge with the node argument, providing fields from the child node. The hook gets the parent type information,
    // and a list of data with the defined fields for every child loaded by the parent query.
    //
    // The hook is run after fetching the data.
    //
    // The result can be one of the following:
    //
    // - A list of one item, which dictates the result for every child loaded from the edge
    // - A list of many items, each one defining if the child should be shown or not
    //
    // Providing any other response will lead to the whole authorization hook failing and data not
    // returned to the user.
    //
    // The list item can either be an empty Ok, which returns the edge data to the client. Or the
    // item can be an error and the edge access is denied. The error data will be propagated to the
    // response errors.
    export authorize-edge-node-post-execution: func(
        context: shared-context,
        definition: edge-definition,
        nodes: list<string>,
        metadata: string
    ) -> list<result<_, error>>;

    // The hook is called in the request cycle if the schema defines an authorization directive on
    // an edge with the node and fields arguments, providing fields from the child node. The hook gets
    // the parent type information, and a list of data with tuples of the parent data and a list of child data.
    //
    // The first part of the tuple is defined by the directive's fields argument and the second part by
    // the node argument.
    //
    // The hook is run after fetching the data.
    //
    // The result can be one of the following:
    //
    // - A list of one item, which dictates the result for every child loaded from the edge
    // - A list of many items, each one defining if the child should be shown or not
    //
    // Providing any other response will lead to the whole authorization hook failing and data not
    // returned to the user.
    //
    // The list item can either be an empty Ok, which returns the edge data to the client. Or the
    // item can be an error and the edge access is denied. The error data will be propagated to the
    // response errors.
    export authorize-edge-post-execution: func(
        context: shared-context,
        definition: edge-definition,
        edges: list<tuple<string, list<string>>>,
        metadata: string
    ) -> list<result<_, error>>;

    // The hook is called after a subgraph entity has been either requested or fetched from cache.
    // The output is a list of bytes, which will be available in the on-operation-response hook.
    export on-subgraph-response: func(
        context: shared-context,
        request: executed-subgraph-request,
    ) -> list<u8>;

    // The hook is called after a request is handled in the gateway. The output is a list of bytes,
    // which will be available in the on-http-response hook.
    export on-operation-response: func(
        context: shared-context,
        request: executed-operation,
    ) -> list<u8>;

    // The hook is called right before a response is sent to the user.
    export on-http-response: func(
        context: shared-context,
        request: executed-http-request,
    );

    // The hooks initialization function. Must be called before any other hook function.
    export init-hooks: func() -> s64;
}