@libp2p/fetch 0.0.0-05b52d69c

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.
@@ -0,0 +1,28 @@
1
+ import type { Codec } from 'protons-runtime';
2
+ import type { Uint8ArrayList } from 'uint8arraylist';
3
+ export interface FetchRequest {
4
+ identifier: string;
5
+ }
6
+ export declare namespace FetchRequest {
7
+ const codec: () => Codec<FetchRequest>;
8
+ const encode: (obj: Partial<FetchRequest>) => Uint8Array;
9
+ const decode: (buf: Uint8Array | Uint8ArrayList) => FetchRequest;
10
+ }
11
+ export interface FetchResponse {
12
+ status: FetchResponse.StatusCode;
13
+ data: Uint8Array;
14
+ }
15
+ export declare namespace FetchResponse {
16
+ enum StatusCode {
17
+ OK = "OK",
18
+ NOT_FOUND = "NOT_FOUND",
19
+ ERROR = "ERROR"
20
+ }
21
+ namespace StatusCode {
22
+ const codec: () => Codec<StatusCode>;
23
+ }
24
+ const codec: () => Codec<FetchResponse>;
25
+ const encode: (obj: Partial<FetchResponse>) => Uint8Array;
26
+ const decode: (buf: Uint8Array | Uint8ArrayList) => FetchResponse;
27
+ }
28
+ //# sourceMappingURL=proto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proto.d.ts","sourceRoot":"","sources":["../../../src/pb/proto.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,yBAAiB,YAAY,CAAC;IAGrB,MAAM,KAAK,QAAO,MAAM,YAAY,CAwC1C,CAAA;IAEM,MAAM,MAAM,QAAS,QAAQ,YAAY,CAAC,KAAG,UAEnD,CAAA;IAEM,MAAM,MAAM,QAAS,UAAU,GAAG,cAAc,KAAG,YAEzD,CAAA;CACF;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC,UAAU,CAAA;IAChC,IAAI,EAAE,UAAU,CAAA;CACjB;AAED,yBAAiB,aAAa,CAAC;IAC7B,KAAY,UAAU;QACpB,EAAE,OAAO;QACT,SAAS,cAAc;QACvB,KAAK,UAAU;KAChB;IAQD,UAAiB,UAAU,CAAC;QACnB,MAAM,KAAK,QAAO,MAAM,UAAU,CAExC,CAAA;KACF;IAIM,MAAM,KAAK,QAAO,MAAM,aAAa,CAiD3C,CAAA;IAEM,MAAM,MAAM,QAAS,QAAQ,aAAa,CAAC,KAAG,UAEpD,CAAA;IAEM,MAAM,MAAM,QAAS,UAAU,GAAG,cAAc,KAAG,aAEzD,CAAA;CACF"}
@@ -0,0 +1,120 @@
1
+ /* eslint-disable import/export */
2
+ /* eslint-disable complexity */
3
+ /* eslint-disable @typescript-eslint/no-namespace */
4
+ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
5
+ /* eslint-disable @typescript-eslint/no-empty-interface */
6
+ import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime';
7
+ export var FetchRequest;
8
+ (function (FetchRequest) {
9
+ let _codec;
10
+ FetchRequest.codec = () => {
11
+ if (_codec == null) {
12
+ _codec = message((obj, w, opts = {}) => {
13
+ if (opts.lengthDelimited !== false) {
14
+ w.fork();
15
+ }
16
+ if ((obj.identifier != null && obj.identifier !== '')) {
17
+ w.uint32(10);
18
+ w.string(obj.identifier);
19
+ }
20
+ if (opts.lengthDelimited !== false) {
21
+ w.ldelim();
22
+ }
23
+ }, (reader, length) => {
24
+ const obj = {
25
+ identifier: ''
26
+ };
27
+ const end = length == null ? reader.len : reader.pos + length;
28
+ while (reader.pos < end) {
29
+ const tag = reader.uint32();
30
+ switch (tag >>> 3) {
31
+ case 1:
32
+ obj.identifier = reader.string();
33
+ break;
34
+ default:
35
+ reader.skipType(tag & 7);
36
+ break;
37
+ }
38
+ }
39
+ return obj;
40
+ });
41
+ }
42
+ return _codec;
43
+ };
44
+ FetchRequest.encode = (obj) => {
45
+ return encodeMessage(obj, FetchRequest.codec());
46
+ };
47
+ FetchRequest.decode = (buf) => {
48
+ return decodeMessage(buf, FetchRequest.codec());
49
+ };
50
+ })(FetchRequest || (FetchRequest = {}));
51
+ export var FetchResponse;
52
+ (function (FetchResponse) {
53
+ let StatusCode;
54
+ (function (StatusCode) {
55
+ StatusCode["OK"] = "OK";
56
+ StatusCode["NOT_FOUND"] = "NOT_FOUND";
57
+ StatusCode["ERROR"] = "ERROR";
58
+ })(StatusCode = FetchResponse.StatusCode || (FetchResponse.StatusCode = {}));
59
+ let __StatusCodeValues;
60
+ (function (__StatusCodeValues) {
61
+ __StatusCodeValues[__StatusCodeValues["OK"] = 0] = "OK";
62
+ __StatusCodeValues[__StatusCodeValues["NOT_FOUND"] = 1] = "NOT_FOUND";
63
+ __StatusCodeValues[__StatusCodeValues["ERROR"] = 2] = "ERROR";
64
+ })(__StatusCodeValues || (__StatusCodeValues = {}));
65
+ (function (StatusCode) {
66
+ StatusCode.codec = () => {
67
+ return enumeration(__StatusCodeValues);
68
+ };
69
+ })(StatusCode = FetchResponse.StatusCode || (FetchResponse.StatusCode = {}));
70
+ let _codec;
71
+ FetchResponse.codec = () => {
72
+ if (_codec == null) {
73
+ _codec = message((obj, w, opts = {}) => {
74
+ if (opts.lengthDelimited !== false) {
75
+ w.fork();
76
+ }
77
+ if (obj.status != null && __StatusCodeValues[obj.status] !== 0) {
78
+ w.uint32(8);
79
+ FetchResponse.StatusCode.codec().encode(obj.status, w);
80
+ }
81
+ if ((obj.data != null && obj.data.byteLength > 0)) {
82
+ w.uint32(18);
83
+ w.bytes(obj.data);
84
+ }
85
+ if (opts.lengthDelimited !== false) {
86
+ w.ldelim();
87
+ }
88
+ }, (reader, length) => {
89
+ const obj = {
90
+ status: StatusCode.OK,
91
+ data: new Uint8Array(0)
92
+ };
93
+ const end = length == null ? reader.len : reader.pos + length;
94
+ while (reader.pos < end) {
95
+ const tag = reader.uint32();
96
+ switch (tag >>> 3) {
97
+ case 1:
98
+ obj.status = FetchResponse.StatusCode.codec().decode(reader);
99
+ break;
100
+ case 2:
101
+ obj.data = reader.bytes();
102
+ break;
103
+ default:
104
+ reader.skipType(tag & 7);
105
+ break;
106
+ }
107
+ }
108
+ return obj;
109
+ });
110
+ }
111
+ return _codec;
112
+ };
113
+ FetchResponse.encode = (obj) => {
114
+ return encodeMessage(obj, FetchResponse.codec());
115
+ };
116
+ FetchResponse.decode = (buf) => {
117
+ return decodeMessage(buf, FetchResponse.codec());
118
+ };
119
+ })(FetchResponse || (FetchResponse = {}));
120
+ //# sourceMappingURL=proto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proto.js","sourceRoot":"","sources":["../../../src/pb/proto.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@libp2p/fetch",
3
+ "version": "0.0.0-05b52d69c",
4
+ "description": "Implementation of the Fetch Protocol",
5
+ "license": "Apache-2.0 OR MIT",
6
+ "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/protocol-fetch#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/libp2p/js-libp2p.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/libp2p/js-libp2p/issues"
13
+ },
14
+ "type": "module",
15
+ "types": "./dist/src/index.d.ts",
16
+ "files": [
17
+ "src",
18
+ "dist",
19
+ "!dist/test",
20
+ "!**/*.tsbuildinfo"
21
+ ],
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/src/index.d.ts",
25
+ "import": "./dist/src/index.js"
26
+ }
27
+ },
28
+ "eslintConfig": {
29
+ "extends": "ipfs",
30
+ "parserOptions": {
31
+ "project": true,
32
+ "sourceType": "module"
33
+ }
34
+ },
35
+ "scripts": {
36
+ "start": "node dist/src/main.js",
37
+ "build": "aegir build",
38
+ "test": "aegir test",
39
+ "clean": "aegir clean",
40
+ "generate": "protons ./src/pb/index.proto",
41
+ "lint": "aegir lint",
42
+ "test:chrome": "aegir test -t browser --cov",
43
+ "test:chrome-webworker": "aegir test -t webworker",
44
+ "test:firefox": "aegir test -t browser -- --browser firefox",
45
+ "test:firefox-webworker": "aegir test -t webworker -- --browser firefox",
46
+ "test:node": "aegir test -t node --cov",
47
+ "dep-check": "aegir dep-check"
48
+ },
49
+ "dependencies": {
50
+ "@libp2p/interface": "0.1.6-05b52d69c",
51
+ "@libp2p/interface-internal": "0.1.9-05b52d69c",
52
+ "it-protobuf-stream": "^1.0.2",
53
+ "protons-runtime": "^5.0.0",
54
+ "uint8arraylist": "^2.4.3",
55
+ "uint8arrays": "^4.0.6"
56
+ },
57
+ "devDependencies": {
58
+ "@libp2p/logger": "3.1.0-05b52d69c",
59
+ "@libp2p/peer-id-factory": "3.0.8-05b52d69c",
60
+ "aegir": "^41.0.2",
61
+ "it-pair": "^2.0.6",
62
+ "protons": "^7.3.0",
63
+ "sinon": "^17.0.0",
64
+ "sinon-ts": "^2.0.0"
65
+ }
66
+ }
@@ -0,0 +1,3 @@
1
+ // https://github.com/libp2p/specs/tree/master/fetch#wire-protocol
2
+ export const PROTOCOL_VERSION = '0.0.1'
3
+ export const PROTOCOL_NAME = 'fetch'
package/src/fetch.ts ADDED
@@ -0,0 +1,236 @@
1
+ import { CodeError, ERR_INVALID_MESSAGE, ERR_INVALID_PARAMETERS, ERR_TIMEOUT } from '@libp2p/interface/errors'
2
+ import { setMaxListeners } from '@libp2p/interface/events'
3
+ import { pbStream } from 'it-protobuf-stream'
4
+ import { fromString as uint8arrayFromString } from 'uint8arrays/from-string'
5
+ import { toString as uint8arrayToString } from 'uint8arrays/to-string'
6
+ import { PROTOCOL_NAME, PROTOCOL_VERSION } from './constants.js'
7
+ import { FetchRequest, FetchResponse } from './pb/proto.js'
8
+ import type { Fetch as FetchInterface, FetchComponents, FetchInit, LookupFunction } from './index.js'
9
+ import type { AbortOptions, Logger } from '@libp2p/interface'
10
+ import type { Stream } from '@libp2p/interface/connection'
11
+ import type { PeerId } from '@libp2p/interface/peer-id'
12
+ import type { Startable } from '@libp2p/interface/startable'
13
+ import type { IncomingStreamData } from '@libp2p/interface-internal/registrar'
14
+
15
+ const DEFAULT_TIMEOUT = 10000
16
+
17
+ /**
18
+ * A simple libp2p protocol for requesting a value corresponding to a key from a peer.
19
+ * Developers can register one or more lookup function for retrieving the value corresponding to
20
+ * a given key. Each lookup function must act on a distinct part of the overall key space, defined
21
+ * by a fixed prefix that all keys that should be routed to that lookup function will start with.
22
+ */
23
+ export class Fetch implements Startable, FetchInterface {
24
+ public readonly protocol: string
25
+ private readonly components: FetchComponents
26
+ private readonly lookupFunctions: Map<string, LookupFunction>
27
+ private started: boolean
28
+ private readonly init: FetchInit
29
+ private readonly log: Logger
30
+
31
+ constructor (components: FetchComponents, init: FetchInit = {}) {
32
+ this.log = components.logger.forComponent('libp2p:fetch')
33
+ this.started = false
34
+ this.components = components
35
+ this.protocol = `/${init.protocolPrefix ?? 'libp2p'}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`
36
+ this.lookupFunctions = new Map() // Maps key prefix to value lookup function
37
+ this.handleMessage = this.handleMessage.bind(this)
38
+ this.init = init
39
+ }
40
+
41
+ async start (): Promise<void> {
42
+ await this.components.registrar.handle(this.protocol, (data) => {
43
+ void this.handleMessage(data)
44
+ .then(async () => {
45
+ await data.stream.close()
46
+ })
47
+ .catch(err => {
48
+ this.log.error(err)
49
+ })
50
+ }, {
51
+ maxInboundStreams: this.init.maxInboundStreams,
52
+ maxOutboundStreams: this.init.maxOutboundStreams
53
+ })
54
+ this.started = true
55
+ }
56
+
57
+ async stop (): Promise<void> {
58
+ await this.components.registrar.unhandle(this.protocol)
59
+ this.started = false
60
+ }
61
+
62
+ isStarted (): boolean {
63
+ return this.started
64
+ }
65
+
66
+ /**
67
+ * Sends a request to fetch the value associated with the given key from the given peer
68
+ */
69
+ async fetch (peer: PeerId, key: string, options: AbortOptions = {}): Promise<Uint8Array | undefined> {
70
+ this.log('dialing %s to %p', this.protocol, peer)
71
+
72
+ const connection = await this.components.connectionManager.openConnection(peer, options)
73
+ let signal = options.signal
74
+ let stream: Stream | undefined
75
+ let onAbort = (): void => {}
76
+
77
+ // create a timeout if no abort signal passed
78
+ if (signal == null) {
79
+ this.log('using default timeout of %d ms', this.init.timeout)
80
+ signal = AbortSignal.timeout(this.init.timeout ?? DEFAULT_TIMEOUT)
81
+
82
+ setMaxListeners(Infinity, signal)
83
+ }
84
+
85
+ try {
86
+ stream = await connection.newStream(this.protocol, {
87
+ signal
88
+ })
89
+
90
+ onAbort = () => {
91
+ stream?.abort(new CodeError('fetch timeout', ERR_TIMEOUT))
92
+ }
93
+
94
+ // make stream abortable
95
+ signal.addEventListener('abort', onAbort, { once: true })
96
+
97
+ this.log('fetch %s', key)
98
+
99
+ const pb = pbStream(stream)
100
+ await pb.write({
101
+ identifier: key
102
+ }, FetchRequest, options)
103
+
104
+ const response = await pb.read(FetchResponse, options)
105
+ await pb.unwrap().close(options)
106
+
107
+ switch (response.status) {
108
+ case (FetchResponse.StatusCode.OK): {
109
+ this.log('received status for %s ok', key)
110
+ return response.data
111
+ }
112
+ case (FetchResponse.StatusCode.NOT_FOUND): {
113
+ this.log('received status for %s not found', key)
114
+ return
115
+ }
116
+ case (FetchResponse.StatusCode.ERROR): {
117
+ this.log('received status for %s error', key)
118
+ const errmsg = uint8arrayToString(response.data)
119
+ throw new CodeError('Error in fetch protocol response: ' + errmsg, ERR_INVALID_PARAMETERS)
120
+ }
121
+ default: {
122
+ this.log('received status for %s unknown', key)
123
+ throw new CodeError('Unknown response status', ERR_INVALID_MESSAGE)
124
+ }
125
+ }
126
+ } catch (err: any) {
127
+ stream?.abort(err)
128
+ throw err
129
+ } finally {
130
+ signal.removeEventListener('abort', onAbort)
131
+ if (stream != null) {
132
+ await stream.close()
133
+ }
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Invoked when a fetch request is received. Reads the request message off the given stream and
139
+ * responds based on looking up the key in the request via the lookup callback that corresponds
140
+ * to the key's prefix.
141
+ */
142
+ async handleMessage (data: IncomingStreamData): Promise<void> {
143
+ const { stream } = data
144
+ const signal = AbortSignal.timeout(this.init.timeout ?? DEFAULT_TIMEOUT)
145
+
146
+ try {
147
+ const pb = pbStream(stream)
148
+ const request = await pb.read(FetchRequest, {
149
+ signal
150
+ })
151
+
152
+ let response: FetchResponse
153
+ const lookup = this._getLookupFunction(request.identifier)
154
+ if (lookup != null) {
155
+ this.log('look up data with identifier %s', request.identifier)
156
+ const data = await lookup(request.identifier)
157
+ if (data != null) {
158
+ this.log('sending status for %s ok', request.identifier)
159
+ response = { status: FetchResponse.StatusCode.OK, data }
160
+ } else {
161
+ this.log('sending status for %s not found', request.identifier)
162
+ response = { status: FetchResponse.StatusCode.NOT_FOUND, data: new Uint8Array(0) }
163
+ }
164
+ } else {
165
+ this.log('sending status for %s error', request.identifier)
166
+ const errmsg = uint8arrayFromString(`No lookup function registered for key: ${request.identifier}`)
167
+ response = { status: FetchResponse.StatusCode.ERROR, data: errmsg }
168
+ }
169
+
170
+ await pb.write(response, FetchResponse, {
171
+ signal
172
+ })
173
+
174
+ await pb.unwrap().close({
175
+ signal
176
+ })
177
+ } catch (err: any) {
178
+ this.log('error answering fetch request', err)
179
+ stream.abort(err)
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Given a key, finds the appropriate function for looking up its corresponding value, based on
185
+ * the key's prefix.
186
+ */
187
+ _getLookupFunction (key: string): LookupFunction | undefined {
188
+ for (const prefix of this.lookupFunctions.keys()) {
189
+ if (key.startsWith(prefix)) {
190
+ return this.lookupFunctions.get(prefix)
191
+ }
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Registers a new lookup callback that can map keys to values, for a given set of keys that
197
+ * share the same prefix
198
+ *
199
+ * @example
200
+ *
201
+ * ```js
202
+ * // ...
203
+ * libp2p.fetchService.registerLookupFunction('/prefix', (key) => { ... })
204
+ * ```
205
+ */
206
+ registerLookupFunction (prefix: string, lookup: LookupFunction): void {
207
+ if (this.lookupFunctions.has(prefix)) {
208
+ throw new CodeError(`Fetch protocol handler for key prefix '${prefix}' already registered`, 'ERR_KEY_ALREADY_EXISTS')
209
+ }
210
+
211
+ this.lookupFunctions.set(prefix, lookup)
212
+ }
213
+
214
+ /**
215
+ * Registers a new lookup callback that can map keys to values, for a given set of keys that
216
+ * share the same prefix.
217
+ *
218
+ * @example
219
+ *
220
+ * ```js
221
+ * // ...
222
+ * libp2p.fetchService.unregisterLookupFunction('/prefix')
223
+ * ```
224
+ */
225
+ unregisterLookupFunction (prefix: string, lookup?: LookupFunction): void {
226
+ if (lookup != null) {
227
+ const existingLookup = this.lookupFunctions.get(prefix)
228
+
229
+ if (existingLookup !== lookup) {
230
+ return
231
+ }
232
+ }
233
+
234
+ this.lookupFunctions.delete(prefix)
235
+ }
236
+ }
package/src/index.ts ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * An implementation of the Fetch protocol as described here: https://github.com/libp2p/specs/tree/master/fetch
5
+ *
6
+ * The fetch protocol is a simple protocol for requesting a value corresponding to a key from a peer.
7
+ *
8
+ * @example
9
+ *
10
+ * ```typescript
11
+ * import { createLibp2p } from 'libp2p'
12
+ * import { fetch } from '@libp2p/fetch'
13
+ *
14
+ * const libp2p = await createLibp2p({
15
+ * services: {
16
+ * fetch: fetch()
17
+ * }
18
+ * })
19
+ *
20
+ * // Given a key (as a string) returns a value (as a Uint8Array), or null if the key isn't found.
21
+ * // All keys must be prefixed my the same prefix, which will be used to find the appropriate key
22
+ * // lookup function.
23
+ * async function my_subsystem_key_lookup(key) {
24
+ * // app specific callback to lookup key-value pairs.
25
+ * }
26
+ *
27
+ * // Enable this peer to respond to fetch requests for keys that begin with '/my_subsystem_key_prefix/'
28
+ * libp2p.fetch.registerLookupFunction('/my_subsystem_key_prefix/', my_subsystem_key_lookup)
29
+ *
30
+ * const key = '/my_subsystem_key_prefix/{...}'
31
+ * const peerDst = PeerId.parse('Qmfoo...') // or Multiaddr instance
32
+ * const value = await libp2p.fetch(peerDst, key)
33
+ * ```
34
+ */
35
+
36
+ import { Fetch as FetchClass } from './fetch.js'
37
+ import type { AbortOptions, ComponentLogger } from '@libp2p/interface'
38
+ import type { PeerId } from '@libp2p/interface/peer-id'
39
+ import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager'
40
+ import type { Registrar } from '@libp2p/interface-internal/registrar'
41
+
42
+ export interface FetchInit {
43
+ protocolPrefix?: string
44
+ maxInboundStreams?: number
45
+ maxOutboundStreams?: number
46
+
47
+ /**
48
+ * How long we should wait for a remote peer to send any data
49
+ */
50
+ timeout?: number
51
+ }
52
+
53
+ export interface LookupFunction {
54
+ (key: string): Promise<Uint8Array | undefined>
55
+ }
56
+
57
+ export interface FetchComponents {
58
+ registrar: Registrar
59
+ connectionManager: ConnectionManager
60
+ logger: ComponentLogger
61
+ }
62
+
63
+ export interface Fetch {
64
+ /**
65
+ * Sends a request to fetch the value associated with the given key from the given peer
66
+ */
67
+ fetch(peer: PeerId, key: string, options?: AbortOptions): Promise<Uint8Array | undefined>
68
+
69
+ /**
70
+ * Registers a new lookup callback that can map keys to values, for a given set of keys that
71
+ * share the same prefix
72
+ *
73
+ * @example
74
+ *
75
+ * ```js
76
+ * // ...
77
+ * libp2p.fetchService.registerLookupFunction('/prefix', (key) => { ... })
78
+ * ```
79
+ */
80
+ registerLookupFunction(prefix: string, lookup: LookupFunction): void
81
+
82
+ /**
83
+ * Registers a new lookup callback that can map keys to values, for a given set of keys that
84
+ * share the same prefix.
85
+ *
86
+ * @example
87
+ *
88
+ * ```js
89
+ * // ...
90
+ * libp2p.fetchService.unregisterLookupFunction('/prefix')
91
+ * ```
92
+ */
93
+ unregisterLookupFunction(prefix: string, lookup?: LookupFunction): void
94
+ }
95
+
96
+ export function fetch (init: FetchInit = {}): (components: FetchComponents) => Fetch {
97
+ return (components) => new FetchClass(components, init)
98
+ }
@@ -0,0 +1,15 @@
1
+ syntax = "proto3";
2
+
3
+ message FetchRequest {
4
+ string identifier = 1;
5
+ }
6
+
7
+ message FetchResponse {
8
+ StatusCode status = 1;
9
+ enum StatusCode {
10
+ OK = 0;
11
+ NOT_FOUND = 1;
12
+ ERROR = 2;
13
+ }
14
+ bytes data = 2;
15
+ }