corebasic 1.0.231 → 1.0.232

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/dist/libs/dip.js CHANGED
@@ -15,6 +15,7 @@ export const companyMeta = Elabase.companyMeta;
15
15
  export const outletMeta = Elabase.outletMeta;
16
16
  export const customMeta = Elabase.customMeta;
17
17
  export const schema = Elabase.schema;
18
+ export const DipMeta = Elabase.DipMeta;
18
19
  export const isDipMeta = Elabase.isDipMeta;
19
20
  export const excludeCollectionConfig = Elabase.excludeCollectionConfig;
20
21
  export const includeCollectionConfig = Elabase.includeCollectionConfig;
@@ -16,10 +16,13 @@ import axios from 'axios';
16
16
  // @ts-ignore
17
17
  import jwt from 'jsonwebtoken';
18
18
  let features = {};
19
- let apis = {};
20
- const getFeatureMethod = api => api.split(' ')[0].toLowerCase();
21
- const getFeatureUrl = api => api.split(' ')[1];
22
- const getFeature = name => features[name];
19
+ let apis = {
20
+ // <api>: {<feature>: FeatureEntry}
21
+ };
22
+ const getFeatureMethod = (api) => api.split(' ')[0].toLowerCase();
23
+ const getFeatureUrl = (api) => { const url = api.split(' ')[1]; if (!url)
24
+ throw new Error(`Invalid Feature url in api: ${api}`); return url; };
25
+ const getFeature = (name) => features[name];
23
26
  const DEPLOY_TOKEN_SECRET = process.env.DEPLOY_TOKEN_SECRET || "MY_SECRET_DEPLOY_TOKEN";
24
27
  const SERVICE_ACCESS_TOKEN = jwt.sign({ app: process.env.APP_DEPLOYMENT_NAME }, DEPLOY_TOKEN_SECRET, { expiresIn: '365d' });
