@fraym/streams 0.5.1 → 0.6.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.
@@ -1,3 +1,3 @@
1
- import { ServiceClient } from "@fraym/proto/freym/streams/clientchannel";
1
+ import { ServiceClient } from "@fraym/proto/freym/streams/management";
2
2
  import { HandlerFunc } from "./event";
3
- export declare const getAllEvents: (tenantId: string, topic: string, includedEventTypes: string[], perPage: number, handler: HandlerFunc, serviceClient: ServiceClient) => Promise<void>;
3
+ export declare const getAllEvents: (tenantId: string, topic: string, types: string[], perPage: number, handler: HandlerFunc, serviceClient: ServiceClient) => Promise<void>;
@@ -2,32 +2,38 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getAllEvents = void 0;
4
4
  const event_1 = require("./event");
5
- const getAllEvents = async (tenantId, topic, includedEventTypes, perPage, handler, serviceClient) => {
5
+ const util_1 = require("./util");
6
+ const getAllEvents = async (tenantId, topic, types, perPage, handler, serviceClient) => {
6
7
  let page = 0;
7
- let finished = false;
8
- while (!finished) {
9
- await serviceClient.paginateEvents({
8
+ while (true) {
9
+ const events = await getEventPage(tenantId, topic, types, perPage, page, serviceClient);
10
+ if (events.length === 0) {
11
+ return;
12
+ }
13
+ for (const eventData of events) {
14
+ const event = (0, event_1.getSubscriptionEvent)(eventData);
15
+ if (event) {
16
+ await handler(event);
17
+ }
18
+ }
19
+ page++;
20
+ }
21
+ };
22
+ exports.getAllEvents = getAllEvents;
23
+ const getEventPage = async (tenantId, topic, types, perPage, page, serviceClient) => {
24
+ return (0, util_1.retry)(() => new Promise((resolve, reject) => {
25
+ serviceClient.paginateEvents({
10
26
  tenantId,
11
27
  topic,
12
- includedEventTypes,
28
+ types,
13
29
  page: page.toString(),
14
30
  perPage: perPage.toString(),
15
31
  }, async (error, data) => {
16
32
  if (error) {
17
- throw error;
18
- }
19
- if (data.events.length === 0) {
20
- finished = true;
33
+ reject(error);
21
34
  return;
22
35
  }
23
- for (const eventData of data.events) {
24
- const event = (0, event_1.getSubscriptionEvent)(eventData);
25
- if (event) {
26
- await handler(event);
27
- }
28
- }
36
+ resolve(data.events);
29
37
  });
30
- page++;
31
- }
38
+ }));
32
39
  };
33
- exports.getAllEvents = getAllEvents;
@@ -1,22 +1,19 @@
1
- import { ServiceClient } from "@fraym/proto/freym/streams/clientchannel";
1
+ import { ServiceClient } from "@fraym/proto/freym/streams/management";
2
2
  import { ClientConfig } from "./config";
3
3
  import { HandlerFunc, PublishEvent, SubscriptionEvent } from "./event";
4
+ import { Subscription } from "./subscribe";
4
5
  export interface StreamIterator {
5
6
  forEach: (callback: (event: SubscriptionEvent) => void) => Promise<void>;
6
7
  isEmpty: () => Promise<boolean>;
7
8
  }
8
9
  export interface Client {
9
- getAllEvents: (tenantId: string, topic: string, includedEventTypes: string[], perPage: number, handler: HandlerFunc) => Promise<void>;
10
10
  getEvent: (tenantId: string, topic: string, eventId: string) => Promise<SubscriptionEvent>;
11
- getStreamItarator: (tenantId: string, stream: string, perPage: number) => Promise<StreamIterator>;
12
- useEventHandler: (type: string, handler: HandlerFunc) => void;
13
- useEventHandlerForAllEventTypes: (handler: HandlerFunc) => void;
14
- subscribe: (includedTopics?: string[], excludedTopics?: string[]) => Promise<void>;
11
+ iterateAllEvents: (tenantId: string, topic: string, includedEventTypes: string[], perPage: number, handler: HandlerFunc) => Promise<void>;
15
12
  publish: (topic: string, events: PublishEvent[]) => Promise<void>;
13
+ getStreamItarator: (topic: string, tenantId: string, stream: string, perPage: number) => Promise<StreamIterator>;
14
+ subscribe: (topics?: string[], ignoreUnhandledEvents?: boolean) => Subscription;
16
15
  invalidateGdprData: (tenantId: string, topic: string, gdprId: string) => Promise<void>;
17
- introduceGdprOnField: (defaultValue: string, topic: string, eventType: string, fieldName: string, serviceClient: ServiceClient) => Promise<void>;
18
16
  introduceGdprOnEventField: (tenantId: string, defaultValue: string, topic: string, eventId: string, fieldName: string, serviceClient: ServiceClient) => Promise<void>;
19
- createSnapshot: (topic: string, toTime: Date) => Promise<void>;
20
17
  close: () => void;
21
18
  }
22
19
  export declare const newClient: (config: ClientConfig) => Promise<Client>;
@@ -1,66 +1,50 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.newClient = void 0;
4
- const clientchannel_1 = require("@fraym/proto/freym/streams/clientchannel");
4
+ const management_1 = require("@fraym/proto/freym/streams/management");
5
5
  const grpc_js_1 = require("@grpc/grpc-js");
6
- const allEvents_1 = require("./allEvents");
7
6
  const config_1 = require("./config");
8
- const alreadySubscribed_1 = require("./errors/alreadySubscribed");
9
- const handler_1 = require("./handler");
10
- const init_1 = require("./init");
11
- const invalidateGdpr_1 = require("./invalidateGdpr");
7
+ const getEvent_1 = require("./getEvent");
8
+ const allEvents_1 = require("./allEvents");
12
9
  const publish_1 = require("./publish");
13
- const snapshot_1 = require("./snapshot");
10
+ const introduceGdpr_1 = require("./introduceGdpr");
11
+ const invalidateGdpr_1 = require("./invalidateGdpr");
14
12
  const stream_1 = require("./stream");
15
13
  const subscribe_1 = require("./subscribe");
16
- const getEvent_1 = require("./getEvent");
17
- const introduceGdpr_1 = require("./introduceGdpr");
18
14
  const newClient = async (config) => {
19
15
  config = (0, config_1.useConfigDefaults)(config);
20
- const serviceClient = new clientchannel_1.ServiceClient(config.serverAddress, grpc_js_1.credentials.createInsecure(), {
16
+ const serviceClient = new management_1.ServiceClient(config.serverAddress, grpc_js_1.credentials.createInsecure(), {
21
17
  "grpc.keepalive_time_ms": config.keepaliveInterval,
22
18
  "grpc.keepalive_timeout_ms": config.keepaliveTimeout,
23
19
  "grpc.keepalive_permit_without_calls": 1,
24
20
  });
25
- const stream = await (0, init_1.initStream)(config, serviceClient);
26
- const eventHandler = (0, handler_1.initEventHandler)(stream);
27
- let hasSubscribed = false;
21
+ const closeFunctions = [];
28
22
  return {
29
- getAllEvents: async (tenantId, topic, includedEventTypes, perPage, handler) => {
30
- await (0, allEvents_1.getAllEvents)(tenantId, topic, includedEventTypes, perPage, handler, serviceClient);
31
- },
32
23
  getEvent: async (tenantId, topic, eventId) => {
33
24
  return await (0, getEvent_1.getEvent)(tenantId, topic, eventId, serviceClient);
34
25
  },
35
- getStreamItarator: async (tenantId, stream, perPage) => {
26
+ iterateAllEvents: async (tenantId, topic, includedEventTypes, perPage, handler) => {
27
+ await (0, allEvents_1.getAllEvents)(tenantId, topic, includedEventTypes, perPage, handler, serviceClient);
28
+ },
29
+ publish: async (topic, events) => {
30
+ return (0, publish_1.sendPublish)(topic, events, serviceClient);
31
+ },
32
+ getStreamItarator: async (topic, tenantId, stream, perPage) => {
36
33
  return {
37
34
  forEach: async (callback) => {
38
- return await (0, stream_1.getStream)(tenantId, stream, perPage, async (event) => {
35
+ return await (0, stream_1.getStream)(topic, tenantId, stream, perPage, async (event) => {
39
36
  callback(event);
40
37
  }, serviceClient);
41
38
  },
42
39
  isEmpty: async () => {
43
- return (0, stream_1.isStreamEmpty)(tenantId, stream, serviceClient);
40
+ return (0, stream_1.isStreamEmpty)(topic, tenantId, stream, serviceClient);
44
41
  },
45
42
  };
46
43
  },
47
- useEventHandler: (type, handler) => {
48
- eventHandler.addHandler(type, handler);
49
- },
50
- useEventHandlerForAllEventTypes: handler => {
51
- eventHandler.addHandlerForAllTypes(handler);
52
- },
53
- subscribe: async (includedTopics = [], excludedTopics = []) => {
54
- if (hasSubscribed) {
55
- throw new alreadySubscribed_1.AlreadySubscribedError();
56
- }
57
- return await (0, subscribe_1.sendSubscribe)(includedTopics, excludedTopics, config, stream);
58
- },
59
- publish: async (topic, events) => {
60
- return (0, publish_1.sendPublish)(topic, events, serviceClient);
61
- },
62
- introduceGdprOnField: async (defaultValue, topic, eventType, fieldName) => {
63
- return await (0, introduceGdpr_1.introduceGdprOnField)(defaultValue, topic, eventType, fieldName, serviceClient);
44
+ subscribe: (topics = [], ignoreUnhandledEvents = false) => {
45
+ const subscription = (0, subscribe_1.newSubscription)(topics, ignoreUnhandledEvents, config, serviceClient);
46
+ closeFunctions.push(subscription.stop);
47
+ return subscription;
64
48
  },
65
49
  introduceGdprOnEventField: async (tenantId, defaultValue, topic, eventId, fieldName) => {
66
50
  return (0, introduceGdpr_1.introduceGdprOnEventField)(tenantId, defaultValue, topic, eventId, fieldName, serviceClient);
@@ -68,11 +52,8 @@ const newClient = async (config) => {
68
52
  invalidateGdprData: async (tenantId, topic, gdprId) => {
69
53
  return await (0, invalidateGdpr_1.sendInvalidateGdpr)(tenantId, topic, gdprId, serviceClient);
70
54
  },
71
- createSnapshot: async (topic, toTime) => {
72
- return await (0, snapshot_1.sendSnapshot)(topic, toTime, serviceClient);
73
- },
74
55
  close: () => {
75
- stream.end();
56
+ closeFunctions.forEach(close => close());
76
57
  },
77
58
  };
78
59
  };
@@ -1,4 +1,4 @@
1
- import { PublishEventEnvelope } from "@fraym/proto/freym/streams/clientchannel";
1
+ import { Event } from "@fraym/proto/freym/streams/management";
2
2
  export interface SubscriptionEvent extends BaseEvent {
3
3
  topic: string;
4
4
  raisedAt: Date;
@@ -25,4 +25,4 @@ export interface GdprEventData {
25
25
  }
26
26
  export declare const isGdprEventData: (value: EventData) => value is GdprEventData;
27
27
  export type HandlerFunc = (event: SubscriptionEvent) => Promise<void>;
28
- export declare const getSubscriptionEvent: (eventEnvelope: PublishEventEnvelope) => SubscriptionEvent | null;
28
+ export declare const getSubscriptionEvent: (event: Event) => SubscriptionEvent | null;
@@ -9,22 +9,16 @@ const isGdprEventData = (value) => {
9
9
  value.hasOwnProperty("gdprDefault"));
10
10
  };
11
11
  exports.isGdprEventData = isGdprEventData;
12
- const getSubscriptionEvent = (eventEnvelope) => {
13
- const event = eventEnvelope.event;
14
- if (!event) {
15
- return null;
16
- }
12
+ const getSubscriptionEvent = (event) => {
17
13
  const payload = {};
18
14
  for (const key in event.payload) {
19
15
  if (Object.prototype.hasOwnProperty.call(event.payload, key)) {
20
16
  const data = event.payload[key];
21
- if (data.metadata && data.metadata.gdpr) {
17
+ if (data.gdpr) {
22
18
  payload[key] = {
23
19
  value: JSON.parse(data.value),
24
- gdprDefault: data.metadata.gdpr.default
25
- ? JSON.parse(data.metadata.gdpr.default)
26
- : "",
27
- isInvalidated: data.metadata.gdpr.invalidated,
20
+ gdprDefault: data.gdpr.default ? JSON.parse(data.gdpr.default) : "",
21
+ isInvalidated: data.gdpr.isInvalidated,
28
22
  };
29
23
  }
30
24
  else {
@@ -34,14 +28,14 @@ const getSubscriptionEvent = (eventEnvelope) => {
34
28
  }
35
29
  return {
36
30
  id: event.id,
37
- topic: eventEnvelope.topic,
38
- tenantId: eventEnvelope.tenantId,
31
+ topic: event.topic,
32
+ tenantId: event.tenantId,
39
33
  payload,
40
34
  raisedAt: new Date(parseInt(event.raisedAt.slice(0, -6))),
41
35
  stream: event.stream || undefined,
42
36
  type: event.type || undefined,
43
- causationId: event.causationId || undefined,
44
- correlationId: event.correlationId || undefined,
37
+ causationId: event.metadata ? event.metadata.causationId : undefined,
38
+ correlationId: event.metadata ? event.metadata.correlationId : undefined,
45
39
  reason: event.reason || undefined,
46
40
  };
47
41
  };
@@ -1,3 +1,3 @@
1
- import { ServiceClient } from "@fraym/proto/freym/streams/clientchannel";
1
+ import { ServiceClient } from "@fraym/proto/freym/streams/management";
2
2
  import { SubscriptionEvent } from "./event";
3
3
  export declare const getEvent: (tenantId: string, topic: string, eventId: string, serviceClient: ServiceClient) => Promise<SubscriptionEvent>;
@@ -2,23 +2,25 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getEvent = void 0;
4
4
  const event_1 = require("./event");
5
+ const util_1 = require("./util");
5
6
  const getEvent = async (tenantId, topic, eventId, serviceClient) => {
6
- return new Promise((resolve, reject) => {
7
+ return (0, util_1.retry)(() => new Promise((resolve, reject) => {
7
8
  serviceClient.getEvent({
8
9
  tenantId,
9
10
  topic,
10
- eventId,
11
+ id: eventId,
11
12
  }, (error, response) => {
12
13
  if (error) {
13
- reject(error.message);
14
+ reject(error);
14
15
  return;
15
16
  }
16
17
  const event = (0, event_1.getSubscriptionEvent)(response);
17
18
  if (event) {
18
19
  resolve(event);
20
+ return;
19
21
  }
20
22
  reject("unable to resolve event from event data");
21
23
  });
22
- });
24
+ }));
23
25
  };
24
26
  exports.getEvent = getEvent;
@@ -1,3 +1,2 @@
1
- import { ServiceClient } from "@fraym/proto/freym/streams/clientchannel";
2
- export declare const introduceGdprOnField: (defaultValue: string, topic: string, eventType: string, fieldName: string, serviceClient: ServiceClient) => Promise<void>;
1
+ import { ServiceClient } from "@fraym/proto/freym/streams/management";
3
2
  export declare const introduceGdprOnEventField: (tenantId: string, defaultValue: string, topic: string, eventId: string, fieldName: string, serviceClient: ServiceClient) => Promise<void>;
@@ -1,38 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.introduceGdprOnEventField = exports.introduceGdprOnField = void 0;
4
- const introduceGdprOnField = async (defaultValue, topic, eventType, fieldName, serviceClient) => {
5
- return new Promise((resolve, reject) => {
6
- serviceClient.introduceGdprOnField({
7
- defaultValue,
8
- topic,
9
- eventType,
10
- fieldName,
11
- }, error => {
12
- if (error) {
13
- reject(error.message);
14
- return;
15
- }
16
- resolve();
17
- });
18
- });
19
- };
20
- exports.introduceGdprOnField = introduceGdprOnField;
3
+ exports.introduceGdprOnEventField = void 0;
4
+ const util_1 = require("./util");
21
5
  const introduceGdprOnEventField = async (tenantId, defaultValue, topic, eventId, fieldName, serviceClient) => {
22
- return new Promise((resolve, reject) => {
6
+ return (0, util_1.retry)(() => new Promise((resolve, reject) => {
23
7
  serviceClient.introduceGdprOnEventField({
24
- tenant: tenantId,
8
+ tenantId,
25
9
  defaultValue,
26
10
  topic,
27
11
  eventId,
28
12
  fieldName,
29
13
  }, error => {
30
14
  if (error) {
31
- reject(error.message);
15
+ reject(error);
32
16
  return;
33
17
  }
34
18
  resolve();
35
19
  });
36
- });
20
+ }));
37
21
  };
38
22
  exports.introduceGdprOnEventField = introduceGdprOnEventField;
@@ -1,2 +1,2 @@
1
- import { ServiceClient } from "@fraym/proto/freym/streams/clientchannel";
1
+ import { ServiceClient } from "@fraym/proto/freym/streams/management";
2
2
  export declare const sendInvalidateGdpr: (tenantId: string, topic: string, gdprId: string, serviceClient: ServiceClient) => Promise<void>;
@@ -1,19 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sendInvalidateGdpr = void 0;
4
+ const util_1 = require("./util");
4
5
  const sendInvalidateGdpr = async (tenantId, topic, gdprId, serviceClient) => {
5
- return new Promise((resolve, reject) => {
6
+ return (0, util_1.retry)(() => new Promise((resolve, reject) => {
6
7
  serviceClient.invalidateGdpr({
7
8
  tenantId,
8
9
  topic,
9
10
  gdprId,
10
11
  }, error => {
11
12
  if (error) {
12
- reject(error.message);
13
+ reject(error);
13
14
  return;
14
15
  }
15
16
  resolve();
16
17
  });
17
- });
18
+ }));
18
19
  };
19
20
  exports.sendInvalidateGdpr = sendInvalidateGdpr;
@@ -1,3 +1,3 @@
1
1
  import { PublishEvent } from "./event";
2
- import { ServiceClient } from "@fraym/proto/freym/streams/clientchannel";
2
+ import { ServiceClient } from "@fraym/proto/freym/streams/management";
3
3
  export declare const sendPublish: (topic: string, events: PublishEvent[], serviceClient: ServiceClient) => Promise<void>;
@@ -2,22 +2,23 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sendPublish = void 0;
4
4
  const event_1 = require("./event");
5
+ const util_1 = require("./util");
5
6
  const sendPublish = async (topic, events, serviceClient) => {
6
- return new Promise((resolve, reject) => {
7
+ return (0, util_1.retry)(() => new Promise((resolve, reject) => {
7
8
  serviceClient.publish({
8
- events: events.map(getEventEnvelopeFromPublishedEvent),
9
+ events: events.map(getProtobufPublishEventFromPublishedEvent),
9
10
  topic,
10
11
  }, error => {
11
12
  if (error) {
12
- reject(error.message);
13
+ reject(error);
13
14
  return;
14
15
  }
15
16
  resolve();
16
17
  });
17
- });
18
+ }));
18
19
  };
19
20
  exports.sendPublish = sendPublish;
20
- const getEventEnvelopeFromPublishedEvent = (event) => {
21
+ const getProtobufPublishEventFromPublishedEvent = (event) => {
21
22
  var _a, _b, _c, _d, _e, _f, _g;
22
23
  const payload = {};
23
24
  for (const key in event.payload) {
@@ -25,31 +26,30 @@ const getEventEnvelopeFromPublishedEvent = (event) => {
25
26
  payload[key] = (0, event_1.isGdprEventData)(currentData)
26
27
  ? {
27
28
  value: JSON.stringify(currentData.value),
28
- metadata: {
29
- $case: "gdpr",
30
- gdpr: {
31
- default: JSON.stringify(currentData.gdprDefault),
32
- id: (_a = currentData.id) !== null && _a !== void 0 ? _a : "",
33
- invalidated: false,
34
- },
29
+ gdpr: {
30
+ default: JSON.stringify(currentData.gdprDefault),
31
+ id: (_a = currentData.id) !== null && _a !== void 0 ? _a : "",
32
+ isInvalidated: false,
35
33
  },
36
34
  }
37
35
  : {
38
36
  value: JSON.stringify(currentData),
37
+ gdpr: undefined,
39
38
  };
40
39
  }
41
40
  return {
42
- broadcast: (_b = event.broadcast) !== null && _b !== void 0 ? _b : false,
43
- tenantId: event.tenantId,
44
- event: {
45
- id: event.id,
46
- type: (_c = event.type) !== null && _c !== void 0 ? _c : "",
47
- reason: (_d = event.reason) !== null && _d !== void 0 ? _d : "",
48
- stream: (_e = event.stream) !== null && _e !== void 0 ? _e : "",
49
- correlationId: (_f = event.correlationId) !== null && _f !== void 0 ? _f : "",
50
- causationId: (_g = event.causationId) !== null && _g !== void 0 ? _g : "",
51
- payload,
52
- raisedAt: "0",
41
+ id: event.id,
42
+ metadata: {
43
+ causationId: (_b = event.causationId) !== null && _b !== void 0 ? _b : "",
44
+ correlationId: (_c = event.correlationId) !== null && _c !== void 0 ? _c : "",
45
+ },
46
+ options: {
47
+ broadcast: (_d = event.broadcast) !== null && _d !== void 0 ? _d : false,
53
48
  },
49
+ reason: (_e = event.reason) !== null && _e !== void 0 ? _e : "",
50
+ stream: (_f = event.stream) !== null && _f !== void 0 ? _f : "",
51
+ tenantId: event.tenantId,
52
+ type: (_g = event.type) !== null && _g !== void 0 ? _g : "",
53
+ payload,
54
54
  };
55
55
  };
@@ -1,4 +1,4 @@
1
1
  import { HandlerFunc } from "./event";
2
- import { ServiceClient } from "@fraym/proto/freym/streams/clientchannel";
3
- export declare const getStream: (tenantId: string, stream: string, perPage: number, handler: HandlerFunc, serviceClient: ServiceClient) => Promise<void>;
4
- export declare const isStreamEmpty: (tenantId: string, stream: string, serviceClient: ServiceClient) => Promise<boolean>;
2
+ import { ServiceClient } from "@fraym/proto/freym/streams/management";
3
+ export declare const getStream: (topic: string, tenantId: string, stream: string, perPage: number, handler: HandlerFunc, serviceClient: ServiceClient) => Promise<void>;
4
+ export declare const isStreamEmpty: (topic: string, tenantId: string, stream: string, serviceClient: ServiceClient) => Promise<boolean>;
@@ -2,45 +2,54 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isStreamEmpty = exports.getStream = void 0;
4
4
  const event_1 = require("./event");
5
- const getStream = async (tenantId, stream, perPage, handler, serviceClient) => {
5
+ const util_1 = require("./util");
6
+ const getStream = async (topic, tenantId, stream, perPage, handler, serviceClient) => {
6
7
  let page = 0;
7
- let finished = false;
8
- while (!finished) {
9
- await serviceClient.paginateStream({
10
- tenantId,
8
+ while (true) {
9
+ const events = await getStreamPage(topic, tenantId, stream, perPage, page, serviceClient);
10
+ if (events.length === 0) {
11
+ return;
12
+ }
13
+ for (const eventData of events) {
14
+ const event = (0, event_1.getSubscriptionEvent)(eventData);
15
+ if (event) {
16
+ await handler(event);
17
+ }
18
+ }
19
+ page++;
20
+ }
21
+ };
22
+ exports.getStream = getStream;
23
+ const getStreamPage = async (topic, tenantId, stream, perPage, page, serviceClient) => {
24
+ return (0, util_1.retry)(() => new Promise((resolve, reject) => {
25
+ serviceClient.paginateStream({
11
26
  stream,
27
+ tenantId,
28
+ topic,
12
29
  page: page.toString(),
13
30
  perPage: perPage.toString(),
14
31
  }, async (error, data) => {
15
32
  if (error) {
16
- throw error;
17
- }
18
- if (data.events.length === 0) {
19
- finished = true;
33
+ reject(error);
20
34
  return;
21
35
  }
22
- for (const eventData of data.events) {
23
- const event = (0, event_1.getSubscriptionEvent)(eventData);
24
- if (event) {
25
- await handler(event);
26
- }
27
- }
36
+ resolve(data.events);
28
37
  });
29
- page++;
30
- }
38
+ }));
31
39
  };
32
- exports.getStream = getStream;
33
- const isStreamEmpty = async (tenantId, stream, serviceClient) => {
34
- let isEmpty = false;
35
- await serviceClient.isStreamEmpty({
36
- stream,
37
- tenantId,
38
- }, (error, data) => {
39
- if (error) {
40
- throw error;
41
- }
42
- isEmpty = data.isEmpty;
40
+ const isStreamEmpty = async (topic, tenantId, stream, serviceClient) => {
41
+ return new Promise((resolve, reject) => {
42
+ serviceClient.isStreamEmpty({
43
+ topic,
44
+ stream,
45
+ tenantId,
46
+ }, (error, data) => {
47
+ if (error) {
48
+ reject(error);
49
+ return;
50
+ }
51
+ resolve(data.isEmpty);
52
+ });
43
53
  });
44
- return isEmpty;
45
54
  };
46
55
  exports.isStreamEmpty = isStreamEmpty;
@@ -1,3 +1,13 @@
1
+ import { ServiceClient, SubscribeRequest, SubscribeResponse } from "@fraym/proto/freym/streams/management";
2
+ import { ClientDuplexStream } from "@grpc/grpc-js";
1
3
  import { ClientConfig } from "./config";
2
- import { Stream } from "./init";
3
- export declare const sendSubscribe: (includedTopics: string[], excludedTopics: string[], config: ClientConfig, stream: Stream) => Promise<void>;
4
+ import { HandlerFunc } from "./event";
5
+ export interface Subscription {
6
+ useHandler: (type: string, handler: HandlerFunc) => void;
7
+ useHandlerForAllTypes: (handler: HandlerFunc) => void;
8
+ start: () => void;
9
+ stop: () => void;
10
+ }
11
+ export type Stream = ClientDuplexStream<SubscribeRequest, SubscribeResponse>;
12
+ export declare const newSubscription: (topics: string[], ignoreUnhandledEvents: boolean, config: ClientConfig, serviceClient: ServiceClient) => Subscription;
13
+ export declare const initStream: (topics: string[], config: ClientConfig, stream: Stream) => Promise<Stream>;
@@ -1,39 +1,137 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sendSubscribe = void 0;
4
- const sendSubscribe = async (includedTopics, excludedTopics, config, stream) => {
5
- stream.write(newSubscribeRequest(includedTopics, excludedTopics));
6
- return new Promise((resolve, reject) => {
7
- const timeout = setTimeout(() => {
8
- stream.off("data", fn);
9
- reject("did not receive subscribe ack in configured timeout range");
10
- }, config.ackTimeout);
11
- const fn = (data) => {
12
- var _a, _b;
13
- if (((_a = data.data) === null || _a === void 0 ? void 0 : _a.$case) === "subscribeNotAck") {
14
- clearTimeout(timeout);
15
- stream.off("data", fn);
16
- reject(`did receive subscribe not ack message: ${data.data.subscribeNotAck.reason}`);
3
+ exports.initStream = exports.newSubscription = void 0;
4
+ const constants_1 = require("@grpc/grpc-js/build/src/constants");
5
+ const uuid_1 = require("uuid");
6
+ const event_1 = require("./event");
7
+ const newSubscription = (topics, ignoreUnhandledEvents, config, serviceClient) => {
8
+ let stream = null;
9
+ let closed = false;
10
+ const typeHandlerMap = {};
11
+ const globalHandlers = [];
12
+ const reconnect = async (retries) => {
13
+ const newStream = serviceClient.subscribe();
14
+ newStream.on("end", () => {
15
+ if (closed) {
16
+ newStream.end();
17
+ return;
18
+ }
19
+ reconnect(50);
20
+ });
21
+ newStream.on("error", (err) => {
22
+ if (closed) {
23
+ return;
24
+ }
25
+ if (retries === 0 || (err && err.code && err.code === constants_1.Status.UNKNOWN)) {
26
+ closed = true;
27
+ throw err;
28
+ }
29
+ newStream.end();
30
+ setTimeout(() => {
31
+ stream = null;
32
+ reconnect(retries - 1);
33
+ }, 100);
34
+ });
35
+ const dataFn = async (data) => {
36
+ var _a, _b, _c, _d;
37
+ if (!data.payload ||
38
+ ((_a = data.payload) === null || _a === void 0 ? void 0 : _a.$case) === "panic" ||
39
+ ((_b = data.payload) === null || _b === void 0 ? void 0 : _b.$case) === "subscribed") {
40
+ newStream.end();
41
+ return;
42
+ }
43
+ const event = (0, event_1.getSubscriptionEvent)(data.payload.event);
44
+ if (!event) {
17
45
  return;
18
46
  }
19
- if (((_b = data.data) === null || _b === void 0 ? void 0 : _b.$case) === "subscribeAck") {
20
- clearTimeout(timeout);
21
- stream.off("data", fn);
22
- resolve();
47
+ console.log("event", event.id);
48
+ const currentHandlers = (_d = typeHandlerMap[(_c = event.type) !== null && _c !== void 0 ? _c : ""]) !== null && _d !== void 0 ? _d : [];
49
+ currentHandlers.push(...globalHandlers);
50
+ if (currentHandlers.length === 0) {
51
+ if (ignoreUnhandledEvents) {
52
+ newStream.write(newHandledRequest(event.tenantId, event.topic));
53
+ return;
54
+ }
55
+ newStream.write(newHandledRequest(event.tenantId, event.topic, "no handlers for this event, maybe you forgot to register an event handler"));
23
56
  return;
24
57
  }
58
+ try {
59
+ for (const handler of currentHandlers) {
60
+ await handler(event);
61
+ }
62
+ newStream.write(newHandledRequest(event.tenantId, event.topic));
63
+ }
64
+ catch (err) {
65
+ newStream.write(newHandledRequest(event.tenantId, event.topic, err));
66
+ throw err;
67
+ }
25
68
  };
26
- stream.on("data", fn);
69
+ stream = newStream;
70
+ await (0, exports.initStream)(topics, config, newStream);
71
+ newStream.on("data", dataFn);
72
+ };
73
+ return {
74
+ useHandler: (type, handler) => {
75
+ if (!typeHandlerMap[type]) {
76
+ typeHandlerMap[type] = [handler];
77
+ }
78
+ else {
79
+ typeHandlerMap[type].push(handler);
80
+ }
81
+ },
82
+ useHandlerForAllTypes: (handler) => {
83
+ globalHandlers.push(handler);
84
+ },
85
+ start: () => {
86
+ reconnect(50);
87
+ },
88
+ stop: () => {
89
+ if (stream) {
90
+ stream.end();
91
+ stream = null;
92
+ }
93
+ closed = true;
94
+ },
95
+ };
96
+ };
97
+ exports.newSubscription = newSubscription;
98
+ const initStream = async (topics, config, stream) => {
99
+ return new Promise((resolve, reject) => {
100
+ stream.write({
101
+ payload: {
102
+ $case: "subscribe",
103
+ subscribe: {
104
+ metadata: {
105
+ group: config.groupId,
106
+ subscriberId: (0, uuid_1.v4)(),
107
+ },
108
+ topics,
109
+ },
110
+ },
111
+ });
112
+ stream.once("data", (data) => {
113
+ var _a;
114
+ if (((_a = data.payload) === null || _a === void 0 ? void 0 : _a.$case) !== "subscribed") {
115
+ reject("connection to streams service was not initialized correctly");
116
+ return;
117
+ }
118
+ if (data.payload.subscribed.error) {
119
+ reject(`unable to subscribe to streams service: ${data.payload.subscribed.error}`);
120
+ return;
121
+ }
122
+ resolve(stream);
123
+ });
27
124
  });
28
125
  };
29
- exports.sendSubscribe = sendSubscribe;
30
- const newSubscribeRequest = (includedTopics, excludedTopics) => {
126
+ exports.initStream = initStream;
127
+ const newHandledRequest = (tenantId, topic, error) => {
31
128
  return {
32
129
  payload: {
33
- $case: "subscribe",
34
- subscribe: {
35
- excludedTopics,
36
- includedTopics,
130
+ $case: "handled",
131
+ handled: {
132
+ tenantId,
133
+ topic,
134
+ error: error !== null && error !== void 0 ? error : "",
37
135
  },
38
136
  },
39
137
  };
@@ -0,0 +1,2 @@
1
+ export declare const sleep: (delay: number) => Promise<void>;
2
+ export declare const retry: <T extends unknown>(fn: () => Promise<T>, pause?: number, retries?: number) => Promise<T>;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.retry = exports.sleep = void 0;
4
+ const constants_1 = require("@grpc/grpc-js/build/src/constants");
5
+ const sleep = (delay) => new Promise(resolve => {
6
+ setTimeout(() => resolve(), delay);
7
+ });
8
+ exports.sleep = sleep;
9
+ const retry = async (fn, pause = 100, retries = 50) => {
10
+ try {
11
+ return await fn();
12
+ }
13
+ catch (err) {
14
+ if (retries === 0 || (err && err.code && err.code === constants_1.Status.UNKNOWN)) {
15
+ throw err;
16
+ }
17
+ await (0, exports.sleep)(pause);
18
+ return await (0, exports.retry)(fn, pause, retries - 1);
19
+ }
20
+ };
21
+ exports.retry = retry;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fraym/streams",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://github.com/fraym/streams-nodejs",
6
6
  "repository": {
@@ -26,8 +26,8 @@
26
26
  "main": "dist/index.js",
27
27
  "types": "dist/index.d.ts",
28
28
  "dependencies": {
29
- "@fraym/proto": "^0.8.0",
30
- "@grpc/grpc-js": "^1.9.12",
29
+ "@fraym/proto": "^0.11.4",
30
+ "@grpc/grpc-js": "^1.10.0",
31
31
  "uuid": "^9.0.1"
32
32
  },
33
33
  "devDependencies": {
@@ -1,3 +0,0 @@
1
- export declare class AlreadySubscribedError extends Error {
2
- constructor();
3
- }
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AlreadySubscribedError = void 0;
4
- class AlreadySubscribedError extends Error {
5
- constructor() {
6
- super("streams client is already subscribed to a set of topics");
7
- }
8
- }
9
- exports.AlreadySubscribedError = AlreadySubscribedError;
@@ -1,8 +0,0 @@
1
- import { Stream } from "./init";
2
- import { HandlerFunc } from "./event";
3
- interface EventHandler {
4
- addHandler: (type: string, handler: HandlerFunc) => void;
5
- addHandlerForAllTypes: (handler: HandlerFunc) => void;
6
- }
7
- export declare const initEventHandler: (stream: Stream) => EventHandler;
8
- export {};
@@ -1,82 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.initEventHandler = void 0;
4
- const event_1 = require("./event");
5
- const initEventHandler = (stream) => {
6
- const typeHandlerMap = {};
7
- const globalHandlers = [];
8
- stream.on("data", (data) => {
9
- var _a, _b, _c;
10
- if (((_a = data.data) === null || _a === void 0 ? void 0 : _a.$case) !== "event") {
11
- return;
12
- }
13
- const event = (0, event_1.getSubscriptionEvent)(data.data.event);
14
- if (!event) {
15
- return;
16
- }
17
- const typeHandlers = (_c = typeHandlerMap[(_b = event.type) !== null && _b !== void 0 ? _b : ""]) !== null && _c !== void 0 ? _c : [];
18
- typeHandlers.forEach(handler => handleEvent(event, handler, stream));
19
- globalHandlers.forEach(handler => handleEvent(event, handler, stream));
20
- });
21
- return {
22
- addHandler: (type, handler) => {
23
- if (!typeHandlerMap[type]) {
24
- typeHandlerMap[type] = [handler];
25
- }
26
- else {
27
- typeHandlerMap[type].push(handler);
28
- }
29
- },
30
- addHandlerForAllTypes: handler => {
31
- globalHandlers.push(handler);
32
- },
33
- };
34
- };
35
- exports.initEventHandler = initEventHandler;
36
- const handleEvent = (event, handler, stream) => {
37
- stream.write(newEventReceivedRequest(event.tenantId, event.topic, event.id));
38
- handler(event)
39
- .then(() => {
40
- stream.write(newEventHandledRequest(event.tenantId, event.topic, event.id));
41
- })
42
- .catch(e => {
43
- stream.write(newEventNotHandledRequest(event.tenantId, event.topic, event.id, e));
44
- });
45
- };
46
- const newEventReceivedRequest = (tenantId, topic, eventId) => {
47
- return {
48
- payload: {
49
- $case: "eventReceived",
50
- eventReceived: {
51
- eventId,
52
- tenantId,
53
- topic,
54
- },
55
- },
56
- };
57
- };
58
- const newEventHandledRequest = (tenantId, topic, eventId) => {
59
- return {
60
- payload: {
61
- $case: "eventHandled",
62
- eventHandled: {
63
- eventId,
64
- tenantId,
65
- topic,
66
- },
67
- },
68
- };
69
- };
70
- const newEventNotHandledRequest = (tenantId, topic, eventId, reason) => {
71
- return {
72
- payload: {
73
- $case: "eventNotHandled",
74
- eventNotHandled: {
75
- eventId,
76
- tenantId,
77
- topic,
78
- reason,
79
- },
80
- },
81
- };
82
- };
@@ -1,5 +0,0 @@
1
- import { ClientConfig } from "./config";
2
- import { ClientDuplexStream } from "@grpc/grpc-js";
3
- import { SubscribeRequest, ServiceClient, SubscribeResponse } from "@fraym/proto/freym/streams/clientchannel";
4
- export type Stream = ClientDuplexStream<SubscribeRequest, SubscribeResponse>;
5
- export declare const initStream: (config: ClientConfig, serviceClient: ServiceClient) => Promise<Stream>;
@@ -1,34 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.initStream = void 0;
4
- const uuid_1 = require("uuid");
5
- const initStream = async (config, serviceClient) => {
6
- const stream = serviceClient.subscribe();
7
- stream.on("end", stream.end);
8
- return new Promise((resolve, reject) => {
9
- stream.once("data", (data) => {
10
- var _a;
11
- if (((_a = data.data) === null || _a === void 0 ? void 0 : _a.$case) !== "initAck") {
12
- reject("connection to streams service was not initialized correctly");
13
- return;
14
- }
15
- resolve(stream);
16
- });
17
- stream.write(newInitRequest(config));
18
- });
19
- };
20
- exports.initStream = initStream;
21
- const newInitAction = (config) => {
22
- return {
23
- groupId: config.groupId,
24
- subscriberId: (0, uuid_1.v4)(),
25
- };
26
- };
27
- const newInitRequest = (config) => {
28
- return {
29
- payload: {
30
- $case: "init",
31
- init: newInitAction(config),
32
- },
33
- };
34
- };
@@ -1,2 +0,0 @@
1
- import { ServiceClient } from "@fraym/proto/freym/streams/clientchannel";
2
- export declare const sendSnapshot: (topic: string, toTime: Date, serviceClient: ServiceClient) => Promise<void>;
@@ -1,18 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sendSnapshot = void 0;
4
- const sendSnapshot = async (topic, toTime, serviceClient) => {
5
- return new Promise((resolve, reject) => {
6
- serviceClient.snapshot({
7
- topic,
8
- toTime: toTime.toISOString(),
9
- }, error => {
10
- if (error) {
11
- reject(error.message);
12
- return;
13
- }
14
- resolve();
15
- });
16
- });
17
- };
18
- exports.sendSnapshot = sendSnapshot;