@anteros/core 0.0.1-alpha.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.
Files changed (55) hide show
  1. package/README.md +143 -0
  2. package/database/collection.ts +160 -0
  3. package/database/decorator.ts +172 -0
  4. package/database/file.ts +93 -0
  5. package/database/mongodbadapter.ts +1128 -0
  6. package/database/rest.ts +14 -0
  7. package/database/schema.ts +160 -0
  8. package/database/tenant.ts +37 -0
  9. package/database/workflow.ts +384 -0
  10. package/index.ts +28 -0
  11. package/lib/asyncContextStorage.ts +68 -0
  12. package/lib/define.ts +114 -0
  13. package/lib/error.ts +21 -0
  14. package/lib/files.ts +459 -0
  15. package/lib/middleware.ts +66 -0
  16. package/lib/routes.ts +44 -0
  17. package/lib/scripts.ts +47 -0
  18. package/lib/services.ts +45 -0
  19. package/lib/sockets.ts +44 -0
  20. package/lib/workflow.ts +60 -0
  21. package/package.json +31 -0
  22. package/server/api.ts +789 -0
  23. package/server/boot.ts +101 -0
  24. package/server/config.ts +107 -0
  25. package/server/env.ts +16 -0
  26. package/server/hono.ts +176 -0
  27. package/server/io.ts +15 -0
  28. package/server/routes.ts +48 -0
  29. package/server/security.ts +138 -0
  30. package/tests/api.test.ts +281 -0
  31. package/tsconfig.json +36 -0
  32. package/types/activity.d.ts +45 -0
  33. package/types/api.d.ts +85 -0
  34. package/types/collection.d.ts +82 -0
  35. package/types/config.d.ts +55 -0
  36. package/types/field.d.ts +72 -0
  37. package/types/file.d.ts +120 -0
  38. package/types/hook.d.ts +30 -0
  39. package/types/middleware.d.ts +18 -0
  40. package/types/mongo.d.ts +61 -0
  41. package/types/options.d.ts +7 -0
  42. package/types/rest.d.ts +18 -0
  43. package/types/route.d.ts +19 -0
  44. package/types/schema.d.ts +0 -0
  45. package/types/scripts.d.ts +10 -0
  46. package/types/service.d.ts +37 -0
  47. package/types/task.d.ts +12 -0
  48. package/types/tenant.d.ts +16 -0
  49. package/types/token.d.ts +14 -0
  50. package/types/websocket.d.ts +15 -0
  51. package/types/workflow.d.ts +91 -0
  52. package/utils/cache.ts +96 -0
  53. package/utils/crypto.ts +226 -0
  54. package/utils/func.ts +1037 -0
  55. package/utils/index.ts +17 -0
