@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/lib/writer.js CHANGED
@@ -2,14 +2,23 @@ import { promisify } from 'util';
2
2
  const encoder = new TextEncoder();
3
3
  export class WriterInstance {
4
4
  uri;
5
+ localSequenceNumber = 1;
5
6
  client;
6
- write;
7
+ notifyOrchestrator;
7
8
  logger;
8
- constructor(uri, client, write, logger) {
9
+ awaitingProcessed = [];
10
+ openStreams = 0;
11
+ shouldClose = [];
12
+ runnerId;
13
+ constructor(uri, client, notifyOrchestrator, runnerId, logger) {
9
14
  this.client = client;
10
- this.write = write;
15
+ this.notifyOrchestrator = notifyOrchestrator;
11
16
  this.uri = uri;
12
17
  this.logger = logger;
18
+ this.runnerId = runnerId;
19
+ }
20
+ awaitProcessed() {
21
+ return new Promise((res) => this.awaitingProcessed.push(res));
13
22
  }
14
23
  async any(any) {
15
24
  if ('stream' in any) {
@@ -24,34 +33,78 @@ export class WriterInstance {
24
33
  }
25
34
  async buffer(buffer) {
26
35
  this.logger.debug(`${this.uri} sends buffer ${buffer.length} bytes`);
27
- await this.write({ msg: { data: buffer, channel: this.uri } });
36
+ const localSequenceNumber = this.localSequenceNumber++;
37
+ const handledPromise = this.awaitProcessed();
38
+ await this.notifyOrchestrator({
39
+ msg: { data: buffer, channel: this.uri, localSequenceNumber },
40
+ });
41
+ await handledPromise;
28
42
  }
29
43
  async stream(buffer, transform) {
44
+ this.openStreams += 1;
30
45
  const t = transform || ((x) => x);
31
46
  const stream = this.client.sendStreamMessage();
47
+ const handledPromise = this.awaitProcessed();
48
+ const writeStreamMessageChunk = promisify(stream.write.bind(stream));
49
+ const localSequenceNumber = this.localSequenceNumber++;
50
+ await writeStreamMessageChunk({
51
+ id: {
52
+ channel: this.uri,
53
+ localSequenceNumber,
54
+ runner: this.runnerId,
55
+ },
56
+ });
32
57
  const id = await new Promise((res) => stream.once('data', res));
33
- this.logger.debug(`${this.uri} streams message with id ${id.id}`);
34
- await this.write({ streamMsg: { id, channel: this.uri } });
35
- const write = promisify(stream.write.bind(stream));
58
+ this.logger.debug(`${this.uri} streams message with id ${JSON.stringify(id)}`);
36
59
  for await (const msg of buffer) {
37
- await write({ data: t(msg) });
60
+ const processedPromise = new Promise((res) => stream.once('data', res));
61
+ await writeStreamMessageChunk({ data: { data: t(msg) } });
62
+ await processedPromise;
38
63
  }
39
- this.logger.debug(`${this.uri} is done streaming message with id ${id.id}`);
40
64
  stream.end();
65
+ await handledPromise;
66
+ this.openStreams -= 1;
67
+ if (this.shouldClose.length > 0)
68
+ await this.close();
41
69
  }
42
70
  async string(msg) {
43
71
  this.logger.debug(`${this.uri} sends string ${msg.length} characters`);
44
- await this.write({
45
- msg: { data: encoder.encode(msg), channel: this.uri },
72
+ const localSequenceNumber = this.localSequenceNumber++;
73
+ const handledPromise = this.awaitProcessed();
74
+ await this.notifyOrchestrator({
75
+ msg: {
76
+ data: encoder.encode(msg),
77
+ channel: this.uri,
78
+ localSequenceNumber,
79
+ },
46
80
  });
81
+ await handledPromise;
47
82
  }
48
83
  async close(issued = false) {
84
+ if (this.openStreams !== 0) {
85
+ await new Promise((resolve) => this.shouldClose.push(resolve));
86
+ return;
87
+ }
49
88
  this.logger.debug(`${this.uri} closes stream`);
50
89
  if (!issued) {
51
- await this.write({
90
+ await this.notifyOrchestrator({
52
91
  close: { channel: this.uri },
53
92
  });
54
93
  }
94
+ let resolve = this.shouldClose.pop();
95
+ while (resolve) {
96
+ resolve();
97
+ resolve = this.shouldClose.pop();
98
+ }
99
+ }
100
+ handled() {
101
+ if (this.awaitingProcessed.length > 0) {
102
+ this.awaitingProcessed.shift()();
103
+ }
104
+ else {
105
+ this.logger.error('Expected to be waiting for a message to be processed, but this is not the case ' +
106
+ this.uri);
107
+ }
55
108
  }
56
109
  }
57
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid3JpdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3dyaXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sTUFBTSxDQUFBO0FBbUJoQyxNQUFNLE9BQU8sR0FBRyxJQUFJLFdBQVcsRUFBRSxDQUFBO0FBQ2pDLE1BQU0sT0FBTyxjQUFjO0lBQ2hCLEdBQUcsQ0FBUTtJQUNILE1BQU0sQ0FBYztJQUNwQixLQUFLLENBQVU7SUFDZixNQUFNLENBQVE7SUFFL0IsWUFDRSxHQUFXLEVBQ1gsTUFBb0IsRUFDcEIsS0FBZSxFQUNmLE1BQWM7UUFFZCxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtRQUNwQixJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQTtRQUNsQixJQUFJLENBQUMsR0FBRyxHQUFHLEdBQUcsQ0FBQTtRQUNkLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFBO0lBQ3RCLENBQUM7SUFDRCxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQVE7UUFDaEIsSUFBSSxRQUFRLElBQUksR0FBRyxFQUFFLENBQUM7WUFDcEIsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUMvQixDQUFDO1FBQ0QsSUFBSSxRQUFRLElBQUksR0FBRyxFQUFFLENBQUM7WUFDcEIsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUMvQixDQUFDO1FBQ0QsSUFBSSxRQUFRLElBQUksR0FBRyxFQUFFLENBQUM7WUFDcEIsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUMvQixDQUFDO0lBQ0gsQ0FBQztJQUVELEtBQUssQ0FBQyxNQUFNLENBQUMsTUFBa0I7UUFDN0IsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsR0FBRyxpQkFBaUIsTUFBTSxDQUFDLE1BQU0sUUFBUSxDQUFDLENBQUE7UUFDcEUsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsR0FBRyxFQUFFLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsSUFBSSxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsQ0FBQTtJQUNoRSxDQUFDO0lBRUQsS0FBSyxDQUFDLE1BQU0sQ0FDVixNQUF3QixFQUN4QixTQUFnQztRQUVoQyxNQUFNLENBQUMsR0FBRyxTQUFTLElBQUksQ0FBQyxDQUFDLENBQVUsRUFBRSxFQUFFLENBQWEsQ0FBQyxDQUFDLENBQUE7UUFDdEQsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsRUFBRSxDQUFBO1FBQzlDLE1BQU0sRUFBRSxHQUFPLE1BQU0sSUFBSSxPQUFPLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUE7UUFDbkUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsR0FBRyw0QkFBNEIsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUE7UUFDakUsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRSxFQUFFLE9BQU8sRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBRTFELE1BQU0sS0FBSyxHQUFHLFNBQVMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFBO1FBQ2xELElBQUksS0FBSyxFQUFFLE1BQU0sR0FBRyxJQUFJLE1BQU0sRUFBRSxDQUFDO1lBQy9CLE1BQU0sS0FBSyxDQUFDLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUE7UUFDL0IsQ0FBQztRQUVELElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsSUFBSSxDQUFDLEdBQUcsc0NBQXNDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBQzNFLE1BQU0sQ0FBQyxHQUFHLEVBQUUsQ0FBQTtJQUNkLENBQUM7SUFFRCxLQUFLLENBQUMsTUFBTSxDQUFDLEdBQVc7UUFDdEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsR0FBRyxpQkFBaUIsR0FBRyxDQUFDLE1BQU0sYUFBYSxDQUFDLENBQUE7UUFDdEUsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDO1lBQ2YsR0FBRyxFQUFFLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxHQUFHLEVBQUU7U0FDdEQsQ0FBQyxDQUFBO0lBQ0osQ0FBQztJQUVELEtBQUssQ0FBQyxLQUFLLENBQUMsTUFBTSxHQUFHLEtBQUs7UUFDeEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsR0FBRyxnQkFBZ0IsQ0FBQyxDQUFBO1FBQzlDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNaLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQztnQkFDZixLQUFLLEVBQUUsRUFBRSxPQUFPLEVBQUUsSUFBSSxDQUFDLEdBQUcsRUFBRTthQUM3QixDQUFDLENBQUE7UUFDSixDQUFDO0lBQ0gsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSWQsIE9yY2hlc3RyYXRvck1lc3NhZ2UsIFJ1bm5lckNsaWVudCB9IGZyb20gJ0ByZGZjL3Byb3RvJ1xuaW1wb3J0IHsgcHJvbWlzaWZ5IH0gZnJvbSAndXRpbCdcbmltcG9ydCB7IExvZ2dlciB9IGZyb20gJ3dpbnN0b24nXG5pbXBvcnQgeyBBbnkgfSBmcm9tICcuL3JlYWRlcidcblxudHlwZSBXcml0YWJsZSA9IChtc2c6IE9yY2hlc3RyYXRvck1lc3NhZ2UpID0+IFByb21pc2U8dW5rbm93bj5cbmV4cG9ydCBpbnRlcmZhY2UgV3JpdGVyIHtcbiAgcmVhZG9ubHkgdXJpOiBzdHJpbmdcbiAgYnVmZmVyKGJ1ZmZlcjogVWludDhBcnJheSk6IFByb21pc2U8dm9pZD5cblxuICBzdHJlYW0oYnVmZmVyOiBBc3luY0l0ZXJhYmxlPFVpbnQ4QXJyYXk+KTogUHJvbWlzZTx2b2lkPlxuICBzdHJlYW08VD4oXG4gICAgYnVmZmVyOiBBc3luY0l0ZXJhYmxlPFQ+LFxuICAgIHRyYW5mb3JtOiAoeDogVCkgPT4gVWludDhBcnJheSxcbiAgKTogUHJvbWlzZTx2b2lkPlxuXG4gIHN0cmluZyhidWZmZXI6IHN0cmluZyk6IFByb21pc2U8dm9pZD5cbiAgYW55KGFueTogQW55KTogUHJvbWlzZTx2b2lkPlxuICBjbG9zZSgpOiBQcm9taXNlPHZvaWQ+XG59XG5jb25zdCBlbmNvZGVyID0gbmV3IFRleHRFbmNvZGVyKClcbmV4cG9ydCBjbGFzcyBXcml0ZXJJbnN0YW5jZSBpbXBsZW1lbnRzIFdyaXRlciB7XG4gIHJlYWRvbmx5IHVyaTogc3RyaW5nXG4gIHByaXZhdGUgcmVhZG9ubHkgY2xpZW50OiBSdW5uZXJDbGllbnRcbiAgcHJpdmF0ZSByZWFkb25seSB3cml0ZTogV3JpdGFibGVcbiAgcHJpdmF0ZSByZWFkb25seSBsb2dnZXI6IExvZ2dlclxuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHVyaTogc3RyaW5nLFxuICAgIGNsaWVudDogUnVubmVyQ2xpZW50LFxuICAgIHdyaXRlOiBXcml0YWJsZSxcbiAgICBsb2dnZXI6IExvZ2dlcixcbiAgKSB7XG4gICAgdGhpcy5jbGllbnQgPSBjbGllbnRcbiAgICB0aGlzLndyaXRlID0gd3JpdGVcbiAgICB0aGlzLnVyaSA9IHVyaVxuICAgIHRoaXMubG9nZ2VyID0gbG9nZ2VyXG4gIH1cbiAgYXN5bmMgYW55KGFueTogQW55KTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKCdzdHJlYW0nIGluIGFueSkge1xuICAgICAgYXdhaXQgdGhpcy5zdHJlYW0oYW55LnN0cmVhbSlcbiAgICB9XG4gICAgaWYgKCdidWZmZXInIGluIGFueSkge1xuICAgICAgYXdhaXQgdGhpcy5idWZmZXIoYW55LmJ1ZmZlcilcbiAgICB9XG4gICAgaWYgKCdzdHJpbmcnIGluIGFueSkge1xuICAgICAgYXdhaXQgdGhpcy5zdHJpbmcoYW55LnN0cmluZylcbiAgICB9XG4gIH1cblxuICBhc3luYyBidWZmZXIoYnVmZmVyOiBVaW50OEFycmF5KTogUHJvbWlzZTx2b2lkPiB7XG4gICAgdGhpcy5sb2dnZXIuZGVidWcoYCR7dGhpcy51cml9IHNlbmRzIGJ1ZmZlciAke2J1ZmZlci5sZW5ndGh9IGJ5dGVzYClcbiAgICBhd2FpdCB0aGlzLndyaXRlKHsgbXNnOiB7IGRhdGE6IGJ1ZmZlciwgY2hhbm5lbDogdGhpcy51cmkgfSB9KVxuICB9XG5cbiAgYXN5bmMgc3RyZWFtPFQgPSBVaW50OEFycmF5PihcbiAgICBidWZmZXI6IEFzeW5jSXRlcmFibGU8VD4sXG4gICAgdHJhbnNmb3JtPzogKHg6IFQpID0+IFVpbnQ4QXJyYXksXG4gICkge1xuICAgIGNvbnN0IHQgPSB0cmFuc2Zvcm0gfHwgKCh4OiB1bmtub3duKSA9PiA8VWludDhBcnJheT54KVxuICAgIGNvbnN0IHN0cmVhbSA9IHRoaXMuY2xpZW50LnNlbmRTdHJlYW1NZXNzYWdlKClcbiAgICBjb25zdCBpZDogSWQgPSBhd2FpdCBuZXcgUHJvbWlzZSgocmVzKSA9PiBzdHJlYW0ub25jZSgnZGF0YScsIHJlcykpXG4gICAgdGhpcy5sb2dnZXIuZGVidWcoYCR7dGhpcy51cml9IHN0cmVhbXMgbWVzc2FnZSB3aXRoIGlkICR7aWQuaWR9YClcbiAgICBhd2FpdCB0aGlzLndyaXRlKHsgc3RyZWFtTXNnOiB7IGlkLCBjaGFubmVsOiB0aGlzLnVyaSB9IH0pXG5cbiAgICBjb25zdCB3cml0ZSA9IHByb21pc2lmeShzdHJlYW0ud3JpdGUuYmluZChzdHJlYW0pKVxuICAgIGZvciBhd2FpdCAoY29uc3QgbXNnIG9mIGJ1ZmZlcikge1xuICAgICAgYXdhaXQgd3JpdGUoeyBkYXRhOiB0KG1zZykgfSlcbiAgICB9XG5cbiAgICB0aGlzLmxvZ2dlci5kZWJ1ZyhgJHt0aGlzLnVyaX0gaXMgZG9uZSBzdHJlYW1pbmcgbWVzc2FnZSB3aXRoIGlkICR7aWQuaWR9YClcbiAgICBzdHJlYW0uZW5kKClcbiAgfVxuXG4gIGFzeW5jIHN0cmluZyhtc2c6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRoaXMubG9nZ2VyLmRlYnVnKGAke3RoaXMudXJpfSBzZW5kcyBzdHJpbmcgJHttc2cubGVuZ3RofSBjaGFyYWN0ZXJzYClcbiAgICBhd2FpdCB0aGlzLndyaXRlKHtcbiAgICAgIG1zZzogeyBkYXRhOiBlbmNvZGVyLmVuY29kZShtc2cpLCBjaGFubmVsOiB0aGlzLnVyaSB9LFxuICAgIH0pXG4gIH1cblxuICBhc3luYyBjbG9zZShpc3N1ZWQgPSBmYWxzZSk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRoaXMubG9nZ2VyLmRlYnVnKGAke3RoaXMudXJpfSBjbG9zZXMgc3RyZWFtYClcbiAgICBpZiAoIWlzc3VlZCkge1xuICAgICAgYXdhaXQgdGhpcy53cml0ZSh7XG4gICAgICAgIGNsb3NlOiB7IGNoYW5uZWw6IHRoaXMudXJpIH0sXG4gICAgICB9KVxuICAgIH1cbiAgfVxufVxuIl19
110
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid3JpdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3dyaXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sTUFBTSxDQUFBO0FBbUJoQyxNQUFNLE9BQU8sR0FBRyxJQUFJLFdBQVcsRUFBRSxDQUFBO0FBQ2pDLE1BQU0sT0FBTyxjQUFjO0lBQ2hCLEdBQUcsQ0FBUTtJQUNwQixtQkFBbUIsR0FBVyxDQUFDLENBQUE7SUFDZCxNQUFNLENBQWM7SUFDcEIsa0JBQWtCLENBQVU7SUFDNUIsTUFBTSxDQUFRO0lBRXZCLGlCQUFpQixHQUFzQixFQUFFLENBQUE7SUFFekMsV0FBVyxHQUFXLENBQUMsQ0FBQTtJQUN2QixXQUFXLEdBQXNCLEVBQUUsQ0FBQTtJQUUxQixRQUFRLENBQVE7SUFFakMsWUFDRSxHQUFXLEVBQ1gsTUFBb0IsRUFDcEIsa0JBQTRCLEVBQzVCLFFBQWdCLEVBQ2hCLE1BQWM7UUFFZCxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtRQUNwQixJQUFJLENBQUMsa0JBQWtCLEdBQUcsa0JBQWtCLENBQUE7UUFDNUMsSUFBSSxDQUFDLEdBQUcsR0FBRyxHQUFHLENBQUE7UUFDZCxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtRQUNwQixJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQTtJQUMxQixDQUFDO0lBRU8sY0FBYztRQUNwQixPQUFPLElBQUksT0FBTyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUE7SUFDL0QsQ0FBQztJQUVELEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBUTtRQUNoQixJQUFJLFFBQVEsSUFBSSxHQUFHLEVBQUUsQ0FBQztZQUNwQixNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFBO1FB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R0FBRyxFQUFFLENBQUE7UUFDcEMsT0FBTyxPQUFPLEVBQUUsQ0FBQztZQUNmLE9BQU8sRUFBRSxDQUFBO1lBQ1QsT0FBTyxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsR0FBRyxFQUFFLENBQUE7UUFDbEMsQ0FBQztJQUNILENBQUM7SUFLRCxPQUFPO1FBQ0wsSUFBSSxJQUFJLENBQUMsaUJBQWlCLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQ3RDLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLLEVBQUcsRUFBRSxDQUFBO1FBQ25DLENBQUM7YUFBTSxDQUFDO1lBQ04sSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQ2YsaUZBQWlGO2dCQUMvRSxJQUFJLENBQUMsR0FBRyxDQUNYLENBQUE7UUFDSCxDQUFDO0lBQ0gsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRnJvbVJ1bm5lciwgUnVubmVyQ2xpZW50IH0gZnJvbSAnQHJkZmMvcHJvdG8nXG5pbXBvcnQgeyBwcm9taXNpZnkgfSBmcm9tICd1dGlsJ1xuaW1wb3J0IHsgTG9nZ2VyIH0gZnJvbSAnd2luc3RvbidcbmltcG9ydCB7IEFueSB9IGZyb20gJy4vcmVhZGVyJ1xuXG50eXBlIFdyaXRhYmxlID0gKG1zZzogRnJvbVJ1bm5lcikgPT4gUHJvbWlzZTx1bmtub3duPlxuZXhwb3J0IGludGVyZmFjZSBXcml0ZXIge1xuICByZWFkb25seSB1cmk6IHN0cmluZ1xuICBidWZmZXIoYnVmZmVyOiBVaW50OEFycmF5KTogUHJvbWlzZTx2b2lkPlxuXG4gIHN0cmVhbShidWZmZXI6IEFzeW5jSXRlcmFibGU8VWludDhBcnJheT4pOiBQcm9taXNlPHZvaWQ+XG4gIHN0cmVhbTxUPihcbiAgICBidWZmZXI6IEFzeW5jSXRlcmFibGU8VD4sXG4gICAgdHJhbnNmb3JtOiAoeDogVCkgPT4gVWludDhBcnJheSxcbiAgKTogUHJvbWlzZTx2b2lkPlxuXG4gIHN0cmluZyhidWZmZXI6IHN0cmluZyk6IFByb21pc2U8dm9pZD5cbiAgYW55KGFueTogQW55KTogUHJvbWlzZTx2b2lkPlxuICBjbG9zZSgpOiBQcm9taXNlPHZvaWQ+XG59XG5jb25zdCBlbmNvZGVyID0gbmV3IFRleHRFbmNvZGVyKClcbmV4cG9ydCBjbGFzcyBXcml0ZXJJbnN0YW5jZSBpbXBsZW1lbnRzIFdyaXRlciB7XG4gIHJlYWRvbmx5IHVyaTogc3RyaW5nXG4gIGxvY2FsU2VxdWVuY2VOdW1iZXI6IG51bWJlciA9IDFcbiAgcHJpdmF0ZSByZWFkb25seSBjbGllbnQ6IFJ1bm5lckNsaWVudFxuICBwcml2YXRlIHJlYWRvbmx5IG5vdGlmeU9yY2hlc3RyYXRvcjogV3JpdGFibGVcbiAgcHJpdmF0ZSByZWFkb25seSBsb2dnZXI6IExvZ2dlclxuXG4gIHByaXZhdGUgYXdhaXRpbmdQcm9jZXNzZWQ6IEFycmF5PCgpID0+IHZvaWQ+ID0gW11cblxuICBwcml2YXRlIG9wZW5TdHJlYW1zOiBudW1iZXIgPSAwXG4gIHByaXZhdGUgc2hvdWxkQ2xvc2U6IEFycmF5PCgpID0+IHZvaWQ+ID0gW11cblxuICBwcml2YXRlIHJlYWRvbmx5IHJ1bm5lcklkOiBzdHJpbmdcblxuICBjb25zdHJ1Y3RvcihcbiAgICB1cmk6IHN0cmluZyxcbiAgICBjbGllbnQ6IFJ1bm5lckNsaWVudCxcbiAgICBub3RpZnlPcmNoZXN0cmF0b3I6IFdyaXRhYmxlLFxuICAgIHJ1bm5lcklkOiBzdHJpbmcsXG4gICAgbG9nZ2VyOiBMb2dnZXIsXG4gICkge1xuICAgIHRoaXMuY2xpZW50ID0gY2xpZW50XG4gICAgdGhpcy5ub3RpZnlPcmNoZXN0cmF0b3IgPSBub3RpZnlPcmNoZXN0cmF0b3JcbiAgICB0aGlzLnVyaSA9IHVyaVxuICAgIHRoaXMubG9nZ2VyID0gbG9nZ2VyXG4gICAgdGhpcy5ydW5uZXJJZCA9IHJ1bm5lcklkXG4gIH1cblxuICBwcml2YXRlIGF3YWl0UHJvY2Vzc2VkKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzKSA9PiB0aGlzLmF3YWl0aW5nUHJvY2Vzc2VkLnB1c2gocmVzKSlcbiAgfVxuXG4gIGFzeW5jIGFueShhbnk6IEFueSk6IFByb21pc2U8dm9pZD4ge1xuICAgIGlmICgnc3RyZWFtJyBpbiBhbnkpIHtcbiAgICAgIGF3YWl0IHRoaXMuc3RyZWFtKGFueS5zdHJlYW0pXG4gICAgfVxuICAgIGlmICgnYnVmZmVyJyBpbiBhbnkpIHtcbiAgICAgIGF3YWl0IHRoaXMuYnVmZmVyKGFueS5idWZmZXIpXG4gICAgfVxuICAgIGlmICgnc3RyaW5nJyBpbiBhbnkpIHtcbiAgICAgIGF3YWl0IHRoaXMuc3RyaW5nKGFueS5zdHJpbmcpXG4gICAgfVxuICB9XG5cbiAgYXN5bmMgYnVmZmVyKGJ1ZmZlcjogVWludDhBcnJheSk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRoaXMubG9nZ2VyLmRlYnVnKGAke3RoaXMudXJpfSBzZW5kcyBidWZmZXIgJHtidWZmZXIubGVuZ3RofSBieXRlc2ApXG4gICAgY29uc3QgbG9jYWxTZXF1ZW5jZU51bWJlciA9IHRoaXMubG9jYWxTZXF1ZW5jZU51bWJlcisrXG4gICAgY29uc3QgaGFuZGxlZFByb21pc2UgPSB0aGlzLmF3YWl0UHJvY2Vzc2VkKClcblxuICAgIGF3YWl0IHRoaXMubm90aWZ5T3JjaGVzdHJhdG9yKHtcbiAgICAgIG1zZzogeyBkYXRhOiBidWZmZXIsIGNoYW5uZWw6IHRoaXMudXJpLCBsb2NhbFNlcXVlbmNlTnVtYmVyIH0sXG4gICAgfSlcbiAgICBhd2FpdCBoYW5kbGVkUHJvbWlzZVxuICB9XG5cbiAgYXN5bmMgc3RyZWFtPFQgPSBVaW50OEFycmF5PihcbiAgICBidWZmZXI6IEFzeW5jSXRlcmFibGU8VD4sXG4gICAgdHJhbnNmb3JtPzogKHg6IFQpID0+IFVpbnQ4QXJyYXksXG4gICkge1xuICAgIHRoaXMub3BlblN0cmVhbXMgKz0gMVxuICAgIGNvbnN0IHQgPSB0cmFuc2Zvcm0gfHwgKCh4OiB1bmtub3duKSA9PiA8VWludDhBcnJheT54KVxuICAgIGNvbnN0IHN0cmVhbSA9IHRoaXMuY2xpZW50LnNlbmRTdHJlYW1NZXNzYWdlKClcblxuICAgIGNvbnN0IGhhbmRsZWRQcm9taXNlID0gdGhpcy5hd2FpdFByb2Nlc3NlZCgpXG4gICAgY29uc3Qgd3JpdGVTdHJlYW1NZXNzYWdlQ2h1bmsgPSBwcm9taXNpZnkoc3RyZWFtLndyaXRlLmJpbmQoc3RyZWFtKSlcbiAgICBjb25zdCBsb2NhbFNlcXVlbmNlTnVtYmVyID0gdGhpcy5sb2NhbFNlcXVlbmNlTnVtYmVyKytcbiAgICBhd2FpdCB3cml0ZVN0cmVhbU1lc3NhZ2VDaHVuayh7XG4gICAgICBpZDoge1xuICAgICAgICBjaGFubmVsOiB0aGlzLnVyaSxcbiAgICAgICAgbG9jYWxTZXF1ZW5jZU51bWJlcixcbiAgICAgICAgcnVubmVyOiB0aGlzLnJ1bm5lcklkLFxuICAgICAgfSxcbiAgICB9KVxuXG4gICAgY29uc3QgaWQgPSBhd2FpdCBuZXcgUHJvbWlzZSgocmVzKSA9PiBzdHJlYW0ub25jZSgnZGF0YScsIHJlcykpXG5cbiAgICB0aGlzLmxvZ2dlci5kZWJ1ZyhcbiAgICAgIGAke3RoaXMudXJpfSBzdHJlYW1zIG1lc3NhZ2Ugd2l0aCBpZCAke0pTT04uc3RyaW5naWZ5KGlkKX1gLFxuICAgIClcblxuICAgIGZvciBhd2FpdCAoY29uc3QgbXNnIG9mIGJ1ZmZlcikge1xuICAgICAgY29uc3QgcHJvY2Vzc2VkUHJvbWlzZSA9IG5ldyBQcm9taXNlKChyZXMpID0+IHN0cmVhbS5vbmNlKCdkYXRhJywgcmVzKSlcbiAgICAgIGF3YWl0IHdyaXRlU3RyZWFtTWVzc2FnZUNodW5rKHsgZGF0YTogeyBkYXRhOiB0KG1zZykgfSB9KVxuICAgICAgLy8gQXdhaXQgYSBtZXNzYWdlIG9uIHRoZSBzdHJlYW0sIGluZGljYXRpbmcgdGhhdCB0aGUgY2h1bmsgaGFzIGJlZW4gcHJvY2Vzc2VkXG4gICAgICBhd2FpdCBwcm9jZXNzZWRQcm9taXNlXG4gICAgfVxuXG4gICAgc3RyZWFtLmVuZCgpXG5cbiAgICBhd2FpdCBoYW5kbGVkUHJvbWlzZVxuXG4gICAgdGhpcy5vcGVuU3RyZWFtcyAtPSAxXG5cbiAgICBpZiAodGhpcy5zaG91bGRDbG9zZS5sZW5ndGggPiAwKSBhd2FpdCB0aGlzLmNsb3NlKClcbiAgfVxuXG4gIGFzeW5jIHN0cmluZyhtc2c6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRoaXMubG9nZ2VyLmRlYnVnKGAke3RoaXMudXJpfSBzZW5kcyBzdHJpbmcgJHttc2cubGVuZ3RofSBjaGFyYWN0ZXJzYClcbiAgICBjb25zdCBsb2NhbFNlcXVlbmNlTnVtYmVyID0gdGhpcy5sb2NhbFNlcXVlbmNlTnVtYmVyKytcbiAgICBjb25zdCBoYW5kbGVkUHJvbWlzZSA9IHRoaXMuYXdhaXRQcm9jZXNzZWQoKVxuXG4gICAgYXdhaXQgdGhpcy5ub3RpZnlPcmNoZXN0cmF0b3Ioe1xuICAgICAgbXNnOiB7XG4gICAgICAgIGRhdGE6IGVuY29kZXIuZW5jb2RlKG1zZyksXG4gICAgICAgIGNoYW5uZWw6IHRoaXMudXJpLFxuICAgICAgICBsb2NhbFNlcXVlbmNlTnVtYmVyLFxuICAgICAgfSxcbiAgICB9KVxuXG4gICAgYXdhaXQgaGFuZGxlZFByb21pc2VcbiAgfVxuXG4gIC8qKlxuICAgKiBHcmFjZWZ1bGx5IGNsb3NlcyB0aGlzIGNoYW5uZWwuXG4gICAqXG4gICAqIEJlaGF2aW9yOlxuICAgKiAtIElmIHRoZXJlIGFyZSBzdGlsbCBhY3RpdmUgc3RyZWFtcywgY2xvc2luZyBpcyBkZWZlcnJlZCB1bnRpbCB0aGV5IGNvbXBsZXRlLlxuICAgKiAtIElmIG11bHRpcGxlIGNhbGxlcnMgaW52b2tlIGBjbG9zZSgpYCB3aGlsZSB3YWl0aW5nLCB0aGVpciBQcm9taXNlcyBhcmUgcXVldWVkIGFuZFxuICAgKiAgIHJlc29sdmVkIG9uY2UgdGhlIGNoYW5uZWwgYWN0dWFsbHkgY2xvc2VzLlxuICAgKiAtIElmIHRoaXMgc2lkZSBpbml0aWF0ZWQgdGhlIGNsb3NlIChgaXNzdWVkID0gZmFsc2VgKSwgYSBjbG9zZSBtZXNzYWdlIGlzIHNlbnQgdG8gdGhlIHJlbW90ZS5cbiAgICpcbiAgICogQHBhcmFtIGlzc3VlZCAtIElmIHRydWUsIGluZGljYXRlcyB0aGUgY2xvc2UgcmVxdWVzdCBvcmlnaW5hdGVkIHJlbW90ZWx5XG4gICAqL1xuICBhc3luYyBjbG9zZShpc3N1ZWQgPSBmYWxzZSk6IFByb21pc2U8dm9pZD4ge1xuICAgIC8vIENhc2UgMTogQWN0aXZlIHN0cmVhbXMgc3RpbGwgcnVubmluZyDihpIgd2FpdCB1bnRpbCB0aGV5IGZpbmlzaFxuICAgIGlmICh0aGlzLm9wZW5TdHJlYW1zICE9PSAwKSB7XG4gICAgICBhd2FpdCBuZXcgUHJvbWlzZTx2b2lkPigocmVzb2x2ZSkgPT4gdGhpcy5zaG91bGRDbG9zZS5wdXNoKHJlc29sdmUpKVxuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgLy8gQ2FzZSAyOiBObyBhY3RpdmUgc3RyZWFtcyDihpIgcGVyZm9ybSBhY3R1YWwgY2xvc2VcbiAgICB0aGlzLmxvZ2dlci5kZWJ1ZyhgJHt0aGlzLnVyaX0gY2xvc2VzIHN0cmVhbWApXG4gICAgaWYgKCFpc3N1ZWQpIHtcbiAgICAgIGF3YWl0IHRoaXMubm90aWZ5T3JjaGVzdHJhdG9yKHtcbiAgICAgICAgY2xvc2U6IHsgY2hhbm5lbDogdGhpcy51cmkgfSxcbiAgICAgIH0pXG4gICAgfVxuXG4gICAgbGV0IHJlc29sdmUgPSB0aGlzLnNob3VsZENsb3NlLnBvcCgpXG4gICAgd2hpbGUgKHJlc29sdmUpIHtcbiAgICAgIHJlc29sdmUoKVxuICAgICAgcmVzb2x2ZSA9IHRoaXMuc2hvdWxkQ2xvc2UucG9wKClcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQSBtZXNzYWdlIGlzIGhhbmRsZWQsIGxldCdzIG5vdGlmeSB0aGUgZmlmbyB7QGxpbmsgYXdhaXRQcm9jZXNzZWR9XG4gICAqL1xuICBoYW5kbGVkKCk6IHZvaWQge1xuICAgIGlmICh0aGlzLmF3YWl0aW5nUHJvY2Vzc2VkLmxlbmd0aCA+IDApIHtcbiAgICAgIHRoaXMuYXdhaXRpbmdQcm9jZXNzZWQuc2hpZnQoKSEoKVxuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmxvZ2dlci5lcnJvcihcbiAgICAgICAgJ0V4cGVjdGVkIHRvIGJlIHdhaXRpbmcgZm9yIGEgbWVzc2FnZSB0byBiZSBwcm9jZXNzZWQsIGJ1dCB0aGlzIGlzIG5vdCB0aGUgY2FzZSAnICtcbiAgICAgICAgICB0aGlzLnVyaSxcbiAgICAgIClcbiAgICB9XG4gIH1cbn1cbiJdfQ==
package/minimal.ttl ADDED
@@ -0,0 +1,99 @@
1
+ @prefix prov: <http://www.w3.org/ns/prov#>.
2
+ @prefix sds: <https://w3id.org/sds#>.
3
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
4
+ @prefix owl: <http://www.w3.org/2002/07/owl#>.
5
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
6
+ @prefix sh: <http://www.w3.org/ns/shacl#>.
7
+ @prefix rdfc: <https://w3id.org/rdf-connect#>.
8
+
9
+ ############################################################
10
+ # General statements #
11
+ ############################################################
12
+ # sds:Activity is a prov:Activity
13
+ sds:Activity rdfs:subClassOf prov:Activity.
14
+
15
+ # rdfc:Processor too
16
+ rdfc:Processor rdfs:subClassOf sds:Activity.
17
+
18
+ # sds:implementationOf is subClassOf
19
+ sds:implementationOf rdfs:subPropertyOf rdfs:subClassOf.
20
+
21
+ ############################################################
22
+ # Javascript statements #
23
+ ############################################################
24
+ # specialized for js too
25
+ rdfc:jsImplementationOf rdfs:subPropertyOf sds:implementationOf.
26
+
27
+ # A node runner, runs things that are rdfc:jsImplementationOf rdfc:Processor (aka, rdfs:subClassOf prov:Activity)
28
+ rdfc:NodeRunner a rdfc:Runner;
29
+ rdfc:handlesSubjectsOf rdfc:jsImplementationOf;
30
+ rdfc:command "npx js-runner".
31
+
32
+ # This shouldn't be necessary, should work with sh:targetSubjectsOf
33
+ # rdfc:processor_definition <JsProcessorShape>.
34
+ #
35
+ # Shape that a Js Processor should fulfil;
36
+ [ ] a sh:NodeShape;
37
+ # This shouldn't be necessary,should work with sh:targetSubjectsOf and this isn't a real Class
38
+ sh:targetClass <JsProcessorShape>;
39
+ # We target it with jsImplementationOf
40
+ sh:targetSubjectsOf rdfc:jsImplementationOf;
41
+ sh:property [
42
+ sh:path rdfc:entrypoint;
43
+ sh:name "location";
44
+ sh:minCount 1;
45
+ sh:maxCount 1;
46
+ sh:datatype xsd:iri;
47
+ ], [
48
+ sh:path rdfc:file;
49
+ sh:name "file";
50
+ sh:minCount 1;
51
+ sh:maxCount 1;
52
+ sh:datatype xsd:iri;
53
+ ], [
54
+ sh:path rdfc:class;
55
+ sh:name "clazz";
56
+ sh:maxCount 1;
57
+ sh:datatype xsd:string;
58
+ ].
59
+
60
+ ############################################################
61
+ # Processor statements #
62
+ ############################################################
63
+ rdfc:FooBarProcessor a owl:Class;
64
+ rdfs:label "My Epic FooBar Processor";
65
+ rdfs:description "FooBars everything!";
66
+ rdfc:jsImplementationOf rdfc:Processor;
67
+ rdfc:entrypoint <./>;
68
+ rdfc:file <./lib/processors.js>;
69
+ rdfc:class "FooBarProcessor".
70
+
71
+ [ ] a sh:NodeShape;
72
+ sh:targetClass rdfc:FooBarProcessor;
73
+ sh:property [
74
+ sh:path rdfc:reader;
75
+ sh:name "reader";
76
+ sh:minCount 1;
77
+ sh:maxCount 1;
78
+ sh:class rdfc:Reader;
79
+ ], [
80
+ sh:path rdfc:writer;
81
+ sh:name "writer";
82
+ sh:maxCount 1;
83
+ sh:class rdfc:Writer;
84
+ ].
85
+
86
+ ############################################################
87
+ # Pipeline statements #
88
+ ############################################################
89
+ <> a rdfc:Pipeline;
90
+ rdfc:consistsOf [
91
+ rdfc:instantiates rdfc:NodeRunner;
92
+ rdfc:processor <foobar>;
93
+ ].
94
+
95
+ <incomingMessages> a rdfc:Reader, rdfc:Writer.
96
+ <foobar> a rdfc:FooBarProcessor;
97
+ rdfc:reader <incomingMessages>;
98
+ rdfc:writer <incomingMessages>.
99
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rdfc/js-runner",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "main": "lib/index.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -31,6 +31,7 @@
31
31
  "devDependencies": {
32
32
  "@eslint/js": "^9.21.0",
33
33
  "@rdfjs/types": "^2.0.1",
34
+ "@grpc/grpc-js": "^1.12.6",
34
35
  "@types/jest": "^29.5.14",
35
36
  "@types/jsonld": "^1.5.15",
36
37
  "@types/n3": "^1.21.1",
@@ -53,8 +54,7 @@
53
54
  "vitest": "^3.2.4"
54
55
  },
55
56
  "dependencies": {
56
- "@grpc/grpc-js": "^1.12.6",
57
- "@rdfc/proto": "^0.1.2-alpha.1",
57
+ "@rdfc/proto": "^0.1.2",
58
58
  "@treecg/types": "^0.4.6",
59
59
  "jsonld": "^8.3.3",
60
60
  "jsonld-streaming-parser": "^5.0.0",
package/src/client.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  import * as grpc from '@grpc/grpc-js'
2
2
  import { promisify } from 'util'
3
- import { RunnerClient, RunnerMessage } from '@rdfc/proto'
4
- import winston from 'winston'
3
+ import { RunnerClient, ToRunner } from '@rdfc/proto'
4
+ import { createLogger } from 'winston'
5
5
  import { RpcTransport } from './logger'
6
6
  import { Runner } from './runner'
7
7
 
8
8
  export async function start(addr: string, uri: string) {
9
9
  const client = new RunnerClient(addr, grpc.credentials.createInsecure())
10
10
 
11
- const logger = winston.createLogger({
11
+ const logger = createLogger({
12
12
  transports: [
13
13
  new RpcTransport({
14
14
  entities: [uri, 'cli'],
@@ -25,25 +25,22 @@ export async function start(addr: string, uri: string) {
25
25
 
26
26
  await writable({ identify: { uri } })
27
27
 
28
- let processorsEnd!: (v: unknown) => unknown
29
- const processorsEnded = new Promise((res) => (processorsEnd = res))
30
- ;(async () => {
28
+ /* eslint-disable no-async-promise-executor */
29
+ await new Promise(async (res) => {
31
30
  for await (const chunk of stream) {
32
- const msg: RunnerMessage = chunk
31
+ const msg: ToRunner = chunk
33
32
  if (msg.proc) {
34
33
  await runner.addProcessor(msg.proc)
35
34
  }
36
35
  if (msg.start) {
37
- runner.start().then(processorsEnd)
36
+ runner.start().then(res)
38
37
  }
39
38
 
40
39
  await runner.handleOrchMessage(msg)
41
40
  }
42
41
 
43
42
  logger.error('Stream ended')
44
- })()
45
-
46
- await processorsEnded
43
+ })
47
44
 
48
45
  logger.info('All processors are finished')
49
46
  stream.end()
package/src/logger.ts CHANGED
@@ -1,4 +1,4 @@
1
- import winston, { Logger } from 'winston'
1
+ import { createLogger, LogEntry, Logger } from 'winston'
2
2
  import Transport from 'winston-transport'
3
3
 
4
4
  import * as grpc from '@grpc/grpc-js'
@@ -21,7 +21,7 @@ export class RpcTransport extends Transport {
21
21
  this.aliases = opts.aliases || []
22
22
  }
23
23
 
24
- log(info: winston.LogEntry, callback: () => void) {
24
+ log(info: LogEntry, callback: () => void) {
25
25
  if (!this.stream.closed) {
26
26
  this.stream.write(
27
27
  {
@@ -55,7 +55,7 @@ export function extendLogger(baseLogger: Logger, newEntity: string): Logger {
55
55
  return t
56
56
  })
57
57
 
58
- return winston.createLogger({
58
+ return createLogger({
59
59
  level: baseLogger.level,
60
60
  format: baseLogger.format,
61
61
  defaultMeta: baseLogger.defaultMeta,
package/src/reader.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import { ClientReadableStream } from '@grpc/grpc-js'
2
- import { DataChunk, Message, RunnerClient, StreamMessage } from '@rdfc/proto'
3
- import winston from 'winston'
2
+ import {
3
+ DataChunk,
4
+ ReceivingMessage,
5
+ ReceivingStreamMessage,
6
+ RunnerClient,
7
+ } from '@rdfc/proto'
8
+ import { Logger } from 'winston'
4
9
  import {
5
10
  AnyConvertor,
6
11
  Convertor,
@@ -8,6 +13,8 @@ import {
8
13
  StreamConvertor,
9
14
  StringConvertor,
10
15
  } from './convertor'
16
+ import { Writable } from './runner'
17
+ import { promisify } from 'util'
11
18
 
12
19
  export type Any =
13
20
  | {
@@ -28,41 +35,47 @@ export interface Reader {
28
35
  anys(): AsyncIterable<Any>
29
36
  }
30
37
 
38
+ type Todo<T> = {
39
+ item: T
40
+ onComplete: () => void
41
+ }
42
+
31
43
  class MyIter<T> implements AsyncIterable<T> {
32
44
  private convertor: Convertor<T>
33
- private queue: (T | undefined)[] = []
45
+ private queue: Todo<T | undefined>[] = []
34
46
  private resolveNext: ((value: undefined) => void) | null = null
35
47
 
36
48
  constructor(convertor: Convertor<T>) {
37
49
  this.convertor = convertor
38
50
  }
39
51
 
40
- push(buffer: Uint8Array) {
52
+ push(buffer: Uint8Array, onComplete: () => void) {
41
53
  const item = this.convertor.from(buffer)
42
- this.queue.push(item)
54
+ this.queue.push({ item, onComplete })
43
55
  if (this.resolveNext) {
44
56
  this.resolveNext(undefined)
45
57
  this.resolveNext = null
46
58
  }
47
59
  }
48
60
 
49
- close() {
50
- this.queue.push(undefined)
61
+ close(onComplete: () => void) {
62
+ this.queue.push({ item: undefined, onComplete })
51
63
  if (this.resolveNext) {
52
64
  this.resolveNext(undefined)
53
65
  this.resolveNext = null
54
66
  }
55
67
  }
56
68
 
57
- async pushStream(chunks: ClientReadableStream<DataChunk>) {
69
+ async pushStream(chunks: AsyncIterable<DataChunk>, onComplete: () => void) {
70
+ // This is an async generator that transforms DataChunks to Buffers
58
71
  const stream = (async function* (stream) {
59
- for await (const c of stream) {
60
- const chunk: DataChunk = c
72
+ for await (const chunk of stream) {
61
73
  yield chunk.data
62
74
  }
63
75
  })(chunks)
76
+
64
77
  const item = await this.convertor.fromStream(stream)
65
- this.queue.push(item)
78
+ this.queue.push({ item, onComplete })
66
79
  if (this.resolveNext) {
67
80
  this.resolveNext(undefined)
68
81
  this.resolveNext = null
@@ -72,9 +85,15 @@ class MyIter<T> implements AsyncIterable<T> {
72
85
  async *[Symbol.asyncIterator]() {
73
86
  while (true) {
74
87
  if (this.queue.length > 0) {
75
- const item = this.queue.shift()!
76
- if (item === undefined) break
88
+ const { item, onComplete } = this.queue.shift()!
89
+ if (item === undefined) {
90
+ onComplete()
91
+ break
92
+ }
77
93
  yield item
94
+ // Note: execution pauses at `yield` until the consumer calls `.next()` again.
95
+ // We call onComplete *after* resuming, so the producer knows the item was actually consumed.
96
+ onComplete()
78
97
  } else {
79
98
  await new Promise<undefined>((resolve) => (this.resolveNext = resolve))
80
99
  }
@@ -85,58 +104,217 @@ class MyIter<T> implements AsyncIterable<T> {
85
104
  export class ReaderInstance implements Reader {
86
105
  private client: RunnerClient
87
106
  readonly uri: string
88
- private logger: winston.Logger
107
+ private logger: Logger
108
+ private readonly notifyOrchestrator: Writable
89
109
 
90
- private iterators: MyIter<unknown>[] = []
110
+ private consumers: MyIter<unknown>[] = []
91
111
 
92
- constructor(uri: string, client: RunnerClient, logger: winston.Logger) {
112
+ constructor(
113
+ uri: string,
114
+ client: RunnerClient,
115
+ notifyOrchestrator: Writable,
116
+ logger: Logger,
117
+ ) {
93
118
  this.uri = uri
94
119
  this.client = client
95
120
  this.logger = logger
121
+ this.notifyOrchestrator = notifyOrchestrator
96
122
  }
97
123
 
98
124
  anys(): AsyncIterable<Any> {
99
125
  const iter = new MyIter(AnyConvertor)
100
- this.iterators.push(iter)
126
+ this.consumers.push(iter)
101
127
  return iter
102
128
  }
103
129
 
104
130
  strings(): AsyncIterable<string> {
105
131
  const iter = new MyIter(StringConvertor)
106
- this.iterators.push(iter)
132
+ this.consumers.push(iter)
107
133
  return iter
108
134
  }
109
135
 
110
136
  buffers(): AsyncIterable<Uint8Array> {
111
137
  const iter = new MyIter(NoConvertor)
112
- this.iterators.push(iter)
138
+ this.consumers.push(iter)
113
139
  return iter
114
140
  }
115
141
 
116
142
  streams(): AsyncIterable<AsyncGenerator<Uint8Array>> {
117
143
  const iter = new MyIter(StreamConvertor)
118
- this.iterators.push(iter)
144
+ this.consumers.push(iter)
119
145
  return iter
120
146
  }
121
147
 
122
- handleMsg(msg: Message) {
148
+ handleMsg(msg: ReceivingMessage) {
123
149
  this.logger.debug(`${this.uri} handling message`)
124
- for (const iter of this.iterators) {
125
- iter.push(msg.data)
150
+
151
+ const promises = []
152
+ for (const iter of this.consumers) {
153
+ promises.push(new Promise((res) => iter.push(msg.data, () => res(null))))
126
154
  }
155
+
156
+ Promise.all(promises).then(() =>
157
+ this.notifyOrchestrator({
158
+ processed: {
159
+ globalSequenceNumber: msg.globalSequenceNumber,
160
+ channel: this.uri,
161
+ },
162
+ }),
163
+ )
127
164
  }
128
165
 
129
166
  close() {
130
- for (const iter of this.iterators) {
131
- iter.close()
167
+ for (const iter of this.consumers) {
168
+ iter.close(() => {})
132
169
  }
133
170
  }
134
171
 
135
- handleStreamingMessage(msg: StreamMessage) {
172
+ // There is a stream message available for this reader
173
+ async handleStreamingMessage({
174
+ channel,
175
+ globalSequenceNumber,
176
+ }: ReceivingStreamMessage) {
136
177
  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)
178
+
179
+ const chunks = this.client.receiveStreamMessage()
180
+ const writeControlMessage = promisify(chunks.write.bind(chunks))
181
+ const consumersConsumed = []
182
+
183
+ // After each chunk is handled by all consumer, emit a processed message
184
+ let idx = 0
185
+ const messageIterators = fanoutStream(
186
+ chunks,
187
+ this.consumers.length,
188
+ async () => {
189
+ await writeControlMessage({ streamSequenceNumber: idx++ })
190
+ },
191
+ )
192
+
193
+ for (const consumer of this.consumers) {
194
+ consumersConsumed.push(
195
+ new Promise((res) =>
196
+ consumer.pushStream(messageIterators.pop()!, () => res(null)),
197
+ ),
198
+ )
199
+ }
200
+
201
+ await writeControlMessage({ globalSequenceNumber })
202
+
203
+ Promise.all(consumersConsumed).then(() => {
204
+ console.log('Writing processed for streaming message')
205
+ this.notifyOrchestrator({ processed: { globalSequenceNumber, channel } })
206
+ })
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Helper function to tee a stream `numConsumers` times
212
+ * When each tee'd stream has handled a chunk, call {@link onAllHandled}
213
+ */
214
+ function fanoutStream<T>(
215
+ stream: ClientReadableStream<T>,
216
+ numConsumers: number,
217
+ onAllHandled: () => void | Promise<void>,
218
+ ): AsyncIterable<T>[] {
219
+ type Waiter = (value: IteratorResult<T>) => void
220
+
221
+ let ended = false
222
+ const buffer: T[] = []
223
+ const pending: Waiter[] = []
224
+ let activeConsumers = numConsumers
225
+
226
+ // consumer bookkeeping
227
+ let awaitingAck = 0
228
+
229
+ function pushChunk(chunk: T) {
230
+ buffer.push(chunk)
231
+ flush()
232
+ }
233
+
234
+ function flush() {
235
+ while (buffer.length > 0 && pending.length > 0) {
236
+ const chunk = buffer[0] // keep until all consumers ack
237
+ const waiter = pending.shift()!
238
+ waiter({ value: chunk, done: false })
239
+ awaitingAck++
240
+ }
241
+ }
242
+
243
+ function end() {
244
+ ended = true
245
+ while (pending.length > 0) {
246
+ const waiter = pending.shift()!
247
+ waiter({ value: undefined, done: true })
140
248
  }
141
249
  }
250
+
251
+ stream.on('data', (chunk: T) => {
252
+ pushChunk(chunk)
253
+ })
254
+
255
+ stream.on('end', () => {
256
+ end()
257
+ })
258
+
259
+ stream.on('error', (err) => {
260
+ while (pending.length > 0) {
261
+ const waiter = pending.shift()!
262
+ waiter({ value: undefined, done: true })
263
+ }
264
+ throw err
265
+ })
266
+
267
+ function makeIterable(): AsyncIterable<T> {
268
+ return {
269
+ [Symbol.asyncIterator]() {
270
+ return {
271
+ next(): Promise<IteratorResult<T>> {
272
+ if (buffer.length > 0) {
273
+ const chunk = buffer[0]
274
+ awaitingAck++
275
+ return Promise.resolve({ value: chunk, done: false })
276
+ }
277
+ if (ended) {
278
+ return Promise.resolve({ value: undefined, done: true })
279
+ }
280
+ return new Promise((resolve) => {
281
+ pending.push(resolve)
282
+ })
283
+ },
284
+ async return() {
285
+ activeConsumers--
286
+ if (activeConsumers === 0) {
287
+ end()
288
+ }
289
+ return { value: undefined, done: true }
290
+ },
291
+ }
292
+ },
293
+ }
294
+ }
295
+
296
+ async function ack() {
297
+ awaitingAck--
298
+ if (awaitingAck === 0) {
299
+ // all consumers done with the current chunk
300
+ buffer.shift() // drop it
301
+ await onAllHandled()
302
+ flush() // continue with next chunk
303
+ }
304
+ }
305
+
306
+ // wrap consumer so they *must* call ack() after processing
307
+ function wrap(iterable: AsyncIterable<T>): AsyncIterable<T> {
308
+ return {
309
+ async *[Symbol.asyncIterator]() {
310
+ for await (const item of iterable) {
311
+ yield item
312
+ await ack()
313
+ }
314
+ },
315
+ }
316
+ }
317
+
318
+ const rawIterables = Array.from({ length: numConsumers }, makeIterable)
319
+ return rawIterables.map(wrap)
142
320
  }