@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.
- package/.editorconfig +9 -0
- package/.github/renovate.json +3 -0
- package/README.md +127 -3
- package/__tests__/channels.test.ts +131 -74
- package/__tests__/echoProcessor.test.ts +131 -0
- package/__tests__/testProcessor.test.ts +69 -0
- package/eslint.config.mjs +1 -1
- package/examples/echo/.idea/echo.iml +9 -0
- package/examples/echo/.idea/misc.xml +6 -0
- package/{.idea → examples/echo/.idea}/modules.xml +1 -1
- package/examples/echo/.idea/vcs.xml +7 -0
- package/examples/echo/.swls/config.json +1 -0
- package/examples/echo/index.ttl +3 -0
- package/examples/echo/minimal.ttl +90 -0
- package/examples/echo/shacl.ttl +9 -0
- package/examples/echo/shape.ttl +1339 -0
- package/examples/echo/test.ttl +11 -0
- package/examples/echo/untitled:/types/MyType.ttl +0 -0
- package/file:/home/silvius/Projects/mumo-pipeline/ldes/http_3A_2F_2Fdata.mumo.be_2Fstreams_2Fnodes_2Fdefault/root/index.trig +3 -0
- package/index.ttl +3 -31
- package/ldes/http_3A_2F_2Fdata.mumo.be_2Fstreams_2Fnodes_2Fdefault/root/index.trig +3 -0
- package/lib/client.js +6 -9
- package/lib/logger.d.ts +2 -2
- package/lib/logger.js +3 -3
- package/lib/reader.d.ts +8 -6
- package/lib/reader.js +135 -25
- package/lib/runner.d.ts +10 -5
- package/lib/runner.js +86 -46
- package/lib/testUtils/duplex.d.ts +25 -0
- package/lib/testUtils/duplex.js +70 -0
- package/lib/testUtils/index.d.ts +51 -0
- package/lib/testUtils/index.js +243 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/writer.d.ts +12 -5
- package/lib/writer.js +66 -13
- package/minimal.ttl +99 -0
- package/package.json +3 -3
- package/src/client.ts +8 -11
- package/src/logger.ts +3 -3
- package/src/reader.ts +207 -29
- package/src/runner.ts +128 -65
- package/src/testUtils/duplex.ts +112 -0
- package/src/testUtils/index.ts +430 -0
- package/src/writer.ts +106 -16
- package/.idea/LNKD.tech Editor.xml +0 -194
- package/.idea/codeStyles/Project.xml +0 -52
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
- package/.idea/js-runner.iml +0 -12
- package/.idea/vcs.xml +0 -6
- package/dist/args.d.ts +0 -4
- package/dist/args.js +0 -58
- package/dist/connectors/file.d.ts +0 -15
- package/dist/connectors/file.js +0 -89
- package/dist/connectors/http.d.ts +0 -14
- package/dist/connectors/http.js +0 -82
- package/dist/connectors/kafka.d.ts +0 -48
- package/dist/connectors/kafka.js +0 -68
- package/dist/connectors/ws.d.ts +0 -10
- package/dist/connectors/ws.js +0 -72
- package/dist/connectors.d.ts +0 -73
- package/dist/connectors.js +0 -168
- package/dist/index.cjs +0 -732
- package/dist/index.d.ts +0 -42
- package/dist/index.js +0 -83
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/dist/util.d.ts +0 -71
- package/dist/util.js +0 -92
- package/src/jsonld.ts +0 -220
- package/src/testUtils.ts +0 -196
package/src/runner.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
Close,
|
|
3
|
+
FromRunner,
|
|
3
4
|
Processor,
|
|
4
5
|
RunnerClient,
|
|
5
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
68
|
+
notifyOrchestrator: Writable,
|
|
66
69
|
uri: string,
|
|
67
70
|
logger: Logger,
|
|
68
71
|
) {
|
|
69
72
|
this.client = client
|
|
70
|
-
this.
|
|
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 =
|
|
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 =
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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.
|
|
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
|
|
129
|
+
const id = uri.value
|
|
130
130
|
|
|
131
|
-
if (this.writers[
|
|
132
|
-
this.writers[
|
|
131
|
+
if (this.writers[id] !== undefined) {
|
|
132
|
+
return this.writers[id]
|
|
133
133
|
}
|
|
134
|
-
const writer = new WriterInstance(
|
|
135
|
-
|
|
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]
|
|
143
|
-
this.readers[ids]
|
|
148
|
+
if (this.readers[ids] !== undefined) {
|
|
149
|
+
return this.readers[ids]
|
|
144
150
|
}
|
|
145
|
-
const reader = new ReaderInstance(
|
|
146
|
-
|
|
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:
|
|
161
|
+
async handleOrchMessage(msg: ToRunner) {
|
|
151
162
|
if (msg.msg) {
|
|
152
|
-
this.
|
|
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
|
-
|
|
160
|
-
reader.handleStreamingMessage(msg.streamMsg)
|
|
161
|
-
}
|
|
167
|
+
await this.handleStreamMsg(msg.streamMsg)
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
if (msg.close) {
|
|
165
|
-
|
|
171
|
+
await this.handleClose(msg.close)
|
|
172
|
+
}
|
|
166
173
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
174
|
+
if (msg.pipeline) {
|
|
175
|
+
this.handlePipeline(msg.pipeline)
|
|
176
|
+
}
|
|
170
177
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
178
|
+
if (msg.processed) {
|
|
179
|
+
this.handleProcessed(msg.processed)
|
|
174
180
|
}
|
|
181
|
+
}
|
|
175
182
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
+
}
|