@@ -0,0 +1,120 @@
1
+ import type { AppError, fn } from "../lib/error";
2
+ import type { Field } from "./field";
3
+ import type { HooksCollection } from "./hook";
4
+ import type Joi from "joi";
5
+ import type { useRest } from "../database/rest";
6
+ import type { jwt } from "../utils/func";
7
+
8
+ type FileAccessHandler = (ctx: {
9
+ rest: InstanceType<typeof useRest>;
10
+ error: typeof fn.error;
11
+ jwt: typeof jwt;
12
+ token: { value: string | null; decoded: Record<string, unknown> | null; provided: boolean; expired: boolean };
13
+ }) => boolean | Promise<boolean>;
14
+
15
+ type FileApiAccess = {
16
+ /** Access control for POST /upload/:tenant_id/:slug */
17
+ upload?: boolean | FileAccessHandler;
18
+ /** Access control for GET /files/:tenant_id/:slug/:file */
19
+ read?: boolean | FileAccessHandler;
20
+ /** Access control for DELETE /files/:tenant_id/:slug/:file */
21
+ delete?: boolean | FileAccessHandler;
22
+ };
23
+
24
+ /**
25
+ * File collection type.
26
+ *
27
+ * Defined in `files/*.file.ts` — a dedicated collection
28
+ * for handling file uploads, storage, and serving.
29
+ *
30
+ * Each file collection generates:
31
+ * POST /upload/:tenant_id/:slug — upload a file
32
+ * GET /files/:tenant_id/:slug/:file — serve/download a file
33
+ */
34
+ export type FileCollection = {
35
+ /** Unique identifier used in routes: `/upload/:tenant_id/:slug` */
36
+ slug: string;
37
+ hooks?: {
38
+ beforeOperation?: HooksCollection['beforeOperation'];
39
+ afterOperation?: HooksCollection['afterOperation'];
40
+ };
41
+ /** Metadata fields stored alongside the file document */
42
+ fields?: Field[];
43
+ /** Upload validation rules */
44
+ upload?: {
45
+ /** Allowed MIME types for upload.
46
+ * @example ['image/jpeg', 'image/png', 'application/pdf'] */
47
+ allowedMimeTypes?: string[];
48
+ /** Maximum file size in bytes. Default: 10MB */
49
+ maxSize?: number;
50
+ /**
51
+ * Image transformations (resize, format, etc.).
52
+ * Applied on-the-fly when serving via GET /files/:tenant_id/:slug/:file
53
+ */
54
+ transformations?: {
55
+ /** Resize to max width in pixels */
56
+ width?: number;
57
+ /** Resize to max height in pixels */
58
+ height?: number;
59
+ /** Output format */
60
+ format?: 'webp' | 'jpeg' | 'png' | 'avif';
61
+ /** Quality 1-100 (default: 80) */
62
+ quality?: number;
63
+ };
64
+ };
65
+ /**
66
+ * Storage configuration.
67
+ * @default 'disk'
68
+ */
69
+ storage?: {
70
+ /** Storage backend */
71
+ driver: 'disk' | 's3';
72
+ /** Relative path inside the tenant storage directory.
73
+ * @example 'uploads/photos' */
74
+ path?: string;
75
+ /** S3 bucket name (required if driver is 's3') */
76
+ bucket?: string;
77
+ /** S3 region (required if driver is 's3') */
78
+ region?: string;
79
+ };
80
+ /**
81
+ * Replicate uploaded files to remote destinations.
82
+ * Executed after the primary storage save.
83
+ */
84
+ replicate?: {
85
+ /** Destination driver */
86
+ driver: 's3' | 'ssh' | 'sftp';
87
+ /** Remote host (required for ssh/sftp) */
88
+ host?: string;
89
+ /** SSH/SFTP port (default: 22) */
90
+ port?: number;
91
+ /** Remote username (required for ssh/sftp) */
92
+ username?: string;
93
+ /** Remote path or directory */
94
+ path?: string;
95
+ /** S3 bucket (required if driver is 's3') */
96
+ bucket?: string;
97
+ /** S3 region */
98
+ region?: string;
99
+ /** Custom S3 endpoint */
100
+ endpoint?: string;
101
+ /** Private key path for SSH auth */
102
+ privateKey?: string;
103
+ }[];
104
+ /** Enregistrer les métadonnées du fichier en base. Défaut: true */
105
+ trackMetaData?: boolean;
106
+ api?: {
107
+ access?: FileApiAccess;
108
+ privateFields?: (string | RegExp)[];
109
+ readOnlyFields?: (string | RegExp)[];
110
+ };
111
+ /**
112
+ * The tenant id of the file collection
113
+ * @type {string}
114
+ */
115
+ _tenant_?: string;
116
+ _isTimeSerie_?: boolean;
117
+ _isFileCollection_?: boolean;
118
+ _schema_?: Joi.Schema;
119
+ _schemaPartial_?: Joi.Schema;
120
+ }
@@ -0,0 +1,30 @@
1
+ import type { MongoRest } from "../database/mongodbadapter";
2
+ import type { Server as SocketIO } from "socket.io";
3
+ import type { ActionsApiList } from "./api";
4
+ type metaHook = {
5
+ action: ActionsApiList;
6
+ collection: string;
7
+ data?: any;
8
+ params?: any;
9
+ ids?: string[];
10
+ filter?: object;
11
+ id?: string;
12
+ update?: any;
13
+ result?: any;
14
+ pipeline?: any[];
15
+ options?: any;
16
+ }
17
+ export type HooksCollection = {
18
+ beforeOperation?: (ctx: {
19
+ rest: MongoRest;
20
+ action: ActionsApiList;
21
+ meta: metaHook;
22
+ io: SocketIO;
23
+ }) => Promise<void>;
24
+ afterOperation?: (ctx: {
25
+ rest: MongoRest;
26
+ action: ActionsApiList;
27
+ meta: metaHook;
28
+ io: SocketIO;
29
+ }) => Promise<void>;
30
+ }
@@ -0,0 +1,18 @@
1
+ import type { Context, Next } from "hono";
2
+
3
+ export type TenantMiddlewareHandler = (c: Context, next: Next) => Promise<void | Response>;
4
+
5
+ export type TenantMiddlewareConfig = {
6
+ name: string;
7
+ enabled?: boolean;
8
+ handler: TenantMiddlewareHandler;
9
+ _isTenantMiddleware_?: boolean;
10
+ _tenant_?: string;
11
+ };
12
+
13
+ export type GlobalMiddlewareConfig = {
14
+ name: string;
15
+ enabled?: boolean;
16
+ handler: TenantMiddlewareHandler;
17
+ _isGlobalMiddleware_?: boolean;
18
+ };
@@ -0,0 +1,61 @@
1
+ import type { UpdateOptions } from "mongodb"
2
+
3
+ export type updateOptions = UpdateOptions & {
4
+
5
+
6
+ }
7
+
8
+
9
+ export type FindOptions = {
10
+ $match?: {
11
+ _id?: string;
12
+ [key: string]: any;
13
+ };
14
+ $skip?: number;
15
+ $limit?: number;
16
+ $include?: Array<string | LookupOptions>;
17
+ $lookup?: Array<Record<string, any>>;
18
+ $graphLookup?: Array<Record<string, any>>;
19
+ $sort?: {
20
+ [key: string]: 1 | -1;
21
+ }
22
+ $project?: Record<string, unknown>;
23
+
24
+
25
+ $group?: {
26
+ _id: string;
27
+ [key: string]: any;
28
+ };
29
+ $sample?: {
30
+ size: number;
31
+ };
32
+ $sortAfterInclude?: object;
33
+ $projectAfterInclude?: object;
34
+ $matchAfterInclude?: object;
35
+ $unwind?: Array<string | {
36
+ path: string;
37
+ preserveNullAndEmptyArrays?: boolean;
38
+ includeArrayIndex?: string;
39
+ }>;
40
+ }
41
+
42
+ export type findOneByIdOptions = {
43
+ $include?: Array<string | LookupOptions>;
44
+ $matchAfterInclude?: object;
45
+ }
46
+
47
+ export type findOneOptions = {
48
+ $include?: Array<string | LookupOptions>;
49
+ /** Aligné sur `FindOptions.$project` pour les agrégations findOne (buildPipeline). */
50
+ $project?: Record<string, unknown>;
51
+ }
52
+
53
+ export type LookupOptions = {
54
+ from: string;
55
+ localField: string;
56
+ foreignField: string;
57
+ as?: string;
58
+ pipeline?: Array<any>;
59
+ let?: Record<string, any>;
60
+ unwind?: boolean;
61
+ }
@@ -0,0 +1,7 @@
1
+ type LookupOptions = {
2
+ from: string;
3
+ localField: string;
4
+ foreignField: string;
5
+ as?: string;
6
+ pipeline?: Array<any>;
7
+ }
@@ -0,0 +1,18 @@
1
+ import type { UpdateOneModel, UpdateManyModel, ClientSession, MongoClientOptions } from "mongodb";
2
+
3
+ export type RestActions =
4
+ 'insertOne' | 'insertMany' | 'updateOne' | 'updateMany' | 'replaceOne' | 'deleteOne' | 'deleteMany'
5
+
6
+ export type BulkUpdateOperation = { updateOne: UpdateOneModel } | { updateMany: UpdateManyModel };
7
+
8
+ export type RestOptions = {
9
+ internal?: boolean;
10
+ tenant_id?: string;
11
+ useHook?: boolean;
12
+ useCustomApi?: boolean;
13
+ session?: ClientSession | null | undefined;
14
+ database?: {
15
+ uri: string;
16
+ options?: MongoClientOptions;
17
+ };
18
+ }
@@ -0,0 +1,19 @@
1
+ import type { Context } from "hono";
2
+ import type { useRest } from "../database/rest";
3
+
4
+ type routeContext = {
5
+ rest: InstanceType<typeof useRest>;
6
+ jwt: typeof jwt;
7
+ io: InstanceType<typeof IO>;
8
+ c: Context
9
+ }
10
+
11
+ type Route = {
12
+ enabled?: boolean;
13
+ path: string;
14
+ method: 'GET' | 'POST';
15
+ handler: (ctx: routeContext) => void;
16
+ _tenant_?: string;
17
+ _isRoute_?: boolean;
18
+ _prefix_?: string;
19
+ }
File without changes
@@ -0,0 +1,10 @@
1
+ import { useRest } from "../database/rest"
2
+
3
+
4
+ export type Script = {
5
+ _isScript_?: boolean;
6
+ enabled: boolean;
7
+ exec: (ctx: {
8
+ rest: InstanceType<typeof useRest>
9
+ }) => void
10
+ }
@@ -0,0 +1,37 @@
1
+ import type { Server as SocketIO } from "socket.io";
2
+ import type { useRest } from "../database/rest";
3
+ import type { fn } from "../lib/error";
4
+ import type { jwt } from "../utils/func";
5
+
6
+ export type Service = {
7
+ _isService_?: boolean;
8
+ _tenant_?: string;
9
+ name: string;
10
+ enabled: boolean;
11
+ api?: {
12
+ access?: {
13
+ '*'?: ((ctx: {
14
+ rest: InstanceType<typeof useRest>;
15
+ error: typeof fn.error;
16
+ jwt: typeof jwt;
17
+ token: { value: string | null; decoded: Record<string, unknown> | null; provided: boolean; expired: boolean };
18
+ }) => Promise<boolean> | boolean) | boolean;
19
+ [key: string]: ((ctx: {
20
+ rest: InstanceType<typeof useRest>;
21
+ error: typeof fn.error;
22
+ jwt: typeof jwt;
23
+ token: { value: string | null; decoded: Record<string, unknown> | null; provided: boolean; expired: boolean };
24
+ }) => Promise<boolean> | boolean) | boolean;
25
+ };
26
+ };
27
+ actions: {
28
+ [key: string]: (ctx: {
29
+ data: any;
30
+ token: { value: string | null; decoded: Record<string, unknown> | null; provided: boolean; expired: boolean };
31
+ jwt: typeof jwt;
32
+ error: typeof fn.error;
33
+ io: SocketIO;
34
+ rest: InstanceType<typeof useRest>
35
+ }) => Promise<any>
36
+ }
37
+ }
@@ -0,0 +1,12 @@
1
+ import type { io } from "../server/io";
2
+ import type { useRest } from "../database/rest";
3
+
4
+ export type Task = {
5
+ name: string;
6
+ pattern: string;
7
+ enabled: boolean;
8
+ exec: (ctx: {
9
+ io: InstanceType<typeof io>;
10
+ rest: InstanceType<typeof useRest>
11
+ }) => void
12
+ }
@@ -0,0 +1,16 @@
1
+ import type { Db, MongoClient, MongoClientOptions } from "mongodb";
2
+ export type Tenant = {
3
+ id: string;
4
+ name?: string;
5
+ description?: string;
6
+ dir: string;
7
+ routes?: {
8
+ prefix?: string;
9
+ },
10
+ database: {
11
+ uri: string;
12
+ options?: MongoClientOptions;
13
+ db?: Db;
14
+ client?: MongoClient;
15
+ }
16
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Token information available in request context and handlers.
3
+ * Always present (even without a token — use `provided` to check).
4
+ */
5
+ export type Token = {
6
+ /** Raw JWT string from the `Authorization` header, or null if not sent */
7
+ value: string | null;
8
+ /** Verified payload (claims), or null if invalid / expired / not sent */
9
+ decoded: Record<string, unknown> | null;
10
+ /** Whether a token was provided in the Authorization header */
11
+ provided: boolean;
12
+ /** Whether the token is expired */
13
+ expired: boolean;
14
+ };
@@ -0,0 +1,15 @@
1
+ import type { Server as SocketIO, Socket as SocketClient } from "socket.io";
2
+ import type { useRest } from "../database/rest";
3
+
4
+
5
+
6
+ export type WebSocketHandler = {
7
+ _isWebSocket_?: boolean;
8
+ _tenant_?: string;
9
+ enabled: boolean;
10
+ exec: (ctx: {
11
+ io: SocketIO;
12
+ socket: SocketClient;
13
+ rest: InstanceType<typeof useRest>;
14
+ }) => void | Promise<void>;
15
+ };
@@ -0,0 +1,91 @@
1
+ export type WorkflowStepHandler<TData = any> = (ctx: {
2
+ data: TData;
3
+ prevOutput: any;
4
+ input?: any;
5
+ rest: any;
6
+ error: any;
7
+ jwt: any;
8
+ }) => Promise<any>;
9
+
10
+ export type WorkflowCompensationHandler<TData = any> = (ctx: {
11
+ data: TData;
12
+ /** The output of the failed step (if any) */
13
+ stepOutput: any;
14
+ /** The error that caused the failure */
15
+ stepError: { message: string; code?: string };
16
+ rest: any;
17
+ error: any;
18
+ jwt: any;
19
+ }) => Promise<void>;
20
+
21
+ export type WorkflowStep<TData = any> = {
22
+ id: string;
23
+ name?: string;
24
+ description?: string;
25
+ exec: WorkflowStepHandler<TData>;
26
+ /** Compensation handler called when this step fails (executed in reverse order) */
27
+ compensate?: WorkflowCompensationHandler<TData>;
28
+ /** List of step IDs this compensation depends on / targets */
29
+ depend?: string[];
30
+ condition?: (ctx: {
31
+ data: TData;
32
+ prevOutput: any;
33
+ }) => boolean | Promise<boolean>;
34
+ input?: any;
35
+ output?: any;
36
+ };
37
+
38
+ export type WorkflowContextField = {
39
+ type: 'string' | 'number' | 'date';
40
+ index?: boolean | 1 | -1;
41
+ };
42
+
43
+ export type WorkflowContextFields = Record<string, WorkflowContextField>;
44
+
45
+ export type WorkflowDefinition<TData = any> = {
46
+ id: string;
47
+ name: string;
48
+ description?: string;
49
+ version?: number;
50
+ context?: WorkflowContextFields;
51
+ exec?: WorkflowStepHandler<TData>;
52
+ steps: WorkflowStep<TData>[];
53
+ /** Global compensation handlers executed when any step fails (reverse order) */
54
+ compensations?: WorkflowStep<TData>[];
55
+ _tenant_?: string;
56
+ _isWorkflow_?: boolean;
57
+ };
58
+
59
+ export type WorkflowRunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'paused' | 'cancelled' | 'skipped';
60
+
61
+ export type WorkflowRunStep = {
62
+ stepId: string;
63
+ name?: string;
64
+ status: WorkflowRunStatus;
65
+ input?: any;
66
+ output?: any;
67
+ error?: { message: string; code?: string };
68
+ startedAt: Date;
69
+ completedAt?: Date;
70
+ };
71
+
72
+ export type WorkflowRun = {
73
+ _id: string;
74
+ workflowId: string;
75
+ workflowVersion?: number;
76
+ tenant_id: string;
77
+ status: WorkflowRunStatus;
78
+ progress: number;
79
+ data: any;
80
+ context?: any;
81
+ currentStep: number;
82
+ totalExecuted: number;
83
+ totalSkipped: number;
84
+ steps: WorkflowRunStep[];
85
+ /** Compensation steps that were executed */
86
+ compensations?: WorkflowRunStep[];
87
+ error?: { message: string; code?: string; stepId?: string };
88
+ createdAt: Date;
89
+ updatedAt: Date;
90
+ completedAt?: Date;
91
+ };
package/utils/cache.ts ADDED
@@ -0,0 +1,96 @@
1
+ import { BentoCache, bentostore } from "bentocache";
2
+ import { memoryDriver } from "bentocache/drivers/memory";
3
+ import { redisDriver } from "bentocache/drivers/redis";
4
+ import { fileDriver } from "bentocache/drivers/file";
5
+ import path from "path";
6
+ const drivers = {
7
+ memoryDriver,
8
+ redisDriver,
9
+ fileDriver,
10
+ }
11
+
12
+ type Driver = 'memory' | 'redis' | 'filesystem';
13
+
14
+ type CacheOptions = {
15
+ filesystem?: {
16
+ directory: string;
17
+ },
18
+ redis?: {
19
+ connection: {
20
+ host: string;
21
+ port: number;
22
+ password: string;
23
+ };
24
+ },
25
+ pruneInterval?: string;
26
+ }
27
+
28
+ class Cache {
29
+ #cache: InstanceType<typeof BentoCache>;
30
+ constructor(driver: Driver, options?: CacheOptions) {
31
+ let store = bentostore().useL1Layer(memoryDriver({
32
+ }))
33
+
34
+ if (driver == 'filesystem') {
35
+ store = store.useL2Layer(fileDriver({
36
+ directory: path.join(process.cwd(), '.cache'),
37
+ pruneInterval: '1h'
38
+ }))
39
+ }
40
+
41
+ if (driver == 'redis' && options?.redis?.connection) {
42
+ store = store.useL2Layer(redisDriver({
43
+ connection: options.redis.connection
44
+ }))
45
+ }
46
+ this.#cache = new BentoCache({
47
+ default: driver,
48
+ stores: {
49
+ [driver]: store,
50
+ }
51
+ })
52
+ }
53
+ }
54
+
55
+
56
+ function useRedisCache(options: {
57
+ connection: {
58
+ host: string;
59
+ port: number;
60
+ password: string;
61
+ };
62
+ pruneInterval?: string;
63
+ }) {
64
+ return new Cache('redis', {
65
+ redis: {
66
+ connection: options.connection,
67
+ },
68
+ pruneInterval: options.pruneInterval,
69
+ });
70
+ }
71
+
72
+ function useMemoryCache() {
73
+ return new Cache('memory');
74
+ }
75
+
76
+ function useFilesystemCache(options: {
77
+ directory: string;
78
+ pruneInterval?: string;
79
+ }) {
80
+ return new Cache('filesystem', {
81
+ filesystem: {
82
+ directory: options.directory,
83
+ },
84
+ pruneInterval: options.pruneInterval,
85
+ });
86
+ }
87
+
88
+
89
+
90
+ export default Cache;
91
+ export {
92
+ Cache,
93
+ useFilesystemCache,
94
+ useMemoryCache,
95
+ useRedisCache
96
+ }