@medyll/idae-db 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/IdaeDbAdapter.d.ts +14 -15
- package/dist/IdaeDbAdapter.js +13 -1
- package/dist/IdaeDbConnection.d.ts +3 -11
- package/dist/IdaeDbConnection.js +31 -76
- package/dist/adapters/MongoDBAdapter.d.ts +7 -3
- package/dist/adapters/MongoDBAdapter.js +33 -3
- package/dist/idaeDb.d.ts +1 -0
- package/dist/idaeDb.js +5 -0
- package/dist/types.d.ts +5 -1
- package/package.json +1 -1
package/dist/IdaeDbAdapter.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DbType, type IdaeDbAdapterInterface, type IdaeDbParams } from './types.js';
|
|
2
2
|
import { IdaeDbConnection } from './IdaeDbConnection.js';
|
|
3
3
|
import { IdaeEventEmitter, type EventListeners } from './IdaeEventEmitter.js';
|
|
4
|
-
type AdapterConstructor<T
|
|
4
|
+
export type AdapterConstructor = new <T extends Document = Document>(collection: string, connection: IdaeDbConnection) => Omit<IdaeDbAdapterInterface<T>, 'connect' | 'getDb' | 'close'>;
|
|
5
5
|
export declare class IdaeDbAdapter<T extends Document> extends IdaeEventEmitter {
|
|
6
6
|
private dbType;
|
|
7
7
|
private adapter;
|
|
@@ -11,7 +11,7 @@ export declare class IdaeDbAdapter<T extends Document> extends IdaeEventEmitter
|
|
|
11
11
|
* @param dbType The type of database for this adapter.
|
|
12
12
|
* @param adapterConstructor The constructor function for the adapter.
|
|
13
13
|
*/
|
|
14
|
-
static addAdapter<A>(dbType: DbType, adapterConstructor: AdapterConstructor
|
|
14
|
+
static addAdapter<A>(dbType: DbType, adapterConstructor: AdapterConstructor): void;
|
|
15
15
|
constructor(collection: string, connection: IdaeDbConnection, dbType: DbType);
|
|
16
16
|
private applyAdapter;
|
|
17
17
|
/**
|
|
@@ -19,17 +19,16 @@ export declare class IdaeDbAdapter<T extends Document> extends IdaeEventEmitter
|
|
|
19
19
|
* @param events An object containing event listeners for different operations.
|
|
20
20
|
*/
|
|
21
21
|
registerEvents(events: EventListeners<T>): void;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
static getAdapterForDbType(dbType: DbType): IdaeDbAdapterInterface<T> | undefined;
|
|
23
|
+
create(data: Partial<T>): Promise<T>;
|
|
24
|
+
findById(id: string): Promise<T[]>;
|
|
25
|
+
find(params: IdaeDbParams<T>): Promise<T[]>;
|
|
26
|
+
findOne(params: IdaeDbParams<T>): Promise<T | null>;
|
|
27
|
+
update(id: string, updateData: Partial<T>): Promise<any>;
|
|
28
|
+
updateWhere(params: IdaeDbParams<T>, updateData: Partial<T>): Promise<any>;
|
|
29
|
+
deleteById(id: string): Promise<any>;
|
|
30
|
+
deleteWhere(params: IdaeDbParams<T>): Promise<{
|
|
31
|
+
deletedCount?: number;
|
|
32
|
+
}>;
|
|
33
|
+
transaction<TResult>(callback: (session: any) => Promise<TResult>): Promise<TResult>;
|
|
34
34
|
}
|
|
35
|
-
export {};
|
package/dist/IdaeDbAdapter.js
CHANGED
|
@@ -8,7 +8,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
-
var _a, _b, _c;
|
|
11
|
+
var _a, _b, _c, _d;
|
|
12
12
|
import { DbType } from './types.js';
|
|
13
13
|
import { IdaeDbConnection } from './IdaeDbConnection.js';
|
|
14
14
|
import { MongoDBAdapter } from './adapters/MongoDBAdapter.js';
|
|
@@ -61,6 +61,9 @@ export class IdaeDbAdapter extends IdaeEventEmitter {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
static getAdapterForDbType(dbType) {
|
|
65
|
+
return IdaeDbAdapter.adapters.get(dbType);
|
|
66
|
+
}
|
|
64
67
|
async create(data) {
|
|
65
68
|
return this.adapter.create(data);
|
|
66
69
|
}
|
|
@@ -85,6 +88,9 @@ export class IdaeDbAdapter extends IdaeEventEmitter {
|
|
|
85
88
|
async deleteWhere(params) {
|
|
86
89
|
return this.adapter.deleteWhere(params);
|
|
87
90
|
}
|
|
91
|
+
async transaction(callback) {
|
|
92
|
+
return this.adapter.transaction(callback);
|
|
93
|
+
}
|
|
88
94
|
}
|
|
89
95
|
__decorate([
|
|
90
96
|
withEmitter(),
|
|
@@ -134,3 +140,9 @@ __decorate([
|
|
|
134
140
|
__metadata("design:paramtypes", [Object]),
|
|
135
141
|
__metadata("design:returntype", Promise)
|
|
136
142
|
], IdaeDbAdapter.prototype, "deleteWhere", null);
|
|
143
|
+
__decorate([
|
|
144
|
+
withEmitter(),
|
|
145
|
+
__metadata("design:type", Function),
|
|
146
|
+
__metadata("design:paramtypes", [Function]),
|
|
147
|
+
__metadata("design:returntype", typeof (_d = typeof Promise !== "undefined" && Promise) === "function" ? _d : Object)
|
|
148
|
+
], IdaeDbAdapter.prototype, "transaction", null);
|
|
@@ -1,25 +1,17 @@
|
|
|
1
|
-
import { Db } from 'mongodb';
|
|
2
|
-
import type { Connection as MysqlConnection } from 'mysql2/promise';
|
|
3
1
|
import { IdaeDBModel } from './IdaeDBModel.js';
|
|
4
2
|
import { IdaeDb } from './idaeDb.js';
|
|
5
3
|
export declare class IdaeDbConnection {
|
|
6
4
|
#private;
|
|
7
5
|
private _idaeDb;
|
|
8
6
|
private _dbName;
|
|
9
|
-
private mongoClient;
|
|
10
|
-
private mongoDb;
|
|
11
|
-
private mysqlConnection;
|
|
12
|
-
private chromaDbConnection;
|
|
13
|
-
private models;
|
|
14
|
-
private _uri;
|
|
15
|
-
private _dbType;
|
|
16
7
|
constructor(_idaeDb: IdaeDb, _dbName: string);
|
|
17
8
|
connect(): Promise<IdaeDbConnection>;
|
|
18
|
-
getDb():
|
|
9
|
+
getDb(): any;
|
|
19
10
|
getModel: <T extends Document>(collectionName: string) => IdaeDBModel<T>;
|
|
20
|
-
|
|
11
|
+
getClient<T>(): T;
|
|
21
12
|
close(): Promise<void>;
|
|
22
13
|
get dbName(): string;
|
|
23
14
|
get connected(): boolean;
|
|
24
15
|
get idaeDb(): IdaeDb;
|
|
16
|
+
private getFullDbName;
|
|
25
17
|
}
|
package/dist/IdaeDbConnection.js
CHANGED
|
@@ -1,110 +1,64 @@
|
|
|
1
1
|
// packages\idae-db\lib\IdaeDbConnection.ts
|
|
2
|
-
import { MongoClient, Db } from 'mongodb';
|
|
3
2
|
import { DbType } from './types.js';
|
|
4
3
|
import { IdaeDBModel } from './IdaeDBModel.js';
|
|
5
4
|
import { IdaeDb } from './idaeDb.js';
|
|
6
5
|
export class IdaeDbConnection {
|
|
7
6
|
_idaeDb;
|
|
8
7
|
_dbName;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
_dbType;
|
|
8
|
+
#models = new Map();
|
|
9
|
+
#uri;
|
|
10
|
+
#dbType;
|
|
11
|
+
#Db;
|
|
12
|
+
#client;
|
|
13
|
+
#adapterClass;
|
|
16
14
|
#connected = false;
|
|
17
15
|
constructor(_idaeDb, _dbName) {
|
|
18
16
|
this._idaeDb = _idaeDb;
|
|
19
17
|
this._dbName = _dbName;
|
|
20
|
-
this
|
|
21
|
-
this
|
|
18
|
+
this.#uri = _idaeDb.uri;
|
|
19
|
+
this.#dbType = _idaeDb.options.dbType;
|
|
20
|
+
this.#adapterClass = _idaeDb.adapterClass;
|
|
22
21
|
// add prefix if requested
|
|
23
22
|
this._dbName = this.getFullDbName();
|
|
24
|
-
console.log('dbName', this._dbName);
|
|
25
23
|
}
|
|
26
24
|
async connect() {
|
|
25
|
+
if (!this.#adapterClass?.connect)
|
|
26
|
+
throw new Error('Adapter does not have a connect method');
|
|
27
27
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
await this.mongoClient.connect();
|
|
32
|
-
this.mongoDb = this.mongoClient.db(this._dbName);
|
|
33
|
-
console.log('Connected to MongoDB');
|
|
34
|
-
this.#connected = true;
|
|
35
|
-
break;
|
|
36
|
-
case DbType.MYSQL:
|
|
37
|
-
// this.mysqlConnection = await mysql.createConnection(...);
|
|
38
|
-
console.log('Connected to MySQL');
|
|
39
|
-
this.#connected = false;
|
|
40
|
-
break;
|
|
41
|
-
case DbType.CHROMADB:
|
|
42
|
-
// this.mysqlConnection = await mysql.createConnection(...);
|
|
43
|
-
console.log('Connected to CHROMADB');
|
|
44
|
-
this.#connected = false;
|
|
45
|
-
break;
|
|
46
|
-
default:
|
|
47
|
-
this.#connected = false;
|
|
48
|
-
throw new Error(`Unsupported database type: ${this._dbType}`);
|
|
49
|
-
}
|
|
28
|
+
this.#client = await this.#adapterClass.connect(this.#uri);
|
|
29
|
+
this.#Db = this.#adapterClass.getDb(this.#client, this._dbName);
|
|
30
|
+
this.#connected = true;
|
|
50
31
|
return this;
|
|
51
32
|
}
|
|
52
33
|
catch (error) {
|
|
53
|
-
console.error(`Error connecting to ${this
|
|
34
|
+
console.error(`Error connecting to ${this.#dbType}:`, error);
|
|
54
35
|
this.#connected = false;
|
|
55
36
|
throw error;
|
|
56
37
|
}
|
|
57
38
|
}
|
|
58
39
|
getDb() {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (!this.mongoDb) {
|
|
62
|
-
throw new Error('MongoDB not connected. Call connect() first.');
|
|
63
|
-
}
|
|
64
|
-
return this.mongoDb;
|
|
65
|
-
case DbType.MYSQL:
|
|
66
|
-
if (!this.mysqlConnection) {
|
|
67
|
-
throw new Error('MySQL not connected. Call connect() first.');
|
|
68
|
-
}
|
|
69
|
-
return this.mysqlConnection;
|
|
70
|
-
case DbType.CHROMADB:
|
|
71
|
-
if (!this.chromaDbConnection) {
|
|
72
|
-
throw new Error('ChromaDb not connected. Call connect() first.');
|
|
73
|
-
}
|
|
74
|
-
return this.chromaDbConnection;
|
|
75
|
-
default:
|
|
76
|
-
throw new Error(`Unsupported database type: ${this._dbType}`);
|
|
40
|
+
if (!this.#Db) {
|
|
41
|
+
throw new Error('Db not connected. Call connect() first.');
|
|
77
42
|
}
|
|
43
|
+
return this.#Db;
|
|
78
44
|
}
|
|
79
45
|
getModel = (collectionName) => {
|
|
80
|
-
if (this.
|
|
81
|
-
return this.models[collectionName];
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
46
|
+
if (!this.#models.has(collectionName)) {
|
|
84
47
|
const model = new IdaeDBModel(this, collectionName, this._idaeDb?.options?.idaeModelOptions);
|
|
85
|
-
this.
|
|
86
|
-
return model;
|
|
48
|
+
this.#models.set(collectionName, model);
|
|
87
49
|
}
|
|
50
|
+
return this.#models.get(collectionName);
|
|
88
51
|
};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
case DbType.MONGODB:
|
|
93
|
-
if (this.mongoClient) {
|
|
94
|
-
await this.mongoClient.close(true);
|
|
95
|
-
this.mongoClient = null;
|
|
96
|
-
this.mongoDb = null;
|
|
97
|
-
console.log('Disconnected from MongoDB');
|
|
98
|
-
this.#connected = false;
|
|
99
|
-
}
|
|
100
|
-
break;
|
|
101
|
-
case DbType.MYSQL:
|
|
102
|
-
if (this.mysqlConnection) {
|
|
103
|
-
await this.mysqlConnection.end();
|
|
104
|
-
console.log('Disconnected from MySQL');
|
|
105
|
-
}
|
|
106
|
-
break;
|
|
52
|
+
getClient() {
|
|
53
|
+
if (!this.#client) {
|
|
54
|
+
throw new Error('Client not initialized. Call connect() first.');
|
|
107
55
|
}
|
|
56
|
+
return this.#client;
|
|
57
|
+
}
|
|
58
|
+
async close() {
|
|
59
|
+
if (!this.#adapterClass?.close)
|
|
60
|
+
throw new Error('Adapter does not have a close method');
|
|
61
|
+
await this.#adapterClass?.close(this.#client);
|
|
108
62
|
}
|
|
109
63
|
get dbName() {
|
|
110
64
|
return this._dbName;
|
|
@@ -115,4 +69,5 @@ export class IdaeDbConnection {
|
|
|
115
69
|
get idaeDb() {
|
|
116
70
|
return this._idaeDb;
|
|
117
71
|
}
|
|
72
|
+
getFullDbName = () => [this.idaeDb.options.dbScope, this._dbName].join('_');
|
|
118
73
|
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { IdaeDbParams, IdaeDbAdapterInterface } from '../types.js';
|
|
2
|
-
import { type Document, type UpdateOptions, type IndexSpecification, type CreateIndexesOptions } from 'mongodb';
|
|
2
|
+
import { type Document, type UpdateOptions, type IndexSpecification, type CreateIndexesOptions, ClientSession, MongoClient, Db } from 'mongodb';
|
|
3
3
|
import { IdaeDbConnection } from '../IdaeDbConnection.js';
|
|
4
4
|
export declare class MongoDBAdapter<T extends Document = Document> implements IdaeDbAdapterInterface<T> {
|
|
5
5
|
private model;
|
|
6
6
|
private connection;
|
|
7
7
|
private fieldId;
|
|
8
8
|
constructor(collection: string, connection: IdaeDbConnection);
|
|
9
|
+
static connect(uri: string): Promise<MongoClient>;
|
|
10
|
+
static getDb(client: MongoClient, dbName: string): Db;
|
|
11
|
+
static close(client: MongoClient): Promise<void>;
|
|
9
12
|
createIndex(fieldOrSpec: IndexSpecification, options?: CreateIndexesOptions): Promise<string>;
|
|
10
|
-
findById(id: string): Promise<
|
|
11
|
-
find(params: IdaeDbParams<T>): Promise<import("mongodb").
|
|
13
|
+
findById(id: string): Promise<T[]>;
|
|
14
|
+
find(params: IdaeDbParams<T>): Promise<import("mongodb").WithId<T>[]>;
|
|
12
15
|
findOne(params: IdaeDbParams<T>): Promise<import("mongodb").WithId<T> | null>;
|
|
13
16
|
create(data: Partial<T>): Promise<import("mongodb").UpdateResult<T>>;
|
|
14
17
|
update(id: string, updateData: Partial<T>, options?: UpdateOptions): Promise<import("mongodb").UpdateResult<T>>;
|
|
@@ -17,5 +20,6 @@ export declare class MongoDBAdapter<T extends Document = Document> implements Id
|
|
|
17
20
|
deleteWhere(params: IdaeDbParams<T>): Promise<{
|
|
18
21
|
deletedCount?: number;
|
|
19
22
|
}>;
|
|
23
|
+
transaction<TResult>(callback: (session: ClientSession) => Promise<TResult>): Promise<TResult>;
|
|
20
24
|
private parseSortOptions;
|
|
21
25
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// packages\idae-db\src\lib\adapters\MongoDBAdapter.ts
|
|
2
2
|
import dotenv from 'dotenv';
|
|
3
|
-
import {} from 'mongodb';
|
|
3
|
+
import { ClientSession, MongoClient, Db } from 'mongodb';
|
|
4
4
|
import { IdaeDbConnection } from '../IdaeDbConnection.js';
|
|
5
5
|
import { IdaeDBModel } from '../IdaeDBModel.js';
|
|
6
6
|
// Load environment variables
|
|
@@ -17,11 +17,24 @@ export class MongoDBAdapter {
|
|
|
17
17
|
this.model = this.connection.getModel(collection);
|
|
18
18
|
this.fieldId = this.model.fieldId;
|
|
19
19
|
}
|
|
20
|
+
static async connect(uri) {
|
|
21
|
+
const client = new MongoClient(uri);
|
|
22
|
+
await client.connect();
|
|
23
|
+
return client;
|
|
24
|
+
}
|
|
25
|
+
static getDb(client, dbName) {
|
|
26
|
+
return client.db(dbName);
|
|
27
|
+
}
|
|
28
|
+
static async close(client) {
|
|
29
|
+
return client.close();
|
|
30
|
+
}
|
|
20
31
|
async createIndex(fieldOrSpec, options) {
|
|
21
32
|
return this.model.collection.createIndex(fieldOrSpec, options);
|
|
22
33
|
}
|
|
23
34
|
async findById(id) {
|
|
24
|
-
return this.model.collection
|
|
35
|
+
return this.model.collection
|
|
36
|
+
.find({ [this.fieldId]: id }, { hint: this.fieldId })
|
|
37
|
+
.toArray();
|
|
25
38
|
}
|
|
26
39
|
async find(params) {
|
|
27
40
|
const { query = {}, sortBy, limit, skip } = params;
|
|
@@ -30,7 +43,8 @@ export class MongoDBAdapter {
|
|
|
30
43
|
.find(query)
|
|
31
44
|
.sort(sortOptions)
|
|
32
45
|
.limit(Number(limit) || 0)
|
|
33
|
-
.skip(Number(skip) || 0)
|
|
46
|
+
.skip(Number(skip) || 0)
|
|
47
|
+
.toArray();
|
|
34
48
|
}
|
|
35
49
|
async findOne(params) {
|
|
36
50
|
return this.model.collection.findOne(params.query);
|
|
@@ -56,6 +70,22 @@ export class MongoDBAdapter {
|
|
|
56
70
|
const result = await this.model.collection.deleteMany(params.query);
|
|
57
71
|
return { deletedCount: result.deletedCount };
|
|
58
72
|
}
|
|
73
|
+
async transaction(callback) {
|
|
74
|
+
const session = this.connection.getClient().startSession();
|
|
75
|
+
try {
|
|
76
|
+
session.startTransaction();
|
|
77
|
+
const result = await callback(session);
|
|
78
|
+
await session.commitTransaction();
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
await session.abortTransaction();
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
await session.endSession();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
59
89
|
parseSortOptions(sortBy) {
|
|
60
90
|
const sortOptions = {};
|
|
61
91
|
if (sortBy) {
|
package/dist/idaeDb.d.ts
CHANGED
|
@@ -47,6 +47,7 @@ export declare class IdaeDb {
|
|
|
47
47
|
private applyEvents;
|
|
48
48
|
closeConnection(): Promise<void>;
|
|
49
49
|
closeAllConnections(): Promise<void>;
|
|
50
|
+
get adapterClass(): import("./types.js").IdaeDbAdapterInterface<T> | undefined;
|
|
50
51
|
get connectionKey(): IdaeDbInstanceKey;
|
|
51
52
|
get uri(): string;
|
|
52
53
|
get options(): Options;
|
package/dist/idaeDb.js
CHANGED
|
@@ -6,6 +6,7 @@ export class IdaeDb {
|
|
|
6
6
|
_uri;
|
|
7
7
|
globalEvents;
|
|
8
8
|
static instances = new Map();
|
|
9
|
+
#adapterClass;
|
|
9
10
|
#connections = new Map();
|
|
10
11
|
#connection = undefined;
|
|
11
12
|
#options = {
|
|
@@ -16,6 +17,7 @@ export class IdaeDb {
|
|
|
16
17
|
constructor(_uri, options = {}) {
|
|
17
18
|
this._uri = _uri;
|
|
18
19
|
this.#options = { ...this.#options, ...options };
|
|
20
|
+
this.#adapterClass = IdaeDbAdapter.getAdapterForDbType(this.options.dbType);
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* Initializes or retrieves an IdaeDb instance.
|
|
@@ -94,6 +96,9 @@ export class IdaeDb {
|
|
|
94
96
|
}
|
|
95
97
|
this.#connections.clear();
|
|
96
98
|
}
|
|
99
|
+
get adapterClass() {
|
|
100
|
+
return this.#adapterClass;
|
|
101
|
+
}
|
|
97
102
|
get connectionKey() {
|
|
98
103
|
return `${this.options.dbType}:${this._uri}`;
|
|
99
104
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type Document, type Filter, type IndexSpecification, type CreateIndexes
|
|
|
2
2
|
export interface IdaeDbAdapterInterface<T extends Document> {
|
|
3
3
|
createIndex(fieldOrSpec: IndexSpecification, options?: CreateIndexesOptions): Promise<string>;
|
|
4
4
|
create(data: Partial<T>): Promise<T>;
|
|
5
|
-
findById(id: string): Promise<T>;
|
|
5
|
+
findById(id: string): Promise<T[]>;
|
|
6
6
|
find(params: IdaeDbParams<T>): Promise<T[]>;
|
|
7
7
|
findOne(params: IdaeDbParams<T>): Promise<T | null>;
|
|
8
8
|
update(id: string, updateData: Partial<T>): Promise<any>;
|
|
@@ -11,6 +11,10 @@ export interface IdaeDbAdapterInterface<T extends Document> {
|
|
|
11
11
|
deleteWhere(params: IdaeDbParams<T>): Promise<{
|
|
12
12
|
deletedCount?: number;
|
|
13
13
|
}>;
|
|
14
|
+
transaction<TResult>(callback: (session: any) => Promise<TResult>): Promise<TResult>;
|
|
15
|
+
connect(uri: string): Promise<any>;
|
|
16
|
+
getDb(client: any, dbName: string): any;
|
|
17
|
+
close(client: any): Promise<void>;
|
|
14
18
|
}
|
|
15
19
|
export interface IdaeDbParams<T extends object = Record<string, unknown>> {
|
|
16
20
|
id?: string;
|