@rdfc/js-runner 2.0.0 → 3.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.
Files changed (70) hide show
  1. package/.editorconfig +9 -0
  2. package/.github/renovate.json +3 -0
  3. package/README.md +127 -3
  4. package/__tests__/channels.test.ts +131 -74
  5. package/__tests__/echoProcessor.test.ts +131 -0
  6. package/__tests__/testProcessor.test.ts +69 -0
  7. package/eslint.config.mjs +1 -1
  8. package/examples/echo/.idea/echo.iml +9 -0
  9. package/examples/echo/.idea/misc.xml +6 -0
  10. package/{.idea → examples/echo/.idea}/modules.xml +1 -1
  11. package/examples/echo/.idea/vcs.xml +7 -0
  12. package/examples/echo/.swls/config.json +1 -0
  13. package/examples/echo/index.ttl +3 -0
  14. package/examples/echo/minimal.ttl +90 -0
  15. package/examples/echo/shacl.ttl +9 -0
  16. package/examples/echo/shape.ttl +1339 -0
  17. package/examples/echo/test.ttl +11 -0
  18. package/examples/echo/untitled:/types/MyType.ttl +0 -0
  19. package/file:/home/silvius/Projects/mumo-pipeline/ldes/http_3A_2F_2Fdata.mumo.be_2Fstreams_2Fnodes_2Fdefault/root/index.trig +3 -0
  20. package/index.ttl +3 -31
  21. package/ldes/http_3A_2F_2Fdata.mumo.be_2Fstreams_2Fnodes_2Fdefault/root/index.trig +3 -0
  22. package/lib/client.js +6 -9
  23. package/lib/logger.d.ts +2 -2
  24. package/lib/logger.js +3 -3
  25. package/lib/reader.d.ts +8 -6
  26. package/lib/reader.js +135 -25
  27. package/lib/runner.d.ts +10 -5
  28. package/lib/runner.js +86 -46
  29. package/lib/testUtils/duplex.d.ts +25 -0
  30. package/lib/testUtils/duplex.js +70 -0
  31. package/lib/testUtils/index.d.ts +51 -0
  32. package/lib/testUtils/index.js +243 -0
  33. package/lib/tsconfig.tsbuildinfo +1 -1
  34. package/lib/writer.d.ts +12 -5
  35. package/lib/writer.js +66 -13
  36. package/minimal.ttl +99 -0
  37. package/package.json +3 -3
  38. package/src/client.ts +8 -11
  39. package/src/logger.ts +3 -3
  40. package/src/reader.ts +207 -29
  41. package/src/runner.ts +128 -65
  42. package/src/testUtils/duplex.ts +112 -0
  43. package/src/testUtils/index.ts +430 -0
  44. package/src/writer.ts +106 -16
  45. package/.idea/LNKD.tech Editor.xml +0 -194
  46. package/.idea/codeStyles/Project.xml +0 -52
  47. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  48. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  49. package/.idea/js-runner.iml +0 -12
  50. package/.idea/vcs.xml +0 -6
  51. package/dist/args.d.ts +0 -4
  52. package/dist/args.js +0 -58
  53. package/dist/connectors/file.d.ts +0 -15
  54. package/dist/connectors/file.js +0 -89
  55. package/dist/connectors/http.d.ts +0 -14
  56. package/dist/connectors/http.js +0 -82
  57. package/dist/connectors/kafka.d.ts +0 -48
  58. package/dist/connectors/kafka.js +0 -68
  59. package/dist/connectors/ws.d.ts +0 -10
  60. package/dist/connectors/ws.js +0 -72
  61. package/dist/connectors.d.ts +0 -73
  62. package/dist/connectors.js +0 -168
  63. package/dist/index.cjs +0 -732
  64. package/dist/index.d.ts +0 -42
  65. package/dist/index.js +0 -83
  66. package/dist/tsconfig.tsbuildinfo +0 -1
  67. package/dist/util.d.ts +0 -71
  68. package/dist/util.js +0 -92
  69. package/src/jsonld.ts +0 -220
  70. package/src/testUtils.ts +0 -196
package/src/runner.ts CHANGED
@@ -1,15 +1,18 @@
1
1
  import {
2
- OrchestratorMessage,
2
+ Close,
3
+ FromRunner,
3
4
  Processor,
4
5
  RunnerClient,
5
- RunnerMessage,
6
+ ToRunner,
7
+ LocalAck,
8
+ ReceivingMessage,
9
+ ReceivingStreamMessage,
6
10
  } from '@rdfc/proto'
7
11
  import { Reader, ReaderInstance } from './reader'
8
12
  import { Writer, WriterInstance } from './writer'
9
13
  import { Processor as Proc } from './processor'
