corebasic 1.0.240 → 1.0.242
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 +5 -5
- package/dist/libs/features.js +28 -33
- package/libs/dip.ts +6 -6
- package/libs/features.ts +47 -57
- package/package.json +1 -1
package/dist/libs/dip.js
CHANGED
|
@@ -60,7 +60,7 @@ export const IdempotentDip = () => {
|
|
|
60
60
|
};
|
|
61
61
|
return idip;
|
|
62
62
|
};
|
|
63
|
-
export const idip = async (
|
|
63
|
+
export const idip = async (meta, txn, callback, cleanup) => {
|
|
64
64
|
if (!callback)
|
|
65
65
|
throw new Error("Dip.idip() callback argument undefined");
|
|
66
66
|
if (!cleanup)
|
|
@@ -73,9 +73,9 @@ export const idip = async (message, callback, cleanup) => {
|
|
|
73
73
|
else
|
|
74
74
|
await cleanup(IDip);
|
|
75
75
|
}
|
|
76
|
-
let IDip = await IdempotentDip().new(
|
|
76
|
+
let IDip = await IdempotentDip().new(meta, txn);
|
|
77
77
|
if (!IDip) {
|
|
78
|
-
IDip = await IdempotentDip().new(
|
|
78
|
+
IDip = await IdempotentDip().new(meta, txn, { blank: true });
|
|
79
79
|
await doCleanup(IDip);
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
@@ -87,7 +87,7 @@ export const insertKeys = (message, meta) => {
|
|
|
87
87
|
if (!isDipMeta(meta))
|
|
88
88
|
throw new Error('Error: Provided meta is not an instance of DipMeta in Dip.insertKeys()');
|
|
89
89
|
return {
|
|
90
|
-
"created": message.date, "updated": message.date,
|
|
90
|
+
"created": message.meta.date, "updated": message.meta.date,
|
|
91
91
|
"createdBy": message.meta.staff,
|
|
92
92
|
"updatedBy": message.meta.staff,
|
|
93
93
|
"createdByUser": message.meta.user,
|
|
@@ -98,7 +98,7 @@ export const insertKeys = (message, meta) => {
|
|
|
98
98
|
};
|
|
99
99
|
export const updateKeys = (message) => {
|
|
100
100
|
return {
|
|
101
|
-
"updated": message.date,
|
|
101
|
+
"updated": message.meta.date,
|
|
102
102
|
"updatedBy": message.meta.staff,
|
|
103
103
|
"updatedByUser": message.meta.user,
|
|
104
104
|
};
|
package/dist/libs/features.js
CHANGED
|
@@ -93,16 +93,17 @@ export const send = async (meta, feature, data, params) => {
|
|
|
93
93
|
writableEnded: false,
|
|
94
94
|
};
|
|
95
95
|
await apiHandler(req, res);
|
|
96
|
-
return response; // tsc already checked that the feature returned proper types at the handler site. Cast here due to ts limitations. So no guarantees lost.
|
|
96
|
+
return response.data; // tsc already checked that the feature returned proper types at the handler site. Cast here due to ts limitations. So no guarantees lost.
|
|
97
97
|
}
|
|
98
98
|
// Workaround: https://stackoverflow.com/questions/72343387/axios-not-sending-headers-request-failing-getting-401-error
|
|
99
|
-
|
|
99
|
+
const response = (await axios({
|
|
100
100
|
method,
|
|
101
101
|
url: `${service}${url}`,
|
|
102
102
|
headers: { jwt: SERVICE_ACCESS_TOKEN, service: true, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN },
|
|
103
103
|
data: payload,
|
|
104
104
|
timeout: 30000 // 30 secs
|
|
105
|
-
})).data;
|
|
105
|
+
})).data;
|
|
106
|
+
return response.data; // tsc already checked that the feature returned proper types at the handler site. Cast here due to ts limitations. So no guarantees lost.;
|
|
106
107
|
};
|
|
107
108
|
let appids = {};
|
|
108
109
|
async function announce() {
|
|
@@ -146,12 +147,11 @@ const apiHandler = async (req, res) => {
|
|
|
146
147
|
throw { status: 404, message: `Resource not found. Feature ${req.body.feature} not available.` };
|
|
147
148
|
}
|
|
148
149
|
feature = feature; // typescript validation with !
|
|
149
|
-
let topic = feature.topic;
|
|
150
150
|
let params = getFeatureUrl(feature.api).split("/").filter((item) => item.startsWith(":")).map((item) => item.replace(":", ""));
|
|
151
151
|
for (let param of params)
|
|
152
152
|
if (!req.params[param])
|
|
153
153
|
throw { status: 404, message: "Resource not found. One or more url parameter not specified." };
|
|
154
|
-
let meta = { ...req.body, data: undefined }; // TODO: Must also include invoiceTxn
|
|
154
|
+
let meta = { ...req.body, data: undefined, topic: feature.topic, date: new Date().getTime() }; // TODO: Must also include invoiceTxn
|
|
155
155
|
if (process.env.USE_DEFAULT_COMPANY) {
|
|
156
156
|
meta.company = 'DEFAULT_COMPANY';
|
|
157
157
|
meta.outlet = 'DEFAULT_OUTLET';
|
|
@@ -161,13 +161,6 @@ const apiHandler = async (req, res) => {
|
|
|
161
161
|
meta,
|
|
162
162
|
data: req.body.data,
|
|
163
163
|
params: req.params,
|
|
164
|
-
// To be removed 1
|
|
165
|
-
date: new Date().getTime(),
|
|
166
|
-
// To be removed 2
|
|
167
|
-
topic,
|
|
168
|
-
feature: req.body.feature,
|
|
169
|
-
user: req.body.user,
|
|
170
|
-
txn: meta.txn,
|
|
171
164
|
};
|
|
172
165
|
const appReq = {
|
|
173
166
|
meta: message.meta,
|
|
@@ -183,34 +176,36 @@ const apiHandler = async (req, res) => {
|
|
|
183
176
|
const appRes = res;
|
|
184
177
|
if (method === "get") {
|
|
185
178
|
try {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
179
|
+
const data = await feature.handler(message, appReq, appRes);
|
|
180
|
+
if (!appRes.headersSent)
|
|
181
|
+
res.json({ data });
|
|
189
182
|
}
|
|
190
183
|
catch (err) {
|
|
191
184
|
if (process.env.DEBUG_MODE)
|
|
192
|
-
console.error('Error: Feature: ',
|
|
185
|
+
console.error('Error: Feature: ', message.meta.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err);
|
|
193
186
|
throw { status: 500, message: "Failed to GET feature", ...err };
|
|
194
187
|
}
|
|
195
188
|
}
|
|
196
189
|
else if (method !== "get" && feature.bypass) {
|
|
197
190
|
try {
|
|
198
|
-
await feature.handler(
|
|
191
|
+
const data = await feature.handler(message, appReq, appRes);
|
|
192
|
+
if (!appRes.headersSent)
|
|
193
|
+
res.json({ data, event: message.meta.feature, txn: message.meta.txn, bypass: true });
|
|
199
194
|
}
|
|
200
195
|
catch (err) {
|
|
201
196
|
if (process.env.DEBUG_MODE)
|
|
202
|
-
console.error('Error: Feature: ',
|
|
197
|
+
console.error('Error: Feature: ', message.meta.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err);
|
|
203
198
|
throw { status: 500, message: "Failed to POST feature", ...err };
|
|
204
199
|
}
|
|
205
200
|
}
|
|
206
201
|
else {
|
|
207
202
|
try {
|
|
208
|
-
await Kafka.send(`Features.${topic}`, message, message.user, { compression: Kafka.CompressionTypes.GZIP });
|
|
203
|
+
await Kafka.send(`Features.${message.meta.topic}`, message, message.meta.user, { compression: Kafka.CompressionTypes.GZIP });
|
|
209
204
|
}
|
|
210
205
|
catch {
|
|
211
206
|
throw { status: 500, message: "Failed to queue the transaction" };
|
|
212
207
|
}
|
|
213
|
-
appRes.json({ data: { txn: message.txn, success: true, status: "Queued", featureQueued: true } });
|
|
208
|
+
appRes.json({ data: { txn: message.meta.txn, success: true, status: "Queued", featureQueued: true } });
|
|
214
209
|
}
|
|
215
210
|
}
|
|
216
211
|
catch (err) {
|
|
@@ -243,9 +238,9 @@ async function registerHandler(features) {
|
|
|
243
238
|
if (featurePath === 'src/transactions/query')
|
|
244
239
|
continue;
|
|
245
240
|
if (featureName.startsWith("flows.") && !featureName.startsWith("flows.query.") && !featureName.startsWith("flows.command.") && !featureName.startsWith("flows.designs."))
|
|
246
|
-
feature.handler = (await import(__rewriteRelativeImportExtension(`${new URL(`src/flows/query.
|
|
241
|
+
feature.handler = (await import(__rewriteRelativeImportExtension(`${new URL(`src/flows/query.ts`, PROJECT_ROOT_URL).toString().replace('file://', '')}`))).FEATURE_HANDLERS[handler];
|
|
247
242
|
else
|
|
248
|
-
feature.handler = (await import(__rewriteRelativeImportExtension(`${new URL(`${featurePath}.
|
|
243
|
+
feature.handler = (await import(__rewriteRelativeImportExtension(`${new URL(`${featurePath}.ts`, PROJECT_ROOT_URL).toString().replace('file://', '')}`)))[handler];
|
|
249
244
|
}
|
|
250
245
|
}
|
|
251
246
|
function registerApi() {
|
|
@@ -291,31 +286,31 @@ export const start = async (app, url, file) => {
|
|
|
291
286
|
for (let topic of kafkaTopics) {
|
|
292
287
|
const groupId = '';
|
|
293
288
|
Kafka.receive(`Features.${topic}`, groupId, async (topic, message) => {
|
|
289
|
+
topic = topic.replace(/^.*Features./, '');
|
|
294
290
|
if (Utils.isEmpty(message.meta?.company))
|
|
295
291
|
message.meta = { ...message.meta, company: "GLOBAL", outlet: "GLOBAL" };
|
|
296
|
-
if (!message
|
|
292
|
+
if (!message.meta.feature)
|
|
297
293
|
return;
|
|
298
294
|
const timer = (ms) => new Promise(res => setTimeout(res, ms)); // A promise that resolves after "ms" Milliseconds
|
|
299
|
-
topic = topic.replace(/^.*Features./, '');
|
|
300
295
|
while (true) {
|
|
301
296
|
try {
|
|
302
|
-
// const iso_date = new Date(message.date).toISOString()
|
|
297
|
+
// const iso_date = new Date(message.meta.date).toISOString()
|
|
303
298
|
// // TODO: use $useChunks: [] once support is added in dip insert
|
|
304
|
-
// 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
|
|
299
|
+
// await Dip.insert(globalMeta, `users.txns`, { _id: `${message.meta.user}_${iso_date}_${message.meta.txn}`, user: message.meta.user, feature: message.meta.feature, date: message.meta.date, created: message.meta.date, updated: message.meta.date, status: "Queued" }, {idempotent: true}) // Can always update the status later
|
|
305
300
|
// TODO: Avoid duplicate processing
|
|
306
|
-
const feature = features[message.feature];
|
|
301
|
+
const feature = features[message.meta.feature];
|
|
307
302
|
if (!feature) {
|
|
308
|
-
console.warn(`Feature ${message.feature} missing, in executing handler during Kafka.receive(topic: ${topic})`);
|
|
309
|
-
throw new Error(`Feature ${message.feature} missing, in executing handler during Kafka.receive(topic: ${topic})`);
|
|
303
|
+
console.warn(`Feature ${message.meta.feature} missing, in executing handler during Kafka.receive(topic: ${message.meta.topic})`);
|
|
304
|
+
throw new Error(`Feature ${message.meta.feature} missing, in executing handler during Kafka.receive(topic: ${message.meta.topic})`);
|
|
310
305
|
}
|
|
311
|
-
await feature.handler(
|
|
312
|
-
await Dip.insert(globalMeta, "Features.txns", { _id: message.txn, status: "Processed" }, { idempotent: true });
|
|
306
|
+
await feature.handler(message);
|
|
307
|
+
await Dip.insert(globalMeta, "Features.txns", { _id: message.meta.txn, status: "Processed" }, { idempotent: true });
|
|
313
308
|
break;
|
|
314
309
|
}
|
|
315
310
|
catch (err) {
|
|
316
311
|
if (process.env.DEBUG_MODE)
|
|
317
|
-
console.log('Error: Feature: ', message.feature, err);
|
|
318
|
-
console.warn(`Feature: ${message.feature}, error in executing handler during Kafka.receive(topic: ${topic})`);
|
|
312
|
+
console.log('Error: Feature: ', message.meta.feature, err);
|
|
313
|
+
console.warn(`Feature: ${message.meta.feature}, error in executing handler during Kafka.receive(topic: ${message.meta.topic})`);
|
|
319
314
|
}
|
|
320
315
|
await timer(10000);
|
|
321
316
|
}
|
package/libs/dip.ts
CHANGED
|
@@ -90,10 +90,10 @@ export const IdempotentDip = (): IdempotentDip => {
|
|
|
90
90
|
return idip
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
type DipMessage = { meta: {staff: string; user: string
|
|
93
|
+
type DipMessage = { meta: {staff: string; user: string; date: number } }
|
|
94
94
|
type IdipCleanup = Record<string, string[]> | ((idip: IdempotentDip) => Promise<void>)
|
|
95
95
|
|
|
96
|
-
export const idip = async (
|
|
96
|
+
export const idip = async (meta: Elabase.DipMeta, txn: string, callback: (idip: IdempotentDip) => Promise<void>, cleanup: IdipCleanup) => {
|
|
97
97
|
if (!callback)
|
|
98
98
|
throw new Error("Dip.idip() callback argument undefined")
|
|
99
99
|
if (!cleanup)
|
|
@@ -106,9 +106,9 @@ export const idip = async (message: {meta: Elabase.DipMeta, txn: string}, callba
|
|
|
106
106
|
await cleanup(IDip)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
let IDip = await IdempotentDip().new(
|
|
109
|
+
let IDip = await IdempotentDip().new(meta, txn)
|
|
110
110
|
if (!IDip) {
|
|
111
|
-
IDip = await IdempotentDip().new(
|
|
111
|
+
IDip = await IdempotentDip().new(meta, txn, {blank: true})
|
|
112
112
|
await doCleanup(IDip as IdempotentDip)
|
|
113
113
|
return
|
|
114
114
|
}
|
|
@@ -121,7 +121,7 @@ export const idip = async (message: {meta: Elabase.DipMeta, txn: string}, callba
|
|
|
121
121
|
export const insertKeys = (message: DipMessage, meta: Elabase.DipMeta) => {
|
|
122
122
|
if (!isDipMeta(meta)) throw new Error('Error: Provided meta is not an instance of DipMeta in Dip.insertKeys()')
|
|
123
123
|
return {
|
|
124
|
-
"created": message.date, "updated": message.date,
|
|
124
|
+
"created": message.meta.date, "updated": message.meta.date,
|
|
125
125
|
"createdBy": message.meta.staff,
|
|
126
126
|
"updatedBy": message.meta.staff,
|
|
127
127
|
"createdByUser": message.meta.user,
|
|
@@ -132,7 +132,7 @@ export const insertKeys = (message: DipMessage, meta: Elabase.DipMeta) => {
|
|
|
132
132
|
}
|
|
133
133
|
export const updateKeys = (message: DipMessage) => {
|
|
134
134
|
return {
|
|
135
|
-
"updated": message.date,
|
|
135
|
+
"updated": message.meta.date,
|
|
136
136
|
"updatedBy": message.meta.staff,
|
|
137
137
|
"updatedByUser": message.meta.user,
|
|
138
138
|
}
|
package/libs/features.ts
CHANGED
|
@@ -25,21 +25,16 @@ type FeatureMeta = {
|
|
|
25
25
|
version: string,
|
|
26
26
|
txn: string,
|
|
27
27
|
// invoiceTxn?: string // TODO: Must also include invoiceTxn
|
|
28
|
+
topic: string,
|
|
29
|
+
date: number,
|
|
28
30
|
}
|
|
29
31
|
export type FeatureParams = Record<string, string>
|
|
30
|
-
type FeatureMessage = {
|
|
32
|
+
type FeatureMessage<I = unknown> = {
|
|
31
33
|
meta: FeatureMeta
|
|
32
|
-
data:
|
|
34
|
+
data: I
|
|
33
35
|
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
36
|
|
|
37
|
+
// feature // Only used as req.body.feature in features.ts, session.ts
|
|
43
38
|
// client // Only used as req.body.client in auth.ts, session.ts and messaging.ts
|
|
44
39
|
// version // Only used as req.body.version in auth.ts and session.ts
|
|
45
40
|
// outlet, company // Only used as req.body.outlet, req.body.company in session.ts
|
|
@@ -90,9 +85,19 @@ type ExpressApplication = { [Method in HttpMethod]: (path: string, handler: Expr
|
|
|
90
85
|
|
|
91
86
|
// export type Req = unknown
|
|
92
87
|
// export type Res = unknown
|
|
93
|
-
export type Query = (req: Req, res: Res) => Promise<void>
|
|
94
|
-
export type Command = (
|
|
95
|
-
type Handler = Query | Command
|
|
88
|
+
// export type Query = (req: Req, res: Res) => Promise<void>
|
|
89
|
+
// export type Command = (message: FeatureMessage, req?: Req, res?: Res) => Promise<void>
|
|
90
|
+
// type Handler = Query | Command
|
|
91
|
+
|
|
92
|
+
declare global {
|
|
93
|
+
type Handler<I, O> = (input: FeatureMessage<I>, req?: Req, res?: Res) => Promise<O>
|
|
94
|
+
|
|
95
|
+
interface FeatureTypes {
|
|
96
|
+
// "privileges.query.check": Handler<{feature: string}, {data: {granted: boolean} }>
|
|
97
|
+
// "products.query.get": typeof import("./src/products/query.ts").get;
|
|
98
|
+
// "products.brands.query.get": typeof import("./src/products/brands/query.ts").get;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
96
101
|
|
|
97
102
|
|
|
98
103
|
type RemoteFeatureEntry = {
|
|
@@ -103,7 +108,7 @@ type RemoteFeatureEntry = {
|
|
|
103
108
|
headers?: {JWT?: string, service?: boolean, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN?: string}
|
|
104
109
|
}
|
|
105
110
|
|
|
106
|
-
type FeatureEntry = { api: string, topic: string, bypass: boolean, featureless: boolean, subscribe: string, handler: Handler }
|
|
111
|
+
type FeatureEntry = { api: string, topic: string, bypass: boolean, featureless: boolean, subscribe: string, handler: Handler<unknown, unknown> }
|
|
107
112
|
|
|
108
113
|
|
|
109
114
|
let features: Record<string, FeatureEntry> = {}
|
|
@@ -151,15 +156,7 @@ export const get = (feature: string) => {
|
|
|
151
156
|
return {api, service, topic}
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
declare global {
|
|
155
|
-
type Handler<I, O> = (input: I) => Promise<O>
|
|
156
159
|
|
|
157
|
-
interface FeatureTypes {
|
|
158
|
-
// "privileges.query.check": Handler<{feature: string}, {data: {granted: boolean} }>
|
|
159
|
-
// "products.query.get": typeof import("./src/products/query.ts").get;
|
|
160
|
-
// "products.brands.query.get": typeof import("./src/products/brands/query.ts").get;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
160
|
|
|
164
161
|
export const send = async <K extends keyof FeatureTypes> (meta: Partial<FeatureMeta>, feature: K, data?: Parameters<FeatureTypes[K]>[0], params?: FeatureParams): Promise<Awaited<ReturnType<FeatureTypes[K]>>> => {
|
|
165
162
|
const throwError = () => {throw new Error(`Feature ${feature} not found in internal or external list during inter feature call`)}
|
|
@@ -199,16 +196,17 @@ export const send = async <K extends keyof FeatureTypes> (meta: Partial<FeatureM
|
|
|
199
196
|
writableEnded: false,
|
|
200
197
|
}
|
|
201
198
|
await apiHandler(req, res);
|
|
202
|
-
return response as Awaited<ReturnType<FeatureTypes[K]>> // tsc already checked that the feature returned proper types at the handler site. Cast here due to ts limitations. So no guarantees lost.
|
|
199
|
+
return (response as unknown as {data: unknown}).data as Awaited<ReturnType<FeatureTypes[K]>> // tsc already checked that the feature returned proper types at the handler site. Cast here due to ts limitations. So no guarantees lost.
|
|
203
200
|
}
|
|
204
201
|
// Workaround: https://stackoverflow.com/questions/72343387/axios-not-sending-headers-request-failing-getting-401-error
|
|
205
|
-
|
|
202
|
+
const response = (await axios({
|
|
206
203
|
method,
|
|
207
204
|
url: `${service}${url}`,
|
|
208
205
|
headers: { jwt: SERVICE_ACCESS_TOKEN, service: true, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN },
|
|
209
206
|
data: payload,
|
|
210
207
|
timeout: 30000 // 30 secs
|
|
211
|
-
})).data
|
|
208
|
+
})).data;
|
|
209
|
+
return (response as {data: unknown}).data as Awaited<ReturnType<FeatureTypes[K]>> // tsc already checked that the feature returned proper types at the handler site. Cast here due to ts limitations. So no guarantees lost.;
|
|
212
210
|
}
|
|
213
211
|
|
|
214
212
|
let appids: Record<string, boolean> = {}
|
|
@@ -261,13 +259,12 @@ const apiHandler = async (req: ExpressRequest, res: ExpressResponse) => {
|
|
|
261
259
|
|
|
262
260
|
feature = feature! // typescript validation with !
|
|
263
261
|
|
|
264
|
-
let topic = feature.topic
|
|
265
262
|
let params = getFeatureUrl(feature.api).split("/").filter((item: string) => item.startsWith(":")).map((item: string) => item.replace(":", ""))
|
|
266
263
|
for (let param of params)
|
|
267
264
|
if (!req.params[param])
|
|
268
265
|
throw { status: 404, message: "Resource not found. One or more url parameter not specified." }
|
|
269
266
|
|
|
270
|
-
let meta = {...req.body, data: undefined} as FeatureMeta // TODO: Must also include invoiceTxn
|
|
267
|
+
let meta = {...req.body, data: undefined, topic: feature.topic, date: new Date().getTime()} as FeatureMeta // TODO: Must also include invoiceTxn
|
|
271
268
|
|
|
272
269
|
if (process.env.USE_DEFAULT_COMPANY) {
|
|
273
270
|
meta.company = 'DEFAULT_COMPANY'
|
|
@@ -280,15 +277,6 @@ const apiHandler = async (req: ExpressRequest, res: ExpressResponse) => {
|
|
|
280
277
|
meta,
|
|
281
278
|
data: req.body.data,
|
|
282
279
|
params: req.params,
|
|
283
|
-
|
|
284
|
-
// To be removed 1
|
|
285
|
-
date: new Date().getTime(),
|
|
286
|
-
|
|
287
|
-
// To be removed 2
|
|
288
|
-
topic,
|
|
289
|
-
feature: req.body.feature,
|
|
290
|
-
user: req.body.user,
|
|
291
|
-
txn: meta.txn,
|
|
292
280
|
}
|
|
293
281
|
|
|
294
282
|
const appReq: Req = {
|
|
@@ -306,29 +294,31 @@ const apiHandler = async (req: ExpressRequest, res: ExpressResponse) => {
|
|
|
306
294
|
|
|
307
295
|
if (method === "get") {
|
|
308
296
|
try {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
297
|
+
const data = await feature.handler(message, appReq, appRes)
|
|
298
|
+
if (!appRes.headersSent)
|
|
299
|
+
res.json({ data })
|
|
312
300
|
} catch (err: any) {
|
|
313
301
|
if (process.env.DEBUG_MODE)
|
|
314
|
-
console.error('Error: Feature: ',
|
|
302
|
+
console.error('Error: Feature: ', message.meta.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err)
|
|
315
303
|
throw {status: 500, message: "Failed to GET feature", ...err}
|
|
316
304
|
}
|
|
317
305
|
} else if (method !== "get" && feature.bypass) {
|
|
318
306
|
try {
|
|
319
|
-
await
|
|
307
|
+
const data = await feature.handler(message, appReq, appRes)
|
|
308
|
+
if (!appRes.headersSent)
|
|
309
|
+
res.json({ data, event: message.meta.feature, txn: message.meta.txn, bypass: true })
|
|
320
310
|
} catch (err: any) {
|
|
321
311
|
if (process.env.DEBUG_MODE)
|
|
322
|
-
console.error('Error: Feature: ',
|
|
312
|
+
console.error('Error: Feature: ', message.meta.feature ?? (featureless ? `Featureless Api:${featureless.api}` : undefined), err)
|
|
323
313
|
throw {status: 500, message: "Failed to POST feature", ...err}
|
|
324
314
|
}
|
|
325
315
|
} else {
|
|
326
316
|
try {
|
|
327
|
-
await Kafka.send(`Features.${topic}`, message, message.user, { compression: Kafka.CompressionTypes.GZIP })
|
|
317
|
+
await Kafka.send(`Features.${message.meta.topic}`, message, message.meta.user, { compression: Kafka.CompressionTypes.GZIP })
|
|
328
318
|
} catch {
|
|
329
319
|
throw {status: 500, message: "Failed to queue the transaction"}
|
|
330
320
|
}
|
|
331
|
-
appRes.json({ data: { txn: message.txn, success: true, status: "Queued", featureQueued: true } })
|
|
321
|
+
appRes.json({ data: { txn: message.meta.txn, success: true, status: "Queued", featureQueued: true } })
|
|
332
322
|
}
|
|
333
323
|
} catch (err: any) {
|
|
334
324
|
if (process.env.DEBUG_MODE)
|
|
@@ -365,9 +355,9 @@ async function registerHandler(features: Record<string, FeatureEntry>) {
|
|
|
365
355
|
continue
|
|
366
356
|
|
|
367
357
|
if (featureName.startsWith("flows.") && !featureName.startsWith("flows.query.") && !featureName.startsWith("flows.command.") && !featureName.startsWith("flows.designs."))
|
|
368
|
-
feature.handler = (await import(`${new URL(`src/flows/query.
|
|
358
|
+
feature.handler = (await import(`${new URL(`src/flows/query.ts`, PROJECT_ROOT_URL).toString().replace('file://', '')}`)).FEATURE_HANDLERS[handler]
|
|
369
359
|
else
|
|
370
|
-
feature.handler = (await import(`${new URL(`${featurePath}.
|
|
360
|
+
feature.handler = (await import(`${new URL(`${featurePath}.ts`, PROJECT_ROOT_URL).toString().replace('file://', '')}`))[handler]
|
|
371
361
|
}
|
|
372
362
|
}
|
|
373
363
|
function registerApi() {
|
|
@@ -419,31 +409,31 @@ export const start = async (app: ExpressApplication, url: URL, file: string) =>
|
|
|
419
409
|
for (let topic of kafkaTopics) {
|
|
420
410
|
const groupId = ''
|
|
421
411
|
Kafka.receive<FeatureMessage>(`Features.${topic}`, groupId, async (topic: string, message: FeatureMessage) => {
|
|
412
|
+
topic = topic.replace(/^.*Features./,'')
|
|
422
413
|
if (Utils.isEmpty(message.meta?.company))
|
|
423
414
|
message.meta = { ...message.meta, company: "GLOBAL", outlet: "GLOBAL" }
|
|
424
|
-
if (!message
|
|
415
|
+
if (!message.meta.feature)
|
|
425
416
|
return
|
|
426
417
|
|
|
427
418
|
const timer = (ms: number) => new Promise(res => setTimeout(res, ms)) // A promise that resolves after "ms" Milliseconds
|
|
428
|
-
topic = topic.replace(/^.*Features./,'')
|
|
429
419
|
while (true) {
|
|
430
420
|
try {
|
|
431
|
-
// const iso_date = new Date(message.date).toISOString()
|
|
421
|
+
// const iso_date = new Date(message.meta.date).toISOString()
|
|
432
422
|
// // TODO: use $useChunks: [] once support is added in dip insert
|
|
433
|
-
// 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
|
|
423
|
+
// await Dip.insert(globalMeta, `users.txns`, { _id: `${message.meta.user}_${iso_date}_${message.meta.txn}`, user: message.meta.user, feature: message.meta.feature, date: message.meta.date, created: message.meta.date, updated: message.meta.date, status: "Queued" }, {idempotent: true}) // Can always update the status later
|
|
434
424
|
// TODO: Avoid duplicate processing
|
|
435
|
-
const feature = features[message.feature]
|
|
425
|
+
const feature = features[message.meta.feature]
|
|
436
426
|
if (!feature) {
|
|
437
|
-
console.warn(`Feature ${message.feature} missing, in executing handler during Kafka.receive(topic: ${topic})`)
|
|
438
|
-
throw new Error(`Feature ${message.feature} missing, in executing handler during Kafka.receive(topic: ${topic})`)
|
|
427
|
+
console.warn(`Feature ${message.meta.feature} missing, in executing handler during Kafka.receive(topic: ${message.meta.topic})`)
|
|
428
|
+
throw new Error(`Feature ${message.meta.feature} missing, in executing handler during Kafka.receive(topic: ${message.meta.topic})`)
|
|
439
429
|
}
|
|
440
|
-
await
|
|
441
|
-
await Dip.insert(globalMeta, "Features.txns", {_id: message.txn, status: "Processed"}, {idempotent: true})
|
|
430
|
+
await feature.handler(message)
|
|
431
|
+
await Dip.insert(globalMeta, "Features.txns", {_id: message.meta.txn, status: "Processed"}, {idempotent: true})
|
|
442
432
|
break
|
|
443
433
|
} catch(err) {
|
|
444
434
|
if (process.env.DEBUG_MODE)
|
|
445
|
-
console.log('Error: Feature: ', message.feature, err)
|
|
446
|
-
console.warn(`Feature: ${message.feature}, error in executing handler during Kafka.receive(topic: ${topic})`)
|
|
435
|
+
console.log('Error: Feature: ', message.meta.feature, err)
|
|
436
|
+
console.warn(`Feature: ${message.meta.feature}, error in executing handler during Kafka.receive(topic: ${message.meta.topic})`)
|
|
447
437
|
}
|
|
448
438
|
await timer(10000);
|
|
449
439
|
}
|