@dnax/core 0.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/types/index.ts ADDED
@@ -0,0 +1,377 @@
1
+ import { updateParams } from "./../driver/mongo/@types";
2
+ import * as v from "valibot";
3
+ import type { Db, MongoClient } from "mongodb";
4
+ import { useRest } from "../driver/mongo/rest";
5
+ import { sessionStorage } from "../lib/asyncLocalStorage";
6
+ import type { Context } from "hono";
7
+ import type { Server as ServerIO, Socket as SocketType } from "socket.io";
8
+
9
+ type Io = InstanceType<typeof ServerIO>;
10
+
11
+ import { fn } from "../utils";
12
+ import type {
13
+ findOneParam,
14
+ findParam,
15
+ updateParams,
16
+ } from "../driver/mongo/@types";
17
+ import type { RouterRoute } from "hono/types";
18
+
19
+ export type Socket = {
20
+ enabled: boolean;
21
+ handler: (ctx: { rest: useRest; io: Io; session: sessionCtx }) => void;
22
+ };
23
+
24
+ export type Tenant = {
25
+ id: string;
26
+ name?: string;
27
+ enabled?: boolean;
28
+ dir: string;
29
+ database: {
30
+ driver: "mongodb";
31
+ /**
32
+ * Connection URI string to connect
33
+ */
34
+ uri: string;
35
+ client?: InstanceType<typeof MongoClient>;
36
+ db?: Db;
37
+ isConnected?: boolean;
38
+ };
39
+ };
40
+ export type Actions =
41
+ | "findOneAndUpdate"
42
+ | "find"
43
+ | "findOne"
44
+ | "insertOne"
45
+ | "insertMany"
46
+ | "updateOne"
47
+ | "updateMany"
48
+ | "deleteOne"
49
+ | "deleteMany"
50
+ | "aggregate";
51
+
52
+ export type Field = {
53
+ name: string;
54
+ type:
55
+ | "boolean"
56
+ | "ipv4"
57
+ | "ipv6"
58
+ | "url"
59
+ | "date"
60
+ | "datetime-local"
61
+ | "email"
62
+ | "array"
63
+ | "file"
64
+ | "number"
65
+ | "integer"
66
+ | "password"
67
+ | "random"
68
+ | "relationship"
69
+ | "string"
70
+ | "enum"
71
+ | "textarea"
72
+ | "richText"
73
+ | "json"
74
+ | "geojson";
75
+ studio?: {
76
+ display?: string;
77
+ suffix?: string;
78
+ prefix?: string;
79
+ };
80
+
81
+ random?: {
82
+ length?: number;
83
+ useLetters?: boolean;
84
+ useNumbers?: boolean;
85
+ includeSymbols?: Array<any>;
86
+ excludeSymbols?: Array<any>;
87
+ startWith?: string;
88
+ endWith?: string;
89
+ toUpperCase?: boolean;
90
+ toLowerCase?: boolean;
91
+ toNumber?: boolean;
92
+ };
93
+ enum?: {
94
+ multiple?: boolean;
95
+ items: Array<any>;
96
+ };
97
+ nullable?: boolean;
98
+ defaultValue?: any;
99
+ unique?: boolean;
100
+ index?: boolean | "text" | "2dsphere";
101
+ description?: string;
102
+ sparse?: boolean;
103
+ relationType?: "ref-to-one" | "ref-to-many";
104
+ validate?: Array<any>;
105
+ relationTo?: string;
106
+ };
107
+
108
+ export type accessCtx = (ctx: {
109
+ token?: string;
110
+ action?: Actions;
111
+ c?: Context;
112
+ isAuth: boolean;
113
+ rest: InstanceType<typeof useRest>;
114
+ session: sessionCtx;
115
+ }) => boolean | undefined | void;
116
+
117
+ export type sessionCtx = {
118
+ set: (session: { state: object | any; token?: string; _v?: object }) => void;
119
+ get: () => { state: object; _v: object };
120
+ };
121
+
122
+ export type hooksCtx = (ctx: {
123
+ filter?: any;
124
+ result?: any;
125
+ driver?: "mongodb" | "postgres";
126
+ data?: object;
127
+ params?: findParam;
128
+ options?: {};
129
+ id?: string;
130
+ ids?: string[];
131
+ update?: updateParams;
132
+ pipeline?: Array<object>;
133
+ sharedData?: any;
134
+ store?: any;
135
+ action?: Actions;
136
+ c?: Context;
137
+ rest: InstanceType<typeof useRest>;
138
+ session?: sessionCtx;
139
+ io: Io;
140
+ }) => any;
141
+
142
+ export type ctxApi = {
143
+ rest: useRest;
144
+ data?: any;
145
+ session?: sessionCtx;
146
+ io?: Io;
147
+ params?: findParam;
148
+ id?: string;
149
+ ids?: string[];
150
+ update?: updateParams;
151
+ };
152
+
153
+ export type Collection = {
154
+ init?: (ctx: {
155
+ rest: InstanceType<typeof useRest>;
156
+ collection: string;
157
+ }) => void;
158
+ type?: "document" | "media";
159
+ media?: {
160
+ /**
161
+ * Folder name to store files in directory uploads
162
+ * It take slug name as default name
163
+ */
164
+ overwriteFolderName?: string;
165
+ /**
166
+ * Enable or disable theses : upload / serve file
167
+ */
168
+ enabled: boolean;
169
+ /**
170
+ * Visibility of the files
171
+ */
172
+ visibility?: "private" | "public";
173
+ /**
174
+ * Mimie type to accept
175
+ */
176
+ accept?: Array<string> | string;
177
+ };
178
+ customApi?: {
179
+ insertOne?: (ctx: ctxApi) => object | null | undefined;
180
+ insertMany?: (ctx: ctxApi) => Array<object> | null | undefined;
181
+ updateOne?: (ctx: ctxApi) => object | null | undefined;
182
+ updateMany?: (ctx: ctxApi) => object | null | undefined;
183
+ deleteOne?: (ctx: ctxApi) => object | null | undefined;
184
+ deleteMany?: (ctx: ctxApi) => object | null | undefined;
185
+ find?: (ctx: ctxApi) => Array<object> | null | undefined;
186
+ findOne?: (ctx: ctxApi) => object;
187
+ };
188
+ schema?: object;
189
+ auth?: {
190
+ enabled?: boolean;
191
+ handler?: (ctx: {
192
+ data: {
193
+ payload?: object | undefined;
194
+ };
195
+ error: typeof fn.error;
196
+ c: Context;
197
+ rest: InstanceType<typeof useRest>;
198
+ session: sessionCtx;
199
+ }) => boolean | any;
200
+ };
201
+ hooks?: {
202
+ beforeOperation?: hooksCtx;
203
+ beforeFind?: hooksCtx;
204
+ afterFind?: hooksCtx;
205
+ beforeUpdate?: hooksCtx;
206
+ afterUpdate?: hooksCtx;
207
+ beforeDelete?: hooksCtx;
208
+ afterDelete?: hooksCtx;
209
+ beforeInsert?: hooksCtx;
210
+ afterInsert?: hooksCtx;
211
+ beforeAggregate?: hooksCtx;
212
+ afterAggregate?: hooksCtx;
213
+ };
214
+ access?: {
215
+ beforeAction?: accessCtx;
216
+ allAction?: accessCtx;
217
+ find?: accessCtx;
218
+ findOne?: accessCtx;
219
+ findOneAndUpdate?: accessCtx;
220
+ insertOne?: accessCtx;
221
+ insertMany?: accessCtx;
222
+ updateMany?: accessCtx;
223
+ updateOne?: accessCtx;
224
+ deleteOne?: accessCtx;
225
+ deleteMany?: accessCtx;
226
+ aggregate?: accessCtx;
227
+ upload?: accessCtx;
228
+ };
229
+ tenant_id?: string;
230
+ slug: string;
231
+ timestamps?: boolean;
232
+ description?: string;
233
+ fields?: Field[];
234
+ privateFields?: string[];
235
+ allow?: ["dropCollection", "renameCollection"];
236
+ indexes?: Array<{
237
+ [key: string]: any;
238
+ unique?: Boolean;
239
+ expireAfterSeconds?: number;
240
+ }>;
241
+ };
242
+
243
+ export type Config = {
244
+ studio?: {
245
+ /**
246
+ * Enable or disable the studio
247
+ */
248
+ enabled?: boolean;
249
+ /**
250
+ * Allow access by IP
251
+ */
252
+ enableIps?: boolean;
253
+ /**
254
+ * WhiteLists IP adresses : ['192.168.1.1']
255
+ */
256
+ whiteListIps?: Array<string>;
257
+ /**
258
+ * Secret key for studio
259
+ */
260
+ secretKey: string;
261
+ };
262
+ debug?: boolean;
263
+ ai?: {
264
+ driver: "mistral" | "openai" | "gemini";
265
+ key: string;
266
+ };
267
+
268
+ server: {
269
+ cors?: {
270
+ origin: string[];
271
+ };
272
+ socket?: {
273
+ /**
274
+ * Socket port
275
+ * Default port is 9000
276
+ */
277
+ port?: Number;
278
+ };
279
+
280
+ /**
281
+ * Port to run the server
282
+ */
283
+ port: number;
284
+ };
285
+ /**
286
+ * Tenants database for API
287
+ */
288
+ tenants: Tenant[];
289
+
290
+ /**
291
+ * Dont touch it automatically generated
292
+ */
293
+ collections?: Array<Collection>;
294
+ /**
295
+ * Dont touch it automatically generated
296
+ */
297
+ endpoints?: Array<Endpoint>;
298
+ /**
299
+ * Dont touch it automatically generated
300
+ */
301
+ services?: Array<Service>;
302
+ /**
303
+ * Dont touch it automatically generated
304
+ */
305
+ cwd?: string;
306
+ io?: any;
307
+ };
308
+
309
+ export type Q = {
310
+ name: string;
311
+ useCache: boolean;
312
+ cleanDeep: boolean;
313
+ collection: string;
314
+ action:
315
+ | "findOneAndUpdate"
316
+ | "find"
317
+ | "findOne"
318
+ | "insertOne"
319
+ | "insertMany"
320
+ | "deleteMany"
321
+ | "deleteOne"
322
+ | "updateOne"
323
+ | "updateMany"
324
+ | "aggregate"
325
+ | "authCollection"
326
+ | "auth"
327
+ | "upload"
328
+ | "execService";
329
+ };
330
+
331
+ export type endpointCtx = {
332
+ enabled: boolean;
333
+ handler: (ctx: {
334
+ router: {
335
+ post: (path: string, clb: (c: Context) => void) => {};
336
+ get: (path: string, clb: (c: Context) => void) => {};
337
+ };
338
+ }) => any;
339
+ };
340
+
341
+ export type Endpoint = endpointCtx;
342
+
343
+ export type Service = {
344
+ /**
345
+ * Dont touch it automatically generated
346
+ */
347
+ tenant_id?: string;
348
+ /**
349
+ * Service's name
350
+ */
351
+ name: string;
352
+ /**
353
+ * Function to execute when service is calling
354
+ * @param ctx
355
+ * @returns
356
+ */
357
+ fx: (ctx: {
358
+ /**
359
+ * Incomming data [payload]
360
+ */
361
+ data: object;
362
+ /**
363
+ * Local rest linked to current tenant
364
+ */
365
+ rest: InstanceType<typeof useRest>;
366
+ error: typeof fn.error;
367
+ /**
368
+ * Session of request
369
+ */
370
+ session: sessionCtx;
371
+ /**
372
+ * authenticated user or no
373
+ */
374
+ isAuth: boolean;
375
+ io: Io;
376
+ }) => any;
377
+ };
package/utils/index.ts ADDED
@@ -0,0 +1,251 @@
1
+ import { ObjectId } from "mongodb";
2
+ import moment from "moment";
3
+ import { mapKeys } from "radash";
4
+ import { cleanDoubleSlashes } from "ufo";
5
+ import path from "path";
6
+ import { parse, format } from "@lukeed/ms";
7
+ import jwto from "jsonwebtoken";
8
+ import generateUniqueId from "generate-unique-id";
9
+
10
+ const JWT_SECRET = process?.env?.JWT_SECRET || "secret-libv";
11
+ import * as _ from "radash";
12
+
13
+ const jwt = {
14
+ verify: (token: string): { decode: any; valid: boolean; error: any } => {
15
+ let decode = null;
16
+ let valid = true;
17
+ let error = null;
18
+ try {
19
+ decode = jwto.verify(token, JWT_SECRET);
20
+ } catch (err: any) {
21
+ valid = false;
22
+ error = err?.message;
23
+ }
24
+
25
+ return { decode, valid, error };
26
+ },
27
+ getToken: (prefix = "Bearer", input: string): string | null => {
28
+ if (input?.startsWith(prefix)) {
29
+ let token = input?.slice(prefix?.length + 1);
30
+ if (!token || token?.replace(/\s/g, "") === "") return null;
31
+ } else {
32
+ return null;
33
+ }
34
+ },
35
+
36
+ sign: (
37
+ payload: any,
38
+ options: { expiresIn: string } = {
39
+ expiresIn: "7d",
40
+ }
41
+ ) => {
42
+ return jwto.sign(payload, JWT_SECRET, {
43
+ ...options,
44
+ });
45
+ },
46
+ auth: (
47
+ payload: any = {},
48
+ options: { expiresIn: string } = {
49
+ expiresIn: "7d",
50
+ }
51
+ ) => {
52
+ return jwto.sign(
53
+ {
54
+ ...payload,
55
+ _v: {
56
+ isAuth: true,
57
+ authAt: new Date(),
58
+ },
59
+ },
60
+ JWT_SECRET,
61
+ {
62
+ ...options,
63
+ }
64
+ );
65
+ },
66
+ };
67
+
68
+ function toJson(data: object) {
69
+ let obj = JSON.stringify(data);
70
+ return JSON.parse(obj);
71
+ }
72
+
73
+ function isDate(date: string): boolean {
74
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
75
+ let isDate_ = !isNaN(Date.parse(date)) && dateRegex.test(date);
76
+ return isDate_;
77
+ }
78
+
79
+ async function hashPassword(
80
+ password: string,
81
+ algorithm: "argon2d" | "bcrypt" = "argon2d"
82
+ ) {
83
+ return await Bun.password.hash(password, {
84
+ algorithm: algorithm || "argon2d",
85
+ });
86
+ }
87
+
88
+ async function verifyHashPassword(
89
+ password: string,
90
+ hash: string,
91
+ algorithm: "argon2d" | "bcrypt" = "argon2d"
92
+ ) {
93
+ try {
94
+ if (await Bun.password.verify(password, hash, algorithm)) {
95
+ return true;
96
+ } else {
97
+ return false;
98
+ }
99
+ } catch (err) {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ function toDate(data: object | string): object | string {
105
+ if (data) {
106
+ if (typeof data == "string" && isDate(data)) {
107
+ data = new Date(data);
108
+ }
109
+ // for object
110
+ if (typeof data == "object") {
111
+ mapKeys(data, (key: string, value: any) => {
112
+ data[key] = toDate(value);
113
+ });
114
+ }
115
+ }
116
+ return data;
117
+ }
118
+
119
+ function deepMerge(target: object, source: object) {
120
+ // Itérer à travers toutes les propriétés de la source
121
+ for (const key in source) {
122
+ const sourceValue = source[key];
123
+ const targetValue = target[key];
124
+
125
+ // Si les deux valeurs sont des objets, faire un appel récursif
126
+ if (
127
+ typeof sourceValue === "object" &&
128
+ sourceValue &&
129
+ !Array.isArray(sourceValue) &&
130
+ typeof targetValue === "object" &&
131
+ targetValue &&
132
+ !Array.isArray(targetValue)
133
+ ) {
134
+ target[key] = deepMerge({ ...targetValue }, sourceValue);
135
+ } else {
136
+ // Pour tout autre type ou lorsque les types ne correspondent pas, écraser avec la valeur source
137
+ target[key] = sourceValue;
138
+ }
139
+ }
140
+ return target;
141
+ }
142
+
143
+ function cleanPath(path: string) {
144
+ return cleanDoubleSlashes(path).toString();
145
+ }
146
+
147
+ function resolvePath(filepath: string) {
148
+ return path.resolve(cleanDoubleSlashes(filepath).toString());
149
+ }
150
+
151
+ function freeze(data: object, keys: string[]): object {
152
+ keys.forEach((cle) => {
153
+ const clePath = cle.split("."); // Divise la clé en parties selon les points
154
+ let currentObjet = data;
155
+
156
+ // Parcourir jusqu'à l'avant-dernier élément pour atteindre l'objet contenant la propriété
157
+ for (let i = 0; i < clePath.length - 1; i++) {
158
+ if (currentObjet.hasOwnProperty(clePath[i])) {
159
+ currentObjet = currentObjet[clePath[i]];
160
+ } else {
161
+ return; // Si le chemin n'existe pas, sortir de la fonction
162
+ }
163
+ }
164
+
165
+ // La dernière partie de clePath est la clé à rendre en lecture seule
166
+ const lastKey = clePath[clePath.length - 1];
167
+ if (currentObjet.hasOwnProperty(lastKey)) {
168
+ Object.defineProperty(currentObjet, lastKey, {
169
+ writable: false,
170
+ configurable: false,
171
+ });
172
+ }
173
+ });
174
+
175
+ return data;
176
+ }
177
+
178
+ /**
179
+ *
180
+ * @param {object|array} data - data
181
+ * @param {string[]} keysToRemove - keys to remove
182
+ * @returns {Array|Object} - data without the keys
183
+ */
184
+ function omit(data: object[] | object, keysToRemove: string[]) {
185
+ var json = data;
186
+ function removeKey(obj: any, keys: any): any {
187
+ if (!obj || keys.length === 0) {
188
+ return obj;
189
+ }
190
+
191
+ const [currentKey, ...remainingKeys] = keys;
192
+
193
+ if (Array.isArray(obj)) {
194
+ return obj.map((item) => removeKey(item, keys));
195
+ } else if (typeof obj === "object" && obj !== null) {
196
+ if (remainingKeys.length === 0) {
197
+ const { [currentKey]: _, ...rest } = obj;
198
+ return rest;
199
+ } else {
200
+ if (obj.hasOwnProperty(currentKey)) {
201
+ return {
202
+ ...obj,
203
+ [currentKey]: removeKey(obj[currentKey], remainingKeys),
204
+ };
205
+ }
206
+ }
207
+ }
208
+ return obj;
209
+ }
210
+
211
+ keysToRemove.forEach((keyPath) => {
212
+ const keys = keyPath.split(".");
213
+ json = removeKey(json, keys);
214
+ });
215
+ return json;
216
+ }
217
+
218
+ class contextError extends Error {
219
+ constructor(message: string, code: number) {
220
+ super(message);
221
+ this.code = code;
222
+ }
223
+ }
224
+
225
+ const fn = {
226
+ error: (message: string, code: number) => {
227
+ throw new contextError(message || "unknow", code);
228
+ },
229
+ };
230
+
231
+ const $ = Bun.$;
232
+
233
+ export {
234
+ moment,
235
+ $,
236
+ fn,
237
+ deepMerge,
238
+ toDate,
239
+ toJson,
240
+ hashPassword,
241
+ verifyHashPassword,
242
+ cleanPath,
243
+ freeze,
244
+ resolvePath,
245
+ isDate,
246
+ contextError,
247
+ jwt,
248
+ _,
249
+ generateUniqueId,
250
+ omit,
251
+ };