@twasik4/pocket-service 1.0.0 → 1.0.1
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/README.md +56 -0
- package/dist/index.d.ts +700 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/worker-thread.d.ts +2 -0
- package/dist/worker-thread.js +2 -0
- package/dist/worker-thread.js.map +1 -0
- package/package.json +3 -1
- package/src/workers/auth/strategies.ts +304 -0
- package/src/workers/express-types.ts +164 -36
- package/src/workers/index.ts +1 -0
- package/src/workers/service.ts +50 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
import { Request, Response, NextFunction, Router, Express } from 'express';
|
|
2
|
+
import { ZodSchema, output } from 'zod';
|
|
3
|
+
import { RedisClientType } from 'redis';
|
|
4
|
+
import { Logger } from 'winston';
|
|
5
|
+
import * as _clickhouse_client from '@clickhouse/client';
|
|
6
|
+
import { Document, Collection, Filter, WithId, MongoClient, Db } from 'mongodb';
|
|
7
|
+
import { PeerCertificate } from 'tls';
|
|
8
|
+
|
|
9
|
+
type RouteMeta = {
|
|
10
|
+
description?: string;
|
|
11
|
+
deprecated?: boolean;
|
|
12
|
+
tags?: string[];
|
|
13
|
+
authRequired?: boolean;
|
|
14
|
+
rateLimit?: "standard" | "strict" | "none";
|
|
15
|
+
version?: string;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
};
|
|
18
|
+
type AuthenticatedUser = {
|
|
19
|
+
userId: string;
|
|
20
|
+
[key: string]: string | string[] | number | boolean;
|
|
21
|
+
};
|
|
22
|
+
type AuthResolver = (req: Request, route: RouteDefinition<any, any, boolean>) => AuthenticatedUser | null | Promise<AuthenticatedUser | null>;
|
|
23
|
+
type AuthStrategy = {
|
|
24
|
+
name: string;
|
|
25
|
+
canHandle?: (req: Request, route: RouteDefinition<any, any, boolean>) => boolean | Promise<boolean>;
|
|
26
|
+
authenticate: AuthResolver;
|
|
27
|
+
};
|
|
28
|
+
declare function defaultHeaderAuthResolver(req: Request): AuthenticatedUser | null;
|
|
29
|
+
declare function createAuthResolver(strategies: AuthStrategy[], opts?: {
|
|
30
|
+
fallbackResolver?: AuthResolver;
|
|
31
|
+
}): AuthResolver;
|
|
32
|
+
type TypedRequest<TBody = unknown, TParams = unknown, TRequireAuth extends boolean = false> = Request<TParams extends ZodSchema ? output<TParams> : Record<string, string>, any, TBody extends ZodSchema ? output<TBody> : any> & (TRequireAuth extends true ? {
|
|
33
|
+
user: AuthenticatedUser;
|
|
34
|
+
} : {
|
|
35
|
+
user?: AuthenticatedUser;
|
|
36
|
+
});
|
|
37
|
+
type TypedRequestHandler<TBody = unknown, TParams = unknown, TRequireAuth extends boolean = false> = (req: TypedRequest<TBody, TParams, TRequireAuth>, res: Response, next: NextFunction) => unknown;
|
|
38
|
+
/**
|
|
39
|
+
* Defines a route with enhanced type safety and built-in support for request validation and authentication. This type is used in conjunction with the `addRoute` function to register routes on an Express router, allowing you to specify the HTTP method, path, authentication requirements, and Zod schemas for validating request bodies and URL parameters. The route definition also includes optional metadata that can be used for documentation or analytics purposes.
|
|
40
|
+
*/
|
|
41
|
+
type RouteDefinition<TBody extends ZodSchema | undefined = undefined, TParams extends ZodSchema | undefined = undefined, TRequireAuth extends boolean = false> = {
|
|
42
|
+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
43
|
+
fullPath: string;
|
|
44
|
+
requireAuth?: TRequireAuth;
|
|
45
|
+
/**
|
|
46
|
+
* Optional Zod schema to validate the request body. Only applicable for non-GET requests. If provided, the middleware will automatically validate the request body against this schema and return a 400 error if validation fails.
|
|
47
|
+
*/
|
|
48
|
+
bodyValidator?: TBody;
|
|
49
|
+
/**
|
|
50
|
+
* Optional Zod schema to validate URL parameters (e.g., :id in /users/:id). If provided, the middleware will automatically validate the URL parameters against this schema and return a 400 error if validation fails.
|
|
51
|
+
*/
|
|
52
|
+
paramsValidator?: TParams;
|
|
53
|
+
/**
|
|
54
|
+
* Optional metadata for the route, which can be used for documentation, analytics, or other purposes.
|
|
55
|
+
*/
|
|
56
|
+
meta?: RouteMeta;
|
|
57
|
+
};
|
|
58
|
+
declare function getRegisteredRoutes(): RouteDefinition<any, any, boolean>[];
|
|
59
|
+
type CreateMetaRouterOptions = {
|
|
60
|
+
authResolver?: AuthResolver;
|
|
61
|
+
onUnauthorized?: (req: Request, res: Response) => void;
|
|
62
|
+
onAuthError?: (err: unknown, req: Request, res: Response) => void;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Utility function to create an Express router with enhanced type safety and built-in support for request validation and authentication. This function allows you to define routes with associated Zod schemas for validating request bodies and URL parameters, as well as specifying whether authentication is required for each route. The returned object includes the configured Express router, a list of registered routes for introspection, and a helper function to add new routes with the specified configurations.
|
|
66
|
+
*/
|
|
67
|
+
declare function createMetaRouter(): {
|
|
68
|
+
/**
|
|
69
|
+
* The Express router instance that can be used to register routes and middleware. This router is configured to work with the enhanced route definitions provided by the `addRoute` function, which supports request validation and authentication requirements.
|
|
70
|
+
*/
|
|
71
|
+
router: Router;
|
|
72
|
+
/**
|
|
73
|
+
* List of registered routes with their configurations, which can be used for introspection, documentation generation, or analytics purposes. Each entry includes the HTTP method, full path, authentication requirement, validation schemas, and any additional metadata provided during route registration.
|
|
74
|
+
*/
|
|
75
|
+
routes: RouteDefinition<any, any, boolean>[];
|
|
76
|
+
/**
|
|
77
|
+
* Adds a new route to the router with the specified configuration and request handler.
|
|
78
|
+
*
|
|
79
|
+
* - If `requireAuth` is set to true, the route will automatically include middleware to check for an authenticated user and return a 401 error if the user is not authenticated.
|
|
80
|
+
* - If `bodyValidator` or `paramsValidator` are provided, the route will include middleware to validate the request body and URL parameters against the provided Zod schemas, returning a 400 error if validation fails.
|
|
81
|
+
*
|
|
82
|
+
* The route definition is also stored in an internal list for introspection purposes.
|
|
83
|
+
*
|
|
84
|
+
* @param route The route definition, including method, path, validation schemas, and authentication requirements.
|
|
85
|
+
* @param handler The request handler function for the route.
|
|
86
|
+
* @returns void
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* addRoute(
|
|
90
|
+
* {
|
|
91
|
+
* fullPath: "/me",
|
|
92
|
+
* method: "GET",
|
|
93
|
+
* requireAuth: true,
|
|
94
|
+
* meta: {
|
|
95
|
+
* description: "Get current authenticated user info",
|
|
96
|
+
* },
|
|
97
|
+
* },
|
|
98
|
+
* async (req, res) => {
|
|
99
|
+
* try {
|
|
100
|
+
* ...fetch user info from database using req.user.userId...
|
|
101
|
+
* res.status(200).json(...user info...);
|
|
102
|
+
* } catch (err) {
|
|
103
|
+
* console.error("Failed to fetch user info:", err);
|
|
104
|
+
* res
|
|
105
|
+
* .status(500)
|
|
106
|
+
* .json({ isSuccess: false, message: "Internal server error" });
|
|
107
|
+
* }
|
|
108
|
+
* },
|
|
109
|
+
* );
|
|
110
|
+
*/
|
|
111
|
+
addRoute: <TBody extends ZodSchema | undefined = undefined, TParams extends ZodSchema | undefined = undefined, TRequireAuth extends boolean = false>(route: RouteDefinition<TBody, TParams, TRequireAuth>,
|
|
112
|
+
/** Request handler for the route */ handler: TypedRequestHandler<TBody, TParams, TRequireAuth>) => void;
|
|
113
|
+
};
|
|
114
|
+
declare function createMetaRouterWithAuth(options?: CreateMetaRouterOptions): {
|
|
115
|
+
/**
|
|
116
|
+
* The Express router instance that can be used to register routes and middleware. This router is configured to work with the enhanced route definitions provided by the `addRoute` function, which supports request validation and authentication requirements.
|
|
117
|
+
*/
|
|
118
|
+
router: Router;
|
|
119
|
+
/**
|
|
120
|
+
* List of registered routes with their configurations, which can be used for introspection, documentation generation, or analytics purposes. Each entry includes the HTTP method, full path, authentication requirement, validation schemas, and any additional metadata provided during route registration.
|
|
121
|
+
*/
|
|
122
|
+
routes: RouteDefinition<any, any, boolean>[];
|
|
123
|
+
/**
|
|
124
|
+
* Adds a new route to the router with the specified configuration and request handler.
|
|
125
|
+
*
|
|
126
|
+
* - If `requireAuth` is set to true, the route will automatically include middleware to check for an authenticated user and return a 401 error if the user is not authenticated.
|
|
127
|
+
* - If `bodyValidator` or `paramsValidator` are provided, the route will include middleware to validate the request body and URL parameters against the provided Zod schemas, returning a 400 error if validation fails.
|
|
128
|
+
*
|
|
129
|
+
* The route definition is also stored in an internal list for introspection purposes.
|
|
130
|
+
*
|
|
131
|
+
* @param route The route definition, including method, path, validation schemas, and authentication requirements.
|
|
132
|
+
* @param handler The request handler function for the route.
|
|
133
|
+
* @returns void
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* addRoute(
|
|
137
|
+
* {
|
|
138
|
+
* fullPath: "/me",
|
|
139
|
+
* method: "GET",
|
|
140
|
+
* requireAuth: true,
|
|
141
|
+
* meta: {
|
|
142
|
+
* description: "Get current authenticated user info",
|
|
143
|
+
* },
|
|
144
|
+
* },
|
|
145
|
+
* async (req, res) => {
|
|
146
|
+
* try {
|
|
147
|
+
* ...fetch user info from database using req.user.userId...
|
|
148
|
+
* res.status(200).json(...user info...);
|
|
149
|
+
* } catch (err) {
|
|
150
|
+
* console.error("Failed to fetch user info:", err);
|
|
151
|
+
* res
|
|
152
|
+
* .status(500)
|
|
153
|
+
* .json({ isSuccess: false, message: "Internal server error" });
|
|
154
|
+
* }
|
|
155
|
+
* },
|
|
156
|
+
* );
|
|
157
|
+
*/
|
|
158
|
+
addRoute: <TBody extends ZodSchema | undefined = undefined, TParams extends ZodSchema | undefined = undefined, TRequireAuth extends boolean = false>(route: RouteDefinition<TBody, TParams, TRequireAuth>,
|
|
159
|
+
/** Request handler for the route */ handler: TypedRequestHandler<TBody, TParams, TRequireAuth>) => void;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
interface ClickhouseTable<TSchema extends Record<string, any>> {
|
|
163
|
+
name: string;
|
|
164
|
+
createSQL: string;
|
|
165
|
+
schema: () => TSchema;
|
|
166
|
+
}
|
|
167
|
+
interface ClickhouseConfig<TTables extends Record<string, ClickhouseTable<any>>> {
|
|
168
|
+
url: string;
|
|
169
|
+
username?: string;
|
|
170
|
+
password?: string;
|
|
171
|
+
tables: TTables;
|
|
172
|
+
}
|
|
173
|
+
declare function createTypedClickhouse<TTables extends Record<string, ClickhouseTable<any>>>(config: ClickhouseConfig<TTables>): {
|
|
174
|
+
client: _clickhouse_client.ClickHouseClient;
|
|
175
|
+
ensureTables: () => Promise<void>;
|
|
176
|
+
} & { [K in keyof TTables]: {
|
|
177
|
+
select(where?: Partial<ReturnType<TTables[K]["schema"]>>): Promise<ReturnType<TTables[K]["schema"]>[]>;
|
|
178
|
+
insert(rows: ReturnType<TTables[K]["schema"]>[]): Promise<void>;
|
|
179
|
+
}; };
|
|
180
|
+
|
|
181
|
+
type SchemaParser<T extends Document> = {
|
|
182
|
+
parse: (input: unknown) => T;
|
|
183
|
+
shape?: unknown;
|
|
184
|
+
_def?: {
|
|
185
|
+
shape?: unknown | (() => unknown);
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
declare class CollectionWrapper<T extends Document> {
|
|
189
|
+
private readonly collection;
|
|
190
|
+
private readonly schema?;
|
|
191
|
+
private readonly revision?;
|
|
192
|
+
constructor(collection: Collection<T>, schema?: SchemaParser<T> | undefined, revision?: Collection<T> | undefined);
|
|
193
|
+
private parseWrite;
|
|
194
|
+
private parseRead;
|
|
195
|
+
private trackRevision;
|
|
196
|
+
/**
|
|
197
|
+
* Get all documents from the collection.
|
|
198
|
+
* @param filter - The filter to find documents to retrieve. If no filter is provided, it will return all documents in the collection.
|
|
199
|
+
* @returns
|
|
200
|
+
*/
|
|
201
|
+
get(filter?: Filter<T>): Promise<WithId<T>[]>;
|
|
202
|
+
/**
|
|
203
|
+
* Get a single document from the collection.
|
|
204
|
+
* @param filter - The filter to find the document to retrieve. If no filter is provided, it will return the first document in the collection.
|
|
205
|
+
* @returns
|
|
206
|
+
*/
|
|
207
|
+
getOne(filter?: Filter<T>): Promise<WithId<T> | null>;
|
|
208
|
+
/**
|
|
209
|
+
* Get all revisions for a document.
|
|
210
|
+
* @param filter - The filter to find revisions to retrieve. If no filter is provided, it will return all revisions in the revision collection.
|
|
211
|
+
* @returns An array of revision documents.
|
|
212
|
+
*/
|
|
213
|
+
getRevisions(filter?: Filter<T>): Promise<WithId<T>[]>;
|
|
214
|
+
/**
|
|
215
|
+
* Get the most recent revision for a document.
|
|
216
|
+
* @returns Revision document or null if no revisions exist or if revision tracking is not enabled.
|
|
217
|
+
*/
|
|
218
|
+
getMostRecentRevision(): Promise<WithId<T> | null>;
|
|
219
|
+
/**
|
|
220
|
+
* Insert a document into the collection.
|
|
221
|
+
*
|
|
222
|
+
* If this collection has revision tracking enabled, it will also insert the document into the revision collection.
|
|
223
|
+
*
|
|
224
|
+
* @param doc - The document to insert into the collection.
|
|
225
|
+
*/
|
|
226
|
+
insert(doc: T): Promise<void>;
|
|
227
|
+
/**
|
|
228
|
+
* Update multiple documents by filter with a partial document.
|
|
229
|
+
* @param filter - The filter to find documents to update.
|
|
230
|
+
* @param update - The partial document to update.
|
|
231
|
+
*/
|
|
232
|
+
update(filter: Filter<T>, update: Partial<T>): Promise<void>;
|
|
233
|
+
/**
|
|
234
|
+
* Update a single document by filter with a partial document. This will automatically set the `updatedAt` field to the current date if it exists in the schema, and increment the `revision` field by 1 if it exists in the schema. It will also insert the updated document into the revision collection if revision tracking is enabled.
|
|
235
|
+
* @param filter - The filter to find the document to update.
|
|
236
|
+
* @param update - The partial document to update.
|
|
237
|
+
*/
|
|
238
|
+
updateOne(filter: Filter<T>, update: Partial<T>): Promise<void>;
|
|
239
|
+
/**
|
|
240
|
+
* Soft delete a document by setting the `deletedAt` field to the current date.
|
|
241
|
+
* If the schema does not have a `deletedAt` field, it will perform a hard delete.
|
|
242
|
+
* @param filter - The filter to find documents to delete.
|
|
243
|
+
*/
|
|
244
|
+
delete(filter: Filter<T>): Promise<void>;
|
|
245
|
+
/**
|
|
246
|
+
* Checks if the schema has a given property.
|
|
247
|
+
* @param schema - The Zod schema to check against.
|
|
248
|
+
* @param property - The property to check for in the schema.
|
|
249
|
+
* @returns
|
|
250
|
+
*/
|
|
251
|
+
private hasProperty;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
type AnyMongoMethod = (...args: any[]) => any;
|
|
255
|
+
type MongoCollectionMethods = Record<string, AnyMongoMethod>;
|
|
256
|
+
type MongoIndexSpec = {
|
|
257
|
+
/**
|
|
258
|
+
* Index key specification. Example: { userId: 1 } for ascending, { userId: -1 } for descending
|
|
259
|
+
*/
|
|
260
|
+
key: Record<string, 1 | -1>;
|
|
261
|
+
/**
|
|
262
|
+
* Optional name for the index. If not provided, MongoDB will auto-generate.
|
|
263
|
+
*/
|
|
264
|
+
name?: string;
|
|
265
|
+
/**
|
|
266
|
+
* Whether the index should enforce uniqueness.
|
|
267
|
+
*/
|
|
268
|
+
unique?: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* Whether the index should be sparse (only index documents with the indexed field).
|
|
271
|
+
*/
|
|
272
|
+
sparse?: boolean;
|
|
273
|
+
/**
|
|
274
|
+
* Whether to build the index in the background without blocking other operations.
|
|
275
|
+
*/
|
|
276
|
+
background?: boolean;
|
|
277
|
+
/**
|
|
278
|
+
* The time-to-live (TTL) for documents in the collection, in seconds. Only applicable for TTL indexes.
|
|
279
|
+
*/
|
|
280
|
+
expireAfterSeconds?: number;
|
|
281
|
+
};
|
|
282
|
+
type MongoCollectionMethodContext<TDocument extends Document> = {
|
|
283
|
+
/**
|
|
284
|
+
* The MongoDB client instance
|
|
285
|
+
*/
|
|
286
|
+
client: MongoClient;
|
|
287
|
+
/**
|
|
288
|
+
* The MongoDB database instance
|
|
289
|
+
*/
|
|
290
|
+
db: Db;
|
|
291
|
+
/**
|
|
292
|
+
* The MongoDB collection instance
|
|
293
|
+
*/
|
|
294
|
+
collection: Collection<TDocument>;
|
|
295
|
+
/**
|
|
296
|
+
* The base collection wrapper instance
|
|
297
|
+
*/
|
|
298
|
+
base: CollectionWrapper<TDocument>;
|
|
299
|
+
};
|
|
300
|
+
type MongoCollectionDefinition<TDocument extends Document = Document, TMethods extends MongoCollectionMethods = {}> = {
|
|
301
|
+
name?: string;
|
|
302
|
+
/**
|
|
303
|
+
* 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`).
|
|
304
|
+
*/
|
|
305
|
+
trackRevisions: boolean;
|
|
306
|
+
schema: SchemaParser<TDocument>;
|
|
307
|
+
/**
|
|
308
|
+
* Custom methods to add to the collection wrapper. These methods receive a context object with the MongoDB client, database, collection, and base wrapper instance, allowing you to build complex queries and operations while still benefiting from the schema validation and soft delete functionality provided by the base wrapper.
|
|
309
|
+
* @param ctx
|
|
310
|
+
* @returns
|
|
311
|
+
*/
|
|
312
|
+
methods?: (ctx: MongoCollectionMethodContext<TDocument>) => TMethods;
|
|
313
|
+
/**
|
|
314
|
+
* Optional array of index specifications to create on this collection. Indexes are created idempotently on service startup, so duplicate indexes are safely ignored.
|
|
315
|
+
*/
|
|
316
|
+
indexes?: MongoIndexSpec[];
|
|
317
|
+
};
|
|
318
|
+
type MongoCollectionsConfig = Record<string, MongoCollectionDefinition<any, MongoCollectionMethods>>;
|
|
319
|
+
type InferDocument<TDefinition> = TDefinition extends {
|
|
320
|
+
schema: SchemaParser<infer TDocument>;
|
|
321
|
+
} ? TDocument : Document;
|
|
322
|
+
type NormalizeDocument<TDocument> = TDocument extends Document ? TDocument : Document;
|
|
323
|
+
type InferMethods<TDefinition> = TDefinition extends {
|
|
324
|
+
methods: (...args: any[]) => infer TMethods;
|
|
325
|
+
} ? TMethods : TDefinition extends {
|
|
326
|
+
methods?: (...args: any[]) => infer TMethods;
|
|
327
|
+
} ? TMethods extends undefined ? {} : TMethods : {};
|
|
328
|
+
type MongoBoundCollection<TDocument extends Document, TMethods extends MongoCollectionMethods = {}> = CollectionWrapper<TDocument> & TMethods;
|
|
329
|
+
type InferMongoCollections<TCollections extends MongoCollectionsConfig> = {
|
|
330
|
+
[K in keyof TCollections]: MongoBoundCollection<NormalizeDocument<InferDocument<TCollections[K]>>, InferMethods<TCollections[K]>>;
|
|
331
|
+
};
|
|
332
|
+
type MongoDatabase<TCollections extends MongoCollectionsConfig> = {
|
|
333
|
+
client: MongoClient;
|
|
334
|
+
db: Db;
|
|
335
|
+
collections: InferMongoCollections<TCollections>;
|
|
336
|
+
disconnect: () => Promise<void>;
|
|
337
|
+
connect: () => Promise<MongoClient>;
|
|
338
|
+
} & InferMongoCollections<TCollections>;
|
|
339
|
+
type SchemaOutput<TSchema> = TSchema extends {
|
|
340
|
+
parse: (input: unknown) => infer TOutput;
|
|
341
|
+
} ? NormalizeDocument<TOutput> : Document;
|
|
342
|
+
/**
|
|
343
|
+
* Define a MongoDB collection with an associated Zod schema for validation and optional custom methods. The returned collection definition can then be used to create a MongoDatabase instance, which will automatically wrap the collection with the provided schema validation and methods.
|
|
344
|
+
* @param config The configuration object for the MongoDB collection, including the schema and optional custom methods.
|
|
345
|
+
*/
|
|
346
|
+
declare function defineMongoCollection<TSchema extends SchemaParser<any>>(config: {
|
|
347
|
+
/**
|
|
348
|
+
* Optional name for the collection. If not provided, the key used in the collections config will be used as the collection name. This allows you to have more control over the actual collection names in the database, while still using descriptive keys in your code.
|
|
349
|
+
*/
|
|
350
|
+
name?: string;
|
|
351
|
+
/**
|
|
352
|
+
* Zod schema for validating documents in the collection. The schema will be used to parse and validate documents returned from the database, ensuring that they conform to the expected structure and types. If a document does not match the schema, an error will be thrown.
|
|
353
|
+
*/
|
|
354
|
+
schema: TSchema;
|
|
355
|
+
methods?: undefined;
|
|
356
|
+
/**
|
|
357
|
+
* Optional array of index specifications to create on this collection.
|
|
358
|
+
*/
|
|
359
|
+
indexes?: MongoIndexSpec[];
|
|
360
|
+
}): MongoCollectionDefinition<SchemaOutput<TSchema>, {}>;
|
|
361
|
+
/**
|
|
362
|
+
* Define a MongoDB collection with an associated Zod schema for validation and optional custom methods. The returned collection definition can then be used to create a MongoDatabase instance, which will automatically wrap the collection with the provided schema validation and methods.
|
|
363
|
+
* @param config The configuration object for the MongoDB collection, including the schema and optional custom methods.
|
|
364
|
+
*/
|
|
365
|
+
declare function defineMongoCollection<TSchema extends SchemaParser<any>, TMethods extends MongoCollectionMethods>(config: {
|
|
366
|
+
/**
|
|
367
|
+
* Optional name for the collection. If not provided, the key used in the collections config will be used as the collection name. This allows you to have more control over the actual collection names in the database, while still using descriptive keys in your code.
|
|
368
|
+
*/
|
|
369
|
+
name?: string;
|
|
370
|
+
/**
|
|
371
|
+
* Zod schema for validating documents in the collection. The schema will be used to parse and validate documents returned from the database, ensuring that they conform to the expected structure and types. If a document does not match the schema, an error will be thrown.
|
|
372
|
+
*/
|
|
373
|
+
schema: TSchema;
|
|
374
|
+
/**
|
|
375
|
+
* Optional custom methods for the collection. These methods will be added to the collection instance and can be used to implement custom queries or operations.
|
|
376
|
+
* @param ctx The context object containing the MongoDB client, database, collection, and base collection wrapper.
|
|
377
|
+
* @returns An object containing the custom methods to be added to the collection instance.
|
|
378
|
+
*/
|
|
379
|
+
methods: (ctx: MongoCollectionMethodContext<SchemaOutput<TSchema>>) => TMethods;
|
|
380
|
+
/**
|
|
381
|
+
* Optional array of index specifications to create on this collection.
|
|
382
|
+
*/
|
|
383
|
+
indexes?: MongoIndexSpec[];
|
|
384
|
+
}): MongoCollectionDefinition<SchemaOutput<TSchema>, TMethods>;
|
|
385
|
+
declare function createMongo<TCollections extends MongoCollectionsConfig>(dbName: string, config: TCollections, options?: {
|
|
386
|
+
uri?: string;
|
|
387
|
+
}): MongoDatabase<TCollections>;
|
|
388
|
+
|
|
389
|
+
type Message = {
|
|
390
|
+
type: string;
|
|
391
|
+
userId: string;
|
|
392
|
+
teamId: string;
|
|
393
|
+
timestamp: number;
|
|
394
|
+
payload: Record<string, string | number | boolean | null>;
|
|
395
|
+
};
|
|
396
|
+
type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
397
|
+
type BaseExpressOptions = {
|
|
398
|
+
/**
|
|
399
|
+
* Don't include the default health check and metrics routes.
|
|
400
|
+
*/
|
|
401
|
+
omitDefaultRoutes?: boolean;
|
|
402
|
+
/**
|
|
403
|
+
* Whether to parse incoming request bodies as JSON.
|
|
404
|
+
*/
|
|
405
|
+
asJson: boolean;
|
|
406
|
+
/**
|
|
407
|
+
* An array of custom Express middleware functions to apply to the Express app globally (before all routes).
|
|
408
|
+
*/
|
|
409
|
+
customMiddleware?: ExpressMiddleware[];
|
|
410
|
+
/**
|
|
411
|
+
* Whether to allow CORS requests with credentials (cookies, authorization headers, etc.). This option is only relevant if CORS is enabled via either `corsFn` or `corsWhitelist`.
|
|
412
|
+
*/
|
|
413
|
+
credentials?: boolean;
|
|
414
|
+
};
|
|
415
|
+
type ExpressOptionsWithCorsFn = BaseExpressOptions & {
|
|
416
|
+
/**
|
|
417
|
+
* A function to determine whether to allow CORS requests from a given origin.
|
|
418
|
+
* @param origin The origin of the request.
|
|
419
|
+
* @param callback A callback function to indicate whether the origin is allowed.
|
|
420
|
+
*/
|
|
421
|
+
corsFn: (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => void;
|
|
422
|
+
};
|
|
423
|
+
type ExpressOptionsWithCorsWhitelist = BaseExpressOptions & {
|
|
424
|
+
/**
|
|
425
|
+
* An array of allowed origins for CORS requests. If the origin of an incoming request is in this whitelist, the request will be allowed.
|
|
426
|
+
*/
|
|
427
|
+
corsWhitelist: string[];
|
|
428
|
+
};
|
|
429
|
+
type ExpressOptions = ExpressOptionsWithCorsFn | ExpressOptionsWithCorsWhitelist;
|
|
430
|
+
type CustomModuleFactory = (log: Logger) => boolean | Promise<boolean>;
|
|
431
|
+
type CustomModules = {
|
|
432
|
+
init: CustomModuleFactory;
|
|
433
|
+
shutdown?: CustomModuleFactory;
|
|
434
|
+
name: string;
|
|
435
|
+
}[];
|
|
436
|
+
|
|
437
|
+
declare class Service<TCollections extends MongoCollectionsConfig = {}> {
|
|
438
|
+
private name;
|
|
439
|
+
private url;
|
|
440
|
+
private port;
|
|
441
|
+
private workerCount;
|
|
442
|
+
private handlers;
|
|
443
|
+
private loadedEventListeners;
|
|
444
|
+
private routers;
|
|
445
|
+
private startedAt;
|
|
446
|
+
private dependencies;
|
|
447
|
+
private dependencyOrder;
|
|
448
|
+
private isWorkerRunning;
|
|
449
|
+
private serviceStreamSubscriptions;
|
|
450
|
+
private _status;
|
|
451
|
+
private readonly HEARTBEAT_INTERVAL;
|
|
452
|
+
private authStrategies;
|
|
453
|
+
private authResolverOverride?;
|
|
454
|
+
private useDefaultHeaderAuthFallback;
|
|
455
|
+
private expressOptions;
|
|
456
|
+
private getAuthResolver;
|
|
457
|
+
get streamKey(): string;
|
|
458
|
+
get simpleName(): string;
|
|
459
|
+
get fullUrl(): string;
|
|
460
|
+
get api(): Express;
|
|
461
|
+
get log(): Logger;
|
|
462
|
+
get routes(): {
|
|
463
|
+
path: string;
|
|
464
|
+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
465
|
+
requireAuth: boolean;
|
|
466
|
+
meta: RouteMeta | undefined;
|
|
467
|
+
}[];
|
|
468
|
+
get status(): "starting" | "running" | "stopped" | "stopping";
|
|
469
|
+
/**
|
|
470
|
+
* MongoDB client instance.
|
|
471
|
+
*
|
|
472
|
+
* @requires MongoDB module to be added via withMongo()
|
|
473
|
+
*/
|
|
474
|
+
get mongo(): MongoDatabase<TCollections>;
|
|
475
|
+
/**
|
|
476
|
+
* Typed MongoDB collection wrappers registered through `withMongo()`.
|
|
477
|
+
*
|
|
478
|
+
* @requires MongoDB module to be added via withMongo()
|
|
479
|
+
*/
|
|
480
|
+
get db(): InferMongoCollections<TCollections>;
|
|
481
|
+
/**
|
|
482
|
+
* Clickhouse client instance. This will throw an error if Clickhouse module has not been added.
|
|
483
|
+
*
|
|
484
|
+
* @requires ClickhouseModule to be added via withClickhouse()
|
|
485
|
+
* @beta
|
|
486
|
+
*/
|
|
487
|
+
get clickhouse(): ReturnType<typeof createTypedClickhouse>;
|
|
488
|
+
/**
|
|
489
|
+
* Redis client instance. This will throw an error if Redis module has not been added.
|
|
490
|
+
*
|
|
491
|
+
* @requires RedisModule to be added via withRedis()
|
|
492
|
+
*/
|
|
493
|
+
get redis(): RedisClientType;
|
|
494
|
+
/**
|
|
495
|
+
* Registers the service in Redis for service discovery.
|
|
496
|
+
*
|
|
497
|
+
* @requires RedisModule to be added via withRedis()
|
|
498
|
+
*/
|
|
499
|
+
register(): Promise<void>;
|
|
500
|
+
/**
|
|
501
|
+
* Periodically update the service registration in Redis.
|
|
502
|
+
*
|
|
503
|
+
* @requires RedisModule to be added via withRedis()
|
|
504
|
+
*/
|
|
505
|
+
heartbeat(): Promise<void>;
|
|
506
|
+
/**
|
|
507
|
+
* Builds and starts the service by initializing dependencies, setting up routes, and starting the API server.
|
|
508
|
+
*
|
|
509
|
+
* The build process includes:
|
|
510
|
+
* 1. Initializing dependencies in the order they were added (MongoDB, Redis, Clickhouse). This includes the redis workers module if withWorkers() was called.
|
|
511
|
+
* 2. Initializing any custom modules provided through withModules().
|
|
512
|
+
* 3. Setting up Express middleware and CORS based on provided options.
|
|
513
|
+
* 4. Registering default routes like /health and /workers.
|
|
514
|
+
* 5. Registering any custom routers added through addRouter().
|
|
515
|
+
* 6. Starting the Express server on the configured port.
|
|
516
|
+
*/
|
|
517
|
+
build(): this;
|
|
518
|
+
/**
|
|
519
|
+
* Reads all files in the src/handlers directory and imports them dynamically.
|
|
520
|
+
*/
|
|
521
|
+
private loadHandlers;
|
|
522
|
+
/**
|
|
523
|
+
* Registers default routes like /health and /workers
|
|
524
|
+
*/
|
|
525
|
+
private registerDefaultRoutes;
|
|
526
|
+
private initBaseRoutes;
|
|
527
|
+
/**
|
|
528
|
+
* Default worker routes under /workers
|
|
529
|
+
*/
|
|
530
|
+
private initWorkerRoutes;
|
|
531
|
+
/**
|
|
532
|
+
* Spawns worker threads to process messages from Redis streams.
|
|
533
|
+
* Each worker thread runs the worker-thread.ts file.
|
|
534
|
+
* Each worker thread is passed the service name, redis URL, and handlers.
|
|
535
|
+
* Workers communicate back via parentPort to log messages.
|
|
536
|
+
*/
|
|
537
|
+
private runWorkers;
|
|
538
|
+
/**
|
|
539
|
+
* The MongoDB module. This is initialized by calling withMongo() and provides access to the configured MongoDB collections through the `db` property. The MongoDB client will automatically connect when the service is built.
|
|
540
|
+
*
|
|
541
|
+
* @param config MongoDB configuration including URI, database name, and collections.
|
|
542
|
+
*
|
|
543
|
+
* @example
|
|
544
|
+
* const t = new Service()
|
|
545
|
+
* .withMongo({
|
|
546
|
+
* dbName: "test",
|
|
547
|
+
* collections: {
|
|
548
|
+
* users: { schema: z.object({ id: z.string(), name: z.string() }) },
|
|
549
|
+
* },
|
|
550
|
+
* })
|
|
551
|
+
* .build();
|
|
552
|
+
*
|
|
553
|
+
* const l = await t.db.users.get({ id: "123" });
|
|
554
|
+
*/
|
|
555
|
+
withMongo<TNextCollections extends MongoCollectionsConfig>(config: {
|
|
556
|
+
dbName: string;
|
|
557
|
+
uri?: string;
|
|
558
|
+
collections: TNextCollections;
|
|
559
|
+
}): Service<TNextCollections>;
|
|
560
|
+
/**
|
|
561
|
+
* The Clickhouse module. This is initialized by calling withClickhouse() and provides access to the configured Clickhouse tables through the `clickhouse` property. The Clickhouse client will automatically connect when the service is built.
|
|
562
|
+
*
|
|
563
|
+
* This module is a **WIP**.
|
|
564
|
+
*
|
|
565
|
+
* @param config Clickhouse configuration including tables.
|
|
566
|
+
* @beta
|
|
567
|
+
*/
|
|
568
|
+
withClickhouse<TTables extends Record<string, any>>(config: ClickhouseConfig<TTables>): Service<TCollections> & {
|
|
569
|
+
clickhouse: ReturnType<typeof createTypedClickhouse<TTables>>;
|
|
570
|
+
};
|
|
571
|
+
/**
|
|
572
|
+
* Optional. Port number for the express server.
|
|
573
|
+
*
|
|
574
|
+
* Defaults in the following order:
|
|
575
|
+
* 1. PORT environment variable
|
|
576
|
+
* 2. 3100
|
|
577
|
+
*
|
|
578
|
+
* @param port port number
|
|
579
|
+
*/
|
|
580
|
+
withPort(port?: number): this;
|
|
581
|
+
/**
|
|
582
|
+
* Optional. Base URL without port.
|
|
583
|
+
*
|
|
584
|
+
* Defaults in the following order:
|
|
585
|
+
* 1. SERVICE_URL environment variable
|
|
586
|
+
* 2. "http://localhost"
|
|
587
|
+
*
|
|
588
|
+
* @param url base url without port
|
|
589
|
+
*/
|
|
590
|
+
withUrl(url: string): this;
|
|
591
|
+
/**
|
|
592
|
+
* Optional. Name of the service. Used for logging and service discovery.
|
|
593
|
+
*
|
|
594
|
+
* Defaults in the following order:
|
|
595
|
+
* 1. SERVICE_NAME environment variable
|
|
596
|
+
* 2. A random hex string
|
|
597
|
+
* @param name Name of the service
|
|
598
|
+
*/
|
|
599
|
+
withName(name: string): this;
|
|
600
|
+
/**
|
|
601
|
+
* The Redis module. This is initialized by calling withRedis() and provides a Redis client instance through the `redis` property. The Redis client will automatically connect when the service is built.
|
|
602
|
+
* @param url Redis URL. Defaults to: redis://redis:6379
|
|
603
|
+
*/
|
|
604
|
+
withRedis(url?: string): this;
|
|
605
|
+
/**
|
|
606
|
+
* Adds worker threads to the service. These are used to process messages from Redis streams. Worker threads will be spawned when build() is called. This must be called after withRedis() to work properly.
|
|
607
|
+
*
|
|
608
|
+
* **Note**: This will not initialize without at least one service stream.
|
|
609
|
+
* @param workerCount Defaults to 1
|
|
610
|
+
* @param serviceStreams Defaults to empty array
|
|
611
|
+
* @returns
|
|
612
|
+
*/
|
|
613
|
+
withWorkers(workerCount: number, serviceStreams?: string[]): this;
|
|
614
|
+
/**
|
|
615
|
+
* Loads custom dependencies that can be used in the service. The factory functions will be called during build() after all other dependencies are loaded and can be used to initialize any custom logic or connections. The result of each factory function will be logged.
|
|
616
|
+
* @param dependencies Object where keys are dependency names and values are factory functions that return a boolean indicating success or failure of initialization.
|
|
617
|
+
*/
|
|
618
|
+
withModules(dependencies: CustomModules): this;
|
|
619
|
+
withExpressOptions(options: Partial<ExpressOptions>): this;
|
|
620
|
+
withAuthStrategy(strategy: AuthStrategy): this;
|
|
621
|
+
withAuthStrategies(strategies: AuthStrategy[]): this;
|
|
622
|
+
withAuthResolver(resolver: AuthResolver): this;
|
|
623
|
+
withoutDefaultHeaderAuthFallback(): this;
|
|
624
|
+
private internalAddRouter;
|
|
625
|
+
/**
|
|
626
|
+
* Registers an express router under a base path.
|
|
627
|
+
* @param base Base path for this router.
|
|
628
|
+
* @param router Router instance.
|
|
629
|
+
* @param routes List of route definitions.
|
|
630
|
+
* @returns
|
|
631
|
+
*/
|
|
632
|
+
addRouter(base: string, router: Router, routes?: RouteDefinition<any, any, boolean>[]): this;
|
|
633
|
+
/**
|
|
634
|
+
* Gracefully shuts down the service by closing all connections and active workers.
|
|
635
|
+
*/
|
|
636
|
+
shutdown(opts?: {
|
|
637
|
+
/**
|
|
638
|
+
* Optional callback to run before shutdown. Can be used to perform cleanup tasks like closing database connections, etc.
|
|
639
|
+
*/
|
|
640
|
+
beforeShutdown?: () => Promise<void>;
|
|
641
|
+
/**
|
|
642
|
+
* Optional callback to run after shutdown. Can be used to perform any final tasks before the process exits.
|
|
643
|
+
*/
|
|
644
|
+
afterShutdown?: () => Promise<void>;
|
|
645
|
+
}): Promise<void>;
|
|
646
|
+
[Symbol.dispose](): void;
|
|
647
|
+
[Symbol.iterator](): Generator<any, void, unknown>;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
type RedisStreamHandlerContext = {
|
|
651
|
+
service: string;
|
|
652
|
+
emit: (ev: {
|
|
653
|
+
type: string;
|
|
654
|
+
data: Record<string, string | number | boolean | null>;
|
|
655
|
+
}) => Promise<void>;
|
|
656
|
+
log: {
|
|
657
|
+
info: (msg: string) => void;
|
|
658
|
+
warn: (msg: string) => void;
|
|
659
|
+
error: (msg: string) => void;
|
|
660
|
+
};
|
|
661
|
+
};
|
|
662
|
+
type RedisStreamEventHandler = {
|
|
663
|
+
type: string;
|
|
664
|
+
execute: (ev: Record<string, any>, ctx: RedisStreamHandlerContext) => Promise<void>;
|
|
665
|
+
};
|
|
666
|
+
declare function redisStreamHandler(type: string, fn: RedisStreamEventHandler["execute"]): RedisStreamEventHandler;
|
|
667
|
+
|
|
668
|
+
type JwtStrategyClaims = {
|
|
669
|
+
sub?: string;
|
|
670
|
+
[key: string]: unknown;
|
|
671
|
+
};
|
|
672
|
+
type JwtTokenVerifier = (token: string, req: Request, route: RouteDefinition<any, any, boolean>) => JwtStrategyClaims | null | Promise<JwtStrategyClaims | null>;
|
|
673
|
+
type CreateJwtAuthStrategyOptions = {
|
|
674
|
+
name?: string;
|
|
675
|
+
headerName?: string;
|
|
676
|
+
tokenPrefix?: string;
|
|
677
|
+
verifyToken: JwtTokenVerifier;
|
|
678
|
+
mapPayloadToUser?: (claims: JwtStrategyClaims, req: Request, route: RouteDefinition<any, any, boolean>) => AuthenticatedUser | null | Promise<AuthenticatedUser | null>;
|
|
679
|
+
canHandle?: AuthStrategy["canHandle"];
|
|
680
|
+
};
|
|
681
|
+
type CreateJoseJwtVerifierOptions = {
|
|
682
|
+
jwksUri?: string;
|
|
683
|
+
secret?: string | Uint8Array;
|
|
684
|
+
issuer?: string | string[];
|
|
685
|
+
audience?: string | string[];
|
|
686
|
+
algorithms?: string[];
|
|
687
|
+
clockTolerance?: string | number;
|
|
688
|
+
requiredClaims?: string[];
|
|
689
|
+
};
|
|
690
|
+
type CreateMtlsAuthStrategyOptions = {
|
|
691
|
+
name?: string;
|
|
692
|
+
requireAuthorized?: boolean;
|
|
693
|
+
mapCertificateToUser?: (certificate: PeerCertificate, req: Request, route: RouteDefinition<any, any, boolean>) => AuthenticatedUser | null | Promise<AuthenticatedUser | null>;
|
|
694
|
+
canHandle?: AuthStrategy["canHandle"];
|
|
695
|
+
};
|
|
696
|
+
declare function createJoseJwtVerifier(options: CreateJoseJwtVerifierOptions): JwtTokenVerifier;
|
|
697
|
+
declare function createJwtAuthStrategy(options: CreateJwtAuthStrategyOptions): AuthStrategy;
|
|
698
|
+
declare function createMtlsAuthStrategy(options?: CreateMtlsAuthStrategyOptions): AuthStrategy;
|
|
699
|
+
|
|
700
|
+
export { type AuthResolver, type AuthStrategy, type AuthenticatedUser, type BaseExpressOptions, CollectionWrapper, type CreateJoseJwtVerifierOptions, type CreateJwtAuthStrategyOptions, type CreateMtlsAuthStrategyOptions, type CustomModuleFactory, type CustomModules, type ExpressMiddleware, type ExpressOptions, type ExpressOptionsWithCorsFn, type ExpressOptionsWithCorsWhitelist, type InferMongoCollections, type JwtStrategyClaims, type JwtTokenVerifier, type Message, type MongoBoundCollection, type MongoCollectionDefinition, type MongoCollectionMethodContext, type MongoCollectionMethods, type MongoCollectionsConfig, type MongoDatabase, type MongoIndexSpec, type RedisStreamEventHandler, type RedisStreamHandlerContext, type RouteDefinition, type RouteMeta, type SchemaParser, Service, type TypedRequest, type TypedRequestHandler, createAuthResolver, createJoseJwtVerifier, createJwtAuthStrategy, createMetaRouter, createMetaRouterWithAuth, createMongo, createMtlsAuthStrategy, defaultHeaderAuthResolver, defineMongoCollection, getRegisteredRoutes, redisStreamHandler };
|