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

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 (79) hide show
  1. package/.husky/pre-commit +6 -0
  2. package/.prettierrc +4 -0
  3. package/README.md +3 -38
  4. package/__tests__/channels.test.ts +133 -0
  5. package/bin/runner.js +8 -0
  6. package/eslint.config.mjs +21 -0
  7. package/examples/echo/package-lock.json +80 -0
  8. package/examples/echo/package.json +18 -0
  9. package/examples/echo/pipeline.ttl +30 -0
  10. package/examples/echo/processors.ttl +84 -0
  11. package/examples/echo/src/processors.ts +75 -0
  12. package/examples/echo/test.ttl +14 -0
  13. package/examples/echo/tsconfig.json +114 -0
  14. package/examples/echo/untitled:/types/MyType.ttl +0 -0
  15. package/index.ttl +63 -0
  16. package/jest.config.js +2 -0
  17. package/lib/client.d.ts +1 -0
  18. package/lib/client.js +43 -0
  19. package/lib/convertor.d.ts +9 -0
  20. package/lib/convertor.js +51 -0
  21. package/lib/index.d.ts +4 -0
  22. package/lib/index.js +5 -0
  23. package/lib/jsonld.d.ts +17 -0
  24. package/lib/jsonld.js +134 -0
  25. package/lib/logger.d.ts +15 -0
  26. package/lib/logger.js +27 -0
  27. package/lib/processor.d.ts +19 -0
  28. package/lib/processor.js +13 -0
  29. package/lib/reader.d.ts +29 -0
  30. package/lib/reader.js +110 -0
  31. package/lib/runner.d.ts +25 -0
  32. package/lib/runner.js +111 -0
  33. package/lib/tsconfig.tsbuildinfo +1 -0
  34. package/lib/util_processors.d.ts +11 -0
  35. package/lib/util_processors.js +13 -0
  36. package/lib/writer.d.ts +25 -0
  37. package/lib/writer.js +57 -0
  38. package/package.json +49 -51
  39. package/src/client.ts +52 -0
  40. package/src/convertor.ts +59 -0
  41. package/src/index.ts +4 -0
  42. package/src/jsonld.ts +217 -0
  43. package/src/logger.ts +38 -0
  44. package/src/processor.ts +39 -0
  45. package/src/reader.ts +148 -0
  46. package/src/runner.ts +186 -0
  47. package/src/util_processors.ts +20 -0
  48. package/src/writer.ts +89 -0
  49. package/tsconfig.json +33 -0
  50. package/vite.config.ts +10 -0
  51. package/LICENSE +0 -21
  52. package/bin/js-runner.js +0 -4
  53. package/channels/file.ttl +0 -37
  54. package/channels/http.ttl +0 -59
  55. package/channels/kafka.ttl +0 -98
  56. package/channels/ws.ttl +0 -33
  57. package/dist/args.d.ts +0 -4
  58. package/dist/args.js +0 -59
  59. package/dist/connectors/file.d.ts +0 -15
  60. package/dist/connectors/file.js +0 -89
  61. package/dist/connectors/http.d.ts +0 -14
  62. package/dist/connectors/http.js +0 -82
  63. package/dist/connectors/kafka.d.ts +0 -48
  64. package/dist/connectors/kafka.js +0 -64
  65. package/dist/connectors/ws.d.ts +0 -10
  66. package/dist/connectors/ws.js +0 -69
  67. package/dist/connectors.d.ts +0 -54
  68. package/dist/connectors.js +0 -145
  69. package/dist/index.cjs +0 -677
  70. package/dist/index.d.ts +0 -33
  71. package/dist/index.js +0 -74
  72. package/dist/tsconfig.tsbuildinfo +0 -1
  73. package/dist/util.d.ts +0 -43
  74. package/dist/util.js +0 -75
  75. package/ontology.ttl +0 -169
  76. package/processor/echo.ttl +0 -38
  77. package/processor/resc.ttl +0 -34
  78. package/processor/send.ttl +0 -40
  79. package/processor/test.js +0 -35
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,4 @@
1
+ export * from './client'
2
+ export * from './writer'
3
+ export * from './reader'
4
+ export * from './processor'
package/src/jsonld.ts ADDED
@@ -0,0 +1,217 @@
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/ontology#',
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
+ } catch (e) {}
193
+
194
+ return k + ': ' + x
195
+ }),
196
+ ),
197
+ )
198
+
199
+ expandArgs(args[key], cont, logger)
200
+ }
201
+ }
202
+
203
+ export async function parse_jsonld(
204
+ args: string,
205
+ logger: Logger,
206
+ items: RunnerItems,
207
+ ): Promise<unknown> {
208
+ const promises: Promise<unknown>[] = []
209
+ logger.info(args)
210
+ const cont = await jsonldToQuads(JSON.parse(args))
211
+ const out = JSON.parse(args, (k, v) => revive(k, v, logger, promises, items))
212
+
213
+ await Promise.all(promises)
214
+
215
+ expandArgs(out, cont, logger)
216
+ return out
217
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,38 @@
1
+ import winston from 'winston'
2
+ import Transport from 'winston-transport'
3
+
4
+ import * as grpc from '@grpc/grpc-js'
5
+ import { LogMessage } from '@rdfc/proto'
6
+ export class RpcTransport extends Transport {
7
+ private readonly stream: grpc.ClientWritableStream<LogMessage>
8
+ private readonly entities: string[]
9
+ private readonly aliases: string[]
10
+ constructor(opts: {
11
+ stream: grpc.ClientWritableStream<LogMessage>
12
+ entities: string[]
13
+ aliases?: string[]
14
+ }) {
15
+ super({ level: 'debug' })
16
+
17
+ this.stream = opts.stream
18
+ this.entities = opts.entities
19
+ this.aliases = opts.aliases || []
20
+ }
21
+
22
+ log(info: winston.LogEntry, callback: () => void) {
23
+ if (!this.stream.closed) {
24
+ this.stream.write(
25
+ {
26
+ msg: info.message,
27
+ level: info.level,
28
+ entities: this.entities,
29
+ aliases: this.aliases,
30
+ },
31
+ callback,
32
+ )
33
+ } else {
34
+ console.log('Output stream closed')
35
+ callback()
36
+ }
37
+ }
38
+ }
@@ -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,148 @@
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
+ strings(): AsyncIterable<string>
25
+ streams(): AsyncIterable<AsyncGenerator<Uint8Array>>
26
+ buffers(): AsyncIterable<Uint8Array>
27
+ anys(): AsyncIterable<Any>
28
+ }
29
+
30
+ class MyIter<T> implements AsyncIterable<T> {
31
+ private convertor: Convertor<T>
32
+ private queue: (T | undefined)[] = []
33
+ private resolveNext: ((value: T | undefined) => void) | null = null
34
+
35
+ constructor(convertor: Convertor<T>) {
36
+ this.convertor = convertor
37
+ }
38
+
39
+ push(buffer: Uint8Array) {
40
+ const item = this.convertor.from(buffer)
41
+ if (this.resolveNext) {
42
+ this.resolveNext(item)
43
+ this.resolveNext = null
44
+ } else {
45
+ this.queue.push(item)
46
+ }
47
+ }
48
+
49
+ close() {
50
+ if (this.resolveNext) {
51
+ this.resolveNext(undefined)
52
+ this.resolveNext = null
53
+ } else {
54
+ this.queue.push(undefined)
55
+ }
56
+ }
57
+
58
+ async pushStream(chunks: ClientReadableStream<DataChunk>) {
59
+ const stream = (async function* (stream) {
60
+ for await (const c of stream) {
61
+ const chunk: DataChunk = c
62
+ yield chunk.data
63
+ }
64
+ })(chunks)
65
+ const item = await this.convertor.fromStream(stream)
66
+ if (this.resolveNext) {
67
+ this.resolveNext(item)
68
+ this.resolveNext = null
69
+ } else {
70
+ this.queue.push(item)
71
+ }
72
+ }
73
+
74
+ async *[Symbol.asyncIterator]() {
75
+ while (true) {
76
+ if (this.queue.length > 0) {
77
+ const item = this.queue.shift()!
78
+ if (item === undefined) break
79
+ yield item
80
+ } else {
81
+ const item = await new Promise<T | undefined>(
82
+ (resolve) => (this.resolveNext = resolve),
83
+ )
84
+ if (item === undefined) break
85
+ yield item
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ export class ReaderInstance implements Reader {
92
+ private client: RunnerClient
93
+ private uri: string
94
+ private logger: winston.Logger
95
+
96
+ private iterators: MyIter<unknown>[] = []
97
+
98
+ constructor(uri: string, client: RunnerClient, logger: winston.Logger) {
99
+ this.uri = uri
100
+ this.client = client
101
+ this.logger = logger
102
+ }
103
+
104
+ anys(): AsyncIterable<Any> {
105
+ const iter = new MyIter(AnyConvertor)
106
+ this.iterators.push(iter)
107
+ return iter
108
+ }
109
+
110
+ strings(): AsyncIterable<string> {
111
+ const iter = new MyIter(StringConvertor)
112
+ this.iterators.push(iter)
113
+ return iter
114
+ }
115
+
116
+ buffers(): AsyncIterable<Uint8Array> {
117
+ const iter = new MyIter(NoConvertor)
118
+ this.iterators.push(iter)
119
+ return iter
120
+ }
121
+
122
+ streams(): AsyncIterable<AsyncGenerator<Uint8Array>> {
123
+ const iter = new MyIter(StreamConvertor)
124
+ this.iterators.push(iter)
125
+ return iter
126
+ }
127
+
128
+ handleMsg(msg: Message) {
129
+ this.logger.debug(`${this.uri} handling message`)
130
+ for (const iter of this.iterators) {
131
+ iter.push(msg.data)
132
+ }
133
+ }
134
+
135
+ close() {
136
+ for (const iter of this.iterators) {
137
+ iter.close()
138
+ }
139
+ }
140
+
141
+ handleStreamingMessage(msg: StreamMessage) {
142
+ this.logger.debug(`${this.uri} handling streaming message`)
143
+ const chunks = this.client.receiveStreamMessage(msg.id!)
144
+ for (const iter of this.iterators) {
145
+ iter.pushStream(chunks)
146
+ }
147
+ }
148
+ }