diff --git a/.pnp.cjs b/.pnp.cjs index ae6cfe94..9dbcce3c 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -19,6 +19,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "name": "@knexion/core",\ "reference": "workspace:packages/core"\ },\ + {\ + "name": "@knexion/filter",\ + "reference": "workspace:packages/filter"\ + },\ {\ "name": "knexion-test-utils",\ "reference": "workspace:packages/knexion-test-utils"\ @@ -26,15 +30,31 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { {\ "name": "@knexion/page-pagination",\ "reference": "workspace:packages/page-pagination"\ + },\ + {\ + "name": "@knexion/soft-delete",\ + "reference": "workspace:packages/soft-delete"\ + },\ + {\ + "name": "@knexion/sort",\ + "reference": "workspace:packages/sort"\ + },\ + {\ + "name": "@knexion/transform",\ + "reference": "workspace:packages/transform"\ }\ ],\ "enableTopLevelFallback": true,\ "ignorePatternData": "(^(?:\\\\.yarn\\\\/sdks(?:\\\\/(?!\\\\.{1,2}(?:\\\\/|$))(?:(?:(?!(?:^|\\\\/)\\\\.{1,2}(?:\\\\/|$)).)*?)|$))$)",\ "fallbackExclusionList": [\ ["@knexion/core", ["virtual:3aec43cea6dc4660ac6334a335dae0189587e3f9916f01b1459e7224e0298612046a223f3272ec480f1a5a5a644266d2f900c21734d0baf60ae4b1ef952451f0#workspace:packages/core", "virtual:9a9781ba6366f1eef1f192004c88fb8cccb82c5fcc7fa81b2af98b9fdb4318c8755b2b1ab492482bd3c231a462d49f33b5fb6ce79d8f5c55de06e00c2f43481f#workspace:packages/core", "workspace:packages/core"]],\ + ["@knexion/filter", ["workspace:packages/filter"]],\ ["@knexion/page-pagination", ["workspace:packages/page-pagination"]],\ + ["@knexion/soft-delete", ["workspace:packages/soft-delete"]],\ + ["@knexion/sort", ["workspace:packages/sort"]],\ + ["@knexion/transform", ["workspace:packages/transform"]],\ ["knexion", ["workspace:."]],\ - ["knexion-test-utils", ["virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#workspace:packages/knexion-test-utils", "virtual:ed66cff0f9f4c9d10838046a7e70c9f271139bebb00c1200d90fb4bb3dc0cba477174e4323d89084328c18f197db6cce84ccf029f20cee7dac8cee84514be643#workspace:packages/knexion-test-utils", "workspace:packages/knexion-test-utils"]]\ + ["knexion-test-utils", ["virtual:2a92614a8c3b79211f5ccf6e6638e30d7c1947e3c29459cd71bc3ccac90becf0153a0b6a8b479b3783ccab09269d0357ef23fbf89e4a1b27c6594b3b8ab3cfb6#workspace:packages/knexion-test-utils", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#workspace:packages/knexion-test-utils", "workspace:packages/knexion-test-utils"]]\ ],\ "fallbackPool": [\ ],\ @@ -1595,7 +1615,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/rxjs", null],\ ["jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.2.2"],\ ["knex", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:2.3.0"],\ - ["knexion-test-utils", "virtual:ed66cff0f9f4c9d10838046a7e70c9f271139bebb00c1200d90fb4bb3dc0cba477174e4323d89084328c18f197db6cce84ccf029f20cee7dac8cee84514be643#workspace:packages/knexion-test-utils"],\ + ["knexion-test-utils", "virtual:2a92614a8c3b79211f5ccf6e6638e30d7c1947e3c29459cd71bc3ccac90becf0153a0b6a8b479b3783ccab09269d0357ef23fbf89e4a1b27c6594b3b8ab3cfb6#workspace:packages/knexion-test-utils"],\ ["reflect-metadata", "npm:0.1.13"],\ ["rxjs", "npm:7.5.7"],\ ["ts-jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.0.3"],\ @@ -1640,6 +1660,30 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "SOFT"\ }]\ ]],\ + ["@knexion/filter", [\ + ["workspace:packages/filter", {\ + "packageLocation": "./packages/filter/",\ + "packageDependencies": [\ + ["@knexion/filter", "workspace:packages/filter"],\ + ["@faker-js/faker", "npm:7.6.0"],\ + ["@knexion/core", "virtual:3aec43cea6dc4660ac6334a335dae0189587e3f9916f01b1459e7224e0298612046a223f3272ec480f1a5a5a644266d2f900c21734d0baf60ae4b1ef952451f0#workspace:packages/core"],\ + ["@nestjs/common", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@nestjs/core", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@nestjs/testing", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@types/jest", "npm:29.2.0"],\ + ["@types/node", "npm:16.18.0"],\ + ["jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.2.2"],\ + ["knex", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:2.3.0"],\ + ["knexion-test-utils", "virtual:2a92614a8c3b79211f5ccf6e6638e30d7c1947e3c29459cd71bc3ccac90becf0153a0b6a8b479b3783ccab09269d0357ef23fbf89e4a1b27c6594b3b8ab3cfb6#workspace:packages/knexion-test-utils"],\ + ["reflect-metadata", "npm:0.1.13"],\ + ["rxjs", "npm:7.5.7"],\ + ["ts-jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.0.3"],\ + ["tslib", "npm:2.4.0"],\ + ["typescript", "patch:typescript@npm%3A4.8.4#~builtin::version=4.8.4&hash=701156"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ ["@knexion/page-pagination", [\ ["workspace:packages/page-pagination", {\ "packageLocation": "./packages/page-pagination/",\ @@ -1654,7 +1698,79 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/node", "npm:16.18.0"],\ ["jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.2.2"],\ ["knex", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:2.3.0"],\ - ["knexion-test-utils", "virtual:ed66cff0f9f4c9d10838046a7e70c9f271139bebb00c1200d90fb4bb3dc0cba477174e4323d89084328c18f197db6cce84ccf029f20cee7dac8cee84514be643#workspace:packages/knexion-test-utils"],\ + ["knexion-test-utils", "virtual:2a92614a8c3b79211f5ccf6e6638e30d7c1947e3c29459cd71bc3ccac90becf0153a0b6a8b479b3783ccab09269d0357ef23fbf89e4a1b27c6594b3b8ab3cfb6#workspace:packages/knexion-test-utils"],\ + ["reflect-metadata", "npm:0.1.13"],\ + ["rxjs", "npm:7.5.7"],\ + ["ts-jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.0.3"],\ + ["tslib", "npm:2.4.0"],\ + ["typescript", "patch:typescript@npm%3A4.8.4#~builtin::version=4.8.4&hash=701156"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ + ["@knexion/soft-delete", [\ + ["workspace:packages/soft-delete", {\ + "packageLocation": "./packages/soft-delete/",\ + "packageDependencies": [\ + ["@knexion/soft-delete", "workspace:packages/soft-delete"],\ + ["@faker-js/faker", "npm:7.6.0"],\ + ["@knexion/core", "virtual:3aec43cea6dc4660ac6334a335dae0189587e3f9916f01b1459e7224e0298612046a223f3272ec480f1a5a5a644266d2f900c21734d0baf60ae4b1ef952451f0#workspace:packages/core"],\ + ["@nestjs/common", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@nestjs/core", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@nestjs/testing", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@types/jest", "npm:29.2.0"],\ + ["@types/node", "npm:16.18.0"],\ + ["jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.2.2"],\ + ["knex", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:2.3.0"],\ + ["knexion-test-utils", "virtual:2a92614a8c3b79211f5ccf6e6638e30d7c1947e3c29459cd71bc3ccac90becf0153a0b6a8b479b3783ccab09269d0357ef23fbf89e4a1b27c6594b3b8ab3cfb6#workspace:packages/knexion-test-utils"],\ + ["reflect-metadata", "npm:0.1.13"],\ + ["rxjs", "npm:7.5.7"],\ + ["ts-jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.0.3"],\ + ["tslib", "npm:2.4.0"],\ + ["typescript", "patch:typescript@npm%3A4.8.4#~builtin::version=4.8.4&hash=701156"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ + ["@knexion/sort", [\ + ["workspace:packages/sort", {\ + "packageLocation": "./packages/sort/",\ + "packageDependencies": [\ + ["@knexion/sort", "workspace:packages/sort"],\ + ["@faker-js/faker", "npm:7.6.0"],\ + ["@knexion/core", "virtual:3aec43cea6dc4660ac6334a335dae0189587e3f9916f01b1459e7224e0298612046a223f3272ec480f1a5a5a644266d2f900c21734d0baf60ae4b1ef952451f0#workspace:packages/core"],\ + ["@nestjs/common", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@nestjs/core", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@nestjs/testing", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@types/jest", "npm:29.2.0"],\ + ["@types/node", "npm:16.18.0"],\ + ["jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.2.2"],\ + ["knex", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:2.3.0"],\ + ["knexion-test-utils", "virtual:2a92614a8c3b79211f5ccf6e6638e30d7c1947e3c29459cd71bc3ccac90becf0153a0b6a8b479b3783ccab09269d0357ef23fbf89e4a1b27c6594b3b8ab3cfb6#workspace:packages/knexion-test-utils"],\ + ["reflect-metadata", "npm:0.1.13"],\ + ["rxjs", "npm:7.5.7"],\ + ["ts-jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.0.3"],\ + ["tslib", "npm:2.4.0"],\ + ["typescript", "patch:typescript@npm%3A4.8.4#~builtin::version=4.8.4&hash=701156"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ + ["@knexion/transform", [\ + ["workspace:packages/transform", {\ + "packageLocation": "./packages/transform/",\ + "packageDependencies": [\ + ["@knexion/transform", "workspace:packages/transform"],\ + ["@faker-js/faker", "npm:7.6.0"],\ + ["@knexion/core", "virtual:3aec43cea6dc4660ac6334a335dae0189587e3f9916f01b1459e7224e0298612046a223f3272ec480f1a5a5a644266d2f900c21734d0baf60ae4b1ef952451f0#workspace:packages/core"],\ + ["@nestjs/common", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@nestjs/core", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@nestjs/testing", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ + ["@types/jest", "npm:29.2.0"],\ + ["@types/node", "npm:16.18.0"],\ + ["jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.2.2"],\ + ["knex", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:2.3.0"],\ + ["knexion-test-utils", "virtual:2a92614a8c3b79211f5ccf6e6638e30d7c1947e3c29459cd71bc3ccac90becf0153a0b6a8b479b3783ccab09269d0357ef23fbf89e4a1b27c6594b3b8ab3cfb6#workspace:packages/knexion-test-utils"],\ ["reflect-metadata", "npm:0.1.13"],\ ["rxjs", "npm:7.5.7"],\ ["ts-jest", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:29.0.3"],\ @@ -8097,10 +8213,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["knexion-test-utils", [\ - ["virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#workspace:packages/knexion-test-utils", {\ - "packageLocation": "./.yarn/__virtual__/knexion-test-utils-virtual-3aec43cea6/1/packages/knexion-test-utils/",\ + ["virtual:2a92614a8c3b79211f5ccf6e6638e30d7c1947e3c29459cd71bc3ccac90becf0153a0b6a8b479b3783ccab09269d0357ef23fbf89e4a1b27c6594b3b8ab3cfb6#workspace:packages/knexion-test-utils", {\ + "packageLocation": "./.yarn/__virtual__/knexion-test-utils-virtual-5a62b15735/1/packages/knexion-test-utils/",\ "packageDependencies": [\ - ["knexion-test-utils", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#workspace:packages/knexion-test-utils"],\ + ["knexion-test-utils", "virtual:2a92614a8c3b79211f5ccf6e6638e30d7c1947e3c29459cd71bc3ccac90becf0153a0b6a8b479b3783ccab09269d0357ef23fbf89e4a1b27c6594b3b8ab3cfb6#workspace:packages/knexion-test-utils"],\ ["@faker-js/faker", "npm:7.6.0"],\ ["@knexion/core", "virtual:3aec43cea6dc4660ac6334a335dae0189587e3f9916f01b1459e7224e0298612046a223f3272ec480f1a5a5a644266d2f900c21734d0baf60ae4b1ef952451f0#workspace:packages/core"],\ ["@nestjs/common", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ @@ -8129,12 +8245,12 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ - ["virtual:ed66cff0f9f4c9d10838046a7e70c9f271139bebb00c1200d90fb4bb3dc0cba477174e4323d89084328c18f197db6cce84ccf029f20cee7dac8cee84514be643#workspace:packages/knexion-test-utils", {\ - "packageLocation": "./.yarn/__virtual__/knexion-test-utils-virtual-7867077121/1/packages/knexion-test-utils/",\ + ["virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#workspace:packages/knexion-test-utils", {\ + "packageLocation": "./.yarn/__virtual__/knexion-test-utils-virtual-3aec43cea6/1/packages/knexion-test-utils/",\ "packageDependencies": [\ - ["knexion-test-utils", "virtual:ed66cff0f9f4c9d10838046a7e70c9f271139bebb00c1200d90fb4bb3dc0cba477174e4323d89084328c18f197db6cce84ccf029f20cee7dac8cee84514be643#workspace:packages/knexion-test-utils"],\ + ["knexion-test-utils", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#workspace:packages/knexion-test-utils"],\ ["@faker-js/faker", "npm:7.6.0"],\ - ["@knexion/core", "virtual:9a9781ba6366f1eef1f192004c88fb8cccb82c5fcc7fa81b2af98b9fdb4318c8755b2b1ab492482bd3c231a462d49f33b5fb6ce79d8f5c55de06e00c2f43481f#workspace:packages/core"],\ + ["@knexion/core", "virtual:3aec43cea6dc4660ac6334a335dae0189587e3f9916f01b1459e7224e0298612046a223f3272ec480f1a5a5a644266d2f900c21734d0baf60ae4b1ef952451f0#workspace:packages/core"],\ ["@nestjs/common", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ ["@nestjs/core", "virtual:c26d04eaa466cff0d462370274ddb85385b330a8cad28720ac6da81b1fe844da0127444795e52f37f1b2677307d0cb7fb59a428e202f80c4e91069629ab915dc#npm:9.1.6"],\ ["@types/knex", null],\ diff --git a/packages/core/lib/execution-context.ts b/packages/core/lib/execution-context.ts index 93e7da43..ccb16cd5 100644 --- a/packages/core/lib/execution-context.ts +++ b/packages/core/lib/execution-context.ts @@ -22,9 +22,10 @@ export class ExecutionContext< } constructor( - public readonly buildQueryBuilder: ( - trx?: Knex.Transaction, - ) => Knex.QueryBuilder, + public readonly buildQueryBuilder: () => Knex.QueryBuilder< + TRecord, + TResult + >, private readonly _queryBuilder: Knex.QueryBuilder, private readonly _rawBuilder: Knex.RawBuilder, private readonly _constructorRef: Function, diff --git a/packages/core/lib/interfaces/database-options.interface.ts b/packages/core/lib/interfaces/database-options.interface.ts index 7ec1ce4e..a86f1a52 100644 --- a/packages/core/lib/interfaces/database-options.interface.ts +++ b/packages/core/lib/interfaces/database-options.interface.ts @@ -12,4 +12,6 @@ export interface DatabaseOptions { export interface SelectDatabaseOptions extends DatabaseOptions, - AliasableRepositoryDatabaseOptions {} + AliasableRepositoryDatabaseOptions { + [field: string]: unknown; +} diff --git a/packages/core/lib/repository.ts b/packages/core/lib/repository.ts index 022c9bef..c1f55fcf 100644 --- a/packages/core/lib/repository.ts +++ b/packages/core/lib/repository.ts @@ -212,7 +212,7 @@ export class Repository< handler?: Function, ): Knex.QueryBuilder { const context = new ExecutionContext( - (trx?: Knex.Transaction) => this.pureQueryBuilder(trx), + () => this.queryBuilder(options, handler), queryBuilder, this.rawBuilder(options?.transaction), this.constructor, diff --git a/packages/filter/.npmignore b/packages/filter/.npmignore new file mode 100644 index 00000000..eb3ddabb --- /dev/null +++ b/packages/filter/.npmignore @@ -0,0 +1,6 @@ +* +!package.json +!lib/**/*.js +!index.js +!index.d.ts +!*.d.ts diff --git a/packages/filter/README.md b/packages/filter/README.md new file mode 100644 index 00000000..ccdc95d6 --- /dev/null +++ b/packages/filter/README.md @@ -0,0 +1,11 @@ +# `@knexion/filter` + +> TODO: description + +## Usage + +``` +const filter = require('@knexion/filter'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/filter/index.ts b/packages/filter/index.ts new file mode 100644 index 00000000..f41a696f --- /dev/null +++ b/packages/filter/index.ts @@ -0,0 +1 @@ +export * from './lib'; diff --git a/packages/filter/jest.config.js b/packages/filter/jest.config.js new file mode 100644 index 00000000..b413e106 --- /dev/null +++ b/packages/filter/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; \ No newline at end of file diff --git a/packages/filter/lib/index.ts b/packages/filter/lib/index.ts new file mode 100644 index 00000000..c10f5c4c --- /dev/null +++ b/packages/filter/lib/index.ts @@ -0,0 +1,2 @@ +export * from './interceptors'; +export * from './interfaces'; diff --git a/packages/filter/lib/interceptors/filter-by-array.interceptor.ts b/packages/filter/lib/interceptors/filter-by-array.interceptor.ts new file mode 100644 index 00000000..e9a94bb7 --- /dev/null +++ b/packages/filter/lib/interceptors/filter-by-array.interceptor.ts @@ -0,0 +1,42 @@ +import { Observable } from 'rxjs'; +import { + addPrefixColumn, + ExecutionContext, + RepositoryInterceptor, + RepositoryInterceptorNext, + SelectDatabaseOptions, +} from '@knexion/core'; +import { FilterOptions } from '../interfaces'; + +type FilterByArrayOperator = 'in' | string; + +export class FilterByArrayInterceptor + implements RepositoryInterceptor +{ + constructor( + private readonly name: keyof TRecord, + private readonly value: string[], + private readonly operator: FilterByArrayOperator = 'in', + private readonly options: FilterOptions = {}, + ) {} + + public intercept( + context: ExecutionContext< + TRecord, + TResult, + SelectDatabaseOptions + >, + next: RepositoryInterceptorNext, + ): Observable { + const { useAlias = true } = this.options; + const column = useAlias + ? addPrefixColumn(this.name as string, context.options.alias) + : (this.name as string); + if (this.operator === 'in') { + context.queryBuilder.whereIn(column, this.value); + } else { + context.queryBuilder.where(column, this.operator, this.value); + } + return next.handle(); + } +} diff --git a/packages/filter/lib/interceptors/filter-by-number-range.interceptor.ts b/packages/filter/lib/interceptors/filter-by-number-range.interceptor.ts new file mode 100644 index 00000000..f875822c --- /dev/null +++ b/packages/filter/lib/interceptors/filter-by-number-range.interceptor.ts @@ -0,0 +1,35 @@ +import { Observable } from 'rxjs'; +import { + addPrefixColumn, + ExecutionContext, + RepositoryInterceptor, + RepositoryInterceptorNext, + SelectDatabaseOptions, +} from '@knexion/core'; +import { buildRangeNumberFilter } from '../utils'; +import { FilterOptions, RangeNumberFilter } from '../interfaces'; + +export class FilterByNumberRangeInterceptor + implements RepositoryInterceptor +{ + constructor( + private readonly name: keyof TRecord, + private readonly value: RangeNumberFilter, + private readonly options: FilterOptions = {}, + ) {} + public intercept( + context: ExecutionContext< + TRecord, + TResult, + SelectDatabaseOptions + >, + next: RepositoryInterceptorNext, + ): Observable { + const { useAlias = true } = this.options; + const column = useAlias + ? addPrefixColumn(this.name as string, context.options.alias) + : (this.name as string); + buildRangeNumberFilter(context.queryBuilder, column, this.value); + return next.handle(); + } +} diff --git a/packages/filter/lib/interceptors/filter-timestamp.interceptor.ts b/packages/filter/lib/interceptors/filter-timestamp.interceptor.ts new file mode 100644 index 00000000..973d4339 --- /dev/null +++ b/packages/filter/lib/interceptors/filter-timestamp.interceptor.ts @@ -0,0 +1,80 @@ +import { Observable } from 'rxjs'; +import { Knex } from 'knex'; +import { + addPrefixColumn, + ExecutionContext, + RepositoryInterceptor, + RepositoryInterceptorNext, + SelectDatabaseOptions, +} from '@knexion/core'; +import { RangeNumberFilter, TimestampFilter } from '../interfaces'; + +export class FilterTimestampInterceptor + implements RepositoryInterceptor +{ + constructor(private readonly timestampField: keyof TRecord) {} + + public intercept( + context: ExecutionContext< + TRecord, + TResult, + SelectDatabaseOptions + >, + next: RepositoryInterceptorNext, + ): Observable { + if (!context.options[this.timestampField as string]) { + return next.handle(); + } + const timestampFilter = context.options[this.timestampField as string]; + if (this.isNumber(timestampFilter)) { + context.queryBuilder.where( + addPrefixColumn('created_at', context.options.alias), + timestampFilter, + ); + } else { + this.buildComplexDateFilterQuery( + context.queryBuilder, + timestampFilter, + context.options, + ); + } + return next.handle(); + } + + private isNumber(dateFilter: number | TimestampFilter): dateFilter is number { + return typeof dateFilter === 'number'; + } + + private buildComplexDateFilterQuery( + queryBuilder: Knex.QueryBuilder, + dateFilter: RangeNumberFilter, + options: SelectDatabaseOptions, + ): void { + Object.entries(dateFilter).forEach(([operator, value]) => + this.getQueryForDateOperator( + queryBuilder, + operator as 'gt' | 'gte' | 'lt' | 'lte', + value, + options.alias, + ), + ); + } + + private getQueryForDateOperator( + queryBuilder: Knex.QueryBuilder, + operator: 'gt' | 'gte' | 'lt' | 'lte', + value: number, + alias?: string, + ): void { + const createdAtColumnName = addPrefixColumn('created_at', alias); + if (operator === 'gt') { + queryBuilder.where(createdAtColumnName, '>', value); + } else if (operator === 'gte') { + queryBuilder.where(createdAtColumnName, '>=', value); + } else if (operator === 'lt') { + queryBuilder.where(createdAtColumnName, '<', value); + } else if (operator === 'lte') { + queryBuilder.where(createdAtColumnName, '<=', value); + } + } +} diff --git a/packages/filter/lib/interceptors/filter.interceptor.ts b/packages/filter/lib/interceptors/filter.interceptor.ts new file mode 100644 index 00000000..6759e94e --- /dev/null +++ b/packages/filter/lib/interceptors/filter.interceptor.ts @@ -0,0 +1,50 @@ +import { Observable } from 'rxjs'; +import { + addPrefixColumn, + ExecutionContext, + RepositoryInterceptor, + RepositoryInterceptorNext, + SelectDatabaseOptions, +} from '@knexion/core'; +import { FilterObject, FilterObjectOptions } from '../interfaces'; +import { isPlainObject } from '@nestjs/common/utils/shared.utils'; + +export class FilterInterceptor + implements RepositoryInterceptor +{ + constructor(private readonly options: FilterObjectOptions = {}) {} + + public intercept( + context: ExecutionContext< + TRecord, + TResult, + SelectDatabaseOptions + >, + next: RepositoryInterceptorNext, + ): Observable { + const filterObject = context.options[this.options.optionKey ?? 'filter']; + if (isPlainObject(filterObject)) { + context.queryBuilder.where( + this.appendAliasToFilterColumns( + filterObject as FilterObject, + context.options.alias, + ), + ); + } + + return next.handle(); + } + + private appendAliasToFilterColumns( + filter: Record, + alias?: string, + ): Record { + return Object.fromEntries( + Object.entries(filter).map(([name, value]) => { + const { useAlias = true } = this.options; + const column = useAlias ? addPrefixColumn(name, alias) : name; + return [column, value]; + }), + ); + } +} diff --git a/packages/filter/lib/interceptors/index.ts b/packages/filter/lib/interceptors/index.ts new file mode 100644 index 00000000..e6dbdd8e --- /dev/null +++ b/packages/filter/lib/interceptors/index.ts @@ -0,0 +1,4 @@ +export * from './filter.interceptor'; +export * from './filter-by-array.interceptor'; +export * from './filter-by-number-range.interceptor'; +export * from './filter-timestamp.interceptor'; diff --git a/packages/filter/lib/interfaces/filter-options.interface.ts b/packages/filter/lib/interfaces/filter-options.interface.ts new file mode 100644 index 00000000..59b02f8b --- /dev/null +++ b/packages/filter/lib/interfaces/filter-options.interface.ts @@ -0,0 +1,11 @@ +export interface FilterOptions { + useAlias?: boolean; +} + +export type FilterObject = Partial< + Record +>; + +export interface FilterObjectOptions extends FilterOptions { + optionKey?: string; +} diff --git a/packages/filter/lib/interfaces/index.ts b/packages/filter/lib/interfaces/index.ts new file mode 100644 index 00000000..695eed01 --- /dev/null +++ b/packages/filter/lib/interfaces/index.ts @@ -0,0 +1,3 @@ +export * from './filter-options.interface'; +export * from './range-number-filter.interface'; +export * from './timestamp-filter.interface'; diff --git a/packages/filter/lib/interfaces/range-number-filter.interface.ts b/packages/filter/lib/interfaces/range-number-filter.interface.ts new file mode 100644 index 00000000..fbbb2545 --- /dev/null +++ b/packages/filter/lib/interfaces/range-number-filter.interface.ts @@ -0,0 +1,21 @@ +export interface RangeNumberFilter { + /** + * Return records where the field is after this value. + */ + gt?: number; + + /** + * Return records where the field is after or equal to this value. + */ + gte?: number; + + /** + * Return records where the field is before this value. + */ + lt?: number; + + /** + * Return records where the field is before or equal to this value. + */ + lte?: number; +} diff --git a/packages/filter/lib/interfaces/timestamp-filter.interface.ts b/packages/filter/lib/interfaces/timestamp-filter.interface.ts new file mode 100644 index 00000000..d22a0507 --- /dev/null +++ b/packages/filter/lib/interfaces/timestamp-filter.interface.ts @@ -0,0 +1,3 @@ +import { RangeNumberFilter } from './range-number-filter.interface'; + +export type TimestampFilter = number | RangeNumberFilter; diff --git a/packages/filter/lib/utils/build-range-number-filter.ts b/packages/filter/lib/utils/build-range-number-filter.ts new file mode 100644 index 00000000..a5adb1f4 --- /dev/null +++ b/packages/filter/lib/utils/build-range-number-filter.ts @@ -0,0 +1,34 @@ +import { Knex } from 'knex'; +import { RangeNumberFilter } from '../interfaces'; + +const getQueryForDateOperator = ( + queryBuilder: Knex.QueryBuilder, + name: string, + operator: 'gt' | 'gte' | 'lt' | 'lte', + value: number, +): void => { + if (operator === 'gt') { + queryBuilder.where(name, '>', value); + } else if (operator === 'gte') { + queryBuilder.where(name, '>=', value); + } else if (operator === 'lt') { + queryBuilder.where(name, '<', value); + } else if (operator === 'lte') { + queryBuilder.where(name, '<=', value); + } +}; + +export const buildRangeNumberFilter = ( + queryBuilder: Knex.QueryBuilder, + name: string, + numberFilter: RangeNumberFilter, +): void => { + Object.entries(numberFilter).forEach(([operator, value]) => + getQueryForDateOperator( + queryBuilder, + name as string, + operator as 'gt' | 'gte' | 'lt' | 'lte', + value, + ), + ); +}; diff --git a/packages/filter/lib/utils/index.ts b/packages/filter/lib/utils/index.ts new file mode 100644 index 00000000..a7d54f6d --- /dev/null +++ b/packages/filter/lib/utils/index.ts @@ -0,0 +1 @@ +export * from './build-range-number-filter'; diff --git a/packages/filter/package.json b/packages/filter/package.json new file mode 100644 index 00000000..a1ead104 --- /dev/null +++ b/packages/filter/package.json @@ -0,0 +1,48 @@ +{ + "name": "@knexion/filter", + "version": "0.0.1-alpha.3", + "description": "> TODO: description", + "author": "Seedium ", + "homepage": "https://github.com/seedium/knexion/tree/main/packages/filter#readme", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/seedium/knexion.git" + }, + "bugs": { + "url": "https://github.com/seedium/knexion/issues" + }, + "scripts": { + "build": "tsc", + "clean": "rm -rf ./**/!(*jest.config).js ./**/*.d.ts || true", + "test": "jest tests/**/*.spec.ts --coverage" + }, + "peerDependencies": { + "@knexion/core": "^0.0.0", + "@nestjs/common": "^8.0.0 || ^9.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0", + "knex": "^2.3.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.2.0" + }, + "devDependencies": { + "@faker-js/faker": "7.6.0", + "@knexion/core": "workspace:^", + "@nestjs/common": "9.1.6", + "@nestjs/core": "9.1.6", + "@nestjs/testing": "9.1.6", + "@types/jest": "29.2.0", + "@types/node": "16.18.0", + "jest": "29.2.2", + "knex": "2.3.0", + "knexion-test-utils": "workspace:^", + "reflect-metadata": "0.1.13", + "rxjs": "7.5.7", + "ts-jest": "29.0.3", + "tslib": "2.4.0", + "typescript": "4.8.4" + } +} diff --git a/packages/filter/tests/filter-by-array-interceptor.spec.ts b/packages/filter/tests/filter-by-array-interceptor.spec.ts new file mode 100644 index 00000000..1414620f --- /dev/null +++ b/packages/filter/tests/filter-by-array-interceptor.spec.ts @@ -0,0 +1,54 @@ +import { + buildTestKnexionModule, + buildTestRepository, + getKnex, + TestRepository, +} from 'knexion-test-utils'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Knex } from 'knex'; +import { faker } from '@faker-js/faker'; +import { FilterByArrayInterceptor } from '../lib'; + +describe('FilterByArrayInterceptor', () => { + const { + name: testTableName, + init, + forFeature, + createTable, + dropTable, + truncate, + } = buildTestRepository(); + let app: TestingModule; + let knex: Knex; + let testRepository: TestRepository; + + beforeAll(async () => { + knex = await getKnex(); + init(knex); + await createTable(); + app = await Test.createTestingModule({ + imports: [buildTestKnexionModule(), forFeature()], + }).compile(); + testRepository = app.get(TestRepository); + }); + afterAll(async () => { + await app.close(); + await dropTable(); + await knex.destroy(); + }); + afterEach(async () => { + await truncate(); + }); + + test('should filter by one value in array', async () => { + const fakeFoo = faker.random.word(); + const [fixtureRecord] = await knex(testTableName).insert( + { foo: fakeFoo }, + '*', + ); + const result = await testRepository.list({ + intercept: [new FilterByArrayInterceptor('foo', [fakeFoo])], + }); + expect(result).toStrictEqual([fixtureRecord]); + }); +}); diff --git a/packages/filter/tests/filter-by-number-range-interceptor.spec.ts b/packages/filter/tests/filter-by-number-range-interceptor.spec.ts new file mode 100644 index 00000000..b857284a --- /dev/null +++ b/packages/filter/tests/filter-by-number-range-interceptor.spec.ts @@ -0,0 +1,64 @@ +import { + buildTestKnexionModule, + buildTestRepository, + getKnex, + TestRecord, + TestRepository, +} from 'knexion-test-utils'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Knex } from 'knex'; +import { faker } from '@faker-js/faker'; +import { FilterByNumberRangeInterceptor } from '../lib'; + +describe('FilterByNumberRangeInterceptor', () => { + const { + name: testTableName, + init, + forFeature, + createTable, + dropTable, + truncate, + } = buildTestRepository({ + createTable: (table) => { + table.integer('bar').notNullable(); + }, + }); + let app: TestingModule; + let knex: Knex; + let testRepository: TestRepository; + + beforeAll(async () => { + knex = await getKnex(); + init(knex); + await createTable(); + app = await Test.createTestingModule({ + imports: [buildTestKnexionModule(), forFeature()], + }).compile(); + testRepository = app.get(TestRepository); + }); + afterAll(async () => { + await app.close(); + await dropTable(); + await knex.destroy(); + }); + afterEach(async () => { + await truncate(); + }); + + test('should filter by range', async () => { + const fakeFoo = faker.random.word(); + const [fixtureRecord] = await knex(testTableName).insert( + { foo: fakeFoo, bar: 2 }, + '*', + ); + const result = await testRepository.list({ + intercept: [ + new FilterByNumberRangeInterceptor('bar', { + gt: 1, + lte: 2, + }), + ], + }); + expect(result).toStrictEqual([fixtureRecord]); + }); +}); diff --git a/packages/filter/tests/filter-by-timestamp-interceptor.spec.ts b/packages/filter/tests/filter-by-timestamp-interceptor.spec.ts new file mode 100644 index 00000000..a730682a --- /dev/null +++ b/packages/filter/tests/filter-by-timestamp-interceptor.spec.ts @@ -0,0 +1,61 @@ +import { + buildTestKnexionModule, + buildTestRepository, + getKnex, + TestRecord, + TestRepository, +} from 'knexion-test-utils'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Knex } from 'knex'; +import { faker } from '@faker-js/faker'; +import { FilterTimestampInterceptor } from '../lib'; + +describe('FilterTimestampInterceptor', () => { + const { + name: testTableName, + init, + forFeature, + createTable, + dropTable, + truncate, + } = buildTestRepository({ + createTable: (table) => { + table.integer('created_at').notNullable(); + }, + }); + let app: TestingModule; + let knex: Knex; + let testRepository: TestRepository; + + beforeAll(async () => { + knex = await getKnex(); + init(knex); + await createTable(); + app = await Test.createTestingModule({ + imports: [buildTestKnexionModule(), forFeature()], + }).compile(); + testRepository = app.get(TestRepository); + }); + afterAll(async () => { + await app.close(); + await dropTable(); + await knex.destroy(); + }); + afterEach(async () => { + await truncate(); + }); + + test('should filter by simple timestamp', async () => { + const fakeFoo = faker.random.word(); + const now = Math.round(Date.now() / 1000); + const [fixtureRecord] = await knex(testTableName).insert( + { foo: fakeFoo, created_at: now }, + '*', + ); + const result = await testRepository.list({ + intercept: [new FilterTimestampInterceptor('created_at')], + created_at: now, + }); + expect(result).toStrictEqual([fixtureRecord]); + }); +}); diff --git a/packages/filter/tests/filter-interceptor.spec.ts b/packages/filter/tests/filter-interceptor.spec.ts new file mode 100644 index 00000000..9620b4e9 --- /dev/null +++ b/packages/filter/tests/filter-interceptor.spec.ts @@ -0,0 +1,57 @@ +import { + buildTestKnexionModule, + buildTestRepository, + getKnex, + TestRepository, +} from 'knexion-test-utils'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Knex } from 'knex'; +import { faker } from '@faker-js/faker'; +import { FilterInterceptor } from '../lib'; + +describe('FilterInterceptor', () => { + const { + name: testTableName, + init, + forFeature, + createTable, + dropTable, + truncate, + } = buildTestRepository(); + let app: TestingModule; + let knex: Knex; + let testRepository: TestRepository; + + beforeAll(async () => { + knex = await getKnex(); + init(knex); + await createTable(); + app = await Test.createTestingModule({ + imports: [buildTestKnexionModule(), forFeature()], + }).compile(); + testRepository = app.get(TestRepository); + }); + afterAll(async () => { + await app.close(); + await dropTable(); + await knex.destroy(); + }); + afterEach(async () => { + await truncate(); + }); + + test('should filter', async () => { + const fakeFoo = faker.random.word(); + const [fixtureRecord] = await knex(testTableName).insert( + { foo: fakeFoo }, + '*', + ); + const result = await testRepository.list({ + intercept: [new FilterInterceptor()], + filter: { + foo: fakeFoo, + }, + }); + expect(result).toStrictEqual([fixtureRecord]); + }); +}); diff --git a/packages/filter/tsconfig.json b/packages/filter/tsconfig.json new file mode 100644 index 00000000..c0910577 --- /dev/null +++ b/packages/filter/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "./../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./" + }, + "include": ["./*.ts", "lib/**/*.ts"] +} diff --git a/packages/filter/tsconfig.spec.json b/packages/filter/tsconfig.spec.json new file mode 100644 index 00000000..efded48e --- /dev/null +++ b/packages/filter/tsconfig.spec.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*.spec.ts"], + "exclude": ["**/.d.ts"] +} diff --git a/packages/knexion-test-utils/lib/build-test-repository.ts b/packages/knexion-test-utils/lib/build-test-repository.ts index a4689766..f39c24cb 100644 --- a/packages/knexion-test-utils/lib/build-test-repository.ts +++ b/packages/knexion-test-utils/lib/build-test-repository.ts @@ -8,12 +8,18 @@ export interface TestRecord { foo: string | null; } -export class TestRepository extends Repository< - TestRecord, +export class TestRepository< + TRecord extends TestRecord = TestRecord, +> extends Repository< + TRecord, { idType: TestRecord['id']; omitCreateFields: 'id'; omitUpdateFields: 'id' } > {} -export const buildTestRepository = (): { +export const buildTestRepository = ({ + createTable, +}: { + createTable?: (table: Knex.CreateTableBuilder) => void; +} = {}): { name: string; init: (knex: Knex) => void; forFeature: () => DynamicModule; @@ -34,6 +40,9 @@ export const buildTestRepository = (): { knex.schema.createTable(name, (table) => { table.increments('id').primary(); table.string('foo'); + if (createTable) { + createTable(table); + } }), dropTable: () => knex.schema.dropTable(name), truncate: () => knex(name).truncate(), diff --git a/packages/page-pagination/tests/paga-pagination-interceptor.spec.ts b/packages/page-pagination/tests/paga-pagination-interceptor.spec.ts index 5ac6dfe6..521be79a 100644 --- a/packages/page-pagination/tests/paga-pagination-interceptor.spec.ts +++ b/packages/page-pagination/tests/paga-pagination-interceptor.spec.ts @@ -72,7 +72,6 @@ describe('PagePaginationInterceptor', () => { await generateTestRecords(3); const result = await testRepository.list>({ intercept: [new PagePaginationInterceptor()], - // @ts-expect-error Will be extended on repository level limit: 2, }); expect(result.data.length).toBe(2); @@ -82,7 +81,6 @@ describe('PagePaginationInterceptor', () => { await generateTestRecords(1); const result = await testRepository.list>({ intercept: [new PagePaginationInterceptor()], - // @ts-expect-error Will be extended on repository level limit: 1, page: 1, }); diff --git a/packages/soft-delete/.npmignore b/packages/soft-delete/.npmignore new file mode 100644 index 00000000..eb3ddabb --- /dev/null +++ b/packages/soft-delete/.npmignore @@ -0,0 +1,6 @@ +* +!package.json +!lib/**/*.js +!index.js +!index.d.ts +!*.d.ts diff --git a/packages/soft-delete/README.md b/packages/soft-delete/README.md new file mode 100644 index 00000000..03b22e2b --- /dev/null +++ b/packages/soft-delete/README.md @@ -0,0 +1,11 @@ +# `@knexion/soft-delete` + +> TODO: description + +## Usage + +``` +const softDelete = require('@knexion/soft-delete'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/soft-delete/index.ts b/packages/soft-delete/index.ts new file mode 100644 index 00000000..f41a696f --- /dev/null +++ b/packages/soft-delete/index.ts @@ -0,0 +1 @@ +export * from './lib'; diff --git a/packages/soft-delete/jest.config.js b/packages/soft-delete/jest.config.js new file mode 100644 index 00000000..b413e106 --- /dev/null +++ b/packages/soft-delete/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; \ No newline at end of file diff --git a/packages/soft-delete/lib/index.ts b/packages/soft-delete/lib/index.ts new file mode 100644 index 00000000..c10f5c4c --- /dev/null +++ b/packages/soft-delete/lib/index.ts @@ -0,0 +1,2 @@ +export * from './interceptors'; +export * from './interfaces'; diff --git a/packages/soft-delete/lib/interceptors/filter-soft-deleted.interceptor.ts b/packages/soft-delete/lib/interceptors/filter-soft-deleted.interceptor.ts new file mode 100644 index 00000000..14c44122 --- /dev/null +++ b/packages/soft-delete/lib/interceptors/filter-soft-deleted.interceptor.ts @@ -0,0 +1,32 @@ +import { Observable } from 'rxjs'; +import { + addPrefixColumn, + ExecutionContext, + RepositoryInterceptor, + RepositoryInterceptorNext, + SelectDatabaseOptions, +} from '@knexion/core'; +import { SoftDeleteOptions } from '../interfaces'; + +export class FilterSoftDeletedInterceptor + implements RepositoryInterceptor +{ + constructor(private readonly options: SoftDeleteOptions = {}) {} + + public intercept( + context: ExecutionContext< + TRecord, + TResult, + SelectDatabaseOptions + >, + next: RepositoryInterceptorNext, + ): Observable { + context.queryBuilder.whereNull( + addPrefixColumn( + (this.options.field as string) ?? 'deleted_at', + context.options.alias, + ), + ); + return next.handle(); + } +} diff --git a/packages/soft-delete/lib/interceptors/index.ts b/packages/soft-delete/lib/interceptors/index.ts new file mode 100644 index 00000000..df43ce4c --- /dev/null +++ b/packages/soft-delete/lib/interceptors/index.ts @@ -0,0 +1 @@ +export * from './filter-soft-deleted.interceptor'; diff --git a/packages/soft-delete/lib/interfaces/index.ts b/packages/soft-delete/lib/interfaces/index.ts new file mode 100644 index 00000000..b7363ee1 --- /dev/null +++ b/packages/soft-delete/lib/interfaces/index.ts @@ -0,0 +1 @@ +export * from './soft-delete-options.interface'; diff --git a/packages/soft-delete/lib/interfaces/soft-delete-options.interface.ts b/packages/soft-delete/lib/interfaces/soft-delete-options.interface.ts new file mode 100644 index 00000000..eaa30ab4 --- /dev/null +++ b/packages/soft-delete/lib/interfaces/soft-delete-options.interface.ts @@ -0,0 +1,3 @@ +export interface SoftDeleteOptions { + field?: keyof TRecord; +} diff --git a/packages/soft-delete/package.json b/packages/soft-delete/package.json new file mode 100644 index 00000000..7b120734 --- /dev/null +++ b/packages/soft-delete/package.json @@ -0,0 +1,48 @@ +{ + "name": "@knexion/soft-delete", + "version": "0.0.1-alpha.3", + "description": "> TODO: description", + "author": "Seedium ", + "homepage": "https://github.com/seedium/knexion/tree/main/packages/soft-delete#readme", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/seedium/knexion.git" + }, + "bugs": { + "url": "https://github.com/seedium/knexion/issues" + }, + "scripts": { + "build": "tsc", + "clean": "rm -rf ./**/!(*jest.config).js ./**/*.d.ts || true", + "test": "jest tests/**/*.spec.ts --coverage" + }, + "peerDependencies": { + "@knexion/core": "^0.0.0", + "@nestjs/common": "^8.0.0 || ^9.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0", + "knex": "^2.3.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.2.0" + }, + "devDependencies": { + "@faker-js/faker": "7.6.0", + "@knexion/core": "workspace:^", + "@nestjs/common": "9.1.6", + "@nestjs/core": "9.1.6", + "@nestjs/testing": "9.1.6", + "@types/jest": "29.2.0", + "@types/node": "16.18.0", + "jest": "29.2.2", + "knex": "2.3.0", + "knexion-test-utils": "workspace:^", + "reflect-metadata": "0.1.13", + "rxjs": "7.5.7", + "ts-jest": "29.0.3", + "tslib": "2.4.0", + "typescript": "4.8.4" + } +} diff --git a/packages/soft-delete/tests/filter-soft-deleted-interceptor.spec.ts b/packages/soft-delete/tests/filter-soft-deleted-interceptor.spec.ts new file mode 100644 index 00000000..9fcbf5cd --- /dev/null +++ b/packages/soft-delete/tests/filter-soft-deleted-interceptor.spec.ts @@ -0,0 +1,60 @@ +import { + buildTestKnexionModule, + buildTestRepository, + getKnex, + TestRecord, + TestRepository, +} from 'knexion-test-utils'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Knex } from 'knex'; +import { faker } from '@faker-js/faker'; +import { FilterSoftDeletedInterceptor } from '../lib'; + +describe('FilterSoftDeletedInterceptor', () => { + const { + name: testTableName, + init, + forFeature, + createTable, + dropTable, + truncate, + } = buildTestRepository({ + createTable: (table) => { + table.integer('deleted_at'); + }, + }); + let app: TestingModule; + let knex: Knex; + let testRepository: TestRepository< + TestRecord & { deleted_at: number | null } + >; + + beforeAll(async () => { + knex = await getKnex(); + init(knex); + await createTable(); + app = await Test.createTestingModule({ + imports: [buildTestKnexionModule(), forFeature()], + }).compile(); + testRepository = app.get(TestRepository); + }); + afterAll(async () => { + await app.close(); + await dropTable(); + await knex.destroy(); + }); + afterEach(async () => { + await truncate(); + }); + + test('should not return deleted records', async () => { + await knex(testTableName).insert( + { foo: faker.random.word(), deleted_at: 1 }, + '*', + ); + const result = await testRepository.list({ + intercept: [new FilterSoftDeletedInterceptor()], + }); + expect(result).toStrictEqual([]); + }); +}); diff --git a/packages/soft-delete/tsconfig.json b/packages/soft-delete/tsconfig.json new file mode 100644 index 00000000..c0910577 --- /dev/null +++ b/packages/soft-delete/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "./../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./" + }, + "include": ["./*.ts", "lib/**/*.ts"] +} diff --git a/packages/soft-delete/tsconfig.spec.json b/packages/soft-delete/tsconfig.spec.json new file mode 100644 index 00000000..efded48e --- /dev/null +++ b/packages/soft-delete/tsconfig.spec.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*.spec.ts"], + "exclude": ["**/.d.ts"] +} diff --git a/packages/sort/.npmignore b/packages/sort/.npmignore new file mode 100644 index 00000000..eb3ddabb --- /dev/null +++ b/packages/sort/.npmignore @@ -0,0 +1,6 @@ +* +!package.json +!lib/**/*.js +!index.js +!index.d.ts +!*.d.ts diff --git a/packages/sort/README.md b/packages/sort/README.md new file mode 100644 index 00000000..a15af7a9 --- /dev/null +++ b/packages/sort/README.md @@ -0,0 +1,11 @@ +# `@knexion/sort` + +> TODO: description + +## Usage + +``` +const sort = require('@knexion/sort'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/sort/index.ts b/packages/sort/index.ts new file mode 100644 index 00000000..f41a696f --- /dev/null +++ b/packages/sort/index.ts @@ -0,0 +1 @@ +export * from './lib'; diff --git a/packages/sort/jest.config.js b/packages/sort/jest.config.js new file mode 100644 index 00000000..b413e106 --- /dev/null +++ b/packages/sort/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; \ No newline at end of file diff --git a/packages/sort/lib/index.ts b/packages/sort/lib/index.ts new file mode 100644 index 00000000..c10f5c4c --- /dev/null +++ b/packages/sort/lib/index.ts @@ -0,0 +1,2 @@ +export * from './interceptors'; +export * from './interfaces'; diff --git a/packages/sort/lib/interceptors/index.ts b/packages/sort/lib/interceptors/index.ts new file mode 100644 index 00000000..1d394d00 --- /dev/null +++ b/packages/sort/lib/interceptors/index.ts @@ -0,0 +1 @@ +export * from './sort.interceptor'; diff --git a/packages/sort/lib/interceptors/sort.interceptor.ts b/packages/sort/lib/interceptors/sort.interceptor.ts new file mode 100644 index 00000000..32d140b8 --- /dev/null +++ b/packages/sort/lib/interceptors/sort.interceptor.ts @@ -0,0 +1,45 @@ +import { Observable } from 'rxjs'; +import { + ExecutionContext, + RepositoryInterceptorNext, + RepositoryInterceptor, + addPrefixColumn, + SelectDatabaseOptions, +} from '@knexion/core'; +import { getSortDirection } from '../utils'; +import { SortOptions } from '../interfaces'; + +export class SortInterceptor + implements RepositoryInterceptor +{ + constructor(private readonly options: SortOptions = {}) {} + + public intercept( + context: ExecutionContext< + TRecord, + TResult, + SelectDatabaseOptions + >, + next: RepositoryInterceptorNext, + ): Observable { + const sortOptionKey = this.options.optionKey ?? 'sort'; + const sort = context.options[sortOptionKey]; + + if (!Array.isArray(sort)) { + throw new Error(`'${sortOptionKey}' is not array`); + } + + if (sort) { + sort.forEach((property) => { + const [direction, column] = getSortDirection(property); + context.queryBuilder.orderBy( + addPrefixColumn(column, context.options.alias), + direction, + direction === 'desc' ? 'last' : 'first', + ); + }); + } + + return next.handle(); + } +} diff --git a/packages/sort/lib/interfaces/index.ts b/packages/sort/lib/interfaces/index.ts new file mode 100644 index 00000000..d012ef44 --- /dev/null +++ b/packages/sort/lib/interfaces/index.ts @@ -0,0 +1 @@ +export * from './sort-options.interface'; diff --git a/packages/sort/lib/interfaces/sort-options.interface.ts b/packages/sort/lib/interfaces/sort-options.interface.ts new file mode 100644 index 00000000..4d8fe530 --- /dev/null +++ b/packages/sort/lib/interfaces/sort-options.interface.ts @@ -0,0 +1,3 @@ +export interface SortOptions { + optionKey?: string; +} diff --git a/packages/sort/lib/utils/get-sort-direction.ts b/packages/sort/lib/utils/get-sort-direction.ts new file mode 100644 index 00000000..15fafc55 --- /dev/null +++ b/packages/sort/lib/utils/get-sort-direction.ts @@ -0,0 +1,12 @@ +export const getSortDirection = ( + property: string, +): ['asc' | 'desc', string] => { + const sortSymbol = property[0]; + if (sortSymbol === '+') { + return ['asc', property.slice(1, property.length)]; + } else if (sortSymbol === '-') { + return ['desc', property.slice(1, property.length)]; + } else { + return ['asc', property]; + } +}; diff --git a/packages/sort/lib/utils/index.ts b/packages/sort/lib/utils/index.ts new file mode 100644 index 00000000..4a9ce3e1 --- /dev/null +++ b/packages/sort/lib/utils/index.ts @@ -0,0 +1 @@ +export * from './get-sort-direction'; diff --git a/packages/sort/package.json b/packages/sort/package.json new file mode 100644 index 00000000..c7959d45 --- /dev/null +++ b/packages/sort/package.json @@ -0,0 +1,48 @@ +{ + "name": "@knexion/sort", + "version": "0.0.1-alpha.3", + "description": "> TODO: description", + "author": "Seedium ", + "homepage": "https://github.com/seedium/knexion/tree/main/packages/sort#readme", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/seedium/knexion.git" + }, + "bugs": { + "url": "https://github.com/seedium/knexion/issues" + }, + "scripts": { + "build": "tsc", + "clean": "rm -rf ./**/!(*jest.config).js ./**/*.d.ts || true", + "test": "jest tests/**/*.spec.ts --coverage" + }, + "peerDependencies": { + "@knexion/core": "^0.0.0", + "@nestjs/common": "^8.0.0 || ^9.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0", + "knex": "^2.3.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.2.0" + }, + "devDependencies": { + "@faker-js/faker": "7.6.0", + "@knexion/core": "workspace:^", + "@nestjs/common": "9.1.6", + "@nestjs/core": "9.1.6", + "@nestjs/testing": "9.1.6", + "@types/jest": "29.2.0", + "@types/node": "16.18.0", + "jest": "29.2.2", + "knex": "2.3.0", + "knexion-test-utils": "workspace:^", + "reflect-metadata": "0.1.13", + "rxjs": "7.5.7", + "ts-jest": "29.0.3", + "tslib": "2.4.0", + "typescript": "4.8.4" + } +} diff --git a/packages/sort/tests/sort-interceptor.spec.ts b/packages/sort/tests/sort-interceptor.spec.ts new file mode 100644 index 00000000..183e41a5 --- /dev/null +++ b/packages/sort/tests/sort-interceptor.spec.ts @@ -0,0 +1,71 @@ +import { + buildTestKnexionModule, + buildTestRepository, + getKnex, + TestRecord, + TestRepository, +} from 'knexion-test-utils'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Knex } from 'knex'; +import { faker } from '@faker-js/faker'; +import { SortInterceptor } from '../lib'; + +describe('SortInterceptor', () => { + const { + name: testTableName, + init, + forFeature, + createTable, + dropTable, + truncate, + } = buildTestRepository({ + createTable: (table) => { + table.integer('bar').notNullable(); + }, + }); + let app: TestingModule; + let knex: Knex; + let testRepository: TestRepository; + + beforeAll(async () => { + knex = await getKnex(); + init(knex); + await createTable(); + app = await Test.createTestingModule({ + imports: [buildTestKnexionModule(), forFeature()], + }).compile(); + testRepository = app.get(TestRepository); + }); + afterAll(async () => { + await app.close(); + await dropTable(); + await knex.destroy(); + }); + afterEach(async () => { + await truncate(); + }); + + test('should sort by ascending order', async () => { + const [[firstFixtureRecord], [secondFixtureRecord]] = await Promise.all([ + knex(testTableName).insert({ foo: faker.random.word(), bar: 1 }, '*'), + knex(testTableName).insert({ foo: faker.random.word(), bar: 2 }, '*'), + ]); + const result = await testRepository.list({ + intercept: [new SortInterceptor()], + sort: ['bar'], + }); + expect(result).toStrictEqual([firstFixtureRecord, secondFixtureRecord]); + }); + + test('should sort by descending order', async () => { + const [[firstFixtureRecord], [secondFixtureRecord]] = await Promise.all([ + knex(testTableName).insert({ foo: faker.random.word(), bar: 1 }, '*'), + knex(testTableName).insert({ foo: faker.random.word(), bar: 2 }, '*'), + ]); + const result = await testRepository.list({ + intercept: [new SortInterceptor()], + sort: ['-bar'], + }); + expect(result).toStrictEqual([secondFixtureRecord, firstFixtureRecord]); + }); +}); diff --git a/packages/sort/tsconfig.json b/packages/sort/tsconfig.json new file mode 100644 index 00000000..c0910577 --- /dev/null +++ b/packages/sort/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "./../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./" + }, + "include": ["./*.ts", "lib/**/*.ts"] +} diff --git a/packages/sort/tsconfig.spec.json b/packages/sort/tsconfig.spec.json new file mode 100644 index 00000000..efded48e --- /dev/null +++ b/packages/sort/tsconfig.spec.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*.spec.ts"], + "exclude": ["**/.d.ts"] +} diff --git a/packages/transform/.npmignore b/packages/transform/.npmignore new file mode 100644 index 00000000..eb3ddabb --- /dev/null +++ b/packages/transform/.npmignore @@ -0,0 +1,6 @@ +* +!package.json +!lib/**/*.js +!index.js +!index.d.ts +!*.d.ts diff --git a/packages/transform/README.md b/packages/transform/README.md new file mode 100644 index 00000000..ab2423e5 --- /dev/null +++ b/packages/transform/README.md @@ -0,0 +1,11 @@ +# `@knexion/transform` + +> TODO: description + +## Usage + +``` +const transform = require('@knexion/transform'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/transform/index.ts b/packages/transform/index.ts new file mode 100644 index 00000000..f41a696f --- /dev/null +++ b/packages/transform/index.ts @@ -0,0 +1 @@ +export * from './lib'; diff --git a/packages/transform/jest.config.js b/packages/transform/jest.config.js new file mode 100644 index 00000000..b413e106 --- /dev/null +++ b/packages/transform/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; \ No newline at end of file diff --git a/packages/transform/lib/decorators/field-transform.decorator.ts b/packages/transform/lib/decorators/field-transform.decorator.ts new file mode 100644 index 00000000..f4e3d861 --- /dev/null +++ b/packages/transform/lib/decorators/field-transform.decorator.ts @@ -0,0 +1,9 @@ +import { SetMetadata } from '@nestjs/common'; +import { FieldTransformOptions } from 'lib/interfaces'; +import { FIELD_TRANSFORM_SCHEMA } from '../transform.constants'; + +export const FieldTransform = ( + schema: FieldTransformOptions, +): ClassDecorator & MethodDecorator => { + return SetMetadata(FIELD_TRANSFORM_SCHEMA, schema); +}; diff --git a/packages/transform/lib/decorators/index.ts b/packages/transform/lib/decorators/index.ts new file mode 100644 index 00000000..51631607 --- /dev/null +++ b/packages/transform/lib/decorators/index.ts @@ -0,0 +1 @@ +export * from './field-transform.decorator'; diff --git a/packages/transform/lib/helpers/convert-pg-array-to-js.ts b/packages/transform/lib/helpers/convert-pg-array-to-js.ts new file mode 100644 index 00000000..12563db2 --- /dev/null +++ b/packages/transform/lib/helpers/convert-pg-array-to-js.ts @@ -0,0 +1,5 @@ +export const convertPgArrayToJs = (value: string): string[] => + value + .slice(1, -1) + .split(',') + .filter((val) => !!val); diff --git a/packages/transform/lib/helpers/index.ts b/packages/transform/lib/helpers/index.ts new file mode 100644 index 00000000..1d2cf41f --- /dev/null +++ b/packages/transform/lib/helpers/index.ts @@ -0,0 +1 @@ +export * from './convert-pg-array-to-js'; diff --git a/packages/transform/lib/index.ts b/packages/transform/lib/index.ts new file mode 100644 index 00000000..f10b74d0 --- /dev/null +++ b/packages/transform/lib/index.ts @@ -0,0 +1,4 @@ +export * from './decorators'; +export * from './helpers'; +export * from './interceptors'; +export * from './interfaces'; diff --git a/packages/transform/lib/interceptors/field-transform.interceptor.ts b/packages/transform/lib/interceptors/field-transform.interceptor.ts new file mode 100644 index 00000000..d0d3fe5b --- /dev/null +++ b/packages/transform/lib/interceptors/field-transform.interceptor.ts @@ -0,0 +1,84 @@ +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Reflector } from '@nestjs/core'; +import { Injectable } from '@nestjs/common'; +import { + ExecutionContext, + RepositoryInterceptorNext, + RepositoryInterceptor, +} from '@knexion/core'; +import { FieldTransformer, FieldTransformOptions } from '../interfaces'; +import { FIELD_TRANSFORM_SCHEMA } from '../transform.constants'; + +type TransformSchemaEntries = [string, FieldTransformer][]; + +@Injectable() +export class FieldTransformInterceptor + implements RepositoryInterceptor +{ + constructor(private readonly reflector: Reflector) {} + + public intercept( + context: ExecutionContext, + next: RepositoryInterceptorNext, + ): Observable { + const { schema, resolver } = this.retrieveTransformSchema(context); + const schemaEntries = Object.entries(schema); + + if (!schemaEntries.length) { + return next.handle(); + } + + return next.handle().pipe( + map((result) => { + if (resolver) { + const resolvedData = resolver(result); + if (!resolvedData) { + return null; + } + if (Array.isArray(resolvedData)) { + return this.transformArray(resolvedData, schemaEntries); + } + return this.transform(resolvedData, schemaEntries); + } + + if (!result) { + return result; + } + if (Array.isArray(result)) { + return this.transformArray(result, schemaEntries); + } + return this.transform(result, schemaEntries); + }), + ); + } + + private transformArray( + data: TResult[], + schema: TransformSchemaEntries, + ): TResult[] { + return data.map((item) => this.transform(item, schema)); + } + + private transform(result: TResult, schema: TransformSchemaEntries): TResult { + for (const [field, transform] of schema) { + result[field as keyof TResult] = transform( + result[field as keyof TResult], + ) as TResult[keyof TResult]; + } + return result; + } + + private retrieveTransformSchema( + context: ExecutionContext, + ): FieldTransformOptions { + const handler = context.getHandler(); + if (handler) { + const handlerSchema = this.reflector.get(FIELD_TRANSFORM_SCHEMA, handler); + if (handlerSchema) { + return handlerSchema; + } + } + return this.reflector.get(FIELD_TRANSFORM_SCHEMA, context.getClass()); + } +} diff --git a/packages/transform/lib/interceptors/index.ts b/packages/transform/lib/interceptors/index.ts new file mode 100644 index 00000000..1cd233d6 --- /dev/null +++ b/packages/transform/lib/interceptors/index.ts @@ -0,0 +1 @@ +export * from './field-transform.interceptor'; diff --git a/packages/transform/lib/interfaces/field-transform-options.interface.ts b/packages/transform/lib/interfaces/field-transform-options.interface.ts new file mode 100644 index 00000000..4569622e --- /dev/null +++ b/packages/transform/lib/interfaces/field-transform-options.interface.ts @@ -0,0 +1,12 @@ +export type FieldTransformer = ( + value: TValue, +) => TResult; + +export type FieldTransformSchema = Record; + +export interface FieldTransformOptions { + schema: FieldTransformSchema; + resolver?: ( + data: TResult | TResult[] | null | unknown, + ) => TResult | TResult[] | null; +} diff --git a/packages/transform/lib/interfaces/index.ts b/packages/transform/lib/interfaces/index.ts new file mode 100644 index 00000000..10282914 --- /dev/null +++ b/packages/transform/lib/interfaces/index.ts @@ -0,0 +1 @@ +export * from './field-transform-options.interface'; diff --git a/packages/transform/lib/transform.constants.ts b/packages/transform/lib/transform.constants.ts new file mode 100644 index 00000000..9a46034d --- /dev/null +++ b/packages/transform/lib/transform.constants.ts @@ -0,0 +1 @@ +export const FIELD_TRANSFORM_SCHEMA = Symbol.for('__field_transform_schema__'); diff --git a/packages/transform/package.json b/packages/transform/package.json new file mode 100644 index 00000000..7392927f --- /dev/null +++ b/packages/transform/package.json @@ -0,0 +1,48 @@ +{ + "name": "@knexion/transform", + "version": "0.0.1-alpha.3", + "description": "> TODO: description", + "author": "Seedium ", + "homepage": "https://github.com/seedium/knexion/tree/main/packages/transform#readme", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/seedium/knexion.git" + }, + "bugs": { + "url": "https://github.com/seedium/knexion/issues" + }, + "scripts": { + "build": "tsc", + "clean": "rm -rf ./**/!(*jest.config).js ./**/*.d.ts || true", + "test": "jest tests/**/*.spec.ts --coverage" + }, + "peerDependencies": { + "@knexion/core": "^0.0.0", + "@nestjs/common": "^8.0.0 || ^9.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0", + "knex": "^2.3.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.2.0" + }, + "devDependencies": { + "@faker-js/faker": "7.6.0", + "@knexion/core": "workspace:^", + "@nestjs/common": "9.1.6", + "@nestjs/core": "9.1.6", + "@nestjs/testing": "9.1.6", + "@types/jest": "29.2.0", + "@types/node": "16.18.0", + "jest": "29.2.2", + "knex": "2.3.0", + "knexion-test-utils": "workspace:^", + "reflect-metadata": "0.1.13", + "rxjs": "7.5.7", + "ts-jest": "29.0.3", + "tslib": "2.4.0", + "typescript": "4.8.4" + } +} diff --git a/packages/transform/tests/field-transform-interceptor.spec.ts b/packages/transform/tests/field-transform-interceptor.spec.ts new file mode 100644 index 00000000..07b703b7 --- /dev/null +++ b/packages/transform/tests/field-transform-interceptor.spec.ts @@ -0,0 +1,60 @@ +import { + buildTestKnexionModule, + buildTestRepository, + getKnex, + TestRepository, +} from 'knexion-test-utils'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Knex } from 'knex'; +import { FieldTransform, FieldTransformInterceptor } from '../lib'; +import { faker } from '@faker-js/faker'; +import { UseRepositoryInterceptors } from '@knexion/core/lib'; +import { REPOSITORY_INTERCEPTORS } from '@knexion/core/lib/knexion.constants'; +import { FIELD_TRANSFORM_SCHEMA } from '../lib/transform.constants'; + +describe('FieldTransformInterceptor', () => { + const { + name: testTableName, + init, + forFeature, + createTable, + dropTable, + truncate, + } = buildTestRepository(); + let app: TestingModule; + let knex: Knex; + let testRepository: TestRepository; + + beforeAll(async () => { + knex = await getKnex(); + init(knex); + await createTable(); + app = await Test.createTestingModule({ + imports: [buildTestKnexionModule(), forFeature()], + }).compile(); + testRepository = app.get(TestRepository); + }); + afterAll(async () => { + await app.close(); + await dropTable(); + await knex.destroy(); + }); + afterEach(async () => { + await truncate(); + Reflect.deleteMetadata(REPOSITORY_INTERCEPTORS, TestRepository); + Reflect.deleteMetadata(FIELD_TRANSFORM_SCHEMA, TestRepository); + }); + + test('should transform field foo', async () => { + const fakeTransformed = faker.random.word(); + UseRepositoryInterceptors(FieldTransformInterceptor)(TestRepository); + FieldTransform({ schema: { foo: () => fakeTransformed } })(TestRepository); + await testRepository.onModuleInit(); + const [fixtureRecord] = await knex(testTableName).insert( + { foo: faker.random.word() }, + '*', + ); + const result = await testRepository.list(); + expect(result).toMatchObject([{ ...fixtureRecord, foo: fakeTransformed }]); + }); +}); diff --git a/packages/transform/tsconfig.json b/packages/transform/tsconfig.json new file mode 100644 index 00000000..c0910577 --- /dev/null +++ b/packages/transform/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "./../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./" + }, + "include": ["./*.ts", "lib/**/*.ts"] +} diff --git a/packages/transform/tsconfig.spec.json b/packages/transform/tsconfig.spec.json new file mode 100644 index 00000000..efded48e --- /dev/null +++ b/packages/transform/tsconfig.spec.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*.spec.ts"], + "exclude": ["**/.d.ts"] +} diff --git a/yarn.lock b/yarn.lock index 802056e3..f03cc071 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1053,6 +1053,35 @@ __metadata: languageName: unknown linkType: soft +"@knexion/filter@workspace:packages/filter": + version: 0.0.0-use.local + resolution: "@knexion/filter@workspace:packages/filter" + dependencies: + "@faker-js/faker": 7.6.0 + "@knexion/core": "workspace:^" + "@nestjs/common": 9.1.6 + "@nestjs/core": 9.1.6 + "@nestjs/testing": 9.1.6 + "@types/jest": 29.2.0 + "@types/node": 16.18.0 + jest: 29.2.2 + knex: 2.3.0 + knexion-test-utils: "workspace:^" + reflect-metadata: 0.1.13 + rxjs: 7.5.7 + ts-jest: 29.0.3 + tslib: 2.4.0 + typescript: 4.8.4 + peerDependencies: + "@knexion/core": ^0.0.0 + "@nestjs/common": ^8.0.0 || ^9.0.0 + "@nestjs/core": ^8.0.0 || ^9.0.0 + knex: ^2.3.0 + reflect-metadata: ^0.1.13 + rxjs: ^7.2.0 + languageName: unknown + linkType: soft + "@knexion/page-pagination@workspace:packages/page-pagination": version: 0.0.0-use.local resolution: "@knexion/page-pagination@workspace:packages/page-pagination" @@ -1082,6 +1111,93 @@ __metadata: languageName: unknown linkType: soft +"@knexion/soft-delete@workspace:packages/soft-delete": + version: 0.0.0-use.local + resolution: "@knexion/soft-delete@workspace:packages/soft-delete" + dependencies: + "@faker-js/faker": 7.6.0 + "@knexion/core": "workspace:^" + "@nestjs/common": 9.1.6 + "@nestjs/core": 9.1.6 + "@nestjs/testing": 9.1.6 + "@types/jest": 29.2.0 + "@types/node": 16.18.0 + jest: 29.2.2 + knex: 2.3.0 + knexion-test-utils: "workspace:^" + reflect-metadata: 0.1.13 + rxjs: 7.5.7 + ts-jest: 29.0.3 + tslib: 2.4.0 + typescript: 4.8.4 + peerDependencies: + "@knexion/core": ^0.0.0 + "@nestjs/common": ^8.0.0 || ^9.0.0 + "@nestjs/core": ^8.0.0 || ^9.0.0 + knex: ^2.3.0 + reflect-metadata: ^0.1.13 + rxjs: ^7.2.0 + languageName: unknown + linkType: soft + +"@knexion/sort@workspace:packages/sort": + version: 0.0.0-use.local + resolution: "@knexion/sort@workspace:packages/sort" + dependencies: + "@faker-js/faker": 7.6.0 + "@knexion/core": "workspace:^" + "@nestjs/common": 9.1.6 + "@nestjs/core": 9.1.6 + "@nestjs/testing": 9.1.6 + "@types/jest": 29.2.0 + "@types/node": 16.18.0 + jest: 29.2.2 + knex: 2.3.0 + knexion-test-utils: "workspace:^" + reflect-metadata: 0.1.13 + rxjs: 7.5.7 + ts-jest: 29.0.3 + tslib: 2.4.0 + typescript: 4.8.4 + peerDependencies: + "@knexion/core": ^0.0.0 + "@nestjs/common": ^8.0.0 || ^9.0.0 + "@nestjs/core": ^8.0.0 || ^9.0.0 + knex: ^2.3.0 + reflect-metadata: ^0.1.13 + rxjs: ^7.2.0 + languageName: unknown + linkType: soft + +"@knexion/transform@workspace:packages/transform": + version: 0.0.0-use.local + resolution: "@knexion/transform@workspace:packages/transform" + dependencies: + "@faker-js/faker": 7.6.0 + "@knexion/core": "workspace:^" + "@nestjs/common": 9.1.6 + "@nestjs/core": 9.1.6 + "@nestjs/testing": 9.1.6 + "@types/jest": 29.2.0 + "@types/node": 16.18.0 + jest: 29.2.2 + knex: 2.3.0 + knexion-test-utils: "workspace:^" + reflect-metadata: 0.1.13 + rxjs: 7.5.7 + ts-jest: 29.0.3 + tslib: 2.4.0 + typescript: 4.8.4 + peerDependencies: + "@knexion/core": ^0.0.0 + "@nestjs/common": ^8.0.0 || ^9.0.0 + "@nestjs/core": ^8.0.0 || ^9.0.0 + knex: ^2.3.0 + reflect-metadata: ^0.1.13 + rxjs: ^7.2.0 + languageName: unknown + linkType: soft + "@lerna/add@npm:6.0.1": version: 6.0.1 resolution: "@lerna/add@npm:6.0.1"