@twasik4/pocket-service 1.0.2 → 1.0.4
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/index.d.ts +36 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +5 -11
- package/src/workers/db/clickhouse/index.ts +5 -5
- package/src/workers/db/mongo/collection.ts +18 -17
- package/src/workers/db/mongo/mongo.ts +126 -10
- package/src/workers/service.ts +111 -64
- package/tests/auth-strategies.spec.ts +150 -0
- package/tests/clickhouse.spec.ts +102 -0
- package/tests/express-types.spec.ts +232 -0
- package/tests/mongo-advanced.spec.ts +172 -0
- package/tests/mongo.spec.ts +49 -0
- package/tests/redis.spec.ts +1 -1
- package/tests/utils.spec.ts +30 -0
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twasik4/pocket-service",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"peerDependencies": {
|
|
8
|
-
"@clickhouse/client": "^1.12.1"
|
|
8
|
+
"@clickhouse/client": "^1.12.1"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
9
11
|
"cors": "^2.8.5",
|
|
10
12
|
"express": "^5.1.0",
|
|
11
13
|
"jose": "^6.2.3",
|
|
@@ -21,16 +23,8 @@
|
|
|
21
23
|
"@types/express": "^5.0.3",
|
|
22
24
|
"@types/node": "^24.5.2",
|
|
23
25
|
"@types/winston": "^2.4.4",
|
|
24
|
-
"cors": "^2.8.5",
|
|
25
|
-
"express": "^5.1.0",
|
|
26
|
-
"jose": "^6.2.3",
|
|
27
|
-
"mongodb": "^6.20.0",
|
|
28
26
|
"mongodb-memory-server": "^11.0.1",
|
|
29
|
-
"
|
|
30
|
-
"vitest": "^3.2.4",
|
|
31
|
-
"winston": "^3.17.0",
|
|
32
|
-
"winston-transport": "^4.9.0",
|
|
33
|
-
"zod": "^4.1.12"
|
|
27
|
+
"vitest": "^3.2.4"
|
|
34
28
|
},
|
|
35
29
|
"scripts": {
|
|
36
30
|
"build": "tsup",
|
|
@@ -7,7 +7,7 @@ export interface ClickhouseTable<TSchema extends Record<string, any>> {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export interface ClickhouseConfig<
|
|
10
|
-
TTables extends Record<string, ClickhouseTable<any
|
|
10
|
+
TTables extends Record<string, ClickhouseTable<any>>,
|
|
11
11
|
> {
|
|
12
12
|
url: string;
|
|
13
13
|
username?: string;
|
|
@@ -16,7 +16,7 @@ export interface ClickhouseConfig<
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function createTypedClickhouse<
|
|
19
|
-
TTables extends Record<string, ClickhouseTable<any
|
|
19
|
+
TTables extends Record<string, ClickhouseTable<any>>,
|
|
20
20
|
>(config: ClickhouseConfig<TTables>) {
|
|
21
21
|
const client = createClient({
|
|
22
22
|
host: config.url,
|
|
@@ -38,7 +38,7 @@ export function createTypedClickhouse<
|
|
|
38
38
|
const select = async (where?: Filter): Promise<Row[]> => {
|
|
39
39
|
let query = `SELECT * FROM ${table.name}`;
|
|
40
40
|
if (where && Object.keys(where).length > 0) {
|
|
41
|
-
const conditions = Object.
|
|
41
|
+
const conditions = Object.entries(where)
|
|
42
42
|
.map(([k, value]) => {
|
|
43
43
|
if (typeof value === "string") return `${k} = '${value}'`;
|
|
44
44
|
return `${k} = ${value}`;
|
|
@@ -62,11 +62,11 @@ export function createTypedClickhouse<
|
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
return [key, { select, insert }];
|
|
65
|
-
})
|
|
65
|
+
}),
|
|
66
66
|
) as {
|
|
67
67
|
[K in keyof TTables]: {
|
|
68
68
|
select(
|
|
69
|
-
where?: Partial<ReturnType<TTables[K]["schema"]
|
|
69
|
+
where?: Partial<ReturnType<TTables[K]["schema"]>>,
|
|
70
70
|
): Promise<ReturnType<TTables[K]["schema"]>[]>;
|
|
71
71
|
insert(rows: ReturnType<TTables[K]["schema"]>[]): Promise<void>;
|
|
72
72
|
};
|
|
@@ -199,12 +199,9 @@ export class CollectionWrapper<T extends Document> {
|
|
|
199
199
|
},
|
|
200
200
|
{
|
|
201
201
|
$merge: {
|
|
202
|
-
into:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
on: "_id",
|
|
206
|
-
whenNotMatched: "insert",
|
|
207
|
-
},
|
|
202
|
+
into: this.revision.collectionName,
|
|
203
|
+
on: "_id",
|
|
204
|
+
whenNotMatched: "insert",
|
|
208
205
|
},
|
|
209
206
|
},
|
|
210
207
|
])
|
|
@@ -218,17 +215,24 @@ export class CollectionWrapper<T extends Document> {
|
|
|
218
215
|
|
|
219
216
|
/**
|
|
220
217
|
* Soft delete a document by setting the `deletedAt` field to the current date.
|
|
218
|
+
* If the schema has a `deletedBy` field, it is set to the provided actor or defaults to `system`.
|
|
221
219
|
* If the schema does not have a `deletedAt` field, it will perform a hard delete.
|
|
222
220
|
* @param filter - The filter to find documents to delete.
|
|
221
|
+
* @param deletedBy - Optional actor identifier for soft deletes.
|
|
223
222
|
*/
|
|
224
|
-
async delete(filter: Filter<T
|
|
223
|
+
async delete(filter: Filter<T>, deletedBy?: string): Promise<void> {
|
|
225
224
|
try {
|
|
226
225
|
if (this.hasProperty("deletedAt")) {
|
|
227
226
|
const deletedAt = new Date();
|
|
227
|
+
const deletedByValue = deletedBy ?? "system";
|
|
228
|
+
const setFields: Record<string, unknown> = {
|
|
229
|
+
deletedAt,
|
|
230
|
+
...(this.hasProperty("deletedBy")
|
|
231
|
+
? { deletedBy: deletedByValue }
|
|
232
|
+
: {}),
|
|
233
|
+
};
|
|
228
234
|
const update = {
|
|
229
|
-
$set:
|
|
230
|
-
deletedAt,
|
|
231
|
-
},
|
|
235
|
+
$set: setFields,
|
|
232
236
|
} as unknown as UpdateFilter<T>;
|
|
233
237
|
await this.collection.updateMany(filter, update);
|
|
234
238
|
if (this.revision) {
|
|
@@ -238,16 +242,13 @@ export class CollectionWrapper<T extends Document> {
|
|
|
238
242
|
$match: filter,
|
|
239
243
|
},
|
|
240
244
|
{
|
|
241
|
-
$set: { _id: new ObjectId(),
|
|
245
|
+
$set: { _id: new ObjectId(), ...setFields },
|
|
242
246
|
},
|
|
243
247
|
{
|
|
244
248
|
$merge: {
|
|
245
|
-
into:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
on: "_id",
|
|
249
|
-
whenNotMatched: "insert",
|
|
250
|
-
},
|
|
249
|
+
into: this.revision.collectionName,
|
|
250
|
+
on: "_id",
|
|
251
|
+
whenNotMatched: "insert",
|
|
251
252
|
},
|
|
252
253
|
},
|
|
253
254
|
])
|
|
@@ -32,6 +32,25 @@ export type MongoIndexSpec = {
|
|
|
32
32
|
expireAfterSeconds?: number;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
export type MongoTimeSeriesSpec = {
|
|
36
|
+
/**
|
|
37
|
+
* Name of the date field that MongoDB uses as the event time.
|
|
38
|
+
*/
|
|
39
|
+
timeField: string;
|
|
40
|
+
/**
|
|
41
|
+
* Optional metadata field used to group measurements.
|
|
42
|
+
*/
|
|
43
|
+
metaField?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Optional bucket granularity hint used by MongoDB.
|
|
46
|
+
*/
|
|
47
|
+
granularity?: "seconds" | "minutes" | "hours";
|
|
48
|
+
/**
|
|
49
|
+
* Optional retention in seconds for automatic expiry.
|
|
50
|
+
*/
|
|
51
|
+
expireAfterSeconds?: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
35
54
|
export type MongoCollectionMethodContext<TDocument extends Document> = {
|
|
36
55
|
/**
|
|
37
56
|
* The MongoDB client instance
|
|
@@ -71,6 +90,10 @@ export type MongoCollectionDefinition<
|
|
|
71
90
|
* Optional array of index specifications to create on this collection. Indexes are created idempotently on service startup, so duplicate indexes are safely ignored.
|
|
72
91
|
*/
|
|
73
92
|
indexes?: MongoIndexSpec[];
|
|
93
|
+
/**
|
|
94
|
+
* Optional MongoDB time-series settings. When provided, the collection is created as a time-series collection if it does not already exist.
|
|
95
|
+
*/
|
|
96
|
+
timeSeries?: MongoTimeSeriesSpec;
|
|
74
97
|
};
|
|
75
98
|
|
|
76
99
|
export type MongoCollectionsConfig = Record<
|
|
@@ -147,6 +170,10 @@ export function defineMongoCollection<
|
|
|
147
170
|
* Optional array of index specifications to create on this collection.
|
|
148
171
|
*/
|
|
149
172
|
indexes?: MongoIndexSpec[];
|
|
173
|
+
/**
|
|
174
|
+
* Optional MongoDB time-series settings.
|
|
175
|
+
*/
|
|
176
|
+
timeSeries?: MongoTimeSeriesSpec;
|
|
150
177
|
}): MongoCollectionDefinition<SchemaOutput<TSchema>, {}>;
|
|
151
178
|
|
|
152
179
|
/**
|
|
@@ -177,6 +204,10 @@ export function defineMongoCollection<
|
|
|
177
204
|
* Optional array of index specifications to create on this collection.
|
|
178
205
|
*/
|
|
179
206
|
indexes?: MongoIndexSpec[];
|
|
207
|
+
/**
|
|
208
|
+
* Optional MongoDB time-series settings.
|
|
209
|
+
*/
|
|
210
|
+
timeSeries?: MongoTimeSeriesSpec;
|
|
180
211
|
}): MongoCollectionDefinition<SchemaOutput<TSchema>, TMethods>;
|
|
181
212
|
|
|
182
213
|
/**
|
|
@@ -207,6 +238,10 @@ export function defineMongoCollection<
|
|
|
207
238
|
* Optional array of index specifications to create on this collection.
|
|
208
239
|
*/
|
|
209
240
|
indexes?: MongoIndexSpec[];
|
|
241
|
+
/**
|
|
242
|
+
* Optional MongoDB time-series settings.
|
|
243
|
+
*/
|
|
244
|
+
timeSeries?: MongoTimeSeriesSpec;
|
|
210
245
|
}): MongoCollectionDefinition<SchemaOutput<TSchema>, TMethods> {
|
|
211
246
|
return {
|
|
212
247
|
name: config.name,
|
|
@@ -218,6 +253,7 @@ export function defineMongoCollection<
|
|
|
218
253
|
*/
|
|
219
254
|
methods: config.methods,
|
|
220
255
|
indexes: config.indexes,
|
|
256
|
+
timeSeries: config.timeSeries,
|
|
221
257
|
/**
|
|
222
258
|
* Whether to automatically track revisions of documents in this collection. If enabled, every update will insert a copy of the updated document into a separate revision collection, allowing you to keep a history of changes. The revision collection will have the same name as the main collection with `_revisions` appended to it (e.g. `users` -> `users_revisions`).
|
|
223
259
|
*/
|
|
@@ -236,6 +272,7 @@ export function createMongo<TCollections extends MongoCollectionsConfig>(
|
|
|
236
272
|
const db = client.db(dbName);
|
|
237
273
|
|
|
238
274
|
const wrapped = {} as InferMongoCollections<TCollections>;
|
|
275
|
+
const setupFns: Array<() => Promise<void>> = [];
|
|
239
276
|
|
|
240
277
|
for (const key in config) {
|
|
241
278
|
const def = config[key];
|
|
@@ -257,16 +294,78 @@ export function createMongo<TCollections extends MongoCollectionsConfig>(
|
|
|
257
294
|
base,
|
|
258
295
|
}) || {};
|
|
259
296
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
297
|
+
setupFns.push(async () => {
|
|
298
|
+
if (def.timeSeries) {
|
|
299
|
+
const existingCollection = await db
|
|
300
|
+
.listCollections({ name: collectionName }, { nameOnly: false })
|
|
301
|
+
.next();
|
|
302
|
+
|
|
303
|
+
if (!existingCollection) {
|
|
304
|
+
await db.createCollection(collectionName, {
|
|
305
|
+
timeseries: {
|
|
306
|
+
timeField: def.timeSeries.timeField,
|
|
307
|
+
...(def.timeSeries.metaField
|
|
308
|
+
? { metaField: def.timeSeries.metaField }
|
|
309
|
+
: {}),
|
|
310
|
+
...(def.timeSeries.granularity
|
|
311
|
+
? { granularity: def.timeSeries.granularity }
|
|
312
|
+
: {}),
|
|
313
|
+
},
|
|
314
|
+
...(typeof def.timeSeries.expireAfterSeconds === "number"
|
|
315
|
+
? { expireAfterSeconds: def.timeSeries.expireAfterSeconds }
|
|
316
|
+
: {}),
|
|
317
|
+
});
|
|
318
|
+
} else {
|
|
319
|
+
const hasTimeSeriesOptions =
|
|
320
|
+
existingCollection.type === "timeseries" ||
|
|
321
|
+
Boolean((existingCollection.options as any)?.timeseries);
|
|
322
|
+
|
|
323
|
+
if (!hasTimeSeriesOptions) {
|
|
324
|
+
throw new Error(
|
|
325
|
+
`Collection ${collectionName} already exists and is not a time-series collection. Migration is required before enabling timeSeries.`,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const existingTimeSeries = (existingCollection.options as any)
|
|
330
|
+
?.timeseries as
|
|
331
|
+
| {
|
|
332
|
+
timeField?: string;
|
|
333
|
+
metaField?: string;
|
|
334
|
+
}
|
|
335
|
+
| undefined;
|
|
336
|
+
|
|
337
|
+
if (
|
|
338
|
+
existingTimeSeries?.timeField &&
|
|
339
|
+
existingTimeSeries.timeField !== def.timeSeries.timeField
|
|
340
|
+
) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
`Collection ${collectionName} has timeField '${existingTimeSeries.timeField}' but config expects '${def.timeSeries.timeField}'.`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (
|
|
347
|
+
def.timeSeries.metaField &&
|
|
348
|
+
existingTimeSeries?.metaField &&
|
|
349
|
+
existingTimeSeries.metaField !== def.timeSeries.metaField
|
|
350
|
+
) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
`Collection ${collectionName} has metaField '${existingTimeSeries.metaField}' but config expects '${def.timeSeries.metaField}'.`,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
268
356
|
}
|
|
269
|
-
|
|
357
|
+
|
|
358
|
+
if (def.indexes && def.indexes.length > 0) {
|
|
359
|
+
for (const indexSpec of def.indexes) {
|
|
360
|
+
const { key, ...indexOptions } = indexSpec;
|
|
361
|
+
await collection.createIndex(key, indexOptions).catch((err) => {
|
|
362
|
+
console.error(
|
|
363
|
+
`Failed to create index on ${collectionName}: ${err.message}`,
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
270
369
|
|
|
271
370
|
wrapped[key] = Object.assign(
|
|
272
371
|
base,
|
|
@@ -274,12 +373,29 @@ export function createMongo<TCollections extends MongoCollectionsConfig>(
|
|
|
274
373
|
) as InferMongoCollections<TCollections>[typeof key];
|
|
275
374
|
}
|
|
276
375
|
|
|
376
|
+
let setupPromise: Promise<void> | null = null;
|
|
377
|
+
|
|
378
|
+
const ensureSetup = async () => {
|
|
379
|
+
if (!setupPromise) {
|
|
380
|
+
setupPromise = (async () => {
|
|
381
|
+
for (const setup of setupFns) {
|
|
382
|
+
await setup();
|
|
383
|
+
}
|
|
384
|
+
})();
|
|
385
|
+
}
|
|
386
|
+
return setupPromise;
|
|
387
|
+
};
|
|
388
|
+
|
|
277
389
|
return {
|
|
278
390
|
client,
|
|
279
391
|
db,
|
|
280
392
|
collections: wrapped,
|
|
281
393
|
...wrapped,
|
|
282
394
|
disconnect: async () => client.close(),
|
|
283
|
-
connect: async () =>
|
|
395
|
+
connect: async () => {
|
|
396
|
+
await client.connect();
|
|
397
|
+
await ensureSetup();
|
|
398
|
+
return client;
|
|
399
|
+
},
|
|
284
400
|
} as MongoDatabase<TCollections>;
|
|
285
401
|
}
|
package/src/workers/service.ts
CHANGED
|
@@ -55,6 +55,7 @@ export class Service<TCollections extends MongoCollectionsConfig = {}> {
|
|
|
55
55
|
asJson: true,
|
|
56
56
|
customMiddleware: [],
|
|
57
57
|
};
|
|
58
|
+
private withoutExpressFlag: boolean = false;
|
|
58
59
|
|
|
59
60
|
private getAuthResolver(): AuthResolver {
|
|
60
61
|
if (this.authResolverOverride) {
|
|
@@ -81,6 +82,11 @@ export class Service<TCollections extends MongoCollectionsConfig = {}> {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
get api(): Express {
|
|
85
|
+
if (this.withoutExpressFlag) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"Express server is disabled. Service built without Express.",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
84
90
|
if (!this.dependencies.has("expressApp")) {
|
|
85
91
|
this.dependencies.set("expressApp", express());
|
|
86
92
|
}
|
|
@@ -326,76 +332,82 @@ export class Service<TCollections extends MongoCollectionsConfig = {}> {
|
|
|
326
332
|
}
|
|
327
333
|
});
|
|
328
334
|
|
|
329
|
-
if (this.
|
|
330
|
-
this.log.
|
|
331
|
-
|
|
332
|
-
|
|
335
|
+
if (this.withoutExpressFlag) {
|
|
336
|
+
this.log.warn(
|
|
337
|
+
"Express server is disabled. Service built without Express.",
|
|
338
|
+
);
|
|
339
|
+
} else {
|
|
340
|
+
if (this.expressOptions.asJson) {
|
|
341
|
+
this.log.info("Configuring Express to parse JSON bodies");
|
|
342
|
+
this.api.use(express.json());
|
|
343
|
+
}
|
|
333
344
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
345
|
+
for (const mw of this.expressOptions.customMiddleware || []) {
|
|
346
|
+
this.log.info("Adding custom middleware to Express");
|
|
347
|
+
this.api.use(mw);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if ("corsWhitelist" in this.expressOptions) {
|
|
351
|
+
this.log.info(
|
|
352
|
+
"Configuring CORS with whitelist: " +
|
|
353
|
+
this.expressOptions.corsWhitelist,
|
|
354
|
+
);
|
|
355
|
+
this.api.use(
|
|
356
|
+
cors({
|
|
357
|
+
origin: this.expressOptions.corsWhitelist,
|
|
358
|
+
credentials: this.expressOptions.credentials,
|
|
359
|
+
}),
|
|
360
|
+
);
|
|
361
|
+
} else if ("corsFn" in this.expressOptions) {
|
|
362
|
+
this.log.info("Configuring CORS with function");
|
|
363
|
+
this.api.use(
|
|
364
|
+
cors({
|
|
365
|
+
origin: this.expressOptions.corsFn,
|
|
366
|
+
credentials: this.expressOptions.credentials,
|
|
367
|
+
}),
|
|
368
|
+
);
|
|
369
|
+
}
|
|
338
370
|
|
|
339
|
-
|
|
371
|
+
// routers
|
|
372
|
+
if (!this.expressOptions.omitDefaultRoutes) {
|
|
373
|
+
this.registerDefaultRoutes();
|
|
374
|
+
}
|
|
375
|
+
this.log.info(`Registering ${this.routers.length} routers`);
|
|
376
|
+
this.routers.forEach(({ base, router }) => {
|
|
377
|
+
this.api.use(base, router);
|
|
378
|
+
});
|
|
340
379
|
this.log.info(
|
|
341
|
-
"
|
|
342
|
-
this.
|
|
380
|
+
"Routers registered:" +
|
|
381
|
+
this.routers
|
|
382
|
+
.map((rt) => {
|
|
383
|
+
const maxMethodLength = rt.routes
|
|
384
|
+
? Math.max(...rt.routes?.map((r) => r.method.length))
|
|
385
|
+
: 0;
|
|
386
|
+
const maxPathLength = rt.routes
|
|
387
|
+
? Math.max(...rt.routes?.map((r) => r.fullPath.length))
|
|
388
|
+
: 0;
|
|
389
|
+
const maxAuthLength = rt.routes
|
|
390
|
+
? Math.max(...rt.routes?.map((r) => (r.requireAuth ? 6 : 8)))
|
|
391
|
+
: 0;
|
|
392
|
+
|
|
393
|
+
return `\nRouter ${rt.base}: ${
|
|
394
|
+
rt.routes?.length || 0
|
|
395
|
+
} routes registered.\n${
|
|
396
|
+
rt.routes
|
|
397
|
+
?.map(
|
|
398
|
+
(r) =>
|
|
399
|
+
`\t${("[" + r.method + "]").padEnd(maxMethodLength + 2)} ${(r.requireAuth ? "[Auth]" : "[Public]").padEnd(maxAuthLength)} ${r.fullPath.padEnd(maxPathLength)} | ${r.meta?.description || ""}`,
|
|
400
|
+
)
|
|
401
|
+
.join("\n") || ""
|
|
402
|
+
}`;
|
|
403
|
+
})
|
|
404
|
+
.join("\n"),
|
|
343
405
|
);
|
|
344
|
-
this.api.use(
|
|
345
|
-
cors({
|
|
346
|
-
origin: this.expressOptions.corsWhitelist,
|
|
347
|
-
credentials: this.expressOptions.credentials,
|
|
348
|
-
}),
|
|
349
|
-
);
|
|
350
|
-
} else if ("corsFn" in this.expressOptions) {
|
|
351
|
-
this.log.info("Configuring CORS with function");
|
|
352
|
-
this.api.use(
|
|
353
|
-
cors({
|
|
354
|
-
origin: this.expressOptions.corsFn,
|
|
355
|
-
credentials: this.expressOptions.credentials,
|
|
356
|
-
}),
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
406
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
407
|
+
this.api.listen(this.port, () => {
|
|
408
|
+
this.log.info(`API listening at ${this.fullUrl}`);
|
|
409
|
+
});
|
|
363
410
|
}
|
|
364
|
-
this.log.info(`Registering ${this.routers.length} routers`);
|
|
365
|
-
this.routers.forEach(({ base, router }) => {
|
|
366
|
-
this.api.use(base, router);
|
|
367
|
-
});
|
|
368
|
-
this.log.info(
|
|
369
|
-
"Routers registered:" +
|
|
370
|
-
this.routers
|
|
371
|
-
.map((rt) => {
|
|
372
|
-
const maxMethodLength = rt.routes
|
|
373
|
-
? Math.max(...rt.routes?.map((r) => r.method.length))
|
|
374
|
-
: 0;
|
|
375
|
-
const maxPathLength = rt.routes
|
|
376
|
-
? Math.max(...rt.routes?.map((r) => r.fullPath.length))
|
|
377
|
-
: 0;
|
|
378
|
-
const maxAuthLength = rt.routes
|
|
379
|
-
? Math.max(...rt.routes?.map((r) => (r.requireAuth ? 6 : 8)))
|
|
380
|
-
: 0;
|
|
381
|
-
|
|
382
|
-
return `\nRouter ${rt.base}: ${
|
|
383
|
-
rt.routes?.length || 0
|
|
384
|
-
} routes registered.\n${
|
|
385
|
-
rt.routes
|
|
386
|
-
?.map(
|
|
387
|
-
(r) =>
|
|
388
|
-
`\t${("[" + r.method + "]").padEnd(maxMethodLength + 2)} ${(r.requireAuth ? "[Auth]" : "[Public]").padEnd(maxAuthLength)} ${r.fullPath.padEnd(maxPathLength)} | ${r.meta?.description || ""}`,
|
|
389
|
-
)
|
|
390
|
-
.join("\n") || ""
|
|
391
|
-
}`;
|
|
392
|
-
})
|
|
393
|
-
.join("\n"),
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
this.api.listen(this.port, () => {
|
|
397
|
-
this.log.info(`API listening at ${this.fullUrl}`);
|
|
398
|
-
});
|
|
399
411
|
|
|
400
412
|
this._status = "running";
|
|
401
413
|
return this;
|
|
@@ -852,22 +864,51 @@ export class Service<TCollections extends MongoCollectionsConfig = {}> {
|
|
|
852
864
|
return this;
|
|
853
865
|
}
|
|
854
866
|
|
|
867
|
+
withoutExpress() {
|
|
868
|
+
this.withoutExpressFlag = true;
|
|
869
|
+
return this;
|
|
870
|
+
}
|
|
871
|
+
|
|
855
872
|
withExpressOptions(options: Partial<ExpressOptions>) {
|
|
873
|
+
if (this.withoutExpressFlag) {
|
|
874
|
+
this.log.warn(
|
|
875
|
+
"Express server is disabled. Express options will not be applied.",
|
|
876
|
+
);
|
|
877
|
+
return this;
|
|
878
|
+
}
|
|
856
879
|
this.expressOptions = { ...this.expressOptions, ...options };
|
|
857
880
|
return this;
|
|
858
881
|
}
|
|
859
882
|
|
|
860
883
|
withAuthStrategy(strategy: AuthStrategy) {
|
|
884
|
+
if (this.withoutExpressFlag) {
|
|
885
|
+
this.log.warn(
|
|
886
|
+
"Express server is disabled. Auth strategies will not be applied.",
|
|
887
|
+
);
|
|
888
|
+
return this;
|
|
889
|
+
}
|
|
861
890
|
this.authStrategies.push(strategy);
|
|
862
891
|
return this;
|
|
863
892
|
}
|
|
864
893
|
|
|
865
894
|
withAuthStrategies(strategies: AuthStrategy[]) {
|
|
895
|
+
if (this.withoutExpressFlag) {
|
|
896
|
+
this.log.warn(
|
|
897
|
+
"Express server is disabled. Auth strategies will not be applied.",
|
|
898
|
+
);
|
|
899
|
+
return this;
|
|
900
|
+
}
|
|
866
901
|
this.authStrategies.push(...strategies);
|
|
867
902
|
return this;
|
|
868
903
|
}
|
|
869
904
|
|
|
870
905
|
withAuthResolver(resolver: AuthResolver) {
|
|
906
|
+
if (this.withoutExpressFlag) {
|
|
907
|
+
this.log.warn(
|
|
908
|
+
"Express server is disabled. Auth resolver will not be applied.",
|
|
909
|
+
);
|
|
910
|
+
return this;
|
|
911
|
+
}
|
|
871
912
|
this.authResolverOverride = resolver;
|
|
872
913
|
return this;
|
|
873
914
|
}
|
|
@@ -898,6 +939,12 @@ export class Service<TCollections extends MongoCollectionsConfig = {}> {
|
|
|
898
939
|
router: Router,
|
|
899
940
|
routes: RouteDefinition<any, any, boolean>[] = [],
|
|
900
941
|
) {
|
|
942
|
+
if (this.withoutExpressFlag) {
|
|
943
|
+
this.log.warn(
|
|
944
|
+
"Express server is disabled. Routers will not be registered.",
|
|
945
|
+
);
|
|
946
|
+
return this;
|
|
947
|
+
}
|
|
901
948
|
if (base === "/") {
|
|
902
949
|
this.log.warn("Base path '/' is not allowed. Skipping.");
|
|
903
950
|
return this;
|