@rdfc/js-runner 1.0.0 → 2.0.0-alpha.10

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.
Files changed (89) hide show
  1. package/.husky/pre-commit +6 -0
  2. package/.idea/LNKD.tech Editor.xml +194 -0
  3. package/.idea/codeStyles/Project.xml +52 -0
  4. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  5. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  6. package/.idea/js-runner.iml +12 -0
  7. package/.idea/modules.xml +8 -0
  8. package/.idea/vcs.xml +6 -0
  9. package/.prettierrc +4 -0
  10. package/README.md +3 -38
  11. package/__tests__/channels.test.ts +96 -0
  12. package/bin/runner.js +8 -0
  13. package/dist/args.d.ts +3 -3
  14. package/dist/args.js +50 -51
  15. package/dist/connectors/file.d.ts +11 -11
  16. package/dist/connectors/file.js +79 -79
  17. package/dist/connectors/http.d.ts +10 -10
  18. package/dist/connectors/http.js +76 -76
  19. package/dist/connectors/kafka.d.ts +36 -36
  20. package/dist/connectors/kafka.js +66 -62
  21. package/dist/connectors/ws.d.ts +6 -6
  22. package/dist/connectors/ws.js +66 -63
  23. package/dist/connectors.d.ts +61 -42
  24. package/dist/connectors.js +155 -132
  25. package/dist/index.cjs +650 -595
  26. package/dist/index.d.ts +40 -31
  27. package/dist/index.js +72 -63
  28. package/dist/util.d.ts +63 -35
  29. package/dist/util.js +80 -63
  30. package/eslint.config.mjs +21 -0
  31. package/examples/echo/package-lock.json +80 -0
  32. package/examples/echo/package.json +18 -0
  33. package/examples/echo/pipeline.ttl +48 -0
  34. package/examples/echo/processors.ttl +82 -0
  35. package/examples/echo/src/processors.ts +74 -0
  36. package/examples/echo/tsconfig.json +114 -0
  37. package/index.ttl +71 -0
  38. package/jest.config.js +2 -0
  39. package/lib/client.d.ts +1 -0
  40. package/lib/client.js +43 -0
  41. package/lib/convertor.d.ts +9 -0
  42. package/lib/convertor.js +51 -0
  43. package/lib/index.d.ts +7 -0
  44. package/lib/index.js +8 -0
  45. package/lib/jsonld.d.ts +17 -0
  46. package/lib/jsonld.js +135 -0
  47. package/lib/logger.d.ts +17 -0
  48. package/lib/logger.js +49 -0
  49. package/lib/processor.d.ts +19 -0
  50. package/lib/processor.js +13 -0
  51. package/lib/reader.d.ts +30 -0
  52. package/lib/reader.js +101 -0
  53. package/lib/reexports.d.ts +3 -0
  54. package/lib/reexports.js +4 -0
  55. package/lib/runner.d.ts +26 -0
  56. package/lib/runner.js +121 -0
  57. package/lib/testUtils.d.ts +24 -0
  58. package/lib/testUtils.js +150 -0
  59. package/lib/tsconfig.tsbuildinfo +1 -0
  60. package/lib/util_processors.d.ts +11 -0
  61. package/lib/util_processors.js +13 -0
  62. package/lib/writer.d.ts +26 -0
  63. package/lib/writer.js +57 -0
  64. package/package.json +49 -51
  65. package/src/client.ts +52 -0
  66. package/src/convertor.ts +59 -0
  67. package/src/index.ts +8 -0
  68. package/src/jsonld.ts +220 -0
  69. package/src/logger.ts +64 -0
  70. package/src/processor.ts +39 -0
  71. package/src/reader.ts +142 -0
  72. package/src/reexports.ts +6 -0
  73. package/src/runner.ts +197 -0
  74. package/src/testUtils.ts +196 -0
  75. package/src/util_processors.ts +20 -0
  76. package/src/writer.ts +90 -0
  77. package/tsconfig.json +33 -0
  78. package/vite.config.ts +10 -0
  79. package/LICENSE +0 -21
  80. package/bin/js-runner.js +0 -4
  81. package/channels/file.ttl +0 -37
  82. package/channels/http.ttl +0 -59
  83. package/channels/kafka.ttl +0 -98
  84. package/channels/ws.ttl +0 -33
  85. package/ontology.ttl +0 -169
  86. package/processor/echo.ttl +0 -38
  87. package/processor/resc.ttl +0 -34
  88. package/processor/send.ttl +0 -40
  89. package/processor/test.js +0 -35