10
- import { Logger } from 'winston'
14
+ import { createLogger, Logger } from 'winston'
11
15
 
12
- import winston from 'winston'
13
16
  import { RpcTransport } from './logger'
14
17
  import { Cont, empty, extractShapes, Shapes } from 'rdf-lens'
15
18
  import { NamedNode, Parser } from 'n3'
@@ -36,7 +39,7 @@ const RDFC = createNamespace(
36
39
  'Writer',
37
40
  )
38
41
 
39
- export type Writable = (msg: OrchestratorMessage) => Promise<unknown>
42
+ export type Writable = (msg: FromRunner) => Promise<unknown>
40
43
 
41
44
  type ProcessorConfig = {
42
45
  location: string
@@ -47,10 +50,10 @@ type ProcessorConfig = {
47
50
  export type FullProc<C extends Proc<unknown>> =
48
51
  C extends Proc<infer T> ? T & C : unknown
49
52
  export class Runner {
50
- private readonly readers: { [uri: string]: ReaderInstance[] } = {}
51
- private readonly writers: { [uri: string]: WriterInstance[] } = {}
53
+ private readonly readers: { [uri: string]: ReaderInstance } = {}
54
+ private readonly writers: { [uri: string]: WriterInstance } = {}
52
55
  private readonly client: RunnerClient
53
- private readonly write: Writable
56
+ private readonly notifyOrchestrator: Writable
54
57
  private readonly logger: Logger
55
58
  private shapes: Shapes
56
59
  private quads: Quad[] = []
@@ -62,12 +65,12 @@ export class Runner {
62
65
 
63
66
  constructor(
64
67
  client: RunnerClient,
65
- write: Writable,
68
+ notifyOrchestrator: Writable,
66
69
  uri: string,
67
70
  logger: Logger,
68
71
  ) {
69
72
  this.client = client
70
- this.write = write
73
+ this.notifyOrchestrator = notifyOrchestrator
71
74
  this.uri = uri
72
75
  this.logger = logger
73
76
  }
@@ -75,7 +78,7 @@ export class Runner {
75
78
  async addProcessor<P extends Proc<unknown>>(
76
79
  proc: Processor,
77
80
  ): Promise<FullProc<P>> {
78
- const procLogger = winston.createLogger({
81
+ const procLogger = createLogger({
79
82
  transports: [
80
83
  new RpcTransport({
81
84
  entities: [proc.uri, this.uri],
@@ -84,14 +87,13 @@ export class Runner {
84
87
  ],
85
88
  })
86
89
 
87
- const ty = JSON.stringify(
88
- this.quads
89
- .filter(
90
- (x) =>
91
- x.subject.value === proc.uri && x.predicate.equals(RDF.terms.type),
92
- )
93
- .map((x) => x.object.value),
94
- )
90
+ const ty = this.quads
91
+ .filter(
92
+ (x) =>
93
+ x.subject.value === proc.uri && x.predicate.equals(RDF.terms.type),
94
+ )
95
+ .map((x) => x.object.value)
96
+
95
97
  this.logger.info('parsing ' + proc.uri + ' type ' + ty)
96
98
  const args = this.shapes.lenses[RDFL.TypedExtract].execute({
97
99
  id: new NamedNode(proc.uri),
@@ -99,8 +101,6 @@ export class Runner {
99
101
  })
100
102
 
101
103
  const config: ProcessorConfig = JSON.parse(proc.config)
102
- // const url = new URL(config.location)
103
- // process.chdir(url.pathname)
104
104
  const jsProgram = await import(config.file)
105
105
  const clazz = jsProgram[config.clazz || 'default']
106
106
  const instance: Proc<unknown> = new clazz(args, procLogger)
@@ -111,7 +111,7 @@ export class Runner {
111
111
  this.processors.push(instance)
112
112
  this.processorTransforms.push(instance.transform())
113
113
 
114
- await this.write({ init: { uri: proc.uri } })
114
+ await this.notifyOrchestrator({ initialized: { uri: proc.uri } })
115
115
 
116
116
  return <FullProc<P>>instance
117
117
  }
@@ -126,72 +126,135 @@ export class Runner {
126
126
  }
127
127
 
128
128
  createWriter(uri: Term): Writer {
129
- const ids = uri.value
129
+ const id = uri.value
130
130
 
131
- if (this.writers[ids] === undefined) {
132
- this.writers[ids] = []
131
+ if (this.writers[id] !== undefined) {
132
+ return this.writers[id]
133
133
  }
134
- const writer = new WriterInstance(ids, this.client, this.write, this.logger)
135
- this.writers[ids].push(writer)
134
+ const writer = new WriterInstance(
135
+ id,
136
+ this.client,
137
+ this.notifyOrchestrator,
138
+ this.uri,
139
+ this.logger,
140
+ )
141
+ this.writers[id] = writer
136
142
  return writer
137
143
  }
138
144
 
139
145
  createReader(uri: Term): Reader {
140
146
  const ids = uri.value
141
147
 
142
- if (this.readers[ids] === undefined) {
143
- this.readers[ids] = []
148
+ if (this.readers[ids] !== undefined) {
149
+ return this.readers[ids]
144
150
  }
145
- const reader = new ReaderInstance(ids, this.client, this.logger)
146
- this.readers[ids].push(reader)
151
+ const reader = new ReaderInstance(
152
+ ids,
153
+ this.client,
154
+ this.notifyOrchestrator,
155
+ this.logger,
156
+ )
157
+ this.readers[ids] = reader
147
158
  return reader
148
159
  }
149
160
 
150
- async handleOrchMessage(msg: RunnerMessage) {
161
+ async handleOrchMessage(msg: ToRunner) {
151
162
  if (msg.msg) {
152
- this.logger.debug('Handling data msg for ' + msg.msg.channel)
153
- for (const reader of this.readers[msg.msg.channel] || []) {
154
- reader.handleMsg(msg.msg)
155
- }
163
+ this.handleMsg(msg.msg)
156
164
  }
157
165
 
158
166
  if (msg.streamMsg) {
159
- for (const reader of this.readers[msg.streamMsg.channel] || []) {
160
- reader.handleStreamingMessage(msg.streamMsg)
161
- }
167
+ await this.handleStreamMsg(msg.streamMsg)
162
168
  }
163
169
 
164
170
  if (msg.close) {
165
- const uri = msg.close.channel
171
+ await this.handleClose(msg.close)
172
+ }
166
173
 
167
- for (const reader of this.readers[uri] || []) {
168
- reader.close()
169
- }
174
+ if (msg.pipeline) {
175
+ this.handlePipeline(msg.pipeline)
176
+ }
170
177
 
171
- for (const writer of this.writers[uri] || []) {
172
- await writer.close(true)
173
- }
178
+ if (msg.processed) {
179
+ this.handleProcessed(msg.processed)
174
180
  }
181
+ }
175
182
 
176
- if (msg.pipeline) {
177
- try {
178
- // here
179
- const quads = new Parser().parse(msg.pipeline)
180
- this.shapes = extractShapes(
181
- quads,
182
- {
183
- [RDFC.Reader]: (x: Cont) => this.createReader(x.id),
184
- [RDFC.Writer]: (x: Cont) => this.createWriter(x.id),
185
- },
186
- {
187
- [RDFC.Reader]: empty<Cont>(),
188
- [RDFC.Writer]: empty<Cont>(),
189
- },
190
- )
191
- this.quads = quads
192
- } catch (ex: unknown) {
193
- this.logger.error('Pipeline failed: ' + JSON.stringify(ex))
194
- }
183
+ private async handleClose(close: Close) {
184
+ const uri = close.channel
185
+ const r = this.readers[uri]
186
+
187
+ let closed = false
188
+ if (r) {
189
+ r.close()
190
+ closed = true
191
+ }
192
+ const w = this.writers[uri]
193
+ if (w) {
194
+ closed = true
195
+ await w.close(true)
196
+ }
197
+
198
+ if (!closed) {
199
+ this.logger.error(
200
+ `Received a close event for channel ${uri}, but neither reader nor writer is present.`,
201
+ )
202
+ }
203
+ }
204
+
205
+ private handlePipeline(pipeline: string) {
206
+ try {
207
+ const quads = new Parser().parse(pipeline)
208
+ this.shapes = extractShapes(
209
+ quads,
210
+ {
211
+ [RDFC.Reader]: (x: Cont) => this.createReader(x.id),
212
+ [RDFC.Writer]: (x: Cont) => this.createWriter(x.id),
213
+ },
214
+ {
215
+ [RDFC.Reader]: empty<Cont>(),
216
+ [RDFC.Writer]: empty<Cont>(),
217
+ },
218
+ )
219
+ this.quads = quads
220
+ } catch (ex: unknown) {
221
+ this.logger.error('Pipeline failed: ' + JSON.stringify(ex))
222
+ }
223
+ }
224
+
225
+ private handleMsg(msg: ReceivingMessage) {
226
+ this.logger.debug('Handling data msg for ' + msg.channel)
227
+ const r = this.readers[msg.channel]
228
+
229
+ if (r) {
230
+ r.handleMsg(msg)
231
+ } else {
232
+ this.logger.error(
233
+ `Received message for channel ${msg.channel}, but no reader was present.`,
234
+ )
235
+ }
236
+ }
237
+
238
+ private async handleStreamMsg(streamMsg: ReceivingStreamMessage) {
239
+ const r = this.readers[streamMsg.channel]
240
+
241
+ if (r) {
242
+ await r.handleStreamingMessage(streamMsg)
243
+ } else {
244
+ this.logger.error(
245
+ `Received stream message for channel ${streamMsg.channel}, but no reader was present.`,
246
+ )
247
+ }
248
+ }
249
+
250
+ private handleProcessed(processed: LocalAck) {
251
+ const writer = this.writers[processed.channel]
252
+ if (writer) {
253
+ writer.handled()
254
+ } else {
255
+ this.logger.error(
256
+ `Received processed message for channel ${processed.channel}, but no writer was present.`,
257
+ )
195
258
  }
196
259
  }
197
260
  }
@@ -0,0 +1,112 @@
1
+ import { Duplex } from 'stream'
2
+ import type {
3
+ ClientDuplexStream,
4
+ InterceptingCallInterface,
5
+ } from '@grpc/grpc-js'
6
+ import { AuthContext } from '@grpc/grpc-js/build/src/auth-context'
7
+
8
+ type Matcher<Req, T> = (req: Req) => T | undefined
9
+ type Handler<Req, Res> = (req: Req, send: (res: Res) => void) => void
10
+
11
+ type MatchObject<Req, Res, T> = {
12
+ matcher: Matcher<Req, T>
13
+ handler: Handler<T, Res>
14
+ }
15
+
16
+ let count = 0
17
+ export class MockClientDuplexStream<Req, Res>
18
+ extends Duplex
19
+ implements ClientDuplexStream<Req, Res>
20
+ {
21
+ private capabilities: Array<MatchObject<Req, Res, unknown>> = []
22
+ private onceCapabilities: Array<MatchObject<Req, Res, unknown>> = []
23
+
24
+ public readonly id: number
25
+
26
+ call?: InterceptingCallInterface | undefined
27
+ constructor() {
28
+ super({ objectMode: true })
29
+ this.id = count++
30
+ }
31
+
32
+ getAuthContext(): AuthContext | null {
33
+ return null
34
+ }
35
+
36
+ // ---- SurfaceCall stubs ----
37
+ cancel(): void {}
38
+ getPeer(): string {
39
+ return 'mock-peer'
40
+ }
41
+
42
+ // ---- ObjectWritable<Req> ----
43
+ _write(
44
+ chunk: Req,
45
+ _encoding: BufferEncoding,
46
+ callback: (error?: Error | null) => void,
47
+ ): void {
48
+ let handled = false
49
+ // check registered capabilities
50
+ for (const { matcher, handler } of this.capabilities) {
51
+ const o = matcher(chunk)
52
+ if (o !== undefined) {
53
+ handled = true
54
+ handler(o, (res) => this.send(res))
55
+ }
56
+ }
57
+
58
+ const newOnce: typeof this.onceCapabilities = []
59
+ for (const { matcher, handler } of this.onceCapabilities) {
60
+ const o = matcher(chunk)
61
+ if (o !== undefined) {
62
+ handled = true
63
+ handler(o, (res) => this.send(res))
64
+ } else {
65
+ newOnce.push({ matcher, handler })
66
+ }
67
+ }
68
+
69
+ this.onceCapabilities = newOnce
70
+ if (!handled) {
71
+ console.error('Unhandled!', Object.keys(chunk as object))
72
+ }
73
+
74
+ callback()
75
+ }
76
+
77
+ // ---- ObjectReadable<Res> ----
78
+ _read(): void {
79
+ // no-op: we push manually with send()
80
+ }
81
+
82
+ // ---- gRPC-style helpers ----
83
+ serialize(value: Req): Buffer {
84
+ return Buffer.from(JSON.stringify(value))
85
+ }
86
+
87
+ deserialize(chunk: Buffer): Res {
88
+ return JSON.parse(chunk.toString())
89
+ }
90
+
91
+ send(response: Res): void {
92
+ this.push(response)
93
+ }
94
+
95
+ end(): this {
96
+ this.push(null)
97
+ return this
98
+ }
99
+
100
+ // ---- Capability registration ----
101
+ register<T>(matcher: Matcher<Req, T>, handler: Handler<T, Res>): void {
102
+ this.capabilities.push({ matcher, handler })
103
+ }
104
+
105
+ registerOnce<T>(matcher: Matcher<Req, T>, handler: Handler<T, Res>): void {
106
+ this.onceCapabilities.push({ matcher, handler })
107
+ }
108
+
109
+ awaitMsg<T>(matcher: Matcher<Req, T>): Promise<T> {
110
+ return new Promise((res) => this.registerOnce(matcher, res))
111
+ }
112
+ }