25
28
  const NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN = await (async () => { if (process.env.NDCURVE_DEVELOPER_SERVICE)
@@ -50,7 +53,7 @@ async function loadLocalFeatures() {
50
53
  let SLYP_FEATURES_LIST = await loadLocalFeatures();
51
54
  let appId = Utils.uid();
52
55
  let SERVICE_ADDRESS = process.env.APP_ENDPOINT || 'http://127.0.0.1:3000';
53
- export const get = feature => {
56
+ export const get = (feature) => {
54
57
  const throwError = () => { throw new Error(`Feature ${feature} not found in internal or external list during Features.subscribe call`); };
55
58
  const baseFeature = getFeature(feature);
56
59
  let { api, service = SERVICE_ADDRESS, topic } = baseFeature ? baseFeature : (SLYP_FEATURES_LIST[feature] ?? throwError());
@@ -68,16 +71,27 @@ export const send = async (meta, feature, data, params) => {
68
71
  if (url.includes(':'))
69
72
  throw new Error(`Error: Internal feature call send on feature: ${feature} when api/params/substitution`);
70
73
  }
71
- let payload = { ...meta, data, feature, txn: Utils.uid() };
74
+ const payload = { ...meta, data, feature, txn: Utils.uid() };
72
75
  service = service.replace('http://slyp.app', 'https://slyp.app');
73
76
  // return (await axios[method](`${service}${url}`, { data: payload, headers: {jwt: headers.jwt}, timeout: 1000 })).data
74
77
  // Used to work. But request to 127.0.0.1 fails to send the specified headers along with the request.
75
78
  // return (await axios[method](`${service}${url}`, payload, {headers: {jwt: SERVICE_ACCESS_TOKEN, service: true}, timeout: 1000 })).data // Worked Earlier, but issue spotted
76
- if (getFeature(feature)) { // Local call
79
+ if (baseFeature) { // Local call
77
80
  let response;
78
- let req = { meta, body: payload, params, method };
81
+ let req = { body: payload, params: params ?? {}, method, path: url, url: '' };
79
82
  req = JSON.parse(JSON.stringify(req));
80
- let res = { json: payload => response = payload, end: () => true };
83
+ const callback = (payload) => { response = payload; };
84
+ const res = {
85
+ json: callback,
86
+ send: callback,
87
+ end: () => { },
88
+ status: (_status) => { return { send: callback, json: callback }; },
89
+ sendStatus: (_status) => { },
90
+ setHeader: (_header, _value) => { },
91
+ flushHeaders: () => { },
92
+ headersSent: false,
93
+ writableEnded: false,
94
+ };
81
95
  await apiHandler(req, res);
82
96
  return response;
83
97
  }
@@ -109,7 +123,8 @@ async function announce() {
109
123
  function getFeaturelessFeature(req) {
110
124
  if (Session.ALLOWED_URLS.includes(req.path)) {
111
125
  for (let key in features) {
112
- if (features[key].featureless && features[key].api === req.method.toUpperCase() + ' ' + req.path) {
126
+ const feature = features[key];
127
+ if (feature.featureless && feature.api === req.method.toUpperCase() + ' ' + req.path) {
113
128
  return getFeature(key);
114
129
  }
115
130
  }
@@ -129,44 +144,77 @@ const apiHandler = async (req, res) => {
129
144
  console.warn(`Feature: ${req.body.feature} not available`);
130
145
  throw { status: 404, message: `Resource not found. Feature ${req.body.feature} not available.` };
131
146
  }
147
+ feature = feature; // typescript validation with !
132
148
  let topic = feature.topic;
133
- let params = getFeatureUrl(feature.api).split("/").filter(item => item.startsWith(":")).map(item => item.replace(":", ""));
149
+ let params = getFeatureUrl(feature.api).split("/").filter((item) => item.startsWith(":")).map((item) => item.replace(":", ""));
134
150
  for (let param of params)
135
151
  if (!req.params[param])
136
152
  throw { status: 404, message: "Resource not found. One or more url parameter not specified." };
137
- let meta = { ...req.body, data: undefined };
138
- req.meta = meta;
153
+ let meta = { ...req.body, data: undefined }; // TODO: Must also include invoiceTxn
139
154
  if (process.env.USE_DEFAULT_COMPANY) {
140
- req.meta.company = 'DEFAULT_COMPANY';
141
- req.meta.outlet = 'DEFAULT_OUTLET';
155
+ meta.company = 'DEFAULT_COMPANY';
156
+ meta.outlet = 'DEFAULT_OUTLET';
142
157
  }
158
+ meta.txn = meta.txn ?? Utils.uid();
159
+ const message = {
160
+ meta,
161
+ data: req.body.data,
162
+ params: req.params,
163
+ // To be removed 1
164
+ date: new Date().getTime(),
165
+ // To be removed 2
166
+ topic,
167
+ feature: req.body.feature,
168
+ user: req.body.user,
169
+ txn: meta.txn,
170
+ };
171
+ const appReq = {
172
+ meta: message.meta,
173
+ body: message,
174
+ params: message.params,
175
+ method: method,
176
+ path: req.path,
177
+ url: req.url,
178
+ headers: req.headers,
179
+ query: req.query,
180
+ on: req.on,
181
+ };
182
+ const appRes = res;
143
183
  if (method === "get") {
144
184
  try {
145
- req.body = { ...req.body, topic };
146
- await feature.handler(req, res);
185
+ // await feature.handler({...req, headers: req.headers, body: {...req.body, topic} }, res)
186
+ // req.body = {...req.body, topic}
187
+ await feature.handler(appReq, appRes);
147
188
  }
148
189
  catch (err) {
149
190
  if (process.env.DEBUG_MODE)
150
- console.log('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err);
191
+ console.error('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err);
151
192
  throw { status: 500, message: "Failed to GET feature", ...err };
152
193
  }
153
194
  }
154
195
  else if (method !== "get" && feature.bypass) {
155
196
  try {
156
- await feature.handler(topic, prepareMessage(req), req, res);
197
+ await feature.handler(topic, message, appReq, appRes);
157
198
  }
158
199
  catch (err) {
159
200
  if (process.env.DEBUG_MODE)
160
- console.log('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err);
201
+ console.error('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err);
161
202
  throw { status: 500, message: "Failed to POST feature", ...err };
162
203
  }
163
204
  }
164
- else
165
- await commandAction(req, res, topic);
205
+ else {
206
+ try {
207
+ await Kafka.send(`Features.${topic}`, message, message.user, { compression: Kafka.CompressionTypes.GZIP });
208
+ }
209
+ catch {
210
+ throw { status: 500, message: "Failed to queue the transaction" };
211
+ }
212
+ appRes.json({ data: { txn: message.txn, success: true, status: "Queued", featureQueued: true } });
213
+ }
166
214
  }
167
215
  catch (err) {
168
216
  if (process.env.DEBUG_MODE)
169
- console.log('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err);
217
+ console.error('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err);
170
218
  try { // Sometimes error occurs when disconnecting stream from front end
171
219
  res.status(err.status ?? 500).json(err);
172
220
  }
@@ -203,6 +251,8 @@ function registerApi() {
203
251
  for (let api in apis) {
204
252
  let method = getFeatureMethod(api);
205
253
  let url = api.split(' ')[1];
254
+ if (!url)
255
+ throw new Error(`No url path found in api ${api} during Features.registerApi()`);
206
256
  if (api === 'GET /transactions/:id')
207
257
  continue;
208
258
  ExpressApp[method](url, apiHandler);
@@ -219,10 +269,10 @@ export const start = async (app, url, file) => {
219
269
  app.get('/features', async (req, res) => {
220
270
  let exp_features = {};
221
271
  for (let [key, { api }] of Object.entries(features))
222
- exp_features[key] = { api, service: `${SERVICE_ADDRESS}` };
272
+ exp_features[key] = { api, service: `${SERVICE_ADDRESS}`, subscribe: '', topic: '' };
223
273
  if (process.env.LOAD_LOCAL_FEATURES) {
224
274
  for (let key in SLYP_FEATURES_LIST)
225
- SLYP_FEATURES_LIST[key].headers = { JWT: SERVICE_ACCESS_TOKEN, service: true, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN };
275
+ SLYP_FEATURES_LIST[key].headers = { JWT: SERVICE_ACCESS_TOKEN, service: true, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN }; // TODO: CRITICAL: Security issue
226
276
  }
227
277
  res.json({ data: { ...SLYP_FEATURES_LIST, ...exp_features } });
228
278
  });
@@ -230,20 +280,21 @@ export const start = async (app, url, file) => {
230
280
  await registerFeatures(features);
231
281
  // Get unique kafka topics list from features
232
282
  let kafkaTopics = [];
233
- for (let name in features) {
283
+ for (const name in features) {
234
284
  let feature = features[name];
235
- if (feature.api.split(' ')[0].toLowerCase() === "get")
285
+ if (feature.api.split(' ')[0]?.toLowerCase() === "get")
236
286
  continue;
237
287
  kafkaTopics = [...new Set(kafkaTopics.concat([feature.topic]))];
238
288
  }
239
289
  // Subscribe to each topic
240
290
  for (let topic of kafkaTopics) {
241
- Kafka.receive(`Features.${topic}`, async (topic, message) => {
291
+ const groupId = '';
292
+ Kafka.receive(`Features.${topic}`, groupId, async (topic, message) => {
242
293
  if (Utils.isEmpty(message.meta?.company))
243
294
  message.meta = { ...message.meta, company: "GLOBAL", outlet: "GLOBAL" };
244
295
  if (!message?.feature)
245
296
  return;
246
- const timer = ms => new Promise(res => setTimeout(res, ms)); // A promise that resolves after "ms" Milliseconds
297
+ const timer = (ms) => new Promise(res => setTimeout(res, ms)); // A promise that resolves after "ms" Milliseconds
247
298
  topic = topic.replace(/^.*Features./, '');
248
299
  while (true) {
249
300
  try {
@@ -251,7 +302,12 @@ export const start = async (app, url, file) => {
251
302
  // // TODO: use $useChunks: [] once support is added in dip insert
252
303
  // await Dip.insert(globalMeta, `users.txns`, { _id: `${message.user}_${iso_date}_${message.txn}`, user: message.user, feature: message.feature, date: message.date, created: message.date, updated: message.date, status: "Queued" }, {idempotent: true}) // Can always update the status later
253
304
  // TODO: Avoid duplicate processing
254
- await features[message.feature].handler(topic, message);
305
+ const feature = features[message.feature];
306
+ if (!feature) {
307
+ console.warn(`Feature ${message.feature} missing, in executing handler during Kafka.receive(topic: ${topic})`);
308
+ throw new Error(`Feature ${message.feature} missing, in executing handler during Kafka.receive(topic: ${topic})`);
309
+ }
310
+ await feature.handler(topic, message);
255
311
  await Dip.insert(globalMeta, "Features.txns", { _id: message.txn, status: "Processed" }, { idempotent: true });
256
312
  break;
257
313
  }
@@ -296,31 +352,13 @@ export const start = async (app, url, file) => {
296
352
  console.log('Messaging failed to start. Maybe missing redis');
297
353
  }
298
354
  };
299
- function prepareMessage(req) {
300
- let txn = req.body.txn ?? Utils.uid();
301
- let _id = req.body.data?._id ?? txn;
302
- let { data, feature, app, user, outlet, company, client, version } = req.body;
303
- let meta = req.meta;
304
- let params = req.params;
305
- return { data, params, meta, feature, app, user, outlet, company, client, version, txn, id: _id, date: new Date().getTime() };
306
- }
307
- const commandAction = async (req, res, topic) => {
308
- let message = prepareMessage(req);
309
- try {
310
- await Kafka.send(`Features.${topic}`, message, message.user, { compression: Kafka.CompressionTypes.GZIP });
311
- }
312
- catch {
313
- throw { status: 500, message: "Failed to queue the transaction" };
314
- }
315
- res.json({ data: { txn: message.txn, success: true, status: "Queued", featureQueued: true } });
316
- };
317
355
  let subscriptions = {
318
356
  // "coins.query.hello": "invoices.command.add", // Consumer : Feature of Topic
319
357
  };
320
358
  let subscribed_consumers = {};
321
359
  async function subscribe() {
322
360
  let Features = { get, send };
323
- for (let [key, { subscribe }] of Object.entries(features))
361
+ for (let [key, { subscribe }] of Object.entries(features)) {
324
362
  if (!Utils.isEmpty(subscribe) && subscriptions[key] !== subscribe && subscribed_consumers[key]) {
325
363
  try {
326
364
  await subscribed_consumers[key].disconnect();
@@ -328,30 +366,37 @@ async function subscribe() {
328
366
  catch (_) { }
329
367
  delete subscribed_consumers[key];
330
368
  }
331
- for (let key in SLYP_FEATURES_LIST)
332
- if (!Utils.isEmpty(SLYP_FEATURES_LIST[key].subscribe) && subscriptions[key] !== SLYP_FEATURES_LIST[key].subscribe && subscribed_consumers[key]) {
369
+ }
370
+ for (let key in SLYP_FEATURES_LIST) {
371
+ const feature = SLYP_FEATURES_LIST[key];
372
+ if (!Utils.isEmpty(feature?.subscribe) && subscriptions[key] !== feature?.subscribe && subscribed_consumers[key]) {
333
373
  try {
334
374
  await subscribed_consumers[key].disconnect();
335
375
  }
336
376
  catch (_) { }
337
377
  delete subscribed_consumers[key];
338
378
  }
339
- for (let key in SLYP_FEATURES_LIST)
340
- if (!Utils.isEmpty(SLYP_FEATURES_LIST[key].subscribe)) {
341
- subscriptions[key] = SLYP_FEATURES_LIST[key].subscribe;
379
+ }
380
+ for (let key in SLYP_FEATURES_LIST) {
381
+ const feature = SLYP_FEATURES_LIST[key];
382
+ if (!Utils.isEmpty(feature?.subscribe)) {
383
+ subscriptions[key] = feature.subscribe;
342
384
  }
343
- for (let [key, { subscribe }] of Object.entries(features))
385
+ }
386
+ for (let [key, { subscribe }] of Object.entries(features)) {
344
387
  if (!Utils.isEmpty(subscribe)) {
345
388
  subscriptions[key] = subscribe;
346
389
  }
390
+ }
347
391
  for (let consumer in subscriptions) {
348
392
  let publisher = subscriptions[consumer];
349
- let { topic } = getFeature(publisher);
350
- if (subscribed_consumers[consumer] || !getFeature(publisher))
393
+ const publisherFeature = getFeature(publisher);
394
+ if (subscribed_consumers[consumer] || !publisherFeature)
351
395
  continue;
352
- let kafka_consumer = Kafka.receive(`Features.${topic}`, consumer, async (topic, message) => {
396
+ let { topic } = publisherFeature;
397
+ let kafka_consumer_promise = Kafka.receive(`Features.${topic}`, consumer, async (topic, message) => {
353
398
  topic = topic.replace(/^.*Features./, '');
354
- const timer = ms => new Promise(res => setTimeout(res, ms)); // A promise that resolves after "ms" Milliseconds
399
+ const timer = (ms) => new Promise(res => setTimeout(res, ms)); // A promise that resolves after "ms" Milliseconds
355
400
  while (true) {
356
401
  try {
357
402
  await Features.send(message.meta, consumer, message.data, message.params);
@@ -363,6 +408,11 @@ async function subscribe() {
363
408
  await timer(10000);
364
409
  }
365
410
  });
366
- subscribed_consumers[consumer] = kafka_consumer;
411
+ kafka_consumer_promise
412
+ .then(kafka_consumer => {
413
+ subscribed_consumers[consumer] = kafka_consumer;
414
+ }).catch(err => {
415
+ throw new Error("Error: Remote service to service kafka Feature subscription.");
416
+ });
367
417
  }
368
418
  }
@@ -33,23 +33,21 @@ export const createTopic = async (topic, partition, replicas) => {
33
33
  // ===========
34
34
  let consumers = [];
35
35
  const start_consumer = async function (topic, groupId, callback) {
36
- callback = typeof groupId === "string" ? callback : groupId;
37
- groupId = typeof groupId === "string" ? `${appName}.${groupId}` : `${appName}.${topic}`;
36
+ groupId = groupId ? `${appName}.${groupId}` : `${appName}.${topic}`;
38
37
  const consumer = kafka.consumer({ groupId: groupId });
39
38
  consumers.push(consumer);
40
39
  await consumer.connect();
41
40
  await consumer.subscribe({ topic: topic, fromBeginning: true });
42
- const fn = callback;
43
41
  await consumer.run({
44
42
  autoCommit: false,
45
43
  eachMessage: async ({ topic, partition, message }) => {
46
- // fn(topic, message.value.toString(), partition) // toString() returns array so won't parse if json.
44
+ // callback(topic, message.value.toString(), partition) // toString() returns array so won't parse if json.
47
45
  let success;
48
46
  try {
49
- success = await fn(topic, JSON.parse(message.value), partition);
47
+ success = await callback(topic, JSON.parse(message.value), partition);
50
48
  }
51
49
  catch (ex) {
52
- success = await fn(topic, message.value, partition);
50
+ success = await callback(topic, message.value, partition); // NOTE: Beware: Ensure Kafka.receive<T> can handle raw message type that is not json parseable
53
51
  }
54
52
  await consumer.commitOffsets([{ topic, partition, offset: (Number(message.offset) + 1).toString() }]);
55
53
  },
@@ -82,7 +80,7 @@ const start_producer = async function (topic, message, key, options) {
82
80
  // start_producer('quickstart-events', 'Hello KafkaJS user! Little')
83
81
  export const receive = async function (topic, groupId, callback) {
84
82
  topic = topicPrefix + topic;
85
- return (await start_consumer(topic, groupId, callback));
83
+ return await start_consumer(topic, groupId, callback);
86
84
  };
87
85
  export const send = async function (topic, message, key, options) {
88
86
  topic = topicPrefix + topic;
package/libs/dip.ts CHANGED
@@ -18,6 +18,8 @@ export const companyMeta = Elabase.companyMeta
18
18
  export const outletMeta = Elabase.outletMeta
19
19
  export const customMeta = Elabase.customMeta
20
20
  export const schema = Elabase.schema
21
+ export type DipMeta = Elabase.DipMeta;
22
+ export const DipMeta = Elabase.DipMeta;
21
23
  export const isDipMeta = Elabase.isDipMeta
22
24
  export const excludeCollectionConfig = Elabase.excludeCollectionConfig
23
25
  export const includeCollectionConfig = Elabase.includeCollectionConfig
package/libs/features.ts CHANGED
@@ -8,11 +8,90 @@ import axios from 'axios'
8
8
  // @ts-ignore
9
9
  import jwt from 'jsonwebtoken'
10
10
 
11
- export type Message = unknown
12
- export type Req = unknown
13
- export type Res = unknown
11
+
12
+ // ===================================
13
+
14
+ type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "head" | "options" | "connect" | "trace"
15
+
16
+
17
+ type FeatureMeta = {
18
+ feature: string,
19
+ app: string,
20
+ user: string,
21
+ outlet: string,
22
+ company: string,
23
+ staff: string,
24
+ client: string,
25
+ version: string,
26
+ txn: string,
27
+ // invoiceTxn?: string // TODO: Must also include invoiceTxn
28
+ }
29
+ type FeatureParams = Record<string, string>
30
+ type FeatureMessage = {
31
+ meta: FeatureMeta
32
+ data: unknown
33
+ params: FeatureParams
34
+ // To be removed 1
35
+ date: number // used as message.date
36
+ // To be removed 2
37
+ topic: string // used as req.body.topic
38
+ feature: string // used as req.body.feature or message.feature
39
+ user: string // used as req.body.user or message.user
40
+ txn: string // used as message.txn
41
+
42
+
43
+ // client // Only used as req.body.client in auth.ts, session.ts and messaging.ts
44
+ // version // Only used as req.body.version in auth.ts and session.ts
45
+ // outlet, company // Only used as req.body.outlet, req.body.company in session.ts
46
+ // app // Only used as req.body.app in auth.ts and session.ts
47
+ }
48
+ type PayloadMessage = FeatureMeta & {
49
+ data: unknown
50
+ params?: FeatureParams
51
+ }
52
+
53
+ // =========
54
+
55
+ export type Req = {
56
+ meta: FeatureMeta
57
+ body: FeatureMessage
58
+ params: FeatureParams
59
+ method: string
60
+ path: string
61
+ url?: string
62
+ headers?: Record<string, string | string[] | undefined>
63
+ query?: Record<string, unknown>
64
+ on?: (event: string, callback: () => void) => void
65
+ }
66
+
67
+ type ResJson = (response: unknown) => void
68
+ type ResSend = (response: unknown) => void;
69
+ type ResStatus = (status: number) => {send: ResSend, json: ResJson};
70
+ export type Res = {
71
+ send: ResSend
72
+ json: ResJson
73
+ end: (finalData?: string) => void
74
+ status: ResStatus
75
+ sendStatus: (status: number) => void
76
+ setHeader: (header: string, value: string) => void
77
+ flushHeaders: () => void
78
+ headersSent: boolean
79
+ writableEnded: boolean
80
+ }
81
+
82
+
83
+ // ========= Express Types ==========
84
+ type ExpressRequest = Omit<Req, "meta" | "body"> & { body: PayloadMessage }
85
+ type ExpressResponse = Res
86
+ type ExpressHandler = (req: ExpressRequest, res: ExpressResponse) => Promise<void>
87
+ type ExpressApplication = { [Method in HttpMethod]: (path: string, handler: ExpressHandler) => unknown }
88
+ // ===================================
89
+
90
+
91
+ // export type Req = unknown
92
+ // export type Res = unknown
14
93
  export type Query = (req: Req, res: Res) => Promise<void>
15
- export type Command = (topic: string, message: Message, req?: Req, res?: Res) => Promise<void>
94
+ export type Command = (topic: string, message: FeatureMessage, req?: Req, res?: Res) => Promise<void>
16
95
  type Handler = Query | Command
17
96
 
18
97
 
@@ -28,11 +107,13 @@ type FeatureEntry = { api: string, topic: string, bypass: boolean, featureless:
28
107
 
29
108
 
30
109
  let features: Record<string, FeatureEntry> = {}
31
- let apis = {}
110
+ let apis: Record<string, Record<string, FeatureEntry>> = {
111
+ // <api>: {<feature>: FeatureEntry}
112
+ }
32
113
 
33
- const getFeatureMethod = api => api.split(' ')[0].toLowerCase()
34
- const getFeatureUrl = api => api.split(' ')[1]
35
- const getFeature = name => features[name]
114
+ const getFeatureMethod = (api: string): HttpMethod => api.split(' ')[0]!.toLowerCase() as HttpMethod
115
+ const getFeatureUrl = (api: string): string => { const url = api.split(' ')[1]; if (!url) throw new Error(`Invalid Feature url in api: ${api}`); return url}
116
+ const getFeature = (name: string): FeatureEntry | undefined => features[name]
36
117
 
37
118
  const DEPLOY_TOKEN_SECRET = process.env.DEPLOY_TOKEN_SECRET || "MY_SECRET_DEPLOY_TOKEN"
38
119
  const SERVICE_ACCESS_TOKEN = jwt.sign({app: process.env.APP_DEPLOYMENT_NAME}, DEPLOY_TOKEN_SECRET, { expiresIn: '365d' });
@@ -63,39 +144,50 @@ let appId = Utils.uid()
63
144
  let SERVICE_ADDRESS = process.env.APP_ENDPOINT || 'http://127.0.0.1:3000'
64
145
 
65
146
 
66
- export const get = feature => {
147
+ export const get = (feature: string) => {
67
148
  const throwError = () => {throw new Error(`Feature ${feature} not found in internal or external list during Features.subscribe call`)}
68
- const baseFeature: FeatureEntry = getFeature(feature)
149
+ const baseFeature: FeatureEntry | undefined = getFeature(feature)
69
150
  let {api, service = SERVICE_ADDRESS, topic} = baseFeature ? baseFeature as FeatureEntry & {service: string} : (SLYP_FEATURES_LIST[feature] ?? throwError())
70
151
  return {api, service, topic}
71
152
  }
72
153
 
73
- export const send = async (meta, feature, data, params) => {
154
+ export const send = async (meta: Partial<FeatureMeta>, feature: string, data?: unknown, params?: FeatureParams) => {
74
155
  const throwError = () => {throw new Error(`Feature ${feature} not found in internal or external list during inter feature call`)}
75
- const baseFeature: FeatureEntry = getFeature(feature)
156
+ const baseFeature: FeatureEntry | undefined = getFeature(feature)
76
157
  let {api, service = SERVICE_ADDRESS} = baseFeature ? baseFeature as FeatureEntry & {service: string} : (SLYP_FEATURES_LIST[feature] ?? throwError())
77
158
  let method = getFeatureMethod(api)
78
159
  let url = getFeatureUrl(api)
79
160
 
80
161
  if (params || url.includes(':')) {
81
162
  for (let key in params)
82
- url = url.replace(`:${key}`, params[key])
163
+ url = url.replace(`:${key}`, params[key]!)
83
164
  if (url.includes(':'))
84
165
  throw new Error(`Error: Internal feature call send on feature: ${feature} when api/params/substitution`)
85
166
  }
86
167
 
87
- let payload = { ...meta, data, feature, txn: Utils.uid() }
168
+ const payload: PayloadMessage = { ...meta, data, feature, txn: Utils.uid() } as PayloadMessage
88
169
  service = service.replace('http://slyp.app', 'https://slyp.app')
89
170
 
90
171
  // return (await axios[method](`${service}${url}`, { data: payload, headers: {jwt: headers.jwt}, timeout: 1000 })).data
91
172
 
92
173
  // Used to work. But request to 127.0.0.1 fails to send the specified headers along with the request.
93
174
  // return (await axios[method](`${service}${url}`, payload, {headers: {jwt: SERVICE_ACCESS_TOKEN, service: true}, timeout: 1000 })).data // Worked Earlier, but issue spotted
94
- if (getFeature(feature)) { // Local call
175
+ if (baseFeature) { // Local call
95
176
  let response;
96
- let req = {meta,body: payload, params,method}
177
+ let req: ExpressRequest = { body: payload, params: params ?? {}, method, path: url, url: ''}
97
178
  req = JSON.parse(JSON.stringify(req))
98
- let res = {json: payload => response = payload, end: () => true }
179
+ const callback = (payload: unknown): void => { response = payload }
180
+ const res: ExpressResponse = {
181
+ json: callback,
182
+ send: callback,
183
+ end: () => {},
184
+ status: (_status: number) => { return {send: callback, json: callback} },
185
+ sendStatus: (_status: number) => {},
186
+ setHeader: (_header: string, _value: string) => {},
187
+ flushHeaders: () => {},
188
+ headersSent: false,
189
+ writableEnded: false,
190
+ }
99
191
  await apiHandler(req, res);
100
192
  return response
101
193
  }
@@ -109,7 +201,7 @@ export const send = async (meta, feature, data, params) => {
109
201
  })).data;
110
202
  }
111
203
 
112
- let appids = {}
204
+ let appids: Record<string, boolean> = {}
113
205
  async function announce() {
114
206
  let exp_features: Record<string, RemoteFeatureEntry> = {}
115
207
  for (let [key, {api,subscribe}] of Object.entries(features))
@@ -128,17 +220,18 @@ async function announce() {
128
220
  }
129
221
 
130
222
 
131
- function getFeaturelessFeature(req) {
223
+ function getFeaturelessFeature(req: ExpressRequest) {
132
224
  if (Session.ALLOWED_URLS.includes(req.path)) {
133
- for(let key in features){
134
- if (features[key].featureless && features[key].api === req.method.toUpperCase() + ' ' + req.path) {
225
+ for(let key in features) {
226
+ const feature = features[key]!
227
+ if (feature.featureless && feature.api === req.method.toUpperCase() + ' ' + req.path) {
135
228
  return getFeature(key)
136
229
  }
137
230
  }
138
231
  }
139
232
  }
140
233
 
141
- const apiHandler = async (req, res) => {
234
+ const apiHandler = async (req: ExpressRequest, res: ExpressResponse) => {
142
235
  let method = req.method.toLowerCase()
143
236
  let featureless
144
237
  try {
@@ -155,50 +248,89 @@ const apiHandler = async (req, res) => {
155
248
  throw { status: 404, message: `Resource not found. Feature ${req.body.feature} not available.` }
156
249
  }
157
250
 
251
+ feature = feature! // typescript validation with !
252
+
158
253
  let topic = feature.topic
159
- let params = getFeatureUrl(feature.api).split("/").filter(item => item.startsWith(":")).map(item => item.replace(":", ""))
254
+ let params = getFeatureUrl(feature.api).split("/").filter((item: string) => item.startsWith(":")).map((item: string) => item.replace(":", ""))
160
255
  for (let param of params)
161
256
  if (!req.params[param])
162
257
  throw { status: 404, message: "Resource not found. One or more url parameter not specified." }
163
258
 
164
- let meta = {...req.body, data: undefined}
165
- req.meta = meta
259
+ let meta = {...req.body, data: undefined} as FeatureMeta // TODO: Must also include invoiceTxn
260
+
166
261
  if (process.env.USE_DEFAULT_COMPANY) {
167
- req.meta.company = 'DEFAULT_COMPANY'
168
- req.meta.outlet = 'DEFAULT_OUTLET'
262
+ meta.company = 'DEFAULT_COMPANY'
263
+ meta.outlet = 'DEFAULT_OUTLET'
169
264
  }
170
265
 
266
+ meta.txn = meta.txn ?? Utils.uid()
267
+
268
+ const message: FeatureMessage = {
269
+ meta,
270
+ data: req.body.data,
271
+ params: req.params,
272
+
273
+ // To be removed 1
274
+ date: new Date().getTime(),
275
+
276
+ // To be removed 2
277
+ topic,
278
+ feature: req.body.feature,
279
+ user: req.body.user,
280
+ txn: meta.txn,
281
+ }
282
+
283
+ const appReq: Req = {
284
+ meta: message.meta,
285
+ body: message,
286
+ params: message.params,
287
+ method: method,
288
+ path: req.path,
289
+ url: req.url,
290
+ headers: req.headers,
291
+ query: req.query,
292
+ on: req.on,
293
+ }
294
+ const appRes: Res = res as Res
295
+
171
296
  if (method === "get") {
172
297
  try {
173
- req.body = {...req.body, topic}
174
- await (feature.handler as Query)(req, res)
175
- } catch (err) {
298
+ // await feature.handler({...req, headers: req.headers, body: {...req.body, topic} }, res)
299
+ // req.body = {...req.body, topic}
300
+ await (feature.handler as Query)(appReq, appRes)
301
+ } catch (err: any) {
176
302
  if (process.env.DEBUG_MODE)
177
- console.log('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err)
303
+ console.error('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err)
178
304
  throw {status: 500, message: "Failed to GET feature", ...err}
179
305
  }
180
306
  } else if (method !== "get" && feature.bypass) {
181
307
  try {
182
- await (feature.handler as Command)(topic, prepareMessage(req), req, res)
183
- } catch (err) {
308
+ await (feature.handler as Command)(topic, message, appReq, appRes)
309
+ } catch (err: any) {
184
310
  if (process.env.DEBUG_MODE)
185
- console.log('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err)
311
+ console.error('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err)
186
312
  throw {status: 500, message: "Failed to POST feature", ...err}
187
313
  }
188
- } else
189
- await commandAction(req, res, topic)
190
- } catch (err) {
314
+ } else {
315
+ try {
316
+ await Kafka.send(`Features.${topic}`, message, message.user, { compression: Kafka.CompressionTypes.GZIP })
317
+ } catch {
318
+ throw {status: 500, message: "Failed to queue the transaction"}
319
+ }
320
+ appRes.json({ data: { txn: message.txn, success: true, status: "Queued", featureQueued: true } })
321
+ }
322
+ } catch (err: any) {
191
323
  if (process.env.DEBUG_MODE)
192
- console.log('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err)
324
+ console.error('Error: Feature: ', req.body?.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err)
193
325
  try { // Sometimes error occurs when disconnecting stream from front end
194
326
  res.status(err.status ?? 500).json(err)
195
327
  } catch (_) { }
196
328
  }
197
329
  }
198
330
 
199
- let ExpressApp
200
- let PROJECT_ROOT_URL
201
- export async function registerFeatures(newFeatures) {
331
+ let ExpressApp: ExpressApplication
332
+ let PROJECT_ROOT_URL: URL
333
+ export async function registerFeatures(newFeatures: Record<string, FeatureEntry>) {
202
334
  // Registering handlers
203
335
  await registerHandler(newFeatures)
204
336
 
@@ -207,15 +339,15 @@ export async function registerFeatures(newFeatures) {
207
339
  // Registering apis
208
340
  registerApi()
209
341
  }
210
- async function registerHandler(features) {
342
+ async function registerHandler(features: Record<string, FeatureEntry>) {
211
343
  for (let name in features) {
212
- let feature = features[name]
344
+ let feature = features[name]!
213
345
  apis[feature.api] = apis[feature.api] ?? {}
214
- apis[feature.api][name] = feature
346
+ apis[feature.api]![name] = feature
215
347
  const featureName = name
216
348
 
217
349
  const nameParts = name.split('.')
218
- let handler = nameParts.pop()
350
+ let handler = nameParts.pop()!
219
351
  const featurePath = `src/${nameParts.join('/')}`
220
352
 
221
353
  if (featurePath === 'src/transactions/query')
@@ -229,8 +361,10 @@ async function registerHandler(features) {
229
361
  }
230
362
  function registerApi() {
231
363
  for (let api in apis) {
232
- let method = getFeatureMethod(api)
364
+ let method: HttpMethod = getFeatureMethod(api)
233
365
  let url = api.split(' ')[1]
366
+ if (!url)
367
+ throw new Error(`No url path found in api ${api} during Features.registerApi()`)
234
368
  if (api === 'GET /transactions/:id')
235
369
  continue
236
370
 
@@ -238,22 +372,22 @@ function registerApi() {
238
372
  }
239
373
  }
240
374
 
241
- export const start = async (app, url, file) => {
375
+ export const start = async (app: ExpressApplication, url: URL, file: string) => {
242
376
  const globalMeta = Dip.globalMeta()
243
377
  Dip.excludeCollectionConfig("users.txns")
244
378
  Dip.excludeCollectionConfig("Features.txns")
245
379
  ExpressApp = app
246
380
  PROJECT_ROOT_URL = url
247
- features = await Utils.fileToJson(url, file)
381
+ features = await Utils.fileToJson(url, file) as Record<string, FeatureEntry>
248
382
  await announce()
249
383
 
250
- app.get('/features', async (req, res) => {
251
- let exp_features = {}
384
+ app.get('/features', async (req: ExpressRequest, res: ExpressResponse) => {
385
+ let exp_features: Record<string, RemoteFeatureEntry> = {}
252
386
  for (let [key, {api}] of Object.entries(features))
253
- exp_features[key] = {api, service: `${SERVICE_ADDRESS}`}
387
+ exp_features[key] = {api, service: `${SERVICE_ADDRESS}`, subscribe: '', topic: ''}
254
388
  if (process.env.LOAD_LOCAL_FEATURES) {
255
389
  for (let key in SLYP_FEATURES_LIST)
256
- SLYP_FEATURES_LIST[key].headers = {JWT: SERVICE_ACCESS_TOKEN, service: true, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN}
390
+ SLYP_FEATURES_LIST[key]!.headers = {JWT: SERVICE_ACCESS_TOKEN, service: true, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN} // TODO: CRITICAL: Security issue
257
391
  }
258
392
  res.json({ data: { ...SLYP_FEATURES_LIST, ...exp_features } })
259
393
  })
@@ -262,23 +396,24 @@ export const start = async (app, url, file) => {
262
396
  await registerFeatures(features)
263
397
 
264
398
  // Get unique kafka topics list from features
265
- let kafkaTopics = []
266
- for (let name in features) {
267
- let feature = features[name]
268
- if (feature.api.split(' ')[0].toLowerCase() === "get" )
399
+ let kafkaTopics: string[] = []
400
+ for (const name in features) {
401
+ let feature = features[name]!
402
+ if (feature.api.split(' ')[0]?.toLowerCase() === "get" )
269
403
  continue
270
404
  kafkaTopics = [... new Set(kafkaTopics.concat([feature.topic]))]
271
405
  }
272
406
 
273
407
  // Subscribe to each topic
274
408
  for (let topic of kafkaTopics) {
275
- Kafka.receive(`Features.${topic}`, async (topic, message) => {
409
+ const groupId = ''
410
+ Kafka.receive<FeatureMessage>(`Features.${topic}`, groupId, async (topic: string, message: FeatureMessage) => {
276
411
  if (Utils.isEmpty(message.meta?.company))
277
412
  message.meta = { ...message.meta, company: "GLOBAL", outlet: "GLOBAL" }
278
413
  if (!message?.feature)
279
414
  return
280
415
 
281
- const timer = ms => new Promise(res => setTimeout(res, ms)) // A promise that resolves after "ms" Milliseconds
416
+ const timer = (ms: number) => new Promise(res => setTimeout(res, ms)) // A promise that resolves after "ms" Milliseconds
282
417
  topic = topic.replace(/^.*Features./,'')
283
418
  while (true) {
284
419
  try {
@@ -286,7 +421,12 @@ export const start = async (app, url, file) => {
286
421
  // // TODO: use $useChunks: [] once support is added in dip insert
287
422
  // await Dip.insert(globalMeta, `users.txns`, { _id: `${message.user}_${iso_date}_${message.txn}`, user: message.user, feature: message.feature, date: message.date, created: message.date, updated: message.date, status: "Queued" }, {idempotent: true}) // Can always update the status later
288
423
  // TODO: Avoid duplicate processing
289
- await (features[message.feature].handler as Command)(topic, message)
424
+ const feature = features[message.feature]
425
+ if (!feature) {
426
+ console.warn(`Feature ${message.feature} missing, in executing handler during Kafka.receive(topic: ${topic})`)
427
+ throw new Error(`Feature ${message.feature} missing, in executing handler during Kafka.receive(topic: ${topic})`)
428
+ }
429
+ await (feature.handler as Command)(topic, message)
290
430
  await Dip.insert(globalMeta, "Features.txns", {_id: message.txn, status: "Processed"}, {idempotent: true})
291
431
  break
292
432
  } catch(err) {
@@ -302,9 +442,9 @@ export const start = async (app, url, file) => {
302
442
  await subscribe()
303
443
 
304
444
  // Transaction status api
305
- app.get('/transactions/:id', async (req, res) => { // Front end calls this as a regular feature call. so `req.meta` exists
445
+ app.get('/transactions/:id', async (req: ExpressRequest, res: ExpressResponse) => { // Front end calls this as a regular feature call. so `req.meta` exists
306
446
  try {
307
- let items = await Dip.query(globalMeta, "Features.txns", {_id: req.params.id})
447
+ let items = await Dip.query(globalMeta, "Features.txns", {_id: req.params!.id})
308
448
  if (items.length)
309
449
  res.json({ data: { ...items[0], txn: items._id } })
310
450
  else
@@ -325,70 +465,61 @@ export const start = async (app, url, file) => {
325
465
  // }
326
466
  // })
327
467
 
328
- try { await Messaging.start(app) } catch { console.log('Messaging failed to start. Maybe missing redis') }
468
+ try { await Messaging.start((app as unknown) as Messaging.ExpressApp ) } catch { console.log('Messaging failed to start. Maybe missing redis') }
329
469
  }
330
470
 
331
- function prepareMessage(req) {
332
- let txn = req.body.txn ?? Utils.uid()
333
- let _id = req.body.data?._id ?? txn
334
- let {data, feature, app, user, outlet, company, client, version} = req.body
335
- let meta = req.meta
336
- let params = req.params
337
- return { data, params, meta, feature, app, user, outlet, company, client, version, txn, id: _id, date: new Date().getTime() }
338
- }
339
- const commandAction = async (req, res, topic) => {
340
- let message = prepareMessage(req)
341
- try {
342
- await Kafka.send(`Features.${topic}`, message, message.user, { compression: Kafka.CompressionTypes.GZIP })
343
- } catch {
344
- throw {status: 500, message: "Failed to queue the transaction"}
345
- }
346
- res.json({ data: { txn: message.txn, success: true, status: "Queued", featureQueued: true } })
347
- }
348
471
 
349
472
 
350
473
 
351
- let subscriptions = {
474
+ let subscriptions: Record<string, string> = {
352
475
  // "coins.query.hello": "invoices.command.add", // Consumer : Feature of Topic
353
476
  }
354
- let subscribed_consumers = {
355
- }
477
+ let subscribed_consumers: Record<string, Kafka.KafkaConsumer> = {}
356
478
  async function subscribe() {
357
479
  let Features = {get,send}
358
480
 
359
- for (let [key, {subscribe}] of Object.entries(features))
481
+ for (let [key, {subscribe}] of Object.entries(features)) {
360
482
  if (!Utils.isEmpty(subscribe) && subscriptions[key] !== subscribe && subscribed_consumers[key]) {
361
483
  try { await subscribed_consumers[key].disconnect() } catch (_) {}
362
484
  delete subscribed_consumers[key]
363
485
  }
364
- for (let key in SLYP_FEATURES_LIST)
365
- if (!Utils.isEmpty(SLYP_FEATURES_LIST[key].subscribe) && subscriptions[key] !== SLYP_FEATURES_LIST[key].subscribe && subscribed_consumers[key]) {
486
+ }
487
+ for (let key in SLYP_FEATURES_LIST) {
488
+ const feature = SLYP_FEATURES_LIST[key]
489
+ if (!Utils.isEmpty(feature?.subscribe) && subscriptions[key] !== feature?.subscribe && subscribed_consumers[key]) {
366
490
  try { await subscribed_consumers[key].disconnect() } catch (_) {}
367
491
  delete subscribed_consumers[key]
368
492
  }
493
+ }
369
494
 
370
495
 
371
- for (let key in SLYP_FEATURES_LIST)
372
- if (!Utils.isEmpty(SLYP_FEATURES_LIST[key].subscribe)) {
373
- subscriptions[key] = SLYP_FEATURES_LIST[key].subscribe
496
+ for (let key in SLYP_FEATURES_LIST) {
497
+ const feature = SLYP_FEATURES_LIST[key]
498
+ if (!Utils.isEmpty(feature?.subscribe)) {
499
+ subscriptions[key] = feature!.subscribe
374
500
  }
375
- for (let [key, {subscribe}] of Object.entries(features))
501
+ }
502
+ for (let [key, {subscribe}] of Object.entries(features)) {
376
503
  if (!Utils.isEmpty(subscribe)) {
377
504
  subscriptions[key] = subscribe
378
505
  }
506
+ }
379
507
 
380
508
 
381
509
  for (let consumer in subscriptions) {
382
- let publisher = subscriptions[consumer]
383
- let {topic} = getFeature(publisher)
510
+ let publisher = subscriptions[consumer]!
511
+
512
+ const publisherFeature = getFeature(publisher)
384
513
 
385
- if (subscribed_consumers[consumer] || !getFeature(publisher))
514
+ if (subscribed_consumers[consumer] || !publisherFeature)
386
515
  continue
387
516
 
388
- let kafka_consumer = Kafka.receive(`Features.${topic}`, consumer, async (topic, message) => {
517
+ let {topic} = publisherFeature
518
+
519
+ let kafka_consumer_promise = Kafka.receive<FeatureMessage>(`Features.${topic}`, consumer, async (topic: string, message: FeatureMessage) => {
389
520
  topic = topic.replace(/^.*Features./,'')
390
521
 
391
- const timer = ms => new Promise(res => setTimeout(res, ms)) // A promise that resolves after "ms" Milliseconds
522
+ const timer = (ms: number): Promise<void> => new Promise(res => setTimeout(res, ms)) // A promise that resolves after "ms" Milliseconds
392
523
 
393
524
  while (true) {
394
525
  try {
@@ -400,6 +531,12 @@ async function subscribe() {
400
531
  await timer(10000);
401
532
  }
402
533
  })
403
- subscribed_consumers[consumer] = kafka_consumer
534
+
535
+ kafka_consumer_promise
536
+ .then(kafka_consumer => {
537
+ subscribed_consumers[consumer] = kafka_consumer
538
+ }).catch(err => {
539
+ throw new Error("Error: Remote service to service kafka Feature subscription.")
540
+ })
404
541
  }
405
542
  }
package/libs/kafka.ts CHANGED
@@ -5,10 +5,11 @@ export const logLevel = KafkaLogLevel
5
5
  export const CompressionTypes = KafkaCompressionTypes
6
6
 
7
7
 
8
- type KafkaCallback = (topic: string, message: unknown, partition: number) => unknown | Promise<unknown>
8
+ export type KafkaCallback<T = unknown> = (topic: string, message: T, partition: number) => unknown | Promise<unknown>
9
9
 
10
- type KafkaConsumer = {
10
+ export type KafkaConsumer = {
11
11
  connect: () => Promise<void>
12
+ disconnect: () => Promise<void>
12
13
  subscribe: (arg: {topic: string; fromBeginning: boolean}) => Promise<void>
13
14
  run: (arg: {
14
15
  autoCommit: boolean
@@ -100,27 +101,24 @@ export const createTopic = async (topic: string, partition?: number, replicas?:
100
101
 
101
102
  let consumers = []
102
103
 
103
- const start_consumer = async function (topic: string, groupId: string | KafkaCallback, callback?: KafkaCallback) {
104
- callback = typeof groupId === "string" ? callback : groupId
105
- groupId = typeof groupId === "string" ? `${appName}.${groupId}` : `${appName}.${topic}`
104
+ const start_consumer = async function<T>(topic: string, groupId: string, callback: KafkaCallback<T>): Promise<KafkaConsumer> {
105
+ groupId = groupId ? `${appName}.${groupId}` : `${appName}.${topic}`
106
106
 
107
- const consumer = kafka.consumer({ groupId: groupId })
107
+ const consumer: KafkaConsumer = kafka.consumer({ groupId: groupId })
108
108
  consumers.push(consumer)
109
109
 
110
110
  await consumer.connect()
111
111
  await consumer.subscribe({ topic: topic, fromBeginning: true })
112
112
 
113
- const fn = callback!
114
-
115
113
  await consumer.run({
116
114
  autoCommit: false,
117
115
  eachMessage: async ({ topic, partition, message }) => {
118
- // fn(topic, message.value.toString(), partition) // toString() returns array so won't parse if json.
116
+ // callback(topic, message.value.toString(), partition) // toString() returns array so won't parse if json.
119
117
  let success
120
118
  try {
121
- success = await fn(topic, JSON.parse(message.value as string), partition)
119
+ success = await callback(topic, JSON.parse(message.value as string), partition)
122
120
  } catch (ex) {
123
- success = await fn(topic, message.value, partition)
121
+ success = await callback(topic, message.value as T, partition) // NOTE: Beware: Ensure Kafka.receive<T> can handle raw message type that is not json parseable
124
122
  }
125
123
 
126
124
  await consumer.commitOffsets([{ topic, partition, offset: (Number(message.offset) + 1).toString() }]);
@@ -160,9 +158,9 @@ const start_producer = async function (topic: string, message: object, key: stri
160
158
  // start_consumer('quickstart-events')
161
159
  // start_producer('quickstart-events', 'Hello KafkaJS user! Little')
162
160
 
163
- export const receive = async function(topic: string, groupId: string, callback?: KafkaCallback) {
161
+ export const receive = async function<T>(topic: string, groupId: string, callback: KafkaCallback<T>): Promise<KafkaConsumer> {
164
162
  topic = topicPrefix + topic
165
- return (await start_consumer(topic, groupId, callback))
163
+ return await start_consumer(topic, groupId, callback)
166
164
  }
167
165
 
168
166
 
package/libs/messaging.ts CHANGED
@@ -59,7 +59,7 @@ type UserPool = {
59
59
  listeners: UserListeners
60
60
  }
61
61
 
62
- type ExpressApp = {
62
+ export type ExpressApp = {
63
63
  get: (
64
64
  path: string,
65
65
  handler: (req: Request, res: Response) => Promise<void>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "corebasic",
3
3
  "type": "module",
4
- "version": "1.0.231",
4
+ "version": "1.0.232",
5
5
  "description": "",
6
6
  "main": "dist/index.js",
7
7
  "types": "./index.ts",