@dnax/core 0.73.0 → 0.73.2

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/define/index.ts CHANGED
@@ -13,11 +13,11 @@ import type {
13
13
  routeCtx,
14
14
  sessionCtx,
15
15
  Script,
16
+ ETLCfg,
16
17
  TimeSeriesCollection,
17
18
  } from "../types";
18
19
  import { Cfg } from "../config/";
19
20
  import { deepMerge, freeze } from "../utils";
20
- import { config } from "valibot";
21
21
 
22
22
  function Config(
23
23
  config: Omit<
@@ -85,6 +85,10 @@ function Task(config: cronConfig) {
85
85
  return config;
86
86
  }
87
87
 
88
+ function Etl(config: ETLCfg) {
89
+ return config;
90
+ }
91
+
88
92
  function Permission(config: permissionSchema): permissionSchema {
89
93
  return config;
90
94
  }
@@ -136,6 +140,7 @@ const define = {
136
140
  Route,
137
141
  Script,
138
142
  TimeSeriesCollection,
143
+ Etl,
139
144
  };
140
145
 
141
146
  export default define;
@@ -41,6 +41,7 @@ import {
41
41
  import { Cfg } from "../../config";
42
42
  import cleanDeep from "clean-deep";
43
43
  import { useCache } from "../../lib/bento";
44
+ import consola from "consola";
44
45
 
45
46
  type options = {
46
47
  tenant_id: string;
@@ -2549,27 +2550,47 @@ class useRest {
2549
2550
  .dropIndex(indexName);
2550
2551
  }
2551
2552
 
2552
- startTransaction(options?: { forceNew?: boolean }) {
2553
- if (options?.forceNew) {
2554
- this.#session = null;
2555
- }
2556
- if (!this.#session) {
2557
- this.#session = this.#tenant.database.client?.startSession();
2558
- this.#session?.startTransaction();
2553
+ startTransaction(options?: { forceNew?: boolean; silent?: boolean }) {
2554
+ try {
2555
+ if (options?.forceNew) {
2556
+ this.#session = null;
2557
+ }
2558
+ if (!this.#session) {
2559
+ this.#session = this.#tenant.database.client?.startSession();
2560
+ this.#session?.startTransaction();
2561
+ }
2562
+ } catch (err: any) {
2563
+ if (!options?.silent) {
2564
+ consola.error(err?.message | err);
2565
+ }
2559
2566
  }
2560
2567
  }
2561
2568
 
2562
2569
  async abortTransaction() {
2563
- if (this.#session) {
2564
- await this.#session?.abortTransaction();
2565
- }
2570
+ return new Promise(async (resolve, reject) => {
2571
+ try {
2572
+ if (this.#session) {
2573
+ await this.#session?.abortTransaction();
2574
+ return resolve(true);
2575
+ }
2576
+ } catch (err: any) {
2577
+ return reject(err?.message || err);
2578
+ }
2579
+ });
2566
2580
  }
2567
2581
 
2568
2582
  async endSession() {
2569
- if (this.#session) {
2570
- await this.#session?.endSession();
2571
- this.#session = null;
2572
- }
2583
+ return new Promise(async (resolve, reject) => {
2584
+ try {
2585
+ if (this.#session) {
2586
+ await this.#session?.endSession();
2587
+ this.#session = null;
2588
+ }
2589
+ return resolve(true);
2590
+ } catch (err: any) {
2591
+ return reject(err?.message || err);
2592
+ }
2593
+ });
2573
2594
  }
2574
2595
 
2575
2596
  async commitTransaction(options?: { closeSession?: boolean }) {
@@ -2585,8 +2606,8 @@ class useRest {
2585
2606
  }
2586
2607
  resolve(true);
2587
2608
  })
2588
- .catch((err) => {
2589
- reject(err);
2609
+ .catch((err: any) => {
2610
+ reject(err?.message || err);
2590
2611
  });
2591
2612
  }
2592
2613
  });
package/lib/collection.ts CHANGED
@@ -66,8 +66,40 @@ async function syncCollectionDatabase() {
66
66
  ?.listCollections()
67
67
  .toArray();
68
68
 
69
- //t.database.db.
69
+ // System collections
70
+ t.database.db
71
+ ?.collection("_etl_")
72
+ .createIndex({
73
+ createdAt: -1,
74
+ updatedAt: -1,
75
+ })
76
+ .catch();
70
77
 
78
+ t.database.db
79
+ ?.collection("_activity_")
80
+ .createIndex({
81
+ createdAt: -1,
82
+ updatedAt: -1,
83
+ })
84
+ .catch();
85
+
86
+ t.database.db
87
+ ?.collection("_activity_")
88
+ .createIndex({
89
+ "operation.type": 1,
90
+ })
91
+ .catch();
92
+
93
+ t.database.db
94
+ ?.collection("_activity_")
95
+ .createIndex({
96
+ "operation.target": 1,
97
+ })
98
+ .catch();
99
+
100
+ //__________
101
+
102
+ // local Collections
71
103
  if (t.database.driver == "mongodb") {
72
104
  const collections: (Collection & TimeSeriesCollection[]) | undefined =
73
105
  Cfg.collections?.filter((cn) => cn.tenant_id == t.id);
@@ -0,0 +1,119 @@
1
+ import { Cron } from "croner";
2
+ import { Glob } from "bun";
3
+ import { Cfg } from "../../config";
4
+ import path from "path";
5
+ import * as aq from "arquero";
6
+ import type { ETLCfg } from "../../types";
7
+ import cleanDeep from "clean-deep";
8
+ import { useRest } from "../../driver/mongo";
9
+ import { v4 } from "uuid";
10
+ import moment from "moment";
11
+ import { formatData } from "../../driver/mongo/utils";
12
+ import { getRecentDate } from "../../utils";
13
+ async function initETL() {
14
+ if (Cfg?.tenants?.length) {
15
+ for await (let t of Cfg.tenants) {
16
+ let tenantPath = `${t.dir}/connectors/**/**.etl.{ts,js}`;
17
+ const glob = new Glob(tenantPath);
18
+ for await (let file of glob.scan({
19
+ cwd: Cfg.cwd,
20
+ })) {
21
+ let fullPathFile = path.join(Cfg.cwd || "", file);
22
+ await import(fullPathFile)
23
+ .then((inject) => {
24
+ let etlOpt = inject.default as ETLCfg;
25
+ const rest = new useRest({
26
+ tenant_id: t.id,
27
+ });
28
+
29
+ if (
30
+ typeof etlOpt.exec == "function" &&
31
+ etlOpt.name &&
32
+ etlOpt.pattern
33
+ ) {
34
+ let lockExecution = false;
35
+ const cronETL = new Cron(etlOpt.pattern, async () => {
36
+ if (lockExecution) return;
37
+ lockExecution = true;
38
+ try {
39
+ let lastRuntAt = new Date();
40
+ let [c] = (await rest.db
41
+ ?.collection("_etl_")
42
+ .find({
43
+ name: etlOpt.name,
44
+ ...(etlOpt.filter || {}),
45
+ })
46
+ .limit(1)
47
+ .sort({
48
+ createdAt: -1,
49
+ })
50
+ .toArray()) as Object;
51
+
52
+ let traceId = v4();
53
+ await etlOpt.exec({
54
+ extractLatest: getRecentDate,
55
+ rest,
56
+ aq: aq,
57
+ commit: async (op) => {
58
+ return new Promise(async (resolve, reject) => {
59
+ try {
60
+ await rest.db?.collection("_etl_").updateOne(
61
+ {
62
+ name: etlOpt.name,
63
+ tenantId: t.id,
64
+ ...(etlOpt.filter || {}),
65
+ },
66
+ {
67
+ $set: {
68
+ ...formatData({
69
+ tenantId: t.id,
70
+ name: etlOpt.name,
71
+ ts: op?.ts ? new Date(op.ts) : null,
72
+ data: op?.data,
73
+ comment: op?.comment,
74
+ tags: op?.tags || [],
75
+ version: op?.version || "1",
76
+ context: op?.context || {},
77
+ lastRuntAt: lastRuntAt,
78
+ traceId: traceId,
79
+ commitAt: c?.commitAt
80
+ ? new Date(c?.commitAt)
81
+ : new Date(),
82
+ createdAt: new Date(),
83
+ updatedAt: new Date(),
84
+ }),
85
+ },
86
+ },
87
+ {
88
+ upsert: true,
89
+ }
90
+ );
91
+ resolve(true);
92
+ } catch (error: any) {
93
+ reject(error?.message || error);
94
+ }
95
+ });
96
+ },
97
+ lastCommitAt: c?.commitAt || null,
98
+ ts: c?.ts || null,
99
+ lastRuntAt: lastRuntAt,
100
+ traceId,
101
+ });
102
+ } catch (err: any) {
103
+ console.error(err?.message || err);
104
+ }
105
+ lockExecution = false;
106
+ });
107
+ }
108
+ })
109
+ .catch((err) => {
110
+ console.error(err);
111
+ });
112
+ }
113
+ }
114
+ }
115
+
116
+ //Cfg.endpoints = endpoints;
117
+ }
118
+
119
+ export { initETL };
package/lib/crypto.ts CHANGED
@@ -63,8 +63,6 @@ class AES {
63
63
  }
64
64
  }
65
65
 
66
- const message = "Bonjour, comment ça va ?";
67
-
68
66
  const crypt = {
69
67
  AES,
70
68
  };
package/lib/index.ts CHANGED
@@ -3,6 +3,7 @@ import { connectTenantsDatabase } from "../driver";
3
3
  import { loadEndpoints } from "./endpoint";
4
4
  import { loadServices } from "./service";
5
5
  import { initCron } from "./cron";
6
+ import { initETL } from "./connectors";
6
7
  import { loadPermissions } from "./permissions";
7
8
  import { loadSocket } from "../lib/socket";
8
9
  import { loadAutoRoutes } from "./routes";
@@ -49,7 +50,11 @@ async function init(cf = { app: null }) {
49
50
  await loadAutoRoutes();
50
51
  //consola.success("Loaded auto routes");
51
52
 
52
- await initCron();
53
+ // Init Cron
54
+ initCron();
55
+
56
+ // Init ETL
57
+ initETL();
53
58
  }
54
59
 
55
60
  export { init };
@@ -1,28 +1 @@
1
- import { v4 } from "uuid";
2
- import { sessionCache } from "../bento";
3
-
4
- const localSession = {
5
- get: async (id: string) => {
6
- return await sessionCache.get(id);
7
- },
8
- set: (
9
- id: string,
10
- data: object,
11
- options = {
12
- ttl: "7d",
13
- }
14
- ) => {
15
- sessionCache.set({
16
- key: id,
17
- value: data,
18
- // ttl: "1m",
19
- ttl: options?.ttl || "7d",
20
- });
21
-
22
- return {
23
- data: data,
24
- };
25
- },
26
- };
27
-
28
- export { localSession };
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dnax/core",
3
- "version": "0.73.0",
3
+ "version": "0.73.2",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "bin": {},
@@ -21,6 +21,7 @@
21
21
  "dependencies": {
22
22
  "@colors/colors": "^1.6.0",
23
23
  "@lukeed/ms": "^2.0.2",
24
+ "arquero": "^8.0.3",
24
25
  "bentocache": "^1.5.0",
25
26
  "boxen": "^7.1.1",
26
27
  "chokidar": "3.6.0",
@@ -33,7 +34,7 @@
33
34
  "dot-object": "2.1.5",
34
35
  "fs-extra": "^11.2.0",
35
36
  "generate-unique-id": "^2.0.3",
36
- "hono": "4.8.9",
37
+ "hono": "4.9.5",
37
38
  "joi": "17.13.3",
38
39
  "json-joy": "16.8.0",
39
40
  "jsonwebtoken": "^9.0.2",
package/types/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { AnySchema } from "joi";
2
2
  import { Cron } from "croner";
3
+ import * as aq from "arquero";
3
4
  //import type{ updateParams } from "./../driver/mongo/@types";
4
5
  //import * as v from "valibot";
5
6
  import type { Db, MongoClient } from "mongodb";
@@ -12,7 +13,7 @@ import type { Context, Hono } from "hono";
12
13
 
13
14
  import type { socketIoType } from "../lib/socket/instance";
14
15
 
15
- import { fn } from "../utils";
16
+ import { fn, getRecentDate } from "../utils";
16
17
  import type {
17
18
  findOneParam,
18
19
  findParam,
@@ -893,3 +894,29 @@ export type permissionSchema = {
893
894
  export type CoreType = {
894
895
  listCollections: () => Array<Collection>;
895
896
  };
897
+
898
+
899
+ export type ETLCfg ={ // Extract, Transform, Load
900
+ name:string;
901
+ pattern:string; // Cron Expression
902
+ filter?:{ // Filter to get last record of ETL execution
903
+ [key: string]: any;
904
+ },
905
+ exec:(ctx:{
906
+ rest:InstanceType<typeof useRest>;
907
+ lastCommitAt:Date|null;
908
+ ts:Date;
909
+ lastRuntAt:Date;
910
+ traceId:string;
911
+ aq:typeof aq;
912
+ extractLatest:typeof getRecentDate;
913
+ commit:(options:{
914
+ comment?:string;
915
+ tags?:Array<string>;
916
+ data?:any;
917
+ context?:object;
918
+ version?:number;
919
+ ts:Date;
920
+ })=>Promise<boolean|any>
921
+ })=>any;
922
+ }
package/utils/index.ts CHANGED
@@ -19,6 +19,7 @@ import { applyPatch, createPatch, Pointer } from "rfc6902";
19
19
 
20
20
  import * as _ from "radash";
21
21
  import { email } from "../lib/mail";
22
+ import { getPidUsage } from "./os";
22
23
 
23
24
  import { deepEqual } from "json-joy/lib/json-equal/deepEqual";
24
25
  import type { SupportedCryptoAlgorithms } from "bun";
@@ -570,6 +571,29 @@ function useKey(
570
571
  return n;
571
572
  }
572
573
 
574
+ function getRecentDate<T extends Record<string, any>>(
575
+ items: T[],
576
+ key: string = "createdAt" // default key to get recent date
577
+ ): { data: T | null; date: Date | null } {
578
+ if (!items || items.length === 0) return { data: null, date: null };
579
+
580
+ let latest: Date | null = null;
581
+ let latestItem: T | null = null;
582
+
583
+ for (const item of items) {
584
+ const dateStr = item[key];
585
+ if (dateStr) {
586
+ const d = new Date(dateStr);
587
+ if (!isNaN(d.getTime()) && (!latest || d > latest)) {
588
+ latest = d;
589
+ latestItem = item;
590
+ }
591
+ }
592
+ }
593
+
594
+ return { data: latestItem, date: latest };
595
+ }
596
+
573
597
  const contextError = ContextError;
574
598
  export {
575
599
  file,
@@ -607,4 +631,6 @@ export {
607
631
  createPatch,
608
632
  containsForbiddenOperators,
609
633
  useKey,
634
+ getRecentDate,
635
+ getPidUsage,
610
636
  };
package/utils/os.ts CHANGED
@@ -15,8 +15,8 @@ export async function getPidUsage(pids = [process.pid]) {
15
15
  ram: usage.memory / 1024 / 1024, // en MB
16
16
  elapsed: usage.elapsed / 1000, // uptime en secondes
17
17
  }));
18
- } catch (err) {
19
- console.error("Erreur getPidUsage:", err.message);
18
+ } catch (err: any) {
19
+ console.error("Erreur getPidUsage:", err?.message);
20
20
  return [];
21
21
  }
22
22
  }
package/lib/secret.ts DELETED
File without changes
@@ -1,84 +0,0 @@
1
- import { useRest } from "../../driver/mongo";
2
- import { v4 } from "uuid";
3
-
4
- type WorkFlowConfig = {
5
- tenant_id: string;
6
- version?: string;
7
- };
8
-
9
- type StepMeta = {
10
- ms?: number;
11
- [key: string]: any;
12
- };
13
-
14
- type StepStatus = "done" | "failed";
15
-
16
- type StepFunction<TData = any> = (ctx: {
17
- rest: useRest;
18
- traceId: string;
19
- data: TData;
20
- meta?: Record<string, any>;
21
- output: any;
22
- }) => Promise<void>;
23
-
24
- type Step = {
25
- name: string;
26
- handler: StepFunction;
27
- };
28
-
29
- class Workflow<Tdata> {
30
- name: string;
31
- config: WorkFlowConfig;
32
- #rest: useRest;
33
- traceId?: string;
34
- #steps: Step[] = [];
35
- constructor(name: string, config: WorkFlowConfig) {
36
- this.name = name;
37
- this.config = config;
38
- this.#rest = new useRest({
39
- tenant_id: config.tenant_id,
40
- });
41
- }
42
-
43
- step(name: string, handler: StepFunction<Tdata>) {
44
- this.#steps.push({
45
- name,
46
- handler,
47
- });
48
- return this;
49
- }
50
-
51
- async run(data: Tdata, traceId?: string): Promise<void> {
52
- this.traceId = traceId || v4();
53
- let output: any = {};
54
- for (const step of this.#steps) {
55
- let startAt = Date.now();
56
- let ev = {
57
- name: step.name,
58
- traceId: this.traceId,
59
- duration: 0,
60
- status: "pending",
61
- startedAt: new Date(),
62
- endedAt: new Date(),
63
- };
64
- output = await step.handler({
65
- rest: this.#rest,
66
- traceId: this.traceId,
67
- data,
68
- output,
69
- });
70
- let endAt = Date.now();
71
- // duration en ms
72
- let duration = endAt - startAt;
73
- ev.duration = duration;
74
- ev.status = "done";
75
- ev.endedAt = new Date();
76
- }
77
- }
78
- }
79
-
80
- function useWorkflow<T>(name: string, config: WorkFlowConfig) {
81
- return new Workflow<T>(name, config);
82
- }
83
-
84
- export { useWorkflow };
@@ -1,64 +0,0 @@
1
- import { useRest } from "../../driver/mongo";
2
-
3
- export type StepActions = {
4
- done: (comment?: string) => void;
5
- fail: (comment?: string) => void;
6
- abort: (comment?: string) => void;
7
- };
8
-
9
- export type Step<Name extends string = string> = {
10
- name: Name;
11
- description?: string;
12
- exec: (ctx: { data: any; rest: useRest; action?: StepActions }) => void;
13
- };
14
-
15
- export type stepData = {
16
- name: string;
17
- data: any;
18
- at: Date;
19
- comment?: string;
20
- description?: string;
21
- status: "done" | "failed" | "aborted";
22
- traceId: string;
23
- ms: number;
24
- };
25
-
26
- export type EventStep = {
27
- status: "failed" | "aborted" | "done";
28
- data: any;
29
- at: Date;
30
- name: string;
31
- };
32
-
33
- export type WorkflowData = {
34
- _id: string;
35
- name: string;
36
- traceId: string;
37
- currentStep: string;
38
- steps: stepData[];
39
- events: [];
40
- status:
41
- | "processing"
42
- | "completed"
43
- | "failed"
44
- | "aborted"
45
- | "cancelled"
46
- | "initialized";
47
- };
48
-
49
- export type WorkflowType = {
50
- traceId: string;
51
- _id: string;
52
- name: string;
53
- currentStep: string;
54
- failedAtStep?: string;
55
- steps: Step[];
56
- events: [];
57
- completedIf: () => boolean;
58
- failedIf: () => boolean;
59
- abortedIf: () => boolean;
60
- };
61
-
62
- export type StepInstance = {
63
- run: (stepName: string, data?: any) => Promise<void>;
64
- };