corebasic 1.0.264 → 1.0.266
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/features.js +24 -9
- package/libs/features.ts +27 -11
- package/package.json +1 -1
package/dist/libs/features.js
CHANGED
|
@@ -48,6 +48,19 @@ const getFeatureMethod = (api) => api.split(' ')[0].toLowerCase();
|
|
|
48
48
|
const getFeatureUrl = (api) => { const url = api.split(' ')[1]; if (!url)
|
|
49
49
|
throw new Error(`Invalid Feature url in api: ${api}`); return url; };
|
|
50
50
|
const getFeature = (name) => features[name];
|
|
51
|
+
function getLeaf(feature, req) {
|
|
52
|
+
if (req) {
|
|
53
|
+
for (let key in features) {
|
|
54
|
+
const feature = features[key];
|
|
55
|
+
if (feature.featureless && feature.api === req.method.toUpperCase() + ' ' + req.path)
|
|
56
|
+
return key.split(/\.(query|command)\./)[0];
|
|
57
|
+
}
|
|
58
|
+
return "NOLEAF"; // This code will never be reached as apiHandler()'s prior code ensures req is valid and there exists a feature. Included to silence ./tsc
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
return feature.split(/\.(query|command)\./)[0];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
51
64
|
const DEPLOY_TOKEN_SECRET = process.env.DEPLOY_TOKEN_SECRET || "MY_SECRET_DEPLOY_TOKEN";
|
|
52
65
|
const SERVICE_ACCESS_TOKEN = jwt.sign({ app: process.env.APP_DEPLOYMENT_NAME }, DEPLOY_TOKEN_SECRET, { expiresIn: '365d' });
|
|
53
66
|
const NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN = await (async () => { if (process.env.NDCURVE_DEVELOPER_SERVICE)
|
|
@@ -81,8 +94,8 @@ let SERVICE_ADDRESS = process.env.APP_ENDPOINT || 'http://127.0.0.1:3000';
|
|
|
81
94
|
export const get = (feature) => {
|
|
82
95
|
const throwError = () => { throw new Error(`Feature ${feature} not found in internal or external list during Features.subscribe call`); };
|
|
83
96
|
const baseFeature = getFeature(feature);
|
|
84
|
-
let { api, service = SERVICE_ADDRESS
|
|
85
|
-
return { api, service
|
|
97
|
+
let { api, service = SERVICE_ADDRESS } = baseFeature ? baseFeature : (SLYP_FEATURES_LIST[feature] ?? throwError());
|
|
98
|
+
return { api, service };
|
|
86
99
|
};
|
|
87
100
|
export const send = async (meta, feature, data, params) => {
|
|
88
101
|
const throwError = () => { throw new Error(`Feature ${feature} not found in internal or external list during inter feature call`); };
|
|
@@ -134,7 +147,7 @@ let appids = {};
|
|
|
134
147
|
async function announce() {
|
|
135
148
|
let exp_features = {};
|
|
136
149
|
for (let [key, { api, subscribe }] of Object.entries(features))
|
|
137
|
-
exp_features[key] = { api, service: SERVICE_ADDRESS, subscribe
|
|
150
|
+
exp_features[key] = { api, service: SERVICE_ADDRESS, subscribe };
|
|
138
151
|
await Messaging.subscribe(`${process.env.REDIS_CHANNEL_PREFIX}_SLYP_FEATURES_LIST`, async (message, _channel) => {
|
|
139
152
|
let { uid, ...msg } = JSON.parse(message);
|
|
140
153
|
if (uid !== appId && !appids[uid]) {
|
|
@@ -172,11 +185,12 @@ const apiHandler = async (req, res) => {
|
|
|
172
185
|
throw { status: 404, message: `Resource not found. Feature ${req.body.feature} not available.` };
|
|
173
186
|
}
|
|
174
187
|
feature = feature; // typescript validation with !
|
|
188
|
+
const leaf = getLeaf(req.body.feature, req);
|
|
175
189
|
let params = getFeatureUrl(feature.api).split("/").filter((item) => item.startsWith(":")).map((item) => item.replace(":", ""));
|
|
176
190
|
for (let param of params)
|
|
177
191
|
if (!req.params[param])
|
|
178
192
|
throw { status: 404, message: "Resource not found. One or more url parameter not specified." };
|
|
179
|
-
let meta = { ...req.body, data: undefined, topic:
|
|
193
|
+
let meta = { ...req.body, data: undefined, topic: leaf, date: new Date().getTime() }; // TODO: Must also include invoiceTxn
|
|
180
194
|
if (process.env.USE_DEFAULT_COMPANY) {
|
|
181
195
|
meta.company = 'DEFAULT_COMPANY';
|
|
182
196
|
meta.outlet = 'DEFAULT_OUTLET';
|
|
@@ -213,7 +227,7 @@ const apiHandler = async (req, res) => {
|
|
|
213
227
|
data: req.body.data,
|
|
214
228
|
params: featureless ? req.params : req.body.params,
|
|
215
229
|
query: req.body.params?.opt && (feature.opt)?.includes(req.body.params?.opt) ? buildQuery(req.body.params?.opt ?? "_id", req.body.params?.id) : req.body.params?.id ? { _id: req.body.params?.id } : undefined,
|
|
216
|
-
update: req.body.params?.mut && (feature.mut)?.includes(req.body.params?.mut) ? { $set: { [req.body.params.mut]: req.body.data[req.body.params.mut], ...Dip.updateKeys({ meta }) } } : undefined,
|
|
230
|
+
update: req.body.params?.mut && (feature.mut)?.includes(req.body.params?.mut) ? { $set: { [req.body.params.mut.split(":")[0]]: req.body.data[req.body.params.mut], ...Dip.updateKeys({ meta }) } } : undefined,
|
|
217
231
|
};
|
|
218
232
|
const appReq = {
|
|
219
233
|
meta: message.meta,
|
|
@@ -318,7 +332,7 @@ export const start = async (app, url, file) => {
|
|
|
318
332
|
app.get('/features', async (_req, res) => {
|
|
319
333
|
let exp_features = {};
|
|
320
334
|
for (let [key, { api }] of Object.entries(features))
|
|
321
|
-
exp_features[key] = { api, service: `${SERVICE_ADDRESS}`, subscribe: ''
|
|
335
|
+
exp_features[key] = { api, service: `${SERVICE_ADDRESS}`, subscribe: '' };
|
|
322
336
|
if (process.env.LOAD_LOCAL_FEATURES) {
|
|
323
337
|
for (let key in SLYP_FEATURES_LIST)
|
|
324
338
|
SLYP_FEATURES_LIST[key].headers = { JWT: SERVICE_ACCESS_TOKEN, service: true, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN }; // TODO: CRITICAL: Security issue
|
|
@@ -328,12 +342,13 @@ export const start = async (app, url, file) => {
|
|
|
328
342
|
// Registering features
|
|
329
343
|
await registerFeatures(features);
|
|
330
344
|
// Get unique kafka topics list from features
|
|
331
|
-
let kafkaTopics =
|
|
345
|
+
let kafkaTopics = new Set();
|
|
332
346
|
for (const name in features) {
|
|
333
347
|
let feature = features[name];
|
|
334
348
|
if (feature.api.split(' ')[0]?.toLowerCase() === "get")
|
|
335
349
|
continue;
|
|
336
|
-
|
|
350
|
+
const leaf = getLeaf(name);
|
|
351
|
+
kafkaTopics.add(leaf);
|
|
337
352
|
}
|
|
338
353
|
// Subscribe to each topic
|
|
339
354
|
for (let topic of kafkaTopics) {
|
|
@@ -442,7 +457,7 @@ async function subscribe() {
|
|
|
442
457
|
const publisherFeature = getFeature(publisher);
|
|
443
458
|
if (subscribed_consumers[consumer] || !publisherFeature)
|
|
444
459
|
continue;
|
|
445
|
-
let
|
|
460
|
+
let topic = getLeaf(publisher);
|
|
446
461
|
let kafka_consumer_promise = Kafka.receive(`Features.${topic}`, consumer, async (topic, message) => {
|
|
447
462
|
topic = topic.replace(/^.*Features./, '');
|
|
448
463
|
const timer = (ms) => new Promise(res => setTimeout(res, ms)); // A promise that resolves after "ms" Milliseconds
|
package/libs/features.ts
CHANGED
|
@@ -141,11 +141,10 @@ type RemoteFeatureEntry = {
|
|
|
141
141
|
api: string
|
|
142
142
|
service: string
|
|
143
143
|
subscribe: string
|
|
144
|
-
topic: string
|
|
145
144
|
headers?: {JWT?: string, service?: boolean, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN?: string}
|
|
146
145
|
}
|
|
147
146
|
|
|
148
|
-
type FeatureEntry = { api: string,
|
|
147
|
+
type FeatureEntry = { api: string, bypass: boolean, featureless: boolean, subscribe: string, handler: Handler<unknown, unknown, keyof Schema>, opt?: string[], mut?: string[] }
|
|
149
148
|
|
|
150
149
|
|
|
151
150
|
let features: Record<string, FeatureEntry> = {}
|
|
@@ -157,6 +156,20 @@ const getFeatureMethod = (api: string): HttpMethod => api.split(' ')[0]!.toLower
|
|
|
157
156
|
const getFeatureUrl = (api: string): string => { const url = api.split(' ')[1]; if (!url) throw new Error(`Invalid Feature url in api: ${api}`); return url}
|
|
158
157
|
const getFeature = (name: string): FeatureEntry | undefined => features[name]
|
|
159
158
|
|
|
159
|
+
function getLeaf(feature: string, req?: ExpressRequest): string {
|
|
160
|
+
if (req) {
|
|
161
|
+
for(let key in features) {
|
|
162
|
+
const feature = features[key]!
|
|
163
|
+
if (feature.featureless && feature.api === req.method.toUpperCase() + ' ' + req.path)
|
|
164
|
+
return key.split(/\.(query|command)\./)[0]!
|
|
165
|
+
}
|
|
166
|
+
return "NOLEAF" // This code will never be reached as apiHandler()'s prior code ensures req is valid and there exists a feature. Included to silence ./tsc
|
|
167
|
+
} else {
|
|
168
|
+
return feature.split(/\.(query|command)\./)[0]!
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
160
173
|
const DEPLOY_TOKEN_SECRET = process.env.DEPLOY_TOKEN_SECRET || "MY_SECRET_DEPLOY_TOKEN"
|
|
161
174
|
const SERVICE_ACCESS_TOKEN = jwt.sign({app: process.env.APP_DEPLOYMENT_NAME}, DEPLOY_TOKEN_SECRET, { expiresIn: '365d' });
|
|
162
175
|
|
|
@@ -189,8 +202,8 @@ let SERVICE_ADDRESS = process.env.APP_ENDPOINT || 'http://127.0.0.1:3000'
|
|
|
189
202
|
export const get = (feature: string) => {
|
|
190
203
|
const throwError = () => {throw new Error(`Feature ${feature} not found in internal or external list during Features.subscribe call`)}
|
|
191
204
|
const baseFeature: FeatureEntry | undefined = getFeature(feature)
|
|
192
|
-
let {api, service = SERVICE_ADDRESS
|
|
193
|
-
return {api, service
|
|
205
|
+
let {api, service = SERVICE_ADDRESS} = baseFeature ? baseFeature as FeatureEntry & {service: string} : (SLYP_FEATURES_LIST[feature] ?? throwError())
|
|
206
|
+
return {api, service}
|
|
194
207
|
}
|
|
195
208
|
|
|
196
209
|
|
|
@@ -251,7 +264,7 @@ let appids: Record<string, boolean> = {}
|
|
|
251
264
|
async function announce() {
|
|
252
265
|
let exp_features: Record<string, RemoteFeatureEntry> = {}
|
|
253
266
|
for (let [key, {api,subscribe}] of Object.entries(features))
|
|
254
|
-
exp_features[key] = {api, service: SERVICE_ADDRESS, subscribe
|
|
267
|
+
exp_features[key] = {api, service: SERVICE_ADDRESS, subscribe}
|
|
255
268
|
|
|
256
269
|
await Messaging.subscribe(`${process.env.REDIS_CHANNEL_PREFIX}_SLYP_FEATURES_LIST`, async (message, _channel) => {
|
|
257
270
|
let {uid, ...msg} = JSON.parse(message) as Record<string, RemoteFeatureEntry> & {uid: string}
|
|
@@ -297,12 +310,14 @@ const apiHandler = async (req: ExpressRequest, res: ExpressResponse) => {
|
|
|
297
310
|
|
|
298
311
|
feature = feature! // typescript validation with !
|
|
299
312
|
|
|
313
|
+
const leaf = getLeaf(req.body.feature, req)
|
|
314
|
+
|
|
300
315
|
let params = getFeatureUrl(feature.api).split("/").filter((item: string) => item.startsWith(":")).map((item: string) => item.replace(":", ""))
|
|
301
316
|
for (let param of params)
|
|
302
317
|
if (!req.params[param])
|
|
303
318
|
throw { status: 404, message: "Resource not found. One or more url parameter not specified." }
|
|
304
319
|
|
|
305
|
-
let meta = {...req.body, data: undefined, topic:
|
|
320
|
+
let meta = {...req.body, data: undefined, topic: leaf, date: new Date().getTime()} as FeatureMeta // TODO: Must also include invoiceTxn
|
|
306
321
|
|
|
307
322
|
if (process.env.USE_DEFAULT_COMPANY) {
|
|
308
323
|
meta.company = 'DEFAULT_COMPANY'
|
|
@@ -347,7 +362,7 @@ const apiHandler = async (req: ExpressRequest, res: ExpressResponse) => {
|
|
|
347
362
|
data: req.body.data,
|
|
348
363
|
params: featureless ? req.params : req.body.params!,
|
|
349
364
|
query: req.body.params?.opt && (feature.opt!)?.includes(req.body.params?.opt as string) ? buildQuery(req.body.params?.opt ?? "_id", req.body.params?.id) : req.body.params?.id ? {_id: req.body.params?.id} : undefined,
|
|
350
|
-
update: req.body.params?.mut && (feature.mut!)?.includes(req.body.params?.mut as string) ? { $set: { [req.body.params!.mut]: (req.body.data as Record<string, unknown>)[req.body.params!.mut], ...Dip.updateKeys({meta}) } } : undefined,
|
|
365
|
+
update: req.body.params?.mut && (feature.mut!)?.includes(req.body.params?.mut as string) ? { $set: { [req.body.params!.mut.split(":")[0] as string]: (req.body.data as Record<string, unknown>)[req.body.params!.mut], ...Dip.updateKeys({meta}) } } : undefined,
|
|
351
366
|
|
|
352
367
|
}
|
|
353
368
|
|
|
@@ -457,7 +472,7 @@ export const start = async (app: ExpressApplication, url: URL, file: string) =>
|
|
|
457
472
|
app.get('/features', async (_req: ExpressRequest, res: ExpressResponse) => {
|
|
458
473
|
let exp_features: Record<string, RemoteFeatureEntry> = {}
|
|
459
474
|
for (let [key, {api}] of Object.entries(features))
|
|
460
|
-
exp_features[key] = {api, service: `${SERVICE_ADDRESS}`, subscribe: ''
|
|
475
|
+
exp_features[key] = {api, service: `${SERVICE_ADDRESS}`, subscribe: ''}
|
|
461
476
|
if (process.env.LOAD_LOCAL_FEATURES) {
|
|
462
477
|
for (let key in SLYP_FEATURES_LIST)
|
|
463
478
|
SLYP_FEATURES_LIST[key]!.headers = {JWT: SERVICE_ACCESS_TOKEN, service: true, NDCURVE_DEVELOPER_LICENSE_ACCESS_TOKEN} // TODO: CRITICAL: Security issue
|
|
@@ -469,12 +484,13 @@ export const start = async (app: ExpressApplication, url: URL, file: string) =>
|
|
|
469
484
|
await registerFeatures(features)
|
|
470
485
|
|
|
471
486
|
// Get unique kafka topics list from features
|
|
472
|
-
let kafkaTopics
|
|
487
|
+
let kafkaTopics = new Set<string>()
|
|
473
488
|
for (const name in features) {
|
|
474
489
|
let feature = features[name]!
|
|
475
490
|
if (feature.api.split(' ')[0]?.toLowerCase() === "get" )
|
|
476
491
|
continue
|
|
477
|
-
|
|
492
|
+
const leaf = getLeaf(name)
|
|
493
|
+
kafkaTopics.add(leaf)
|
|
478
494
|
}
|
|
479
495
|
|
|
480
496
|
// Subscribe to each topic
|
|
@@ -587,7 +603,7 @@ async function subscribe() {
|
|
|
587
603
|
if (subscribed_consumers[consumer] || !publisherFeature)
|
|
588
604
|
continue
|
|
589
605
|
|
|
590
|
-
let
|
|
606
|
+
let topic = getLeaf(publisher)
|
|
591
607
|
|
|
592
608
|
let kafka_consumer_promise = Kafka.receive<FeatureMessage>(`Features.${topic}`, consumer, async (topic: string, message: FeatureMessage) => {
|
|
593
609
|
topic = topic.replace(/^.*Features./,'')
|