From cda9879ff12d6b075ffe4ac7f47a5309ca0e4462 Mon Sep 17 00:00:00 2001 From: Christophe CAMENSULI Date: Sat, 9 Mar 2024 21:43:02 +0100 Subject: [PATCH] feat : add Orm --- .gitignore | 1 + index.ts | 5 +- nodefony/config/config.ts | 11 +- .../http-config.ts} | 3 + nodefony/config/modules/mongoose-config.ts | 43 +++ nodefony/config/modules/sequelize-config.ts | 88 +++++ package-lock.json | 212 +++++++++- package.json | 2 +- src/modules/test/index.ts | 5 +- src/modules/test/nodefony/config/config.ts | 36 +- .../test/nodefony/entity/BoatEntity.ts | 56 +++ src/modules/test/tsconfig.base.json | 18 - src/modules/test/tsconfig.json | 17 +- src/nodefony/src/Nodefony.ts | 9 + src/nodefony/src/kernel/CliKernel.ts | 2 +- src/nodefony/src/kernel/Kernel.ts | 48 ++- src/nodefony/src/kernel/Module.ts | 23 +- .../src/kernel/decorators/kernelDecorator.ts | 49 ++- src/nodefony/src/kernel/orm/Connector.ts | 77 ++++ src/nodefony/src/kernel/orm/Entity.ts | 88 +++++ src/nodefony/src/kernel/orm/Orm.ts | 88 +++++ src/nodefony/src/types/nodefony.d.ts | 3 + .../@nodefony/framework/tsconfig.base.json | 18 - .../@nodefony/framework/tsconfig.json | 17 +- .../@nodefony/http/nodefony/config/config.ts | 2 +- .../service/sessions/sessions-service.ts | 8 + src/packages/@nodefony/http/rollup.config.ts | 2 + .../@nodefony/http/tsconfig.base.json | 18 - src/packages/@nodefony/http/tsconfig.json | 24 +- src/packages/@nodefony/mongoose/.gitignore | 33 ++ src/packages/@nodefony/mongoose/index.ts | 21 + .../mongoose/nodefony/config/config.ts | 43 +++ .../mongoose/nodefony/entity/sessionEntity.ts | 75 ++++ .../mongoose/nodefony/service/orm.ts | 255 +++++++++++++ .../mongoose/nodefony/src/mongooseStorage.ts | 212 ++++++++++ .../mongoose/nodefony/types/index.d.ts | 4 + src/packages/@nodefony/mongoose/package.json | 55 +++ .../@nodefony/mongoose/rollup.config.ts | 82 ++++ .../tsconfig.json} | 10 +- src/packages/@nodefony/sequelize/index.ts | 22 +- .../sequelize/nodefony/command/sync.ts | 104 +++++ .../sequelize/nodefony/config/config.ts | 54 ++- .../nodefony/decorators/decorator.ts | 0 .../nodefony/entity/sessionEntity.ts | 110 ++++++ .../sequelize/nodefony/service/orm.ts | 342 ++++++++++++++++- .../nodefony/src/sequelizeStorage.ts | 361 ++++++++++++++++++ .../@nodefony/sequelize/tsconfig.json | 26 +- 47 files changed, 2649 insertions(+), 133 deletions(-) rename nodefony/config/{module-http-config.ts => modules/http-config.ts} (96%) create mode 100644 nodefony/config/modules/mongoose-config.ts create mode 100644 nodefony/config/modules/sequelize-config.ts create mode 100644 src/modules/test/nodefony/entity/BoatEntity.ts delete mode 100644 src/modules/test/tsconfig.base.json create mode 100644 src/nodefony/src/kernel/orm/Connector.ts create mode 100644 src/nodefony/src/kernel/orm/Entity.ts create mode 100644 src/nodefony/src/kernel/orm/Orm.ts delete mode 100644 src/packages/@nodefony/framework/tsconfig.base.json delete mode 100644 src/packages/@nodefony/http/tsconfig.base.json create mode 100644 src/packages/@nodefony/mongoose/.gitignore create mode 100644 src/packages/@nodefony/mongoose/index.ts create mode 100644 src/packages/@nodefony/mongoose/nodefony/config/config.ts create mode 100644 src/packages/@nodefony/mongoose/nodefony/entity/sessionEntity.ts create mode 100644 src/packages/@nodefony/mongoose/nodefony/service/orm.ts create mode 100644 src/packages/@nodefony/mongoose/nodefony/src/mongooseStorage.ts create mode 100644 src/packages/@nodefony/mongoose/nodefony/types/index.d.ts create mode 100644 src/packages/@nodefony/mongoose/package.json create mode 100644 src/packages/@nodefony/mongoose/rollup.config.ts rename src/packages/@nodefony/{sequelize/tsconfig.base.json => mongoose/tsconfig.json} (72%) create mode 100644 src/packages/@nodefony/sequelize/nodefony/command/sync.ts create mode 100644 src/packages/@nodefony/sequelize/nodefony/decorators/decorator.ts create mode 100644 src/packages/@nodefony/sequelize/nodefony/entity/sessionEntity.ts create mode 100644 src/packages/@nodefony/sequelize/nodefony/src/sequelizeStorage.ts diff --git a/.gitignore b/.gitignore index 990fe61..7230deb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ dist/ node_modules/ tmp/ nodefony/config/certificates +nodefony/databases/* # Ignore les fichiers générés par TypeScript *.tsbuildinfo diff --git a/index.ts b/index.ts index cdd29d4..dba9445 100644 --- a/index.ts +++ b/index.ts @@ -12,10 +12,11 @@ import config from "./nodefony/config/config"; * The App class extends the Module class and represents an application entry point. */ @modules([ + "@nodefony/sequelize", + "@nodefony/mongoose", "@nodefony/http", - "@nodefony/security", "@nodefony/framework", - "@nodefony/sequelize", + "@nodefony/security", "@nodefony/test", ]) @controllers([AppController]) diff --git a/nodefony/config/config.ts b/nodefony/config/config.ts index 6539589..e62477a 100644 --- a/nodefony/config/config.ts +++ b/nodefony/config/config.ts @@ -23,7 +23,9 @@ import path from "node:path"; import { Nodefony } from "nodefony"; const kernel = Nodefony.kernel; -import http from "./module-http-config"; +import http from "./modules/http-config"; +import sequelize from "./modules/sequelize-config"; +import mongoose from "./modules/mongoose-config"; import pm2 from "./pm2/pm2.config"; let CDN = null; @@ -44,8 +46,9 @@ switch (kernel?.environment) { unitTest = true; domainCheck = true; } +//console.log(sequelize.connectors.nodefony.options); -export default { +const config = { watch: true, domain: "127.0.0.1", // "0.0.0.0" "selectAuto" //domain: "selectAuto", @@ -161,4 +164,8 @@ export default { * OVERRIDE modules config */ "module-http": http, + "module-sequelize": sequelize, + "module-mongoose": mongoose, }; + +export default config; diff --git a/nodefony/config/module-http-config.ts b/nodefony/config/modules/http-config.ts similarity index 96% rename from nodefony/config/module-http-config.ts rename to nodefony/config/modules/http-config.ts index 0bb8c5f..4265345 100644 --- a/nodefony/config/module-http-config.ts +++ b/nodefony/config/modules/http-config.ts @@ -49,4 +49,7 @@ switch (kernel?.environment) { export default { rejectUnauthorized, certificates, + session: { + handler: "sequelize", + }, }; diff --git a/nodefony/config/modules/mongoose-config.ts b/nodefony/config/modules/mongoose-config.ts new file mode 100644 index 0000000..f4401d3 --- /dev/null +++ b/nodefony/config/modules/mongoose-config.ts @@ -0,0 +1,43 @@ +import nodefony from "nodefony"; + +/** + * OVERRIDE ORM BUNDLE MONGOOSE + * + * @see MONGO BUNDLE config for more options + * @more options https://mongoosejs.com/docs/connections.html + * https://mongoosejs.com/docs/api.html#mongoose_Mongoose-createConnection + * + * By default nodefony create connector name nodefony + * for manage Sessions / Users + */ + +const connectors = { + nodefony: {}, +}; + +switch (nodefony.kernel?.appEnvironment.environment) { + case "production": + case "development": + default: + connectors.nodefony = { + host: "localhost", + port: 27017, + dbname: "nodefony", + // credentials: vault, + options: { + user: "nodefony", + pass: "nodefony", + maxPoolSize: 50, + serverSelectionTimeoutMS: 5000, + socketTimeoutMS: 5000, + connectTimeoutMS: 5000, + }, + }; +} + +const config = { + debug: true, + connectors, +}; + +export default config; diff --git a/nodefony/config/modules/sequelize-config.ts b/nodefony/config/modules/sequelize-config.ts new file mode 100644 index 0000000..474f967 --- /dev/null +++ b/nodefony/config/modules/sequelize-config.ts @@ -0,0 +1,88 @@ +import path from "path"; +import nodefony, { Kernel } from "nodefony"; +import { sequelize } from "@nodefony/sequelize"; + +const config = { + connectors: { + nodefony: { + driver: "sqlite", + dbname: path.resolve( + (nodefony.kernel as Kernel).path, + "nodefony", + "databases", + "nodefony.db" + ), + options: { + dialect: "sqlite", + // isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE, + retry: { + match: [/Deadlock/i, "SQLITE_BUSY"], + max: 5, + }, + pool: { + max: 5, + min: 0, + idle: 10000, + }, + }, + }, + // nodefony: { + // driver: "mysql", + // dbname: "nodefony", + // username: "root", + // password: "nodefony", + // //credentials: vault, + // options: { + // dialect: "mysql", + // host: "localhost", + // port: "3306", + // //isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE, + // retry: { + // match: [ + // sequelize.ConnectionError, + // sequelize.ConnectionTimedOutError, + // sequelize.TimeoutError, + // /Deadlock/i, + // ], + // max: 5, + // }, + // pool: { + // max: 20, + // min: 0, + // idle: 10000, + // acquire: 60000, + // }, + // }, + // }, + // nodefony: { + // driver: "postgres", + // dbname: "nodefony", + // username: "postgres", + // password: "nodefony", + // //credentials: vault, + // options: { + // dialect: "postgres", + // host: "localhost", + // port: "5432", + // //isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE, + // retry: { + // match: [ + // sequelize.ConnectionError, + // sequelize.ConnectionTimedOutError, + // sequelize.TimeoutError, + // /Deadlock/i, + // ], + // max: 5, + // }, + // pool: { + // max: 20, + // min: 0, + // idle: 10000, + // acquire: 60000, + // }, + // }, + // }, + }, +}; + +export default config; diff --git a/package-lock.json b/package-lock.json index 059541c..8a9503e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -920,6 +920,14 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz", + "integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nodefony/framework": { "resolved": "src/packages/@nodefony/framework", "link": true @@ -928,6 +936,10 @@ "resolved": "src/packages/@nodefony/http", "link": true }, + "node_modules/@nodefony/mongoose": { + "resolved": "src/packages/@nodefony/mongoose", + "link": true + }, "node_modules/@nodefony/security": { "resolved": "src/packages/@nodefony/security", "link": true @@ -1721,6 +1733,19 @@ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.9.tgz", "integrity": "sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", + "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/wrap-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", @@ -2351,6 +2376,14 @@ "node": ">=8" } }, + "node_modules/bson": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.4.0.tgz", + "integrity": "sha512-6/gSSEdbkuFlSb+ufj5jUSU4+wo8xQOwm2bDSqwmxiPE17JTpsP63eAwoN8iF8Oy4gJYj+PAL3zdRCTdaw5Y1g==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -4740,6 +4773,14 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4978,6 +5019,11 @@ "timers-ext": "^0.1.7" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5084,6 +5130,136 @@ "node": "*" } }, + "node_modules/mongodb": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", + "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^6.2.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mongoose": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.2.0.tgz", + "integrity": "sha512-la93n6zCYRbPS+c5N9oTDAktvREy5OT9OCljp1Tah0y3+p8UPMTAoabWaLZMdzYruOtF9/9GRf6MasaZjiZP1A==", + "dependencies": { + "bson": "^6.2.0", + "kareem": "2.5.1", + "mongodb": "6.3.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6039,7 +6215,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "engines": { "node": ">=6" } @@ -6671,6 +6846,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -6766,6 +6946,14 @@ "source-map": "^0.6.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", @@ -12037,6 +12225,28 @@ "node": ">=0.10.32" } }, + "src/packages/@nodefony/mongoose": { + "version": "10.0.0", + "license": "CECILL-B", + "dependencies": { + "mongodb": "6.3.0", + "mongoose": "8.2.0" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "25.0.7", + "@rollup/plugin-json": "6.1.0", + "@rollup/plugin-node-resolve": "15.2.3", + "@rollup/plugin-replace": "5.0.5", + "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-typescript": "11.1.6", + "@types/node": "20.11.24", + "@typescript-eslint/eslint-plugin": "7.1.1", + "@typescript-eslint/parser": "7.1.1", + "rimraf": "5.0.5", + "rollup": "4.12.1", + "typescript": "5.3.3" + } + }, "src/packages/@nodefony/security": { "version": "10.0.0", "dependencies": { diff --git a/package.json b/package.json index 68e664a..3543c7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nodefony-core", - "version": "1.0.0", + "version": "10.0.0-alpha.1", "description": "Core Nodefony Framework", "type": "module", "types": "dist/types/index.d.ts", diff --git a/src/modules/test/index.ts b/src/modules/test/index.ts index b7a5c8f..c4d62e8 100644 --- a/src/modules/test/index.ts +++ b/src/modules/test/index.ts @@ -1,4 +1,5 @@ import { Kernel, Module, services } from "nodefony"; +import { entities } from "@nodefony/sequelize"; import config from "./nodefony/config/config"; import DefaultController from "./nodefony/controller/DefaultController"; import OpenapiController from "./nodefony/controller/OpenapiController"; @@ -6,6 +7,7 @@ import RestController from "./nodefony/controller/RestController"; import GraphqlController from "./nodefony/controller/GraphqlController"; import HtmlController from "./nodefony/controller/HtmlController"; import { controllers } from "@nodefony/framework"; +import BoatEntity from "./nodefony/entity/BoatEntity"; @services([]) @controllers([ @@ -15,6 +17,7 @@ import { controllers } from "@nodefony/framework"; RestController, OpenapiController, ]) +@entities([BoatEntity]) class Test extends Module { constructor(kernel: Kernel) { super("test", kernel, import.meta.url, config); @@ -22,4 +25,4 @@ class Test extends Module { } export default Test; -export {}; +export { BoatEntity }; diff --git a/src/modules/test/nodefony/config/config.ts b/src/modules/test/nodefony/config/config.ts index cbbd9ad..b05c562 100644 --- a/src/modules/test/nodefony/config/config.ts +++ b/src/modules/test/nodefony/config/config.ts @@ -1,5 +1,39 @@ -import { kernel } from "nodefony"; +import path from "node:path"; +import nodefony, { Kernel } from "nodefony"; export default { watch: false, + + "module-sequelize": { + connectors: { + myconnector: { + driver: "sqlite", + dbname: path.resolve( + (nodefony.kernel as Kernel).path, + "nodefony", + "databases", + "myconnector.db" + ), + options: { + dialect: "sqlite", + // isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE, + retry: { + match: [ + // Sequelize.ConnectionError, + // Sequelize.ConnectionTimedOutError, + // Sequelize.TimeoutError, + /Deadlock/i, + "SQLITE_BUSY", + ], + max: 5, + }, + pool: { + max: 5, + min: 0, + idle: 10000, + }, + }, + }, + }, + }, }; diff --git a/src/modules/test/nodefony/entity/BoatEntity.ts b/src/modules/test/nodefony/entity/BoatEntity.ts new file mode 100644 index 0000000..68aaa92 --- /dev/null +++ b/src/modules/test/nodefony/entity/BoatEntity.ts @@ -0,0 +1,56 @@ +import nodefony, { Entity, Module } from "nodefony"; +import { sequelize, Models } from "@nodefony/sequelize"; + +const { + Model, + //ConnectionOptions, + Transaction, + //Options, + DataTypes, + NOW, + Sequelize, +} = sequelize; + +class Boat extends Entity { + constructor(module: Module) { + /* + * @param module instance + * @param Entity name + * @param orm name + * @param connector name + */ + super(module, "boat", "sequelize", "myconnector"); + } + + getSchema() { + return { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + name: { + type: DataTypes.STRING, + }, + size: { + type: DataTypes.STRING, + }, + }; + } + + override registerModel(db: Sequelize): typeof Model { + class SessionModel extends Model { + declare is: number; + declare name: string; + declare size: JSON; + public static associate(models: Models): void {} + } + SessionModel.init(this.getSchema(), { + sequelize: db, + modelName: this.name, + }); + return SessionModel; + } +} + +export default Boat; diff --git a/src/modules/test/tsconfig.base.json b/src/modules/test/tsconfig.base.json deleted file mode 100644 index 9b5ead5..0000000 --- a/src/modules/test/tsconfig.base.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "target": "ES2022", - "module": "ESNext", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "stripInternal": true, - "declaration": true, - "declarationDir": "./dist/types", - "outDir": "./dist" - } -} diff --git a/src/modules/test/tsconfig.json b/src/modules/test/tsconfig.json index 94c1e3d..5e1d0bb 100644 --- a/src/modules/test/tsconfig.json +++ b/src/modules/test/tsconfig.json @@ -1,9 +1,20 @@ { - "extends": "./tsconfig.base.json", "compilerOptions": { "rootDir": "./", - "outDir": "./dist" - //"allowJs": true + "outDir": "./dist", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "ES2022", + "module": "ESNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "stripInternal": true, + "declaration": true, + "declarationDir": "./dist/types" }, "include": ["index.ts", "rollup.config.ts", "nodefony/**/*.ts"], "exclude": ["node_modules", "dist"] diff --git a/src/nodefony/src/Nodefony.ts b/src/nodefony/src/Nodefony.ts index bca677c..2a6cf98 100644 --- a/src/nodefony/src/Nodefony.ts +++ b/src/nodefony/src/Nodefony.ts @@ -40,8 +40,13 @@ import { injectable, inject, services, + entities, } from "./kernel/decorators/kernelDecorator"; +import Orm from "./kernel/orm/Orm"; +import Entity from "./kernel/orm/Entity"; +import Connector from "./kernel/orm/Connector"; + //import { createRequire } from "module"; //const require = createRequire(import.meta.url); //const {version} = require("../package.json"); @@ -147,8 +152,12 @@ export { Result, Error, Injector, + Orm, + Entity, + Connector, modules, injectable, inject, services, + entities, }; diff --git a/src/nodefony/src/kernel/CliKernel.ts b/src/nodefony/src/kernel/CliKernel.ts index 698381e..92eafcc 100644 --- a/src/nodefony/src/kernel/CliKernel.ts +++ b/src/nodefony/src/kernel/CliKernel.ts @@ -93,7 +93,7 @@ class CliKernel extends Cli { this.commander.exitOverride(); this.commander.name(this.name); this.commander.showHelpAfterError(false); - //this.commander.showSuggestionAfterError(false); + this.commander.showSuggestionAfterError(true); this.commander.configureHelp({ //sortSubcommands: true, sortOptions: true, diff --git a/src/nodefony/src/kernel/Kernel.ts b/src/nodefony/src/kernel/Kernel.ts index 833a0e9..3b7a279 100644 --- a/src/nodefony/src/kernel/Kernel.ts +++ b/src/nodefony/src/kernel/Kernel.ts @@ -19,6 +19,7 @@ import Pm2 from "../service/pm2Service"; import Watcher from "../service/watcherService"; import Rollup from "../service/rollup/rollupService"; import Injector from "./injector/injector"; +import Entity from "./orm/Entity"; //import { StartOptions } from "pm2"; const colorLogEvent = clc.cyan.bgBlue("EVENT KERNEL"); @@ -105,6 +106,11 @@ export interface ServiceConstructor { _inject?: { [key: number]: string }; } +export interface EntityConstructor { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new (...args: any[]): Entity; +} + export interface ModuleConstructor { // eslint-disable-next-line @typescript-eslint/no-explicit-any new (kernel: Kernel, ...args: any[]): Module; @@ -172,8 +178,8 @@ class Kernel extends Service { nodefony.kernel = this; this.kernel = this; this.set("kernel", this); - this.cli = this.setCli(cli); this.type = "CONSOLE"; + this.cli = this.setCli(cli); this.interfaces = this.getNetworkInterfaces(); this.injector = new Injector(this); this.set("injector", this.injector); @@ -207,7 +213,7 @@ class Kernel extends Service { throw e; }); if (this.setCommandComplete(Events.onPreStart)) { - return this; + return this.terminate(0); } // load application @@ -235,8 +241,9 @@ class Kernel extends Service { // this.projectName = this.app.getModuleName(); // } this.started = true; + if (this.cli) this.type = this.cli?.type; if (this.setCommandComplete(Events.onStart)) { - return this; + return this.terminate(0); } return this.preRegister(); }) @@ -255,7 +262,7 @@ class Kernel extends Service { }); if (this.setCommandComplete(Events.onPreRegister)) { - return this; + return this.terminate(0); } if (this.cli) { await this.cli @@ -293,9 +300,17 @@ class Kernel extends Service { .then(() => { this.registered = true; if (this.setCommandComplete(Events.onRegister)) { - return this; + return this.terminate(0); } - return this.boot(); + return ( + this.boot() + // .then(() => { + // return this; + // }) + .catch((e) => { + throw e; + }) + ); }) .catch((e) => { throw e; @@ -347,16 +362,24 @@ class Kernel extends Service { throw e; }); if (this.setCommandComplete(Events.onPreBoot)) { - return this; + return this.terminate(0); } //return; return this.fireAsync("onBoot", this) .then(() => { this.booted = true; if (this.setCommandComplete(Events.onBoot)) { - return this; + return this.terminate(0); } - return this.onReady(); + return ( + this.onReady() + // .then(() => { + // return this; + // }) + .catch((e) => { + throw e; + }) + ); }) .catch((e) => { throw e; @@ -368,7 +391,7 @@ class Kernel extends Service { .then(async () => { this.ready = true; if (this.setCommandComplete(Events.onReady)) { - return this; + return this.terminate(0); } //PM2 if ( @@ -926,7 +949,7 @@ class Kernel extends Service { // return res.version as string; // } - async terminate(code?: number): Promise { + async terminate(code?: number): Promise { if (code === undefined) { code = 0; } @@ -947,7 +970,8 @@ class Kernel extends Service { "DEBUG" ); try { - return resolve(CliKernel.quit(code as number)); + CliKernel.quit(code as number); + return resolve(this); } catch (e) { this.log(e, "ERROR"); return reject(CliKernel.quit(code as number)); diff --git a/src/nodefony/src/kernel/Module.ts b/src/nodefony/src/kernel/Module.ts index a79dd68..1754976 100644 --- a/src/nodefony/src/kernel/Module.ts +++ b/src/nodefony/src/kernel/Module.ts @@ -1,7 +1,11 @@ import { dirname, resolve, basename, isAbsolute } from "node:path"; import { readFile } from "fs/promises"; import { fileURLToPath } from "url"; -import Kernel, { ServiceConstructor, ServiceWithInitialize } from "./Kernel"; +import Kernel, { + ServiceConstructor, + ServiceWithInitialize, + EntityConstructor, +} from "./Kernel"; import { JSONObject } from "../types/globals"; import Service, { DefaultOptionsService } from "../Service"; import Command from "../command/Command"; @@ -28,6 +32,7 @@ import { } from "rollup"; import { FSWatcher } from "chokidar"; import { createRequire } from "node:module"; +import Entity from "./orm/Entity"; export interface PackageJson { name: string; @@ -223,9 +228,9 @@ class Module extends Service { // eslint-disable-next-line @typescript-eslint/no-explicit-any ...args: any[] ): Promise { - if (!module) { - throw new Error(`Applcation not ready`); - } + // if (!module) { + // throw new Error(`Applcation not ready`); + // } const res = await import(service); return this.addService(res.default, ...args); } @@ -237,6 +242,16 @@ class Module extends Service { )) as PackageJson; } + async loadEntity(entity: string) { + const res = await import(entity); + return this.addEntity(res.default); + } + + addEntity(entity: EntityConstructor): Entity { + const inst = new entity(this); + return inst; + } + getDependencies(): string[] { return Module.getPackageDependencies(this.package as PackageJson); } diff --git a/src/nodefony/src/kernel/decorators/kernelDecorator.ts b/src/nodefony/src/kernel/decorators/kernelDecorator.ts index 9616bdf..f79ea71 100644 --- a/src/nodefony/src/kernel/decorators/kernelDecorator.ts +++ b/src/nodefony/src/kernel/decorators/kernelDecorator.ts @@ -3,11 +3,10 @@ import Module from "../Module"; import { ModuleConstructor, ServiceConstructor } from "../Kernel"; import Service from "../../Service"; import Injector from "../injector/injector"; -//import nodefony from "../../Nodefony"; +import Entity, { TypeEntity } from "../orm/Entity"; // eslint-disable-next-line @typescript-eslint/ban-types type Constructor = new (...args: any[]) => Module; - type Injectable = new (...args: any[]) => T; function modules( @@ -88,18 +87,38 @@ function services( }; } -// function injectable( -// name?: string -// ): (constructor: T) => T { -// return function (constructor: T): T { -// //console.log("injectable", name || constructor.name); -// Injector.register( -// name || constructor.name, -// constructor as ServiceConstructor -// ); -// return constructor; -// }; -// } +function entities( + entity: string | (string | TypeEntity)[] | TypeEntity +): (constructor: T) => T { + return function (constructor: T): T { + class NewConstructorEntity extends constructor { + constructor(...args: any[]) { + super(...args); + this.kernel?.once("onBoot", async () => { + return this.initDecoratorEntity(); + }); + } + async initDecoratorEntity() { + if (Array.isArray(entity)) { + for (const ent of entity) { + if (typeof ent === "string") { + await this.loadEntity(ent); + } else { + this.addEntity(ent); + } + } + } else { + if (typeof entity === "string") { + await this.loadEntity(entity); + } else { + this.addEntity(entity); + } + } + } + } + return NewConstructorEntity; + }; +} function injectable( name?: string @@ -135,4 +154,4 @@ function inject(serviceName: string): Function { }; } -export { modules, injectable, inject, services }; +export { modules, injectable, inject, services, entities }; diff --git a/src/nodefony/src/kernel/orm/Connector.ts b/src/nodefony/src/kernel/orm/Connector.ts new file mode 100644 index 0000000..aa708dd --- /dev/null +++ b/src/nodefony/src/kernel/orm/Connector.ts @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Message, Msgid, Orm, Pci, Severity } from "nodefony"; +class Connector { + db: any = null; + name: string; + type: string; + orm: Orm; + intervalId: number | null = null; + state: "DISCONNECTED" | "CONNECTED" = "DISCONNECTED"; + options: any; + constructor( + name: string, + type: string, + options: Record, + orm: Orm + ) { + this.name = name; + this.type = type; + this.db = null; + this.orm = orm; + this.options = options; + } + + toObject() { + return { + state: this.state, + name: this.name, + type: this.type, + options: this.options, + }; + } + + onError(error: Error) { + return error; + } + + async connect(type: string, config: any): Promise { + console.log(`connect must be override `, type), config; + return Promise.resolve(); + } + + async setConnection(db: any, config: any) { + if (!db) { + throw new Error("Cannot create class Connector without db native"); + } + this.db = db; + await this.orm.fireAsync("onConnect", this.name, this.db); + this.state = "CONNECTED"; + const severity: Severity = "INFO"; + this.log( + `Connection been established successfully + Type : ${this.type} + Database : ${config.database}`, + severity + ); + return db; + } + + getConnection() { + return this.db; + } + + async close() { + return Promise.resolve(); + } + + log( + pci: Pci, + severity?: Severity, + msgid: Msgid = `CONNECTOR ${this.type} ${this.name}`, + msg: Message = "" + ) { + return this.orm.log(pci, severity, msgid, msg); + } +} + +export default Connector; diff --git a/src/nodefony/src/kernel/orm/Entity.ts b/src/nodefony/src/kernel/orm/Entity.ts new file mode 100644 index 0000000..b2af922 --- /dev/null +++ b/src/nodefony/src/kernel/orm/Entity.ts @@ -0,0 +1,88 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import Service from "../../Service"; +import Module from "../Module"; +import Container from "../../Container"; +import { Message, Msgid, Pci, Severity } from "../../syslog/Pdu"; + +export type TypeEntity = new (...args: any[]) => T; + +class Entity extends Service { + model: any; + db: any; + module: Module; + connectorName: string; + orm: any; + encoder: any; + constructor( + module: Module, + name: string, + ormName: string, + connectorName: string + ) { + super(name, module.container as Container); + this.module = module; + this.orm = this.get(ormName); + if (!this.orm) { + throw new Error( + `${this.name} entity can't be registered ORM not found : ${ormName}` + ); + } + + this.connectorName = connectorName; + this.model = null; + this.encoder = null; + + this.orm.on("onConnect", (connectorName: string, db: any) => { + if (connectorName === this.connectorName) { + this.db = db; + this.model = this.registerModel(this.db); + this.orm.setEntity(this); + } + }); + } + + registerModel(db: any): any { + console.log(`registerModel must be override`, db); + } + + override logger(pci: Pci, severity: Severity, msgid: Msgid, msg: Message) { + if (!msgid) { + msgid = `Entity ${this.name}`; + } + return super.logger(pci, severity, msgid, msg); + } + + // setEncoder(encoder) { + // if (encoder instanceof nodefony.Encoder) { + // return (this.encoder = encoder); + // } + // throw new Error( + // `setEncoder : Entity ${this.name} encoder must be an instance of nodefony.Encoder` + // ); + // } + + // getEncoder() { + // return this.encoder; + // } + + // hasEncoder() { + // if (this.encoder instanceof nodefony.Encoder) { + // return true; + // } + // if (this.encoder === null) { + // return false; + // } + // throw new Error( + // `setEncoder : Entity ${this.name} encoder must be an instance of nodefony.Encoder` + // ); + // } + + // async encode(value) { + // if (this.hasEncoder()) { + // return await this.encoder.encodePassword.apply(this.encoder, arguments); + // } + // return value; + // } +} + +export default Entity; diff --git a/src/nodefony/src/kernel/orm/Orm.ts b/src/nodefony/src/kernel/orm/Orm.ts new file mode 100644 index 0000000..16b927d --- /dev/null +++ b/src/nodefony/src/kernel/orm/Orm.ts @@ -0,0 +1,88 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import Service, { DefaultOptionsService } from "../../Service"; +import Module from "../Module"; +import Container from "../../Container"; +import Event from "../../Event"; +import Entity from "./Entity"; +import Connector from "./Connector"; + +class Orm extends Service { + entities: Record = {}; + connections: Record = {}; + ready: boolean = false; + debug: boolean = false; + connectionNotification: number = 0; + constructor(name: string, module: Module, options: DefaultOptionsService) { + super( + name, + module.container as Container, + module.notificationsCenter as Event, + options + ); + } + boot() { + this.on("onConnect", async (connection) => this.ormReady(connection)); + this.on("onErrorConnection", async (connection, error) => + this.ormReady(connection, error) + ); + } + + ormReady(connection: Connector, error?: Error) { + return new Promise((resolve, reject) => { + const nbConnectors = Object.keys(this.options.connectors).length; + this.connectionNotification++; + if (error) { + this.log(error, "ERROR"); + return reject(error); + } + if (nbConnectors === this.connectionNotification) { + process.nextTick( + async () => + await this.emitAsync("onOrmReady", this) + .then(() => { + if (this.kernel?.type !== "CONSOLE") { + this.log("onOrmReady", "INFO", `EVENTS ${this.name} ORM`); + } + this.connectionNotification = 0; + this.ready = true; + }) + .catch((e) => { + this.log(e, "ERROR"); + return reject(e); + }) + ); + } + return resolve(this); + }); + } + setEntity(entity: Entity) { + if (!entity) { + throw new Error(`${this.name} setEntity : entity is null `); + } + if (!(entity instanceof Entity)) { + throw new Error( + `${this.name} setEntity : not instance of nodefony.Entity` + ); + } + if (this.entities[entity.name]) { + throw new Error( + `${this.name} setEntity : Entity Already exist ${entity.name}` + ); + } + if (!entity.model) { + throw new Error( + `${this.name} setEntity : Module : ${entity.module.name} Model is undefined in Entity : ${entity.name}` + ); + } + this.entities[entity.name] = entity; + if (this.kernel?.type === "SERVER") { + this.log(`ENTITY ADD : ${entity.name}`, "INFO"); + } + } + + async createConnection(name: string, config: any): Promise { + console.log("createConnection", name, config); + } +} + +export default Orm; diff --git a/src/nodefony/src/types/nodefony.d.ts b/src/nodefony/src/types/nodefony.d.ts index 9830f19..ad8396b 100644 --- a/src/nodefony/src/types/nodefony.d.ts +++ b/src/nodefony/src/types/nodefony.d.ts @@ -21,6 +21,7 @@ // import Builder from "../command/Builder"; // import Cli from "../Cli"; +import { Nodefony } from ".."; import { nodefonyOptions, EnvironmentType, @@ -38,6 +39,8 @@ declare module "nodefony" { } } +declare namespace Nodefony {} + // rapide export * from "./globals"; export * from "../Container"; diff --git a/src/packages/@nodefony/framework/tsconfig.base.json b/src/packages/@nodefony/framework/tsconfig.base.json deleted file mode 100644 index 9b5ead5..0000000 --- a/src/packages/@nodefony/framework/tsconfig.base.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "target": "ES2022", - "module": "ESNext", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "stripInternal": true, - "declaration": true, - "declarationDir": "./dist/types", - "outDir": "./dist" - } -} diff --git a/src/packages/@nodefony/framework/tsconfig.json b/src/packages/@nodefony/framework/tsconfig.json index 94c1e3d..5e1d0bb 100644 --- a/src/packages/@nodefony/framework/tsconfig.json +++ b/src/packages/@nodefony/framework/tsconfig.json @@ -1,9 +1,20 @@ { - "extends": "./tsconfig.base.json", "compilerOptions": { "rootDir": "./", - "outDir": "./dist" - //"allowJs": true + "outDir": "./dist", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "ES2022", + "module": "ESNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "stripInternal": true, + "declaration": true, + "declarationDir": "./dist/types" }, "include": ["index.ts", "rollup.config.ts", "nodefony/**/*.ts"], "exclude": ["node_modules", "dist"] diff --git a/src/packages/@nodefony/http/nodefony/config/config.ts b/src/packages/@nodefony/http/nodefony/config/config.ts index 0dcaf04..fb466a1 100644 --- a/src/packages/@nodefony/http/nodefony/config/config.ts +++ b/src/packages/@nodefony/http/nodefony/config/config.ts @@ -134,7 +134,7 @@ export default { start: false, // false || true || Name Session Context use_strict_mode: true, name: "nodefony", - handler: "files", // files | orm | memcached "nodefony.session.storage" + handler: "files", // files | sequelize| moogoose | memcached "nodefony.session.storage" save_path: "./tmp/sessions", gc_probability: 5, gc_divisor: 100, diff --git a/src/packages/@nodefony/http/nodefony/service/sessions/sessions-service.ts b/src/packages/@nodefony/http/nodefony/service/sessions/sessions-service.ts index 58edddf..09a21e8 100644 --- a/src/packages/@nodefony/http/nodefony/service/sessions/sessions-service.ts +++ b/src/packages/@nodefony/http/nodefony/service/sessions/sessions-service.ts @@ -24,6 +24,8 @@ import HttpRequest from "../../src/context/http/Request"; import url from "node:url"; import Certificate from "../../service/certificates"; import { createHash } from "node:crypto"; +import { sequelizeStorage } from "@nodefony/sequelize"; +import { mongooseStorage } from "@nodefony/mongoose"; import FileSessionStorage from "../../src/session/storage/FileSessionStorage"; @@ -104,6 +106,12 @@ class SessionsService extends Service { case "ORM": //storage = nodefony.session.storage[this.kernel?.getOrm()]; break; + case "sequelize": + storage = sequelizeStorage; + break; + case "mongoose": + storage = mongooseStorage; + break; case "files": storage = FileSessionStorage; break; diff --git a/src/packages/@nodefony/http/rollup.config.ts b/src/packages/@nodefony/http/rollup.config.ts index e521d4b..67e6134 100644 --- a/src/packages/@nodefony/http/rollup.config.ts +++ b/src/packages/@nodefony/http/rollup.config.ts @@ -22,6 +22,8 @@ const sourcemapPathTransform = createPathTransform({ const external: string[] = [ "nodefony", + "@nodefony/sequelize", + "@nodefony/mongoose", "cli-color", "cookie", "formidable", diff --git a/src/packages/@nodefony/http/tsconfig.base.json b/src/packages/@nodefony/http/tsconfig.base.json deleted file mode 100644 index 9b5ead5..0000000 --- a/src/packages/@nodefony/http/tsconfig.base.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "target": "ES2022", - "module": "ESNext", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "stripInternal": true, - "declaration": true, - "declarationDir": "./dist/types", - "outDir": "./dist" - } -} diff --git a/src/packages/@nodefony/http/tsconfig.json b/src/packages/@nodefony/http/tsconfig.json index d3e1eee..2ee990a 100644 --- a/src/packages/@nodefony/http/tsconfig.json +++ b/src/packages/@nodefony/http/tsconfig.json @@ -1,18 +1,26 @@ { - "extends": "./tsconfig.base.json", "compilerOptions": { "rootDir": "./", "outDir": "./dist", - //"allowJs": true + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "ES2022", + "module": "ESNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "stripInternal": true, + "declaration": true, + "declarationDir": "./dist/types" }, "include": [ "index.ts", "rollup.config.ts", "src/**/*.ts", - "nodefony/**/*.ts", + "nodefony/**/*.ts" ], - "exclude": [ - "node_modules", - "dist" - ] -} \ No newline at end of file + "exclude": ["node_modules", "dist"] +} diff --git a/src/packages/@nodefony/mongoose/.gitignore b/src/packages/@nodefony/mongoose/.gitignore new file mode 100644 index 0000000..8205a1c --- /dev/null +++ b/src/packages/@nodefony/mongoose/.gitignore @@ -0,0 +1,33 @@ +# Ignore les fichiers de configuration spécifiques à l'IDE +.vscode/ +.idea/ + +# Ignore les fichiers de sauvegarde automatique et les fichiers de build +*.bak +*.log +dist/ +node_modules/ +tmp/ + +# Ignore les fichiers générés par TypeScript +*.tsbuildinfo +*.js.map + +# Ignore les fichiers d'environnement +.env + +# Ignore les fichiers de sauvegarde +*.swp +*.swo + +# Ignore les fichiers système et cachés +.DS_Store +Thumbs.db +ehthumbs.db + +# Ignore les fichiers d'exemple ou temporaires +example/ +temp/ + +yarn.lock +bun.lockb diff --git a/src/packages/@nodefony/mongoose/index.ts b/src/packages/@nodefony/mongoose/index.ts new file mode 100644 index 0000000..2a9c1f1 --- /dev/null +++ b/src/packages/@nodefony/mongoose/index.ts @@ -0,0 +1,21 @@ +import { Kernel, Module, services, entities } from "nodefony"; +import config from "./nodefony/config/config"; +import orm from "./nodefony/service/orm"; + +import * as mongoose from "mongoose"; +import sessionEntity, { + ISession, + SessionModel, +} from "./nodefony/entity/sessionEntity"; +import mongooseStorage from "./nodefony/src/mongooseStorage"; + +@services([orm]) +@entities([sessionEntity]) +class Mongoose extends Module { + constructor(kernel: Kernel) { + super("mongoose", kernel, import.meta.url, config); + } +} + +export default Mongoose; +export { mongoose, mongooseStorage, ISession, SessionModel }; diff --git a/src/packages/@nodefony/mongoose/nodefony/config/config.ts b/src/packages/@nodefony/mongoose/nodefony/config/config.ts new file mode 100644 index 0000000..f4401d3 --- /dev/null +++ b/src/packages/@nodefony/mongoose/nodefony/config/config.ts @@ -0,0 +1,43 @@ +import nodefony from "nodefony"; + +/** + * OVERRIDE ORM BUNDLE MONGOOSE + * + * @see MONGO BUNDLE config for more options + * @more options https://mongoosejs.com/docs/connections.html + * https://mongoosejs.com/docs/api.html#mongoose_Mongoose-createConnection + * + * By default nodefony create connector name nodefony + * for manage Sessions / Users + */ + +const connectors = { + nodefony: {}, +}; + +switch (nodefony.kernel?.appEnvironment.environment) { + case "production": + case "development": + default: + connectors.nodefony = { + host: "localhost", + port: 27017, + dbname: "nodefony", + // credentials: vault, + options: { + user: "nodefony", + pass: "nodefony", + maxPoolSize: 50, + serverSelectionTimeoutMS: 5000, + socketTimeoutMS: 5000, + connectTimeoutMS: 5000, + }, + }; +} + +const config = { + debug: true, + connectors, +}; + +export default config; diff --git a/src/packages/@nodefony/mongoose/nodefony/entity/sessionEntity.ts b/src/packages/@nodefony/mongoose/nodefony/entity/sessionEntity.ts new file mode 100644 index 0000000..a8fd3ea --- /dev/null +++ b/src/packages/@nodefony/mongoose/nodefony/entity/sessionEntity.ts @@ -0,0 +1,75 @@ +import nodefony, { Entity, Module } from "nodefony"; +import mongoose, { Schema, Document, Model, SchemaDefinition } from "mongoose"; + +interface ISession extends Document { + session_id: string; + context: string; + user?: string; + Attributes: JSON; + flashBag: JSON; + metaBag: JSON; +} + +interface SessionModel extends Model { + fetchAll( + callback: (error: Error | null, result: Session[] | null) => void + ): void; +} + +const schema: SchemaDefinition = { + session_id: { + type: String, + index: true, + unique: true, + }, + context: { + type: String, + default: "default", + }, + // user: { + // type: Schema.Types.ObjectId, + // ref: "user", + // }, + Attributes: { + type: Object, + default: {}, + }, + flashBag: { + type: Object, + default: {}, + }, + metaBag: { + type: Object, + default: {}, + }, +}; + +class Session extends Entity { + constructor(module: Module) { + super(module, "session", "mongoose", "nodefony"); + } + + registerModel(db: mongoose.Connection) { + const mySchema: Schema = new Schema(schema, { + collection: "sessions", + timestamps: { + createdAt: "createdAt", + updatedAt: "updatedAt", + }, + }); + + mySchema.statics.fetchAll = function fetchAll(callback) { + return this.findAll() + .then((result) => callback(null, result)) + .catch((error: Error) => { + if (error) { + return callback(error, null); + } + }); + }; + return db.model(this.name, mySchema); + } +} + +export default Session; +export { SessionModel, ISession }; diff --git a/src/packages/@nodefony/mongoose/nodefony/service/orm.ts b/src/packages/@nodefony/mongoose/nodefony/service/orm.ts new file mode 100644 index 0000000..d7afc14 --- /dev/null +++ b/src/packages/@nodefony/mongoose/nodefony/service/orm.ts @@ -0,0 +1,255 @@ +import { + Service, + Module, + Container, + Event, + Entity, + Orm, + Severity, + typeOf, + extend, +} from "nodefony"; +import mongoose, { Model } from "mongoose"; + +interface Config { + options: mongoose.ConnectOptions; + [key: string]: any; +} + +const defaultconfigServer = { + host: "localhost", + port: 27017, +}; + +const defaultConfigConnection = { + socketTimeoutMS: 0, + // replicaSet: 'rs' +}; + +const serviceName: string = "mongoose"; +class Mongoose extends Orm { + static engine: typeof mongoose = mongoose; + module: Module; + declare db: mongoose.Connection; + constructor(module: Module) { + super(serviceName, module, module.options); + this.module = module; + module.kernel?.once( + "onTerminate", + async () => await this.closeConnections() + ); + module.kernel?.once("onBoot", async () => { + await this.boot().catch((e: Error) => { + throw e; + }); + }); + } + + boot() { + return new Promise(async (resolve, reject) => { + super.boot(); + if ( + this.options.connectors && + Object.keys(this.options.connectors).length + ) { + for (const name in this.options.connectors) { + await this.createConnection(name, this.options.connectors[name]); + } + } else { + process.nextTick(async () => { + this.log("onOrmReady", "DEBUG", "EVENTS MOOGOOSE"); + try { + await this.fireAsync("onOrmReady", this); + this.ready = true; + return resolve(this); + } catch (e) { + this.log(e, "ERROR", "EVENTS onOrmReady"); + return reject(e); + } + }); + } + + this.kernel?.once("onReady", async () => { + if (this.kernel?.type === "SERVER") { + this.displayTable("INFO"); + } else { + this.displayTable(); + } + }); + return resolve(this); + }); + } + + getEntity(name: String): Model | null | Record { + if (name) { + if ((name as string) in this.entities) { + const entity: Entity = this.entities[name as string]; + return entity.model as Model; + } + return null; + } + return this.entities; + } + + async createConnection(name: string, config: Config) { + if (!name) { + throw new Error("Mongodb createConnnetion no name connection"); + } + const host = config.host || this.options.host; + const port = config.port || this.options.port; + const url = `mongodb://${host}:${port}/${config.dbname}`; + const settings = extend(true, {}, defaultConfigConnection, config.options); + try { + if (config.credentials && typeOf(config.credentials) === "function") { + this.log("Try Get Credentials (async method)"); + const auth = await config.credentials(this).catch((e: Error) => { + throw e; + }); + if (auth.user) { + this.log(`Add username Credential for connector ${this.name}`); + settings.user = auth.user; + } else { + this.log("Credentials (async method) no username secret", "WARNING"); + } + if (auth.pass) { + this.log(`Add password Credential for connector ${this.name}`); + settings.pass = auth.pass; + } else { + this.log("Credentials (async method) no password secret", "WARNING"); + } + this.log(`Success Credential (async method) ${auth}`, "DEBUG"); + } + } catch (e) { + this.log(e, "ERROR"); + throw e; + } + return mongoose + .createConnection(url, settings) + .asPromise() + .then((db) => { + this.connections[name] = db; + db.on("close", () => { + this.closeConnetion(name, db); + }); + db.on("reconnect", () => { + this.log(`Reconnection to mongodb database ${name}`, "INFO"); + this.fire("onReconnect", name, db); + this.connections[name] = db; + }); + db.on("timeout", () => { + this.log(`Timeout to mongodb database ${name}`, "INFO"); + this.fire("onTimeout", name, db); + }); + db.on("parseError", (error) => { + this.log(`ParseError on mongodb database ${name}`, "ERROR"); + this.log(error, "ERROR"); + }); + db.on("error", (error) => { + this.log(`Error on mongodb database ${name}`, "ERROR"); + this.log(error, "ERROR"); + }); + db.on("reconnectFailed", (error) => { + this.log( + `Error on mongodb database reconnect Failed ${name}`, + "ERROR" + ); + this.log(error, "ERROR"); + }); + db.on("disconnected", () => { + this.log(`mongodb database disconnected ${name}`, "WARNING"); + }); + this.fire("onConnect", name, db); + this.log( + `Connection been established successfully + Type : Mongoose + DataBase: ${name} + URL: ${url}`, + "INFO", + `CONNECTOR mongoose ${name}` + ); + return db; + }) + .catch((error: Error) => { + this.log( + `Cannot connect to mongodb ( ${host}:${port}/${config.dbname} )`, + "ERROR" + ); + this.fire("onErrorConnection", null, error); + // this.log(error, "ERROR"); + throw error; + }); + } + + static isError(error: Error) { + return error instanceof mongoose.Error; + } + + static errorToString(error: Error) { + return `${error.message}`; + } + + closeConnetion(name: string, connection: mongoose.Connection) { + if (!name) { + throw new Error("Close connection no name connection !!"); + } + this.fire("onClose", name, connection); + this.log(`Close connection to mongodb database ${name}`, "WARNING"); + if (this.connections[name]) { + delete this.connections[name]; + } + } + + displayTable(severity: Severity = "DEBUG") { + const options = { + head: [ + `${this.name.toUpperCase()} CONNECTIONS NAME`, + "NAME DATABASE", + "DRIVER", + "URI", + "status", + ], + }; + const table = this.kernel?.cli?.displayTable([], options); + if (table) { + for (const dbname in this.options.connectors) { + const conn = ["", "", "mongodb", "", ""]; + conn[0] = dbname; + for (const data in this.options.connectors[dbname]) { + switch (data) { + case "dbname": + conn[1] = this.options.connectors[dbname][data]; + break; + case "host": + conn[3] = this.options.connectors[dbname][data]; + break; + case "port": + conn[3] += `:${this.options.connectors[dbname][data]}`; + break; + } + } + conn[4] = + this.connections[dbname].states[this.connections[dbname]._readyState]; + table.push(conn); + if (this.kernel && this.kernel.type === "CONSOLE") { + severity = "DEBUG"; + } + this.log(`ORM CONNECTORS LIST : \n${table.toString()}`, severity); + } + } + } + + async closeConnections() { + for (const conn in this.connections) { + await this.connections[conn] + .close() + .then(() => { + this.log(`close mongo connection ${conn} `); + }) + .catch((e: Error) => { + this.log(e, "ERROR"); + }); + } + } +} + +export default Mongoose; diff --git a/src/packages/@nodefony/mongoose/nodefony/src/mongooseStorage.ts b/src/packages/@nodefony/mongoose/nodefony/src/mongooseStorage.ts new file mode 100644 index 0000000..a3a10af --- /dev/null +++ b/src/packages/@nodefony/mongoose/nodefony/src/mongooseStorage.ts @@ -0,0 +1,212 @@ +import { Severity, extend } from "nodefony"; +import { SessionsService } from "@nodefony/http"; +import mongoose, { Model } from "mongoose"; +import SessionEntity, { SessionModel, ISession } from "../entity/sessionEntity"; +import orm from "../service/orm"; + +const finderGC = function finderGC( + this: SessionStorage, + msMaxlifetime: number, + contextSession: string +) { + const where: mongoose.FilterQuery = { + context: contextSession, + updatedAt: { + $lt: new Date(new Date().getDate() - msMaxlifetime), + }, + }; + if (this.entity) + return this.entity + .deleteMany(where) + .then((results) => { + let severity = "DEBUG"; + if (!results) { + throw new Error("session.storage finderGC no result "); + } + if (results && results.deletedCount) { + severity = "INFO"; + this.manager.log( + `Context : ${contextSession || "default"} GARBADGE COLLECTOR ==> ${results.deletedCount} DELETED`, + "INFO" + ); + } + return results; + }) + .catch((error) => { + this.manager.log(error, "ERROR"); + throw error; + }); +}; + +class SessionStorage { + manager: SessionsService; + gc_maxlifetime: number; + contextSessions: string[]; + entity?: Model; + userEntity?: Model; + orm: orm; + constructor(manager: SessionsService) { + this.manager = manager; + this.orm = this.manager.get("mongoose"); + this.orm.on("onOrmReady", () => { + this.entity = this.orm.getEntity("session") as Model< + ISession, + SessionModel + >; + this.userEntity = this.orm.getEntity("user") as Model; + }); + this.gc_maxlifetime = this.manager.options.gc_maxlifetime; + this.contextSessions = []; + } + + start(id: string, contextSession: string) { + try { + return this.read(id, contextSession); + } catch (e) { + throw e; + } + } + + open(contextSession: string) { + if (this.orm?.kernel?.type !== "CONSOLE") { + this.gc(this.gc_maxlifetime, contextSession); + if (this.entity) + return this.entity + .countDocuments({ + context: contextSession, + }) + .then((sessionCount: number) => { + this.manager.log( + `CONTEXT ${contextSession ? contextSession : "default"} MONGODB SESSIONS STORAGE ==> ${this.manager.options.handler.toUpperCase()} COUNT SESSIONS : ${sessionCount}`, + "INFO" + ); + }); + } + } + + close() { + this.gc(this.gc_maxlifetime); + return true; + } + + destroy(id: string, contextSession: string) { + if (this.entity) + return this.entity + .findOne({ + session_id: id, + context: contextSession, + }) + .then((result) => { + if (result) { + return result + .deleteOne({ + force: true, + }) + .then((session) => { + this.manager.log( + `DB DESTROY SESSION context : ${result.context} ID : ${result.session_id} DELETED` + ); + return session; + }) + .catch((error) => { + this.manager.log( + `DB DESTROY SESSION context : ${contextSession} ID : ${id}`, + "ERROR" + ); + throw error; + }); + } + }) + .catch((error) => { + this.manager.log( + `DB DESTROY SESSION context : ${contextSession} ID : ${id}`, + "ERROR" + ); + throw error; + }); + } + + gc(maxlifetime: number, contextSession?: string) { + const msMaxlifetime = (maxlifetime || this.gc_maxlifetime) * 1000; + if (contextSession) { + finderGC.call(this, msMaxlifetime, contextSession); + } else if (this.contextSessions.length) { + for (let i = 0; i < this.contextSessions.length; i++) { + finderGC.call(this, msMaxlifetime, this.contextSessions[i]); + } + } + } + + read(id: string, contextSession: string) { + let where: mongoose.FilterQuery | null = null; + if (contextSession) { + where = { + session_id: id, + context: contextSession, + }; + } else { + where = { + session_id: id, + }; + } + if (this.entity) + return this.entity + .findOne(where) + .populate([{ path: "user", strictPopulate: false }]) + .then((session) => { + if (session) { + return { + id: session.session_id, + flashBag: session.flashBag, + metaBag: session.metaBag, + Attributes: session.Attributes, + //username: session.username, + }; + } + return {}; + }) + .catch((error) => { + this.manager.log(error, "ERROR"); + throw error; + }); + } + + async write(id: string, serialize: any, contextSession?: string) { + const data = extend({}, serialize, { + session_id: id, + context: contextSession || "default", + }); + if (this.userEntity && data.username) { + const myuser = await this.userEntity.findOne({ + username: data.username.username, + }); + data.user = myuser._id; + } + if (this.entity) + return this.entity + .updateOne( + { + session_id: id, + context: contextSession || "default", + }, + data, + { + upsert: true, + } + ) + .then((result) => { + if (result.modifiedCount) { + this.manager.log(`UPDATE SESSION : ${data.session_id}`, "DEBUG"); + } + if (result.upsertedCount) { + this.manager.log(`ADD SESSION : ${data.session_id}`, "DEBUG"); + } + return data; + }) + .catch((error: Error) => { + throw error; + }); + } +} + +export default SessionStorage; diff --git a/src/packages/@nodefony/mongoose/nodefony/types/index.d.ts b/src/packages/@nodefony/mongoose/nodefony/types/index.d.ts new file mode 100644 index 0000000..57412f3 --- /dev/null +++ b/src/packages/@nodefony/mongoose/nodefony/types/index.d.ts @@ -0,0 +1,4 @@ +declare module "@nodefony/mongoose"; + +export * from "../../dist/types/index"; +export * from "../../dist/types/nodefony/service/orm"; diff --git a/src/packages/@nodefony/mongoose/package.json b/src/packages/@nodefony/mongoose/package.json new file mode 100644 index 0000000..fc717a0 --- /dev/null +++ b/src/packages/@nodefony/mongoose/package.json @@ -0,0 +1,55 @@ +{ + "name": "@nodefony/mongoose", + "version": "10.0.0", + "description": "Nodefony Framework Module Mongoose", + "contributors": [], + "main": "dist/index.js", + "type": "module", + "types": "./nodefony/types/index.d.ts", + "scripts": { + "start": "node dist/index.js", + "build": "rimraf dist && npm run rollup", + "rollup": "rollup --config rollup.config.ts --configPlugin typescript", + "dev": "rimraf dist && npm run rollup -- --watch", + "test": "node -e \"console.log('test')\"" + }, + "private": false, + "keywords": [ + "nodefony", + "javascript", + "mongoose" + ], + "dependencies": { + "mongodb": "6.3.0", + "mongoose": "8.2.0" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "25.0.7", + "@rollup/plugin-json": "6.1.0", + "@rollup/plugin-node-resolve": "15.2.3", + "@rollup/plugin-replace": "5.0.5", + "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-typescript": "11.1.6", + "@types/node": "20.11.24", + "@typescript-eslint/eslint-plugin": "7.1.1", + "@typescript-eslint/parser": "7.1.1", + "rimraf": "5.0.5", + "rollup": "4.12.1", + "typescript": "5.3.3" + }, + "peerDependencies": {}, + "repository": { + "type": "git", + "url": "git://github.com/nodefony/nodefony.git", + "directory": "src/packages/@nodefony/mongoose" + }, + "license": "CECILL-B", + "licenses": [ + { + "type": "CECILL-B", + "url": "http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html" + } + ], + "author": "Christophe CAMENSULI ", + "readmeFilename": "readme.md" +} diff --git a/src/packages/@nodefony/mongoose/rollup.config.ts b/src/packages/@nodefony/mongoose/rollup.config.ts new file mode 100644 index 0000000..745ea69 --- /dev/null +++ b/src/packages/@nodefony/mongoose/rollup.config.ts @@ -0,0 +1,82 @@ +// rollup.config.ts +import path from "node:path"; +import { defineConfig, Plugin, RollupOptions } from "rollup"; +import nodeResolve from "@rollup/plugin-node-resolve"; +import typescript from "@rollup/plugin-typescript"; +//import commonjs from "@rollup/plugin-commonjs"; +//import json from "@rollup/plugin-json"; +//import copy from "rollup-plugin-copy"; + +const external: string[] = ["nodefony", "mongodb", "mongoose", "tslib"]; + +const sharedNodeOptions = defineConfig({ + treeshake: { + moduleSideEffects: "no-external", + propertyReadSideEffects: false, + tryCatchDeoptimization: false, + }, + output: { + dir: "./dist", + entryFileNames: `[name].js`, + //chunkFileNames: "node/chunks/dep-[hash].js", + exports: "auto", + format: "es", + }, + onwarn(warning, warn) { + if (warning.message.includes("Circular dependency")) { + return; + } + warn(warning); + }, +}); + +function createNodePlugins( + isProduction: boolean, + sourceMap: boolean, + declarationDir: string | false +): Plugin[] { + const tab = [ + nodeResolve({ preferBuiltins: true }), + typescript({ + tsconfig: path.resolve("tsconfig.json"), + sourceMap, + declaration: declarationDir !== false, + declarationDir: declarationDir !== false ? declarationDir : undefined, + }), + // commonjs({ + // extensions: [".js"], + // //ignoreDynamicRequires: true + // dynamicRequireTargets: [], + // }), + //json(), + //copy({ + // targets: [], + //}), + ]; + if (isProduction) { + //tab.push(terser()); + } + return tab; +} + +function createNodeConfig(isProduction: boolean): RollupOptions { + return defineConfig({ + //input, + input: "./index.ts", + ...sharedNodeOptions, + output: { + ...sharedNodeOptions.output, + sourcemap: !isProduction, + preserveModules: !isProduction, + preserveModulesRoot: "nodefony", + }, + external, + plugins: [...createNodePlugins(isProduction, true, "dist/types")], + }); +} + +export default (commandLineArgs: any): RollupOptions => { + const isDev = commandLineArgs.watch; + const isProduction = !isDev; + return createNodeConfig(isProduction); +}; diff --git a/src/packages/@nodefony/sequelize/tsconfig.base.json b/src/packages/@nodefony/mongoose/tsconfig.json similarity index 72% rename from src/packages/@nodefony/sequelize/tsconfig.base.json rename to src/packages/@nodefony/mongoose/tsconfig.json index 9b5ead5..5a88bf4 100644 --- a/src/packages/@nodefony/sequelize/tsconfig.base.json +++ b/src/packages/@nodefony/mongoose/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "rootDir": "./", "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "ES2022", @@ -14,5 +15,12 @@ "declaration": true, "declarationDir": "./dist/types", "outDir": "./dist" - } + }, + "include": [ + "index.ts", + "rollup.config.ts", + "src/**/*.ts", + "nodefony/**/*.ts" + ], + "exclude": ["node_modules", "dist"] } diff --git a/src/packages/@nodefony/sequelize/index.ts b/src/packages/@nodefony/sequelize/index.ts index 35c8d6d..0697a3d 100644 --- a/src/packages/@nodefony/sequelize/index.ts +++ b/src/packages/@nodefony/sequelize/index.ts @@ -1,14 +1,30 @@ -import { Service, Kernel, Module, services } from "nodefony"; +import { Entity, Kernel, Module, services, entities } from "nodefony"; import config from "./nodefony/config/config"; import orm from "./nodefony/service/orm"; -import { sequelize } from "./nodefony/service/orm"; +//import { sequelize } from "./nodefony/service/orm"; +import Session from "./nodefony/entity/sessionEntity"; +import Command from "./nodefony/command/sync"; +import { Models } from "./nodefony/service/orm"; +// import sequelize, { +// Model, +// ConnectionOptions, +// Transaction, +// Options, +// Sequelize as NativeSequelize, +// ModelStatic, +// } from "sequelize"; + +import * as sequelize from "sequelize"; +import sequelizeStorage from "./nodefony/src/sequelizeStorage"; @services([orm]) +@entities([Session]) class Sequelize extends Module { constructor(kernel: Kernel) { super("sequelize", kernel, import.meta.url, config); + this.addCommand(Command); } } export default Sequelize; -export { sequelize }; +export { sequelize, entities, Models, sequelizeStorage }; diff --git a/src/packages/@nodefony/sequelize/nodefony/command/sync.ts b/src/packages/@nodefony/sequelize/nodefony/command/sync.ts new file mode 100644 index 0000000..9926a45 --- /dev/null +++ b/src/packages/@nodefony/sequelize/nodefony/command/sync.ts @@ -0,0 +1,104 @@ +import { + OptionsCommandInterface, + CliKernel, + Command, + Connector, +} from "nodefony"; +import service, { ConnectorSequelise, sequelize } from "../service/orm"; + +const options: OptionsCommandInterface = { + showBanner: true, + kernelEvent: "onReady", +}; + +class SequelizeCommand extends Command { + service: service | null = null; + constructor(cli: CliKernel) { + super("sequelize", "Orm sequelize ", cli, options); + this.addArgument("[sync]", "Synchronize Entities "); + this.addArgument("[migrate]", "migration "); + this.addOption("-f, --force ", "drop entities before for sync only"); + this.addOption("-a, --alter", "try alter entities for sync only"); + } + + override async onKernelBoot(): Promise { + this.service = this.get("sequelize"); + } + + override async generate( + arg: string, + name: string, + options: { force?: boolean; alter?: boolean } + ): Promise { + try { + switch (arg) { + case "sync": + await this.syncConnectors(name, options); + console.log("psasa"); + return this; + } + return this; + } catch (e) { + this.log(e, "ERROR"); + throw e; + } + } + + async syncConnectors( + name: string, + options: { force?: boolean; alter?: boolean } + ): Promise { + return new Promise(async (resolve, reject) => { + const tab: any[] = []; + //this.service?.once("onOrmReady", async () => { + for (const connectorName in this.service?.connections) { + const connector: ConnectorSequelise = + this.service?.connections[connectorName]; + if (connector && connector.state === "CONNECTED") { + const res = await this.sync(connector, options).catch((e) => { + return reject(e); + }); + + tab.push(res); + this.log( + `DATABASE : ${connector.type} CONNECTION : ${connectorName}`, + "INFO" + ); + } + } + return resolve(this); + //}); + }); + } + + async sync( + connector: ConnectorSequelise, + options: { force?: boolean; alter?: boolean } + ) { + const { force, alter } = options; + return connector.db + ?.sync({ + force, + alter, + logging: (value: string) => this.log(value, "INFO"), + hooks: true, + }) + .then((db) => { + this.log( + `DATABASE :${db.config.database} CONNECTION : ${connector.name}`, + "INFO", + "SYNC SEQUELIZE" + ); + return db; + }) + .catch((error: Error) => { + this.log( + `DATABASE :${connector.db?.config.database} CONNECTION : ${connector.name} : ${error}`, + "ERROR" + ); + throw error; + }); + } +} + +export default SequelizeCommand; diff --git a/src/packages/@nodefony/sequelize/nodefony/config/config.ts b/src/packages/@nodefony/sequelize/nodefony/config/config.ts index cbbd9ad..4c608e3 100644 --- a/src/packages/@nodefony/sequelize/nodefony/config/config.ts +++ b/src/packages/@nodefony/sequelize/nodefony/config/config.ts @@ -1,5 +1,55 @@ -import { kernel } from "nodefony"; +import path from "node:path"; +import nodefony, { Kernel } from "nodefony"; export default { - watch: false, + debug: true, + strategy: "migrate", // sync || migrate || none when nodefony build or nodefony install + // watch: true, + connectors: { + nodefony: { + driver: "sqlite", + dbname: path.resolve( + (nodefony.kernel as Kernel).path, + "nodefony", + "databases", + "nodefony.db" + ), + options: { + dialect: "sqlite", + // isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE, + retry: { + match: [ + // Sequelize.ConnectionError, + // Sequelize.ConnectionTimedOutError, + // Sequelize.TimeoutError, + /Deadlock/i, + "SQLITE_BUSY", + ], + max: 5, + }, + pool: { + max: 5, + min: 0, + idle: 10000, + }, + }, + }, + }, + migrations: { + storage: "sequelize", // sequelize || memory || json + path: path.resolve( + (nodefony.kernel as Kernel).path, + "nodefony", + "migrations", + "sequelize" + ), + seedeersPath: path.resolve( + (nodefony.kernel as Kernel).path, + "nodefony", + "migrations", + "seedeers" + ), + storageSeedeers: "json", + options: {}, + }, }; diff --git a/src/packages/@nodefony/sequelize/nodefony/decorators/decorator.ts b/src/packages/@nodefony/sequelize/nodefony/decorators/decorator.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/packages/@nodefony/sequelize/nodefony/entity/sessionEntity.ts b/src/packages/@nodefony/sequelize/nodefony/entity/sessionEntity.ts new file mode 100644 index 0000000..9050f75 --- /dev/null +++ b/src/packages/@nodefony/sequelize/nodefony/entity/sessionEntity.ts @@ -0,0 +1,110 @@ +import nodefony, { Entity, Module } from "nodefony"; + +import sequelize, { + Model, + ConnectionOptions, + Transaction, + Options, + DataTypes, + NOW, + Sequelize, + Optional, + ModelStatic, +} from "sequelize"; + +type Models = { + [key: string]: ModelStatic>; +}; + +class Session extends Entity { + constructor(module: Module) { + /* + * @param module instance + * @param Entity name + * @param orm name + * @param connector name + */ + super(module, "session", "sequelize", "nodefony"); + } + + getSchema() { + return { + session_id: { + type: DataTypes.STRING(126), + primaryKey: true, + }, + context: { + type: DataTypes.STRING(126), + defaultValue: "default", + }, + /* username: { + type: DataTypes.STRING(126), + defaultValue: "", + allowNull: true + },*/ + Attributes: { + type: DataTypes.JSON, + }, + flashBag: { + type: DataTypes.JSON, + }, + metaBag: { + type: DataTypes.JSON, + }, + createdAt: { + type: DataTypes.DATE, + defaultValue: NOW, + }, + updatedAt: { + type: DataTypes.DATE, + defaultValue: NOW, + }, + }; + } + + override registerModel(db: Sequelize): typeof Model { + class SessionModel extends Model { + declare session_id: string; + declare context: string; + declare Attributes: JSON; + declare flashBag: JSON; + declare metaBag: JSON; + declare createdAt: Date; + declare updatedAt: Date; + + public static associate(models: Models): void { + if (models.User) { + models.User.hasMany(SessionModel, { + foreignKey: { + allowNull: true, + name: "username", + }, + onDelete: "CASCADE", + onUpdate: "CASCADE", + }); + SessionModel.belongsTo(models.User, { + foreignKey: { + allowNull: true, + name: "username", + }, + onDelete: "CASCADE", + onUpdate: "CASCADE", + }); + } else { + nodefony.kernel?.log( + "ENTITY ASSOCIATION user NOT AVAILABLE", + "WARNING", + `ENTITY ${this.name}` + ); + } + } + } + SessionModel.init(this.getSchema(), { + sequelize: db, + modelName: this.name, + }); + return SessionModel; + } +} + +export default Session; diff --git a/src/packages/@nodefony/sequelize/nodefony/service/orm.ts b/src/packages/@nodefony/sequelize/nodefony/service/orm.ts index 0c1bdfe..4372dfb 100644 --- a/src/packages/@nodefony/sequelize/nodefony/service/orm.ts +++ b/src/packages/@nodefony/sequelize/nodefony/service/orm.ts @@ -1,19 +1,343 @@ -import { Service, Module, Container, Event } from "nodefony"; -import sequelize from "sequelize"; +import { resolve } from "node:path"; +import { + Service, + Module, + Container, + Event, + Orm, + Entity, + Connector, + typeOf, + Severity, + Error as nodefonyError, +} from "nodefony"; +import sequelize, { + Model, + ConnectionOptions, + Transaction, + Options, + Sequelize as NativeSequelize, + ModelStatic, +} from "sequelize"; const serviceName: string = "sequelize"; +export type Strategy = "sync" | "migrate" | "none"; -class Sequelize extends Service { +export type Models = { + [key: string]: ModelStatic>; +}; + +interface Db { + close: () => Promise; +} + +interface Config { + options: Options; + [key: string]: any; +} + +class ConnectorSequelise extends Connector { + declare db: NativeSequelize | null; + constructor(name: string, type: string, options: Config, orm: Sequelize) { + super(name, type, options, orm); + this.db = null; + } + + override onError(err: Error) { + if (this.state !== "DISCONNECTED") { + this.orm.kernel?.fire("onError", err, this); + } + // this.log(err, "ERROR"); + // this.log(this.settings, "INFO", `CONFIGURATION Sequelize ${this.name}`); + if (err.code) { + switch (err.code) { + case "PROTOCOL_CONNECTION_LOST": + case "ECONNREFUSED": + this.state = "DISCONNECTED"; + return new nodefonyError(err.message, err.code); + // return { + // status: 500, + // code: err.code, + // message: err.message, + // }; + default: + return err; + } + } else { + return err; + } + } + + override async setConnection(db: NativeSequelize, config: Options) { + await super.setConnection(db, config); + /* this.db.afterDisconnect((connection)=>{ + this.log(connection,"WARNING"); + }); + this.db.beforeConnect((config)=>{ + this.log(config, "WARNING"); + });*/ + return db; + } + + override async connect( + type: string, + config: Config + ): Promise { + try { + let logging; + if (this.orm.debug) { + logging = (value: any) => { + this.log(value, "INFO"); + }; + } else { + logging = false; + } + const options: Options = { + storage: resolve(config.dbname), + username: config.username, + password: config.password, + host: config.host, + port: config.port, + database: config.dbname, + logging, + ...config.options, + }; + let conn: NativeSequelize; + if (config.credentials && typeOf(config.credentials) === "function") { + this.log("Try Get Credentials (async method)"); + const auth = await config.credentials(this).catch((e: Error) => { + throw e; + }); + if (auth.username) { + this.log(`Add username Credential for connector ${this.name}`); + options.username = auth.username; + } else { + this.log("Credentials (async method) no username secret", "WARNING"); + } + if (auth.password) { + this.log(`Add password Credential for connector ${this.name}`); + options.password = auth.password; + } else { + this.log("Credentials (async method) no password secret", "WARNING"); + } + this.log(`Success Credential (async method) ${auth}`, "DEBUG"); + } + //console.log(options); + conn = new sequelize.Sequelize(options); + return conn + .authenticate() + .then(() => { + return this.setConnection(conn, options); + }) + .catch((err) => { + this.log(`Unable to connect to the database : ${err}`, "ERROR"); + //this.onError(err); + //this.orm.fire("onErrorConnection", this, err); + throw err; + }); + } catch (e) { + console.log("pasasasa"); + this.onError(e as Error); + this.orm.fire("onErrorConnection", this, e); + throw e; + } + } + + async close() { + if (this.db) { + this.orm.log(`Close connection ${this.name}`); + return await this.db.close().catch((e: Error) => { + this.orm.log(e, "ERROR"); + throw e; + }); + } + return Promise.resolve(); + } +} + +class Sequelize extends Orm { static engine: typeof sequelize = sequelize; + engine: typeof sequelize = sequelize; + strategy: Strategy = "migrate"; + isAssociated: boolean; + forceAssociated: boolean; + module: Module; constructor(module: Module) { - super( - serviceName, - module.container as Container, - module.notificationsCenter as Event, - module.options + super(serviceName, module, module.options); + this.strategy = "migrate"; + this.isAssociated = false; + this.forceAssociated = false; + this.module = module; + module.kernel?.once( + "onTerminate", + async () => await this.closeConnections() ); + module.kernel?.once("onBoot", async () => { + await this.boot().catch((e: Error) => { + throw e; + }); + }); + } + + async boot() { + return new Promise(async (resolve, reject) => { + super.boot(); + if ( + this.options.connectors && + Object.keys(this.options.connectors).length + ) { + for (const name in this.options.connectors) { + await this.createConnection( + name, + this.options.connectors[name] + ).catch((e) => { + return reject(e); + }); + } + } else { + this.log("onOrmReady", "DEBUG", "EVENTS SEQUELIZE"); + try { + await this.fireAsync("onOrmReady", this); + this.ready = true; + return resolve(this); + } catch (e) { + this.log(e, "ERROR", "EVENTS onOrmReady"); + return; + } + } + this.prependListener("onOrmReady", async () => { + if (this.isAssociated && !this.forceAssociated) { + return; + } + for (const entity in this.entities) { + const model = this.entities[entity].model as ModelStatic; + //@ts-ignore + if (model && model.associate) { + await this.entities[entity].model.associate( + this.entities[entity].db.models + ); + this.log( + `ASSOCIATE model : ${this.entities[entity].model.name}`, + "DEBUG" + ); + } + } + this.isAssociated = true; + }); + this.kernel?.once("onReady", () => { + if (this.kernel?.type === "SERVER") { + this.displayTable("INFO"); + } else { + this.displayTable(); + } + }); + return resolve(this); + }); + } + + async createConnection(name: string, config: Config) { + try { + if (this.connections[name]) { + delete this.connections[name]; + } + // eslint-disable-next-line new-cap + this.connections[name] = new ConnectorSequelise( + name, + config.driver, + config, + this + ); + } catch (e) { + throw e; + } + return await this.connections[name] + .connect(config.driver, config) + .catch((e: Error) => { + throw e; + }); + } + + getEntity(name: String): Model | null | Record { + if (name) { + if ((name as string) in this.entities) { + const entity: Entity = this.entities[name as string]; + return entity.model as Model; + } + return null; + } + return this.entities; + } + + getNodefonyEntity(name: string): Entity | null | Record { + if (name) { + if (name in this.entities) { + return this.entities[name]; + } + return null; + } + return this.entities; + } + + getConnection(name: string) { + if (this.connections[name]) { + return this.connections[name].db as object; + } + return null; + } + + getConnections(name: string) { + if (name) { + return this.getConnection(name); + } + return this.connections; + } + + getConnectorSettings(tab: any[]) { + for (const dbname in this.options.connectors) { + const conn = ["", "", "", "", ""]; + conn[0] = dbname; + for (const data in this.options.connectors[dbname]) { + switch (data) { + case "dbname": + conn[2] = this.options.connectors[dbname][data]; + break; + case "options": + conn[1] = this.options.connectors[dbname][data].dialect; + if (this.options.connectors[dbname][data].host) { + conn[3] = `${this.options.connectors[dbname][data].host}:${this.options.connectors[dbname][data].port}`; + } + break; + default: + } + } + if (this.connections[dbname]) { + conn[4] = this.connections[dbname].state; + } + tab.push(conn); + } + return tab; + } + + displayTable(severity: Severity = "DEBUG") { + const options = { + head: ["CONNECTOR NAME", "DRIVER", "NAME DATABASE", "HOST", "status"], + }; + const table = this.kernel?.cli?.displayTable([], options); + if (table) { + this.getConnectorSettings(table); + + const res = table.toString(); + this.log(`ORM CONNECTORS LIST : \n${res}`, severity); + return res; + } + } + + async closeConnections() { + for (const connection in this.connections) { + await this.connections[connection].close(); + } } } export default Sequelize; -export { sequelize }; +export { sequelize, ConnectorSequelise }; diff --git a/src/packages/@nodefony/sequelize/nodefony/src/sequelizeStorage.ts b/src/packages/@nodefony/sequelize/nodefony/src/sequelizeStorage.ts new file mode 100644 index 0000000..e9bd05b --- /dev/null +++ b/src/packages/@nodefony/sequelize/nodefony/src/sequelizeStorage.ts @@ -0,0 +1,361 @@ +import { + Sequelize, + DataTypes, + Model, + Transaction, + Op, + DestroyOptions, + ModelStatic, + QueryOptions, + QueryInterfaceIndexOptions, + WhereOptions, + FindOptions, +} from "sequelize"; +import sessionEntity from "../entity/sessionEntity"; +import { SessionsService } from "@nodefony/http"; +import orm, { sequelize } from "../service/orm"; +import { Severity, extend } from "nodefony"; + +class SessionStorage { + manager: SessionsService; + orm: orm; + entity: ModelStatic; + userEntity?: ModelStatic; + dialect: any; + applyTransaction: boolean = false; + gc_maxlifetime: number; + contextSessions: string[] = []; + + constructor(manager: SessionsService) { + this.manager = manager; + this.orm = this.manager.get("sequelize"); + this.entity = {} as ModelStatic; + this.orm.once("onOrmReady", () => { + this.entity = this.orm.getEntity( + "session" + ) as unknown as ModelStatic; + if (!this.entity) { + throw new Error("Entity session not ready"); + } + const seq = this.entity?.sequelize as unknown as orm; + this.dialect = seq.options.dialect; + this.applyTransaction = this.manager.options.applyTransaction; + if (this.applyTransaction === true) { + this.applyTransaction = this.dialect !== "sqlite"; + } + this.userEntity = this.orm.getEntity( + "user" + ) as unknown as ModelStatic; + }); + this.gc_maxlifetime = this.manager.options.gc_maxlifetime; + this.contextSessions = []; + } + + async finderGC(msMaxlifetime: number, contextSession?: string) { + if (!this.entity) { + return Promise.resolve(true); + } + let transaction: Transaction | null = null; + if (this.applyTransaction) { + transaction = + (await this.entity?.sequelize?.transaction()) as Transaction; + } + const now = new Date(); + const mydate = new Date(now.getTime() - msMaxlifetime); + const query: DestroyOptions = {}; + if (transaction) { + query.transaction = transaction; + } + //query.attributes = ["context", "updatedAt", "session_id"]; + query.force = true; + query.where = { + updatedAt: { + // $lt: mydate + [Op.lt]: mydate, + }, + }; + if (contextSession) { + query.where.context = contextSession; + } + return this.entity + .destroy(query) + .then(async (results) => { + if (transaction) { + await transaction.commit(); + } + let severity: Severity = "DEBUG"; + // if (results) { + // severity = "INFO"; + // } + this.manager.log( + `Context : ${contextSession || "default"} GARBAGE COLLECTOR ==> ${results} DELETED`, + severity + ); + return results; + }) + .catch(async (error) => { + if (transaction && !transaction.finished) { + await transaction.rollback(); + } + throw error; + }); + } + + start(id: string, contextSession: string) { + try { + return this.read(id, contextSession); + } catch (e) { + throw e; + } + } + + async open(contextSession: string) { + if (this.orm?.kernel?.type !== "CONSOLE") { + await this.gc(this.gc_maxlifetime, contextSession); + if (!this.entity) { + return Promise.resolve(0); + } + let transaction: Transaction | null = null; + if (this.applyTransaction) { + transaction = + (await this.entity?.sequelize?.transaction()) as Transaction; + } + return this.entity + .count({ + where: { + context: contextSession, + }, + transaction, + }) + .then(async (sessionCount) => { + if (transaction) { + await transaction.commit(); + } + const log = `CONTEXT ${contextSession ? contextSession : "default"} SEQUELIZE SESSIONS STORAGE ==> ${this.manager.options.handler.toUpperCase()} COUNT SESSIONS : ${sessionCount}`; + this.manager.log(log, "INFO"); + }) + .catch(async (e) => { + if (transaction && !transaction.finished) { + await transaction.rollback(); + } + this.manager.log(e, "ERROR", "SESSION Storage"); + }); + } + } + + close() { + this.gc(this.gc_maxlifetime); + return true; + } + + async destroy(id: string, contextSession: string) { + if (!this.entity) { + throw new Error("Entity Session not ready"); + } + let transaction: Transaction | null = null; + if (this.applyTransaction) { + transaction = + (await this.entity?.sequelize?.transaction()) as Transaction; + } + const where: WhereOptions = { + session_id: id, + }; + if (contextSession) { + where.context = contextSession; + } + return this.entity + .findOne({ + where, + transaction, + }) + .then((result) => { + if (result) { + return result + .destroy({ + force: true, + transaction, + }) + .then(async (session: any) => { + if (transaction) { + await transaction.commit(); + } + this.manager.log( + `DB DESTROY SESSION context : ${session.context} ID : ${session.session_id} DELETED` + ); + }) + .catch(async (error: Error) => { + if (transaction && !transaction.finished) { + await transaction.rollback(); + } + this.manager.log( + `DB DESTROY SESSION context : ${contextSession} ID : ${id}`, + "ERROR" + ); + throw error; + }); + } + }) + .catch(async (error) => { + if (transaction && !transaction.finished) { + await transaction.rollback(); + } + throw error; + }); + } + + async gc(maxlifetime: number, contextSession?: string) { + const msMaxlifetime = (maxlifetime || this.gc_maxlifetime) * 1000; + if (contextSession) { + await this.finderGC(msMaxlifetime, contextSession); + } else if (this.contextSessions.length) { + for (let i = 0; i < this.contextSessions.length; i++) { + await this.finderGC(msMaxlifetime, this.contextSessions[i]); + } + } + } + + async read(id: string, contextSession: string) { + if (!this.entity) { + throw new Error("Entity Session not ready"); + } + let myWhere: FindOptions; + let transaction: Transaction | null = null; + if (this.applyTransaction) { + transaction = + (await this.entity?.sequelize?.transaction()) as Transaction; + } + const include = []; + if (this.userEntity) { + include.push({ + model: this.userEntity, + required: false, + }); + } + if (contextSession) { + myWhere = { + where: { + session_id: id, + context: contextSession, + }, + include, + transaction, + }; + } else { + myWhere = { + where: { + session_id: id, + }, + include, + transaction, + }; + } + return this.entity + .findOne(myWhere) + .then(async (result) => { + if (result) { + if (transaction) { + await transaction.commit(); + } + return { + id: result.session_id, + flashBag: result.flashBag, + metaBag: result.metaBag, + Attributes: result.Attributes, + created: result.createdAt, + updated: result.updatedAt, + username: result.username, + }; + } + return {}; + }) + .catch(async (error) => { + if (transaction && !transaction.finished) { + await transaction.rollback(); + } + this.manager.log(error, "ERROR"); + throw error; + }); + } + + async write(id: string, serialize: any, contextSession: string) { + if (!this.entity) { + throw new Error("Entity Session not ready"); + } + let transaction: Transaction | null = null; + if (this.applyTransaction) { + transaction = + (await this.entity?.sequelize?.transaction()) as Transaction; + } + const data = extend({}, serialize, { + session_id: id, + context: contextSession || "default", + }); + if (data.username) { + data.username = data.username.username; + } + const opt: FindOptions = { + where: { + session_id: id, + context: contextSession || "default", + }, + transaction, + }; + return this.entity + .findOne(opt) + .then((result) => { + if (result) { + return result + .update(data, { + where: { + session_id: id, + context: contextSession || "default", + }, + transaction, + }) + .then(async (session) => { + if (transaction) { + await transaction.commit(); + } + return session; + }) + .catch(async (error: Error) => { + if (transaction && !transaction.finished) { + await transaction.rollback(); + } + throw error; + }); + } + return this.entity + .create(data, { + isNewRecord: true, + transaction, + }) + .then(async (session) => { + if (transaction) { + await transaction.commit(); + } + this.manager.log( + `ADD SESSION : ${session.session_id}${session.username ? ` username :${session.username}` : ""}`, + "DEBUG" + ); + return session; + }) + .catch(async (error) => { + if (transaction && !transaction.finished) { + await transaction.rollback(); + } + throw error; + }); + }) + .catch(async (error) => { + if (transaction && !transaction.finished) { + await transaction.rollback(); + } + if (error) { + throw error; + } + }); + } +} + +export default SessionStorage; diff --git a/src/packages/@nodefony/sequelize/tsconfig.json b/src/packages/@nodefony/sequelize/tsconfig.json index d3e1eee..5a88bf4 100644 --- a/src/packages/@nodefony/sequelize/tsconfig.json +++ b/src/packages/@nodefony/sequelize/tsconfig.json @@ -1,18 +1,26 @@ { - "extends": "./tsconfig.base.json", "compilerOptions": { "rootDir": "./", - "outDir": "./dist", - //"allowJs": true + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "ES2022", + "module": "ESNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "stripInternal": true, + "declaration": true, + "declarationDir": "./dist/types", + "outDir": "./dist" }, "include": [ "index.ts", "rollup.config.ts", "src/**/*.ts", - "nodefony/**/*.ts", + "nodefony/**/*.ts" ], - "exclude": [ - "node_modules", - "dist" - ] -} \ No newline at end of file + "exclude": ["node_modules", "dist"] +}