diff --git a/packages/orm/adapters/package.json b/packages/orm/adapters/package.json index c17f2b8badf..26a9e3555b2 100644 --- a/packages/orm/adapters/package.json +++ b/packages/orm/adapters/package.json @@ -27,7 +27,7 @@ "change-case": "^5.4.4", "fs-extra": "11.2.0", "lodash": "^4.17.21", - "lowdb": "1.0.0", + "lowdb": "7.0.1", "tslib": "2.7.0", "uuid": "^10.0.0" }, diff --git a/packages/orm/adapters/src/adapters/FileSyncAdapter.ts b/packages/orm/adapters/src/adapters/FileSyncAdapter.ts index 473cb12814e..8764e1c6cbc 100644 --- a/packages/orm/adapters/src/adapters/FileSyncAdapter.ts +++ b/packages/orm/adapters/src/adapters/FileSyncAdapter.ts @@ -1,12 +1,12 @@ import {nameOf} from "@tsed/core"; import {Configuration, Injectable, Opts, ProviderScope, Scope} from "@tsed/di"; import fs from "fs-extra"; -import low from "lowdb"; -import FileSync from "lowdb/adapters/FileSync.js"; +import {LowSync} from "lowdb"; +import {JSONFileSync} from "lowdb/node"; import {dirname} from "path"; import {AdapterConstructorOptions} from "../domain/Adapter.js"; -import {AdapterModel, LowDbAdapter} from "./LowDbAdapter.js"; +import {AdapterModel, LowDbAdapter, type LowModel} from "./LowDbAdapter.js"; export interface FileSyncAdapterConstructorOptions extends AdapterConstructorOptions { readOnly: true; @@ -20,19 +20,17 @@ export class FileSyncAdapter extends LowDbAdapter { fs.ensureDirSync(dirname(this.dbFilePath)); - const file = new FileSync<{collection: T[]}>(this.dbFilePath); + const file = new JSONFileSync>(this.dbFilePath); - this.db = low(file); - this.db - .defaults({ - collectionName: this.collectionName, - modelName: nameOf(this.model), - collection: [] - }) - .write(); + this.db = new LowSync>(file, { + collectionName: this.collectionName, + modelName: nameOf(this.model), + collection: [] + }); + this.db.write(); if (options.readOnly) { - file.write = () => {}; + file.write = (() => {}) as any; } } } diff --git a/packages/orm/adapters/src/adapters/LowDbAdapter.spec.ts b/packages/orm/adapters/src/adapters/LowDbAdapter.spec.ts new file mode 100644 index 00000000000..d96e54c575d --- /dev/null +++ b/packages/orm/adapters/src/adapters/LowDbAdapter.spec.ts @@ -0,0 +1,205 @@ +import {faker} from "@faker-js/faker"; +import {PlatformTest} from "@tsed/common"; +import {deserialize} from "@tsed/json-mapper"; +import {Format, Name, Property} from "@tsed/schema"; + +import {Adapter, Adapters, MemoryAdapter} from "../../src/index.js"; + +class BaseClient { + @Format("date-time") + createdAt: Date; +} + +class Client extends BaseClient { + @Name("id") + _id: string; + + @Property() + name: string; +} + +describe("MemoryAdapter", () => { + let adapter: Adapter; + beforeEach(() => PlatformTest.create()); + afterEach(() => PlatformTest.reset()); + beforeEach(() => { + adapter = PlatformTest.get(Adapters).invokeAdapter({ + collectionName: "clients", + model: Client, + adapter: MemoryAdapter + }); + }); + + describe("create()", () => { + it("should create a new instance", async () => { + const base = deserialize( + { + name: faker.person.firstName(), + createdAt: faker.date.past() + }, + {type: Client} + ); + + const client = await adapter.create(base); + + expect(client).toBeInstanceOf(Client); + expect(typeof client._id).toBe("string"); + expect(client.name).toBe(base.name); + expect(client.createdAt).toEqual(base.createdAt); + }); + + it("should create a new instance with expireAt", async () => { + const base = { + name: faker.person.firstName() + }; + + const client = await adapter.create(base, new Date()); + + expect(client).toBeInstanceOf(Client); + expect(typeof client._id).toBe("string"); + expect(client.name).toBe(base.name); + }); + }); + + describe("upsert()", () => { + it("should create a new instance if not exists", async () => { + const base: any = { + name: faker.person.firstName() + }; + + const client = await adapter.upsert(base._id, base); + + expect(client).toBeInstanceOf(Client); + expect(typeof client._id).toBe("string"); + expect(client.name).toBe(base.name); + }); + + it("should update instance if exists", async () => { + const base: any = { + name: faker.person.firstName() + }; + + const client = await adapter.upsert(base._id, base); + const client2 = await adapter.upsert(client._id, client); + + expect(client2).toBeInstanceOf(Client); + expect(typeof client2._id).toBe("string"); + expect(client2.name).toBe(base.name); + }); + }); + + describe("updateOne()", () => { + it("should update an instance", async () => { + const base = { + name: faker.person.firstName() + }; + + const client = await adapter.create(base); + + const update = { + _id: client._id, + name: faker.person.firstName() + }; + + const client2 = await adapter.updateOne({_id: client._id}, update); + + expect(client2).toBeInstanceOf(Client); + expect(typeof client2?._id).toBe("string"); + expect(client2?.name).not.toBe(base.name); + expect(client2?.name).toBe(update.name); + }); + it("should return undefined", async () => { + const base = { + name: faker.person.firstName() + }; + + const client = await adapter.updateOne({_id: faker.string.uuid()}, base); + + expect(client).toBeUndefined(); + }); + }); + + describe("findById()", () => { + it("should find by ID", async () => { + const base = { + name: faker.person.firstName() + }; + + const client = await adapter.create(base); + const result = await adapter.findById(client._id); + + expect(result).toBeInstanceOf(Client); + expect(result?._id).toBe(client._id); + expect(result?.name).toBe(base.name); + }); + }); + + describe("findOne()", () => { + it("should find one item", async () => { + const base = { + name: faker.person.firstName() + }; + + const client = await adapter.create(base); + + const result = await adapter.findOne({ + name: base.name + }); + + expect(result).toBeInstanceOf(Client); + expect(result?._id).toBe(client._id); + expect(result?.name).toBe(base.name); + }); + }); + describe("findAll()", () => { + it("should find all items", async () => { + const base = { + name: faker.person.firstName() + }; + + await adapter.create(base); + + const result = await adapter.findAll({ + name: base.name + }); + + expect(result[0]).toBeInstanceOf(Client); + expect(result[0].name).toBe(base.name); + }); + }); + describe("deleteById()", () => { + it("should delete an item by id", async () => { + const base = { + name: faker.person.firstName() + }; + + const client = await adapter.create(base); + + const result = await adapter.deleteById(client._id); + + expect(result).toBeInstanceOf(Client); + expect(result?.name).toBe(base.name); + }); + }); + describe("deleteMany()", () => { + it("should delete many", async () => { + const base = { + name: faker.person.firstName() + }; + + const client = await adapter.create(base); + + await adapter.create({ + name: faker.person.firstName() + }); + await adapter.create({ + name: faker.person.firstName() + }); + + const result = await adapter.deleteMany(client); + + expect(result[0]).toBeInstanceOf(Client); + expect(result[0]?.name).toBe(base.name); + }); + }); +}); diff --git a/packages/orm/adapters/src/adapters/LowDbAdapter.ts b/packages/orm/adapters/src/adapters/LowDbAdapter.ts index f3d05d4e644..cc02a7237fe 100644 --- a/packages/orm/adapters/src/adapters/LowDbAdapter.ts +++ b/packages/orm/adapters/src/adapters/LowDbAdapter.ts @@ -1,6 +1,7 @@ import {cleanObject} from "@tsed/core"; +import _ from "lodash"; import isMatch from "lodash/isMatch.js"; -import low from "lowdb"; +import type {Low, LowSync} from "lowdb"; import {v4 as uuid} from "uuid"; import {Adapter} from "../domain/Adapter.js"; @@ -12,11 +13,17 @@ export interface AdapterModel { [key: string]: any; } +export interface LowModel { + collection: T[]; + collectionName?: string; + modelName?: string; +} + export class LowDbAdapter extends Adapter { - protected db: low.LowdbSync<{collection: T[]}>; + protected db: LowSync> | Low>; get collection() { - return this.db.get("collection"); + return this.db.data!.collection!; } protected get dbFilePath() { @@ -33,7 +40,7 @@ export class LowDbAdapter extends Adapter { await this.validate(payload as T); - await this.collection.push(this.serialize(payload) as T).write(); + await this.db.update(({collection}) => collection.push(this.serialize(payload))); return this.deserialize(payload); } @@ -49,9 +56,9 @@ export class LowDbAdapter extends Adapter { const item = this.serialize(payload); item.expires_at = expiresAt; - await this.collection.push(item).write(); + await this.db.update(({collection}) => collection.push(item)); - return this.deserialize(payload); + return this.deserialize(item); } return (await this.update(id, payload, expiresAt)) as T; @@ -62,27 +69,26 @@ export class LowDbAdapter extends Adapter { } public async updateOne(predicate: Partial, payload: T, expiresAt?: Date): Promise { - let index = this.collection.findIndex(cleanObject(predicate)).value(); + let index = _.findIndex(this.collection, cleanObject(predicate)); if (index === -1) { return; } - let item = this.deserialize(this.collection.get(index).value()); + let item = this.deserialize(this.collection[index]); Object.assign(item, payload, {_id: item._id}); await this.validate(item as T); item.expires_at = expiresAt || item.expires_at; - - await this.collection.set(index, item).write(); + this.db.update(({collection}) => (collection[index] = item)); return this.deserialize(item); } findOne(predicate: Partial): Promise { - const item = this.collection.find(cleanObject(predicate)).value(); + const item = _.find(this.collection, cleanObject(predicate)); return this.deserialize(item); } @@ -91,20 +97,15 @@ export class LowDbAdapter extends Adapter { return this.findOne({_id}); } - public findAll(predicate: Partial = {}): Promise { - return Promise.resolve( - this.collection - .filter(cleanObject(predicate)) - .value() - .map((item) => this.deserialize(item)) - ); + public async findAll(predicate: Partial = {}): Promise { + return _.filter(this.collection, cleanObject(predicate)).map((item) => this.deserialize(item)); } public deleteOne(predicate: Partial): Promise { - const item = this.collection.find(cleanObject(predicate)).value(); + const item = _.find(this.collection, cleanObject(predicate)); if (item) { - this.collection.remove(({_id}) => _id === item._id).write(); + _.remove(this.collection, ({_id}) => _id === item._id); return Promise.resolve(this.deserialize(item)); } @@ -119,15 +120,15 @@ export class LowDbAdapter extends Adapter { public async deleteMany(predicate: Partial): Promise { let removedItems: T[] = []; - await this.collection - .remove((item) => { + this.db.update((data) => { + _.remove(data.collection, (item) => { if (isMatch(item, cleanObject(predicate))) { removedItems.push(this.deserialize(item)); return true; } return false; - }) - .write(); + }); + }); return removedItems; } diff --git a/packages/orm/adapters/src/adapters/MemoryAdapter.ts b/packages/orm/adapters/src/adapters/MemoryAdapter.ts index 8415645e41d..f549227b278 100644 --- a/packages/orm/adapters/src/adapters/MemoryAdapter.ts +++ b/packages/orm/adapters/src/adapters/MemoryAdapter.ts @@ -1,6 +1,5 @@ import {Configuration, Injectable, Opts, ProviderScope, Scope} from "@tsed/di"; -import low from "lowdb"; -import Memory from "lowdb/adapters/Memory.js"; +import {Low, Memory} from "lowdb"; import {AdapterModel, LowDbAdapter} from "./LowDbAdapter.js"; @@ -10,11 +9,12 @@ export class MemoryAdapter extends LowDbAdapter { constructor(@Opts options: any, @Configuration() configuration: Configuration) { super(options, configuration); - this.db = low(new Memory<{collection: T[]}>(this.dbFilePath)); - this.db - .defaults({ - collection: [] - }) - .write(); + this.db = new Low(new Memory(), { + collection: [] + }); + } + + $onInit() { + return this.db.write(); } } diff --git a/packages/orm/adapters/src/decorators/injectAdapter.spec.ts b/packages/orm/adapters/src/decorators/injectAdapter.spec.ts index 83d1ed212b0..98417bac4da 100644 --- a/packages/orm/adapters/src/decorators/injectAdapter.spec.ts +++ b/packages/orm/adapters/src/decorators/injectAdapter.spec.ts @@ -43,7 +43,7 @@ describe("InjectAdapter", () => { await clients.adapter.create(client); - const items = (clients.adapter as MemoryAdapter).collection.value(); + const items = (clients.adapter as MemoryAdapter).collection; expect(items).toEqual([ { _id: expect.any(String), @@ -85,7 +85,7 @@ describe("InjectAdapter", () => { await clients.adapter.create(client); - const items = (clients.adapter as MemoryAdapter).collection.value(); + const items = (clients.adapter as MemoryAdapter).collection; expect(items).toEqual([ { _id: expect.any(String), @@ -185,7 +185,7 @@ describe("InjectAdapter", () => { await clients.adapter.create(client); - const items = (clients.adapter as MemoryAdapter).collection.value(); + const items = (clients.adapter as MemoryAdapter).collection; expect(items).toEqual([ { _id: expect.any(String), @@ -226,7 +226,7 @@ describe("InjectAdapter", () => { await clients.adapter.create(client); - const items = (clients.adapter as MemoryAdapter).collection.value(); + const items = (clients.adapter as MemoryAdapter).collection; expect(items).toEqual([ { _id: expect.any(String), diff --git a/packages/orm/adapters/src/domain/Adapter.ts b/packages/orm/adapters/src/domain/Adapter.ts index 7ad5e5e8b96..ef93ff15820 100644 --- a/packages/orm/adapters/src/domain/Adapter.ts +++ b/packages/orm/adapters/src/domain/Adapter.ts @@ -2,7 +2,7 @@ import {AjvService} from "@tsed/ajv"; import {classOf, isArray, isPlainObject, nameOf, Type} from "@tsed/core"; import {Configuration, Inject, Opts} from "@tsed/di"; import {deserialize, JsonDeserializerOptions, JsonSerializerOptions, serialize} from "@tsed/json-mapper"; -import {getPropertiesStores, JsonEntityStore} from "@tsed/schema"; +import {getPropertiesStores} from "@tsed/schema"; export interface AdapterConstructorOptions extends Record { model: Type | Object; diff --git a/packages/orm/adapters/src/domain/AdaptersSettings.ts b/packages/orm/adapters/src/domain/AdaptersSettings.ts index 0310e93771f..d9daf64aa06 100644 --- a/packages/orm/adapters/src/domain/AdaptersSettings.ts +++ b/packages/orm/adapters/src/domain/AdaptersSettings.ts @@ -22,6 +22,7 @@ export interface AdaptersSettings { declare global { namespace TsED { interface Configuration { + // @ts-ignore adapters: AdaptersSettings; } } diff --git a/packages/security/oidc-provider/package.json b/packages/security/oidc-provider/package.json index e1fd65a98c0..ca4aac1b684 100644 --- a/packages/security/oidc-provider/package.json +++ b/packages/security/oidc-provider/package.json @@ -56,7 +56,7 @@ "@types/uuid": "10.0.0", "cross-env": "7.0.3", "eslint": "9.12.0", - "lowdb": "3.0.0", + "lowdb": "7.0.1", "oidc-provider": "8.5.1", "typescript": "5.4.5", "vitest": "2.1.2" diff --git a/tools/barrels/index.mjs b/tools/barrels/index.mjs index 5d0c6675f8a..a0c1eda270e 100755 --- a/tools/barrels/index.mjs +++ b/tools/barrels/index.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env ts-node import fs from "fs-extra"; -import globby from "globby"; +import {globby} from "globby"; import {join} from "node:path"; function resolveConfig() { diff --git a/yarn.lock b/yarn.lock index 66033aa07f9..75ac8687d7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6565,7 +6565,7 @@ __metadata: eslint: "npm:9.12.0" fs-extra: "npm:11.2.0" lodash: "npm:^4.17.21" - lowdb: "npm:1.0.0" + lowdb: "npm:7.0.1" tslib: "npm:2.7.0" typescript: "npm:5.4.5" uuid: "npm:^10.0.0" @@ -7314,7 +7314,7 @@ __metadata: koa-mount: "npm:^4.0.0" koa-rewrite: "npm:^3.0.1" lodash: "npm:4.17.21" - lowdb: "npm:3.0.0" + lowdb: "npm:7.0.1" oidc-provider: "npm:8.5.1" tslib: "npm:2.7.0" typescript: "npm:5.4.5" @@ -17132,7 +17132,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:4.2.11, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:4.2.11, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 @@ -20737,7 +20737,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4, lodash@npm:4.17.21, lodash@npm:>=4.17.21, lodash@npm:^4.17.10, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.5": +"lodash@npm:4.17.21, lodash@npm:>=4.17.21, lodash@npm:^4.17.10, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.5": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 @@ -20864,25 +20864,12 @@ __metadata: languageName: node linkType: hard -"lowdb@npm:1.0.0": - version: 1.0.0 - resolution: "lowdb@npm:1.0.0" - dependencies: - graceful-fs: "npm:^4.1.3" - is-promise: "npm:^2.1.0" - lodash: "npm:4" - pify: "npm:^3.0.0" - steno: "npm:^0.4.1" - checksum: 10/0c94d2d2fc407424606cd30df903abbac7c403d0cd5a6b55b77c2604a366a6c37d26401d2b7889c53dcd8992031728637b26f01a55c2aa297127b2b4fa30143e - languageName: node - linkType: hard - -"lowdb@npm:3.0.0": - version: 3.0.0 - resolution: "lowdb@npm:3.0.0" +"lowdb@npm:7.0.1": + version: 7.0.1 + resolution: "lowdb@npm:7.0.1" dependencies: - steno: "npm:^2.1.0" - checksum: 10/c519dc6e1ccd9b245acde12a0449b5e47059b0435a1d830360704d5b1dc8bc477c173709cf84915a4c80f5c8c49f3c8a436030eba5dfe97af0e4d9057a3f13f1 + steno: "npm:^4.0.2" + checksum: 10/089cb878515b3b4634980c77b1697991571832a440f0fbc8cabe410ff9bb22b377387fc6cf6ebd9a6430707a835ebd69561f89d30aae7def43c93410b6f82b58 languageName: node linkType: hard @@ -27189,19 +27176,10 @@ __metadata: languageName: node linkType: hard -"steno@npm:^0.4.1": - version: 0.4.4 - resolution: "steno@npm:0.4.4" - dependencies: - graceful-fs: "npm:^4.1.3" - checksum: 10/02e23c2703140db5ddeee2f353830b7c628705fd63337c4d25bd400828cc2a86bb7a56760ae5aafd4a6db60c49d84e0405f3e18ce24bb0821a054c5fcd07bfc8 - languageName: node - linkType: hard - -"steno@npm:^2.1.0": - version: 2.1.0 - resolution: "steno@npm:2.1.0" - checksum: 10/10fc583480c62b783cd9eb906b8c65d11d19f328fa2e7a9353b1421c505048525d667ed81e16abc9658a4e03aa26bf5f964fa513a0d979d130c965ccb336c8e6 +"steno@npm:^4.0.2": + version: 4.0.2 + resolution: "steno@npm:4.0.2" + checksum: 10/cb8beb6b6da410f6a307261da813e57569ff3c85b11695437259f49f081fc6b0eeb26c059f0ec661c4098a09f361057fe834b04d3f94ece885e96a9e0eeba697 languageName: node linkType: hard