package/package.json CHANGED
@@ -1,68 +1,66 @@
1
1
  {
2
2
  "name": "@rdfc/js-runner",
3
- "version": "1.0.0",
3
+ "version": "2.0.0-alpha.10",
4
+ "main": "lib/index.js",
4
5
  "type": "module",
5
- "exports": {
6
- "import": "./dist/index.js",
7
- "require": "./dist/index.cjs"
6
+ "scripts": {
7
+ "build": "tsc && tsc-alias",
8
+ "lint": "eslint . --ext .ts",
9
+ "format": "prettier --write .",
10
+ "test": "npm run build && vitest run",
11
+ "prepare": "husky"
8
12
  },
9
- "description": "",
10
- "main": "./dist/index.js",
11
- "files": [
12
- "./dist/**/*",
13
- "./ontology.ttl",
14
- "./processor/**/*",
15
- "./channels/**/*"
16
- ],
17
- "types": "./dist/index.d.ts",
18
13
  "bin": {
19
- "js-runner": "bin/js-runner.js"
14
+ "js-runner": "./bin/runner.js"
20
15
  },
21
- "scripts": {
22
- "build": "tsc && tsc-alias",
23
- "watch": "tsc -w",
24
- "test": "vitest run --coverage --coverage.include src",
25
- "prepare": "husky",
26
- "prepublishOnly": "npm run build"
16
+ "husky": {
17
+ "hooks": {
18
+ "pre-commit": "echo \"Husky pre-commit\""
19
+ }
20
+ },
21
+ "lint-staged": {
22
+ "*.ts": [
23
+ "eslint --fix",
24
+ "prettier --write"
25
+ ]
27
26
  },
28
27
  "keywords": [],
29
28
  "author": "",
30
- "license": "MIT",
31
- "dependencies": {
32
- "@rdfjs/types": "^2.0.1",
33
- "@treecg/types": "^0.4.6",
34
- "command-line-args": "^6.0.1",
35
- "command-line-usage": "^7.0.3",
36
- "debug": "^4.4.0",
37
- "kafkajs": "^2.2.4",
38
- "n3": "^1.23.1",
39
- "rdf-lens": "^1.3.3",
40
- "stream-to-array": "^2.3.0",
41
- "ws": "^8.18.0"
42
- },
29
+ "license": "ISC",
30
+ "description": "",
43
31
  "devDependencies": {
44
- "@jest/globals": "^29.7.0",
45
- "@knighted/duel": "^2.0.0",
46
- "@types/command-line-args": "^5.2.3",
47
- "@types/command-line-usage": "^5.0.4",
48
- "@types/debug": "^4.1.12",
32
+ "@eslint/js": "^9.21.0",
33
+ "@rdfjs/types": "^2.0.1",
34
+ "@types/jest": "^29.5.14",
35
+ "@types/jsonld": "^1.5.15",
49
36
  "@types/n3": "^1.21.1",
50
- "@types/node": "^22.10.7",
51
- "@types/ws": "^8.5.13",
52
- "rollup": "^4.30.1",
53
- "@typescript-eslint/eslint-plugin": "^8.20.0",
54
- "@typescript-eslint/parser": "^8.20.0",
55
- "@vitest/coverage-v8": "^3.0.2",
56
- "dotenv": "^16.4.7",
57
- "eslint": "^8.56.0",
37
+ "@types/node": "^22.13.5",
38
+ "@typescript-eslint/eslint-plugin": "^8.25.0",
39
+ "@typescript-eslint/parser": "^8.25.0",
40
+ "@vitest/coverage-v8": "^3.2.4",
41
+ "eslint": "^9.21.0",
58
42
  "eslint-config-prettier": "^10.0.1",
43
+ "eslint-plugin-prettier": "^5.2.3",
44
+ "globals": "^16.0.0",
59
45
  "husky": "^9.1.7",
60
- "lint-staged": "^15.4.1",
61
- "prettier": "^3.4.2",
62
- "ts-node": "^10.9.2",
46
+ "jest": "^29.7.0",
47
+ "lint-staged": "^15.4.3",
48
+ "prettier": "^3.5.2",
49
+ "ts-jest": "^29.2.6",
63
50
  "tsc-alias": "^1.8.10",
64
51
  "typescript": "^5.7.3",
65
- "vite-tsconfig-paths": "^5.1.4",
66
- "vitest": "^3.0.2"
52
+ "typescript-eslint": "^8.25.0",
53
+ "vitest": "^3.2.4"
54
+ },
55
+ "dependencies": {
56
+ "@grpc/grpc-js": "^1.12.6",
57
+ "@rdfc/proto": "^0.1.2-alpha.1",
58
+ "@treecg/types": "^0.4.6",
59
+ "jsonld": "^8.3.3",
60
+ "jsonld-streaming-parser": "^5.0.0",
61
+ "n3": "^1.23.1",
62
+ "rdf-lens": "^1.3.5",
63
+ "winston": "^3.17.0",
64
+ "winston-transport": "^4.9.0"
67
65
  }
68
66
  }
package/src/client.ts ADDED
@@ -0,0 +1,52 @@
1
+ import * as grpc from '@grpc/grpc-js'
2
+ import { promisify } from 'util'
3
+ import { RunnerClient, RunnerMessage } from '@rdfc/proto'
4
+ import winston from 'winston'
5
+ import { RpcTransport } from './logger'
6
+ import { Runner } from './runner'
7
+
8
+ export async function start(addr: string, uri: string) {
9
+ const client = new RunnerClient(addr, grpc.credentials.createInsecure())
10
+
11
+ const logger = winston.createLogger({
12
+ transports: [
13
+ new RpcTransport({
14
+ entities: [uri, 'cli'],
15
+ stream: client.logStream(() => {}),
16
+ }),
17
+ ],
18
+ })
19
+
20
+ const stream = client.connect()
21
+
22
+ logger.info('Connected with server ' + addr)
23
+ const writable = promisify(stream.write.bind(stream))
24
+ const runner = new Runner(client, writable, uri, logger)
25
+
26
+ await writable({ identify: { uri } })
27
+
28
+ let processorsEnd!: (v: unknown) => unknown
29
+ const processorsEnded = new Promise((res) => (processorsEnd = res))
30
+ ;(async () => {
31
+ for await (const chunk of stream) {
32
+ const msg: RunnerMessage = chunk
33
+ if (msg.proc) {
34
+ await runner.addProcessor(msg.proc)
35
+ }
36
+ if (msg.start) {
37
+ runner.start().then(processorsEnd)
38
+ }
39
+
40
+ await runner.handleOrchMessage(msg)
41
+ }
42
+
43
+ logger.error('Stream ended')
44
+ })()
45
+
46
+ await processorsEnded
47
+
48
+ logger.info('All processors are finished')
49
+ stream.end()
50
+ client.close()
51
+ process.exit(0)
52
+ }
@@ -0,0 +1,59 @@
1
+ import { Any } from './reader'
2
+
3
+ const decoder = new TextDecoder()
4
+ export interface Convertor<T> {
5
+ from(buffer: Uint8Array): T
6
+ fromStream(stream: AsyncIterable<Uint8Array>): Promise<T>
7
+ }
8
+
9
+ export const AnyConvertor: Convertor<Any> = {
10
+ from: function (buffer: Uint8Array): Any {
11
+ return {
12
+ buffer,
13
+ }
14
+ },
15
+ fromStream: async function (inp: AsyncIterable<Uint8Array>): Promise<Any> {
16
+ const stream = (async function* () {
17
+ yield* inp
18
+ })()
19
+ return { stream }
20
+ },
21
+ }
22
+
23
+ export const StringConvertor: Convertor<string> = {
24
+ from(buffer) {
25
+ return decoder.decode(buffer)
26
+ },
27
+ async fromStream(stream) {
28
+ const chunks: Uint8Array[] = []
29
+ for await (const chunk of stream) {
30
+ chunks.push(chunk)
31
+ }
32
+ return decoder.decode(Buffer.concat(chunks))
33
+ },
34
+ }
35
+ export const StreamConvertor: Convertor<AsyncGenerator<Uint8Array>> = {
36
+ from(buffer) {
37
+ return (async function* () {
38
+ yield buffer
39
+ })()
40
+ },
41
+
42
+ async fromStream(stream) {
43
+ return (async function* () {
44
+ yield* stream
45
+ })()
46
+ },
47
+ }
48
+ export const NoConvertor: Convertor<Uint8Array> = {
49
+ from(buffer) {
50
+ return buffer
51
+ },
52
+ async fromStream(stream) {
53
+ const chunks: Uint8Array[] = []
54
+ for await (const chunk of stream) {
55
+ chunks.push(chunk)
56
+ }
57
+ return Buffer.concat(chunks)
58
+ },
59
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from './client'
2
+ export * from './writer'
3
+ export * from './reader'
4
+ export * from './processor'
5
+ export * from './runner'
6
+ export * from './logger'
7
+
8
+ export * as reexports from './reexports'
package/src/jsonld.ts ADDED
@@ -0,0 +1,220 @@
1
+ import { OrchestratorMessage, RunnerClient } from '@rdfc/proto'
2
+ import { createNamespace, createUriAndTermNamespace } from '@treecg/types'
3
+ import { ReaderInstance } from './reader'
4
+ import { WriterInstance } from './writer'
5
+ import { Logger } from 'winston'
6
+ import { JsonLdParser } from 'jsonld-streaming-parser'
7
+ import { pred, ShaclPath } from 'rdf-lens'
8
+ import { Quad, Term } from '@rdfjs/types'
9
+ import { NamedNode } from 'n3'
10
+
11
+ export type RunnerItems = {
12
+ readers: { [uri: string]: ReaderInstance[] }
13
+ writers: { [uri: string]: WriterInstance[] }
14
+ client: RunnerClient
15
+ write: Writable
16
+ }
17
+
18
+ const RDFL = createUriAndTermNamespace(
19
+ 'https://w3id.org/rdf-lens/ontology#',
20
+ 'CBD',
21
+ 'Path',
22
+ 'PathLens',
23
+ 'Context',
24
+ 'TypedExtract',
25
+ 'EnvVariable',
26
+ 'envKey',
27
+ 'envDefault',
28
+ 'datatype',
29
+ )
30
+
31
+ type Writable = (msg: OrchestratorMessage) => Promise<unknown>
32
+ const RDFC = createNamespace(
33
+ 'https://w3id.org/rdf-connect#',
34
+ (x) => x,
35
+ 'Reader',
36
+ 'Writer',
37
+ )
38
+
39
+ function as_string_array(obj: unknown): string[] {
40
+ const out = Array.isArray(obj) ? obj : [obj]
41
+ return out.filter((x) => typeof x === 'string')
42
+ }
43
+
44
+ function cbdToQuads(value: unknown) {
45
+ const quads: Quad[] = []
46
+ const parser = new JsonLdParser()
47
+ const promise = new Promise<{ value: Quad[] }>((res, rej) =>
48
+ parser
49
+ .on('end', () => res({ value: quads }))
50
+ .on('error', (e) => rej(e))
51
+ .on('data', (q) => {
52
+ quads.push(q)
53
+ }),
54
+ )
55
+
56
+ if (value instanceof Object && '@type' in value) {
57
+ delete value['@type']
58
+ }
59
+ parser.write(JSON.stringify(value))
60
+ parser.end()
61
+ return promise
62
+ }
63
+
64
+ async function cbdToPath(value: unknown): Promise<{ value: unknown }> {
65
+ const qs = (
66
+ await cbdToQuads({
67
+ '@id': 'http://example.com/ns#me1234',
68
+ 'http://example.com/ns#innerPred': value,
69
+ })
70
+ ).value
71
+
72
+ const path = pred(new NamedNode('http://example.com/ns#innerPred'))
73
+ .one()
74
+ .then(ShaclPath)
75
+ .execute({
76
+ quads: qs,
77
+ id: new NamedNode('http://example.com/ns#me1234'),
78
+ })
79
+ return { value: path }
80
+ }
81
+
82
+ type Cont = { quads: Quad[]; id: Term }
83
+ async function jsonldToQuads(value: unknown): Promise<Cont> {
84
+ const qs = (
85
+ await cbdToQuads({
86
+ '@id': 'http://example.com/ns#me1234',
87
+ 'http://example.com/ns#innerPred': value,
88
+ })
89
+ ).value
90
+
91
+ const idx = qs.findIndex(
92
+ (x) =>
93
+ x.predicate.equals(new NamedNode('http://example.com/ns#innerPred')) &&
94
+ x.subject.equals(new NamedNode('http://example.com/ns#me1234')),
95
+ )
96
+ if (idx < 0) throw 'This cannot happen'
97
+ const id = qs[idx].object
98
+
99
+ qs.splice(idx, 1)
100
+
101
+ return {
102
+ quads: qs,
103
+ id,
104
+ }
105
+ }
106
+
107
+ /* eslint-disable @typescript-eslint/no-explicit-any */
108
+ function revive(
109
+ _key: string,
110
+ value: any,
111
+ logger: Logger,
112
+ promises: Promise<unknown>[],
113
+ runnerItems: RunnerItems,
114
+ ): any {
115
+ if (typeof value === 'object') {
116
+ const types = as_string_array(value['@type'] || [])
117
+ const ids = as_string_array(value['@id'] || [])[0] || ''
118
+
119
+ if (types.includes(RDFC.Reader)) {
120
+ if (runnerItems.readers[ids] === undefined) {
121
+ runnerItems.readers[ids] = []
122
+ }
123
+ const reader = new ReaderInstance(ids, runnerItems.client, logger)
124
+ runnerItems.readers[ids].push(reader)
125
+ return reader
126
+ }
127
+
128
+ if (types.includes(RDFC.Writer)) {
129
+ if (runnerItems.writers[ids] === undefined) {
130
+ runnerItems.writers[ids] = []
131
+ }
132
+ const writer = new WriterInstance(
133
+ ids,
134
+ runnerItems.client,
135
+ runnerItems.write,
136
+ logger,
137
+ )
138
+ runnerItems.writers[ids].push(writer)
139
+ return writer
140
+ }
141
+
142
+ if (types.includes(RDFL.CBD)) {
143
+ return cbdToQuads(value)
144
+ }
145
+
146
+ if (types.includes(RDFL.Path)) {
147
+ return cbdToPath(value)
148
+ }
149
+ }
150
+
151
+ Object.entries(value).forEach(([k, v]) => {
152
+ if (v instanceof Promise) {
153
+ logger.info('Found promise')
154
+ promises.push(
155
+ v.then((x) => {
156
+ logger.info('Setting field ' + k)
157
+ value[k] = x.value
158
+ }),
159
+ )
160
+ }
161
+ })
162
+
163
+ return value
164
+ }
165
+
166
+ type Arg = {
167
+ [id: string]: any
168
+ }
169
+ function expandArgs(args: Arg, cont: Cont, logger: Logger) {
170
+ const context = args['@context']
171
+ if (!context) return
172
+
173
+ for (const key of Object.keys(args)) {
174
+ if (key == '@context') continue
175
+ const ctxObj = context[key]
176
+ if (!ctxObj) {
177
+ logger.debug("Didn't find ctx things for " + key)
178
+ continue
179
+ }
180
+
181
+ logger.debug(
182
+ 'Did find ctx things for ' +
183
+ key +
184
+ ' ' +
185
+ JSON.stringify(ctxObj) +
186
+ ': ' +
187
+ JSON.stringify(
188
+ Object.entries(args[key]).map(([k, v]) => {
189
+ let x = 'object'
190
+ try {
191
+ x = JSON.stringify(v)
192
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
193
+ } catch (_e: unknown) {
194
+ // default is set
195
+ }
196
+
197
+ return k + ': ' + x
198
+ }),
199
+ ),
200
+ )
201
+
202
+ expandArgs(args[key], cont, logger)
203
+ }
204
+ }
205
+
206
+ export async function parse_jsonld(
207
+ args: string,
208
+ logger: Logger,
209
+ items: RunnerItems,
210
+ ): Promise<unknown> {
211
+ const promises: Promise<unknown>[] = []
212
+ logger.info(args)
213
+ const cont = await jsonldToQuads(JSON.parse(args))
214
+ const out = JSON.parse(args, (k, v) => revive(k, v, logger, promises, items))
215
+
216
+ await Promise.all(promises)
217
+
218
+ expandArgs(out, cont, logger)
219
+ return out
220
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,64 @@
1
+ import winston, { Logger } from 'winston'
2
+ import Transport from 'winston-transport'
3
+
4
+ import * as grpc from '@grpc/grpc-js'
5
+ import { LogMessage } from '@rdfc/proto'
6
+
7
+ export class RpcTransport extends Transport {
8
+ private readonly stream: grpc.ClientWritableStream<LogMessage>
9
+ private readonly entities: string[]
10
+ private readonly aliases: string[]
11
+
12
+ constructor(opts: {
13
+ stream: grpc.ClientWritableStream<LogMessage>
14
+ entities: string[]
15
+ aliases?: string[]
16
+ }) {
17
+ super({ level: 'debug' })
18
+
19
+ this.stream = opts.stream
20
+ this.entities = opts.entities
21
+ this.aliases = opts.aliases || []
22
+ }
23
+
24
+ log(info: winston.LogEntry, callback: () => void) {
25
+ if (!this.stream.closed) {
26
+ this.stream.write(
27
+ {
28
+ msg: info.message,
29
+ level: info.level,
30
+ entities: this.entities,
31
+ aliases: this.aliases,
32
+ },
33
+ callback,
34
+ )
35
+ } else {
36
+ console.log('Output stream closed')
37
+ callback()
38
+ }
39
+ }
40
+
41
+ withEntity(entity: string): RpcTransport {
42
+ return new RpcTransport({
43
+ stream: this.stream,
44
+ entities: [...this.entities, entity],
45
+ aliases: this.aliases,
46
+ })
47
+ }
48
+ }
49
+
50
+ export function extendLogger(baseLogger: Logger, newEntity: string): Logger {
51
+ const newTransports = baseLogger.transports.map((t) => {
52
+ if (t instanceof RpcTransport) {
53
+ return t.withEntity(newEntity)
54
+ }
55
+ return t
56
+ })
57
+
58
+ return winston.createLogger({
59
+ level: baseLogger.level,
60
+ format: baseLogger.format,
61
+ defaultMeta: baseLogger.defaultMeta,
62
+ transports: newTransports,
63
+ })
64
+ }
@@ -0,0 +1,39 @@
1
+ import { Logger } from 'winston'
2
+ import { Reader } from './reader'
3
+ import { Writer } from './writer'
4
+
5
+ export type Primitive = string | number | Writer | Reader | ProcessorArgs
6
+
7
+ export type ProcessorArgs = {
8
+ [id: string]: Primitive | Primitive[]
9
+ }
10
+ export type BGetter<T> = {
11
+ [K in keyof T]: T[K]
12
+ }
13
+
14
+ export abstract class Processor<T> {
15
+ protected readonly args: T // Store args safely
16
+ protected readonly logger: Logger
17
+
18
+ constructor(args: T, logger: Logger) {
19
+ Object.assign(this, args)
20
+ this.args = args
21
+ this.logger = logger
22
+ }
23
+
24
+ protected get<K extends keyof T>(key: K): T[K] {
25
+ return this.args[key]
26
+ }
27
+
28
+ // This is the first function that is called (and awaited), when creating a processor
29
+ // This is the perfect location to start things like database connections
30
+ abstract init(this: T & this): Promise<void>
31
+
32
+ // Function to start reading channels
33
+ // This function is called for each processor before _produce_ is called
34
+ abstract transform(this: T & this): Promise<void>
35
+
36
+ // Function to start the production of data, starting the pipeline
37
+ // This function is called after all processors are completely setup
38
+ abstract produce(this: T & this): Promise<void>
39
+ }
package/src/reader.ts ADDED
@@ -0,0 +1,142 @@
1
+ import { ClientReadableStream } from '@grpc/grpc-js'
2
+ import { DataChunk, Message, RunnerClient, StreamMessage } from '@rdfc/proto'
3
+ import winston from 'winston'
4
+ import {
5
+ AnyConvertor,
6
+ Convertor,
7
+ NoConvertor,
8
+ StreamConvertor,
9
+ StringConvertor,
10
+ } from './convertor'
11
+
12
+ export type Any =
13
+ | {
14
+ string: string
15
+ }
16
+ | {
17
+ stream: AsyncGenerator<Uint8Array>
18
+ }
19
+ | {
20
+ buffer: Uint8Array
21
+ }
22
+
23
+ export interface Reader {
24
+ readonly uri: string
25
+ strings(): AsyncIterable<string>
26
+ streams(): AsyncIterable<AsyncGenerator<Uint8Array>>
27
+ buffers(): AsyncIterable<Uint8Array>
28
+ anys(): AsyncIterable<Any>
29
+ }
30
+
31
+ class MyIter<T> implements AsyncIterable<T> {
32
+ private convertor: Convertor<T>
33
+ private queue: (T | undefined)[] = []
34
+ private resolveNext: ((value: undefined) => void) | null = null
35
+
36
+ constructor(convertor: Convertor<T>) {
37
+ this.convertor = convertor
38
+ }
39
+
40
+ push(buffer: Uint8Array) {
41
+ const item = this.convertor.from(buffer)
42
+ this.queue.push(item)
43
+ if (this.resolveNext) {
44
+ this.resolveNext(undefined)
45
+ this.resolveNext = null
46
+ }
47
+ }
48
+
49
+ close() {
50
+ this.queue.push(undefined)
51
+ if (this.resolveNext) {
52
+ this.resolveNext(undefined)
53
+ this.resolveNext = null
54
+ }
55
+ }
56
+
57
+ async pushStream(chunks: ClientReadableStream<DataChunk>) {
58
+ const stream = (async function* (stream) {
59
+ for await (const c of stream) {
60
+ const chunk: DataChunk = c
61
+ yield chunk.data
62
+ }
63
+ })(chunks)
64
+ const item = await this.convertor.fromStream(stream)
65
+ this.queue.push(item)
66
+ if (this.resolveNext) {
67
+ this.resolveNext(undefined)
68
+ this.resolveNext = null
69
+ }
70
+ }
71
+
72
+ async *[Symbol.asyncIterator]() {
73
+ while (true) {
74
+ if (this.queue.length > 0) {
75
+ const item = this.queue.shift()!
76
+ if (item === undefined) break
77
+ yield item
78
+ } else {
79
+ await new Promise<undefined>((resolve) => (this.resolveNext = resolve))
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ export class ReaderInstance implements Reader {
86
+ private client: RunnerClient
87
+ readonly uri: string
88
+ private logger: winston.Logger
89
+
90
+ private iterators: MyIter<unknown>[] = []
91
+
92
+ constructor(uri: string, client: RunnerClient, logger: winston.Logger) {
93
+ this.uri = uri
94
+ this.client = client
95
+ this.logger = logger
96
+ }
97
+
98
+ anys(): AsyncIterable<Any> {
99
+ const iter = new MyIter(AnyConvertor)
100
+ this.iterators.push(iter)
101
+ return iter
102
+ }
103
+
104
+ strings(): AsyncIterable<string> {
105
+ const iter = new MyIter(StringConvertor)
106
+ this.iterators.push(iter)
107
+ return iter
108
+ }
109
+
110
+ buffers(): AsyncIterable<Uint8Array> {
111
+ const iter = new MyIter(NoConvertor)
112
+ this.iterators.push(iter)
113
+ return iter
114
+ }
115
+
116
+ streams(): AsyncIterable<AsyncGenerator<Uint8Array>> {
117
+ const iter = new MyIter(StreamConvertor)
118
+ this.iterators.push(iter)
119
+ return iter
120
+ }
121
+
122
+ handleMsg(msg: Message) {
123
+ this.logger.debug(`${this.uri} handling message`)
124
+ for (const iter of this.iterators) {
125
+ iter.push(msg.data)
126
+ }
127
+ }
128
+
129
+ close() {
130
+ for (const iter of this.iterators) {
131
+ iter.close()
132
+ }
133
+ }
134
+
135
+ handleStreamingMessage(msg: StreamMessage) {
136
+ this.logger.debug(`${this.uri} handling streaming message`)
137
+ const chunks = this.client.receiveStreamMessage(msg.id!)
138
+ for (const iter of this.iterators) {
139
+ iter.pushStream(chunks)
140
+ }
141
+ }
142
+ }