@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,14 @@
1
+ import type { RestOptions } from "../types/rest";
2
+ import { MongoRest } from "./mongodbadapter";
3
+
4
+ class Rest extends MongoRest {
5
+ constructor(options: RestOptions) {
6
+ super(options);
7
+ }
8
+ }
9
+
10
+ const useRest = Rest
11
+
12
+ export {
13
+ useRest,
14
+ }
@@ -0,0 +1,160 @@
1
+
2
+ import type { Collection as CollectionType } from "../types/collection";
3
+ import Joi, { type AnySchema } from "joi";
4
+
5
+
6
+ let s = Joi.object({
7
+ name: Joi.string().required(),
8
+ })
9
+
10
+
11
+
12
+
13
+ function buildSchema(col: CollectionType, opts = {
14
+ partial: false
15
+ }) {
16
+
17
+ let propertiesSchema = {
18
+ createdAt: Joi.date(),
19
+ updatedAt: Joi.date(),
20
+ } as {
21
+ [key: string]: AnySchema
22
+ }
23
+ for (const f of col.fields) {
24
+
25
+ if (f.type && f.name) {
26
+
27
+ let fieldName = f.name
28
+
29
+ if (f.type == 'string') {
30
+ propertiesSchema[fieldName] = Joi.string()
31
+ }
32
+
33
+ if (f.type == 'password') {
34
+ propertiesSchema[fieldName] = Joi.string()
35
+ }
36
+
37
+ if (f.type == 'number') {
38
+ propertiesSchema[fieldName] = Joi.number()
39
+ }
40
+
41
+ if (f.type == 'integer') {
42
+ propertiesSchema[fieldName] = Joi.number().integer()
43
+ }
44
+
45
+ if (f.type == 'boolean') {
46
+ propertiesSchema[fieldName] = Joi.boolean()
47
+ }
48
+
49
+ if (f.type.match(/(date|datetime-local)/)) {
50
+ propertiesSchema[fieldName] = Joi.date()
51
+ }
52
+
53
+ if (f.type == 'array') {
54
+ propertiesSchema[fieldName] = Joi.array()
55
+ }
56
+
57
+ if (f.type == 'json') {
58
+ propertiesSchema[fieldName] = Joi.object()
59
+ }
60
+
61
+ if (f.type == 'uuid') {
62
+ propertiesSchema[fieldName] = Joi.string().uuid()
63
+ }
64
+ if (f.type == 'email') {
65
+ propertiesSchema[fieldName] = Joi.string().email()
66
+ }
67
+
68
+ if (f.type == 'url') {
69
+ propertiesSchema[fieldName] = Joi.string().uri()
70
+ }
71
+
72
+ if (f.type == 'ipv4') {
73
+ propertiesSchema[fieldName] = Joi.string().ip({ version: 'ipv4' })
74
+ }
75
+
76
+ if (f.type == 'ipv6') {
77
+ propertiesSchema[fieldName] = Joi.string().ip({ version: 'ipv6' })
78
+ }
79
+
80
+ if (f.type == 'enum' && !f.enumOptions?.multiple) {
81
+ propertiesSchema[fieldName] = Joi.string().valid(...f.enumOptions?.items || [])
82
+ }
83
+
84
+ if (f.type == 'enum' && f.enumOptions?.multiple) {
85
+ propertiesSchema[fieldName] = Joi.array().items(Joi.string().valid(...f.enumOptions?.items || []))
86
+ }
87
+
88
+ if (f.type == 'random') {
89
+ propertiesSchema[fieldName] = Joi.string()
90
+ }
91
+
92
+ if (f.type == 'random' && f?.randomOptions?.toNumber) {
93
+ propertiesSchema[fieldName] = Joi.number()
94
+ }
95
+
96
+ if (f?.type.match(/(geojson\.Point|geojson\.LineString|geojson\.Polygon)/)) {
97
+ propertiesSchema[fieldName] = Joi.object({
98
+ type: Joi.string().valid("Point", "LineString", "Polygon", "MultiPoint").required(),
99
+ coordinates: Joi.alternatives().conditional("type", [
100
+ {
101
+ is: "Point",
102
+ then: Joi.array().items(Joi.number()).length(2).required(), // [lng, lat]
103
+ },
104
+ {
105
+ is: Joi.string().valid("LineString", "MultiPoint"),
106
+ then: Joi.array().items(Joi.array().items(Joi.number()).length(2)).required(), // [[lng, lat], ...]
107
+ },
108
+ {
109
+ is: "Polygon",
110
+ then: Joi.array().items(Joi.array().items(Joi.array().items(Joi.number()).length(2))).required(), // [[[lng, lat], ...]]
111
+ },
112
+ ]),
113
+ });
114
+ }
115
+
116
+ if (f?.type == 'relationship') {
117
+ propertiesSchema[fieldName] = Joi.string().optional().messages({
118
+ 'string.base': `${f.name} must be a string (ObjectId)`,
119
+ })
120
+ }
121
+
122
+ if (f?.type == 'relationship' && f?.relation?.hasMany) {
123
+ propertiesSchema[fieldName] = Joi.array().items(Joi.string().optional()).messages({
124
+ 'string.base': `${f.name} must be a string (ObjectId)`,
125
+ })
126
+ }
127
+
128
+ if (f.validate?.schema) {
129
+ propertiesSchema[fieldName] = f.validate.schema
130
+ }
131
+
132
+ if (f?.required) {
133
+ propertiesSchema[fieldName] = propertiesSchema[fieldName]?.required()!
134
+ } else {
135
+ propertiesSchema[fieldName] = propertiesSchema[fieldName]?.optional()!
136
+ }
137
+
138
+ if (f?.nullable) {
139
+ propertiesSchema[fieldName] = propertiesSchema[fieldName]?.allow(null)
140
+ }
141
+
142
+ if (f?.empty) {
143
+ propertiesSchema[fieldName] = propertiesSchema[fieldName]?.allow('')
144
+ }
145
+
146
+ }
147
+ }
148
+
149
+ let schema = Joi.object(propertiesSchema).min(1)
150
+ return opts.partial ? buildSchemaForkOptional(schema) : schema
151
+ }
152
+
153
+ function buildSchemaForkOptional(schemaPassed: AnySchema): AnySchema {
154
+ return schemaPassed.fork(
155
+ Object.keys(schemaPassed.describe().keys),
156
+ (field) => field.optional()
157
+ )
158
+ }
159
+
160
+ export { buildSchema, buildSchemaForkOptional }
@@ -0,0 +1,37 @@
1
+ import type { Tenant } from "../types/tenant";
2
+ import { cfg } from "../server/config";
3
+ import { useRest } from "./rest";
4
+ async function syncTenants() {
5
+ try {
6
+ for await (let tenant of cfg.tenants ?? []) {
7
+ let rest = new useRest({
8
+ database: {
9
+ uri: tenant.database.uri,
10
+ options: {
11
+ timeoutMS: 2000,
12
+ ...tenant.database.options,
13
+ },
14
+ }
15
+ })
16
+ const { client, db } = await rest.connect()
17
+ tenant.database.client = client
18
+ tenant.database.db = db
19
+ }
20
+ } catch (err: any) {
21
+ console.error('Error bootstrapping tenants', err?.message)
22
+ process.exit(1)
23
+ }
24
+ }
25
+
26
+ function getTenant(tenantId: string): Tenant | null {
27
+ let findTenant = cfg.tenants?.find(tenant => tenant.id == tenantId)
28
+ if (findTenant) {
29
+ return findTenant
30
+ }
31
+ return null
32
+ }
33
+
34
+ export {
35
+ syncTenants,
36
+ getTenant
37
+ }
@@ -0,0 +1,384 @@
1
+ import { cfg } from "../server/config";
2
+ import { getTenant } from "./tenant";
3
+ import { AppError, fn } from "../lib/error";
4
+ import * as func from "../utils/func";
5
+ import { useRest } from "./rest";
6
+ import { getWorkflow } from "../lib/workflow";
7
+ import crypto from "crypto";
8
+ import type { WorkflowDefinition, WorkflowRun, WorkflowRunStep, WorkflowRunStatus } from "../types/workflow";
9
+
10
+ class Workflow {
11
+ #tenant_id: string;
12
+
13
+ constructor(tenant_id: string) {
14
+ this.#tenant_id = tenant_id;
15
+ }
16
+
17
+ private async getCollection() {
18
+ const tenant = getTenant(this.#tenant_id);
19
+ const db = tenant?.database?.db;
20
+ if (!db) throw new AppError('Database not found', { code: 'DB_NOT_FOUND', status: 500 });
21
+ return db.collection('_workflows_');
22
+ }
23
+
24
+ /**
25
+ * Run a workflow by its ID with the given data.
26
+ * Executes each step sequentially and tracks progress.
27
+ */
28
+ async run(workflowId: string, data: any, context?: any): Promise<WorkflowRun> {
29
+ const wf = getWorkflow(workflowId, this.#tenant_id);
30
+ if (!wf) throw new AppError(`Workflow '${workflowId}' not found`, { code: 'WORKFLOW_NOT_FOUND', status: 404 });
31
+
32
+ const runId = crypto.randomUUID();
33
+ const now = new Date();
34
+
35
+ const run: WorkflowRun = {
36
+ _id: runId,
37
+ workflowId,
38
+ workflowVersion: wf.version,
39
+ tenant_id: this.#tenant_id,
40
+ status: 'running',
41
+ progress: 0,
42
+ data,
43
+ context,
44
+ currentStep: 0,
45
+ totalExecuted: 0,
46
+ totalSkipped: 0,
47
+ steps: wf.steps.map(s => ({
48
+ stepId: s.id,
49
+ name: s.name,
50
+ status: 'pending' as WorkflowRunStatus,
51
+ startedAt: now,
52
+ })),
53
+ compensations: [],
54
+ createdAt: now,
55
+ updatedAt: now,
56
+ };
57
+
58
+ await this.saveRun(run);
59
+
60
+ // Execute global exec if present
61
+ try {
62
+ if (wf.exec) {
63
+ await wf.exec({ data, prevOutput: null, rest: this, error: fn.error, jwt: func.jwt, input: undefined });
64
+ }
65
+ } catch (err: any) {
66
+ run.status = 'failed';
67
+ run.error = { message: err.message || 'Workflow execution failed', stepId: '__global__' };
68
+ run.updatedAt = new Date();
69
+ await this.saveRun(run);
70
+ throw err;
71
+ }
72
+
73
+ // Execute steps sequentially
74
+ for (let i = 0; i < wf.steps.length; i++) {
75
+ const stepDef = wf.steps[i]!;
76
+ run.currentStep = i;
77
+ const stepRun: WorkflowRunStep = {
78
+ stepId: stepDef.id,
79
+ name: stepDef.name,
80
+ status: 'running',
81
+ input: data,
82
+ startedAt: new Date(),
83
+ };
84
+
85
+ run.steps[i] = stepRun;
86
+ run.updatedAt = new Date();
87
+ await this.saveRun(run);
88
+
89
+ try {
90
+ const prevOutput = i > 0 ? run.steps[i - 1]?.output ?? null : null;
91
+
92
+ // Vérifier la condition du step
93
+ if (stepDef.condition) {
94
+ const shouldRun = await stepDef.condition({ data, prevOutput });
95
+ if (!shouldRun) {
96
+ stepRun.status = 'skipped';
97
+ stepRun.completedAt = new Date();
98
+ run.steps[i] = stepRun;
99
+ run.totalSkipped = run.steps.filter(s => s.status === 'skipped').length;
100
+ run.progress = Math.round(((i + 1) / wf.steps.length) * 100);
101
+ run.updatedAt = new Date();
102
+ await this.saveRun(run);
103
+ continue; // saute ce step
104
+ }
105
+ }
106
+
107
+ const result = await stepDef.exec({ data, prevOutput, input: stepDef.input, rest: this, error: fn.error, jwt: func.jwt });
108
+ stepRun.status = 'completed';
109
+ stepRun.output = result;
110
+ stepRun.completedAt = new Date();
111
+ run.steps[i] = stepRun;
112
+ run.totalExecuted = run.steps.filter(s => s.status === 'completed').length;
113
+ run.progress = Math.round(((i + 1) / wf.steps.length) * 100);
114
+ } catch (err: any) {
115
+ stepRun.status = 'failed';
116
+ stepRun.error = { message: err.message || 'Step failed', code: err.code };
117
+ stepRun.completedAt = new Date();
118
+ run.steps[i] = stepRun;
119
+ run.totalExecuted = run.steps.filter(s => s.status === 'completed').length;
120
+ run.status = 'failed';
121
+ run.error = { message: err.message || 'Workflow failed', code: err.code, stepId: stepDef.id };
122
+ run.updatedAt = new Date();
123
+ await this.saveRun(run);
124
+
125
+ // Exécuter les compensations (steps réussis en ordre inverse)
126
+ if (wf.compensations?.length) {
127
+ const failedIndex = run.steps.findIndex(s => s.status === 'failed');
128
+ const stepsToCompensate = run.steps.slice(0, failedIndex).filter(s => s.status === 'completed').reverse();
129
+ run.compensations = [];
130
+ for (const completedStep of stepsToCompensate) {
131
+ const compDef = wf.compensations?.find(c => c.depend?.includes(completedStep.stepId) || c.id === completedStep.stepId);
132
+ if (!compDef?.exec) continue;
133
+ try {
134
+ await compDef.exec({
135
+ data: run.data,
136
+ prevOutput: completedStep.output,
137
+ input: undefined,
138
+ rest: this,
139
+ error: fn.error,
140
+ jwt: func.jwt,
141
+ });
142
+ run.compensations.push({
143
+ stepId: compDef.id,
144
+ name: compDef.name,
145
+ status: 'completed',
146
+ output: null,
147
+ startedAt: new Date(),
148
+ completedAt: new Date(),
149
+ });
150
+ } catch (compErr: any) {
151
+ run.compensations.push({
152
+ stepId: compDef.id,
153
+ name: compDef.name,
154
+ status: 'failed',
155
+ error: { message: compErr.message || 'Compensation failed' },
156
+ startedAt: new Date(),
157
+ completedAt: new Date(),
158
+ });
159
+ }
160
+ }
161
+ run.updatedAt = new Date();
162
+ await this.saveRun(run);
163
+ }
164
+
165
+ throw err;
166
+ }
167
+ }
168
+
169
+ run.status = 'completed';
170
+ run.progress = 100;
171
+ run.completedAt = new Date();
172
+ run.updatedAt = new Date();
173
+ await this.saveRun(run);
174
+ return run;
175
+ }
176
+
177
+ /**
178
+ * Resume a paused or failed workflow run from the last failed/pending step.
179
+ */
180
+ async resume(runId: string, data?: any): Promise<WorkflowRun> {
181
+ const run = await this.getRun(runId);
182
+ if (!run) throw new AppError(`Workflow run '${runId}' not found`, { code: 'RUN_NOT_FOUND', status: 404 });
183
+ if (run.status === 'completed') throw new AppError('Workflow already completed', { code: 'RUN_COMPLETED', status: 400 });
184
+
185
+ const wf = getWorkflow(run.workflowId, this.#tenant_id);
186
+ if (!wf) throw new AppError(`Workflow '${run.workflowId}' not found`, { code: 'WORKFLOW_NOT_FOUND', status: 404 });
187
+
188
+ run.status = 'running';
189
+ run.data = data || run.data;
190
+ run.updatedAt = new Date();
191
+
192
+ // Find the first non-completed step
193
+ const startIndex = run.steps.findIndex(s => s.status !== 'completed');
194
+ if (startIndex === -1) {
195
+ run.status = 'completed';
196
+ run.progress = 100;
197
+ run.completedAt = new Date();
198
+ await this.saveRun(run);
199
+ return run;
200
+ }
201
+
202
+ for (let i = startIndex; i < wf.steps.length; i++) {
203
+ const stepDef = wf.steps[i]!;
204
+ run.currentStep = i;
205
+ const stepRun: WorkflowRunStep = {
206
+ stepId: stepDef.id,
207
+ name: stepDef.name,
208
+ status: 'running',
209
+ input: run.data,
210
+ startedAt: new Date(),
211
+ };
212
+ run.steps[i] = stepRun;
213
+ run.updatedAt = new Date();
214
+ await this.saveRun(run);
215
+
216
+ try {
217
+ const prevOutput = i > 0 ? run.steps[i - 1]?.output ?? null : null;
218
+
219
+ // Vérifier la condition du step
220
+ if (stepDef.condition) {
221
+ const shouldRun = await stepDef.condition({ data: run.data, prevOutput });
222
+ if (!shouldRun) {
223
+ stepRun.status = 'skipped';
224
+ stepRun.completedAt = new Date();
225
+ run.steps[i] = stepRun;
226
+ run.totalSkipped = run.steps.filter(s => s.status === 'skipped').length;
227
+ run.progress = Math.round(((i + 1) / wf.steps.length) * 100);
228
+ run.updatedAt = new Date();
229
+ await this.saveRun(run);
230
+ continue; // saute ce step
231
+ }
232
+ }
233
+
234
+ const result = await stepDef.exec({ data: run.data, prevOutput, input: stepDef.input, rest: this, error: fn.error, jwt: func.jwt });
235
+ stepRun.status = 'completed';
236
+ stepRun.output = result;
237
+ stepRun.completedAt = new Date();
238
+ run.steps[i] = stepRun;
239
+ run.totalExecuted = run.steps.filter(s => s.status === 'completed').length;
240
+ run.progress = Math.round(((i + 1) / wf.steps.length) * 100);
241
+ } catch (err: any) {
242
+ stepRun.status = 'failed';
243
+ stepRun.error = { message: err.message || 'Step failed', code: err.code };
244
+ stepRun.completedAt = new Date();
245
+ run.steps[i] = stepRun;
246
+ run.totalExecuted = run.steps.filter(s => s.status === 'completed').length;
247
+ run.status = 'failed';
248
+ run.error = { message: err.message || 'Workflow failed', code: err.code, stepId: stepDef.id };
249
+ run.updatedAt = new Date();
250
+ await this.saveRun(run);
251
+
252
+ // Exécuter les compensations (steps réussis en ordre inverse)
253
+ if (wf.compensations?.length) {
254
+ const failedIndex = run.steps.findIndex(s => s.status === 'failed');
255
+ const stepsToCompensate = run.steps.slice(0, failedIndex).filter(s => s.status === 'completed').reverse();
256
+ run.compensations = [];
257
+ for (const completedStep of stepsToCompensate) {
258
+ const compDef = wf.compensations?.find(c => c.depend?.includes(completedStep.stepId) || c.id === completedStep.stepId);
259
+ if (!compDef?.exec) continue;
260
+ try {
261
+ await compDef.exec({
262
+ data: run.data,
263
+ prevOutput: completedStep.output,
264
+ input: undefined,
265
+ rest: this,
266
+ error: fn.error,
267
+ jwt: func.jwt,
268
+ });
269
+ run.compensations.push({
270
+ stepId: compDef.id,
271
+ name: compDef.name,
272
+ status: 'completed',
273
+ output: null,
274
+ startedAt: new Date(),
275
+ completedAt: new Date(),
276
+ });
277
+ } catch (compErr: any) {
278
+ run.compensations.push({
279
+ stepId: compDef.id,
280
+ name: compDef.name,
281
+ status: 'failed',
282
+ error: { message: compErr.message || 'Compensation failed' },
283
+ startedAt: new Date(),
284
+ completedAt: new Date(),
285
+ });
286
+ }
287
+ }
288
+ run.updatedAt = new Date();
289
+ await this.saveRun(run);
290
+ }
291
+
292
+ throw err;
293
+ }
294
+ }
295
+
296
+ run.status = 'completed';
297
+ run.progress = 100;
298
+ run.completedAt = new Date();
299
+ run.updatedAt = new Date();
300
+ await this.saveRun(run);
301
+ return run;
302
+ }
303
+
304
+ /** Get a workflow run by ID */
305
+ async getRun(runId: string): Promise<WorkflowRun | null> {
306
+ const col = await this.getCollection();
307
+ const doc = await col.findOne({ _id: runId as any });
308
+ return doc as unknown as WorkflowRun | null;
309
+ }
310
+
311
+ /** List all runs for a workflow */
312
+ async listRuns(workflowId: string, limit = 20): Promise<WorkflowRun[]> {
313
+ const col = await this.getCollection();
314
+ const docs = await col.find({ workflowId }).sort({ createdAt: -1 }).limit(limit).toArray();
315
+ return docs as unknown as WorkflowRun[];
316
+ }
317
+
318
+ /** Get progress of a run (0-100) */
319
+ async getProgress(runId: string): Promise<{ progress: number; status: WorkflowRunStatus; currentStep: number; totalSteps: number; totalExecuted: number; totalSkipped: number } | null> {
320
+ const run = await this.getRun(runId);
321
+ if (!run) return null;
322
+ return {
323
+ progress: run.progress,
324
+ status: run.status,
325
+ currentStep: run.currentStep,
326
+ totalSteps: run.steps.length,
327
+ totalExecuted: run.totalExecuted,
328
+ totalSkipped: run.totalSkipped,
329
+ };
330
+ }
331
+
332
+ /** Pause a running workflow */
333
+ async pause(runId: string): Promise<void> {
334
+ const col = await this.getCollection();
335
+ await col.updateOne(
336
+ { _id: runId as any, status: 'running' },
337
+ { $set: { status: 'paused', updatedAt: new Date() } }
338
+ );
339
+ }
340
+
341
+ /** Cancel a workflow run permanently */
342
+ async cancel(runId: string): Promise<void> {
343
+ const col = await this.getCollection();
344
+ await col.updateOne(
345
+ { _id: runId as any },
346
+ { $set: { status: 'cancelled', updatedAt: new Date() } }
347
+ );
348
+ }
349
+
350
+ /** Resume all paused or failed workflows */
351
+ async resumeAll(data?: any): Promise<{ resumed: number; failed: number }> {
352
+ const col = await this.getCollection();
353
+ const runs = await col.find({
354
+ status: { $in: ['paused', 'failed'] }
355
+ }).toArray() as unknown as WorkflowRun[];
356
+
357
+ let resumed = 0;
358
+ let failed = 0;
359
+
360
+ for (const run of runs) {
361
+ try {
362
+ await this.resume(run._id, data);
363
+ resumed++;
364
+ } catch {
365
+ failed++;
366
+ }
367
+ }
368
+
369
+ return { resumed, failed };
370
+ }
371
+
372
+ private async saveRun(run: WorkflowRun): Promise<void> {
373
+ const col = await this.getCollection();
374
+ await col.replaceOne(
375
+ { _id: run._id as any },
376
+ run as any,
377
+ { upsert: true }
378
+ );
379
+ }
380
+ }
381
+
382
+ export function createWorkflow(tenant_id: string): Workflow {
383
+ return new Workflow(tenant_id);
384
+ }
package/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { define } from "./lib/define";
2
+ import { bootApp } from "./server/boot";
3
+ import { useRest } from "./database/rest";
4
+ import * as v from "joi";
5
+ import utils from "./utils";
6
+ import * as crypto from "./utils/crypto";
7
+
8
+
9
+ // Imort bentocache use as cache
10
+ import { useMemoryCache, useFilesystemCache, useRedisCache } from "./utils/cache";
11
+ const cache = {
12
+ useMemoryCache,
13
+ useFilesystemCache,
14
+ useRedisCache
15
+ }
16
+ const app = {
17
+ boot: bootApp
18
+ }
19
+
20
+ export {
21
+ define,
22
+ app,
23
+ useRest,
24
+ v,
25
+ utils,
26
+ cache,
27
+ crypto,
28
+ }
@@ -0,0 +1,68 @@
1
+ import { AsyncLocalStorage } from "async_hooks";
2
+
3
+ const asyncContextStorage = new AsyncLocalStorage<Map<string, unknown>>();
4
+
5
+ const REQUEST_STORAGE_PREFIX = ":::requestStorage:::";
6
+ const SESSION_STORAGE_PREFIX = "::sessionStorage::";
7
+
8
+ const requestCtxStorage = {
9
+ set(key: string, value: unknown): void {
10
+ asyncContextStorage.getStore()?.set(`${REQUEST_STORAGE_PREFIX}${key}`, value);
11
+ },
12
+ get<T = unknown>(key: string): T | undefined {
13
+ return asyncContextStorage.getStore()?.get(`${REQUEST_STORAGE_PREFIX}${key}`) as T | undefined;
14
+ },
15
+ delete(key: string): void {
16
+ asyncContextStorage.getStore()?.delete(`${REQUEST_STORAGE_PREFIX}${key}`);
17
+ },
18
+ clear(): void {
19
+ asyncContextStorage.getStore()?.clear();
20
+ }
21
+ };
22
+
23
+ interface SessionSetParams {
24
+ state: {
25
+ user: Record<string, unknown>;
26
+ [key: string]: unknown;
27
+ };
28
+ role: string;
29
+ token?: string;
30
+ expireIn?: number;
31
+ }
32
+
33
+ interface SessionData {
34
+ state: SessionSetParams["state"];
35
+ uuid: string;
36
+ role: string;
37
+ token?: string;
38
+ expireIn?: number;
39
+ isAuth: boolean;
40
+ _v: unknown;
41
+ }
42
+
43
+ const sessionCtxStorage = {
44
+ set(params: SessionSetParams): void {
45
+ const store = asyncContextStorage.getStore();
46
+ if (!store) return;
47
+ const data: SessionData = {
48
+ state: params.state,
49
+ uuid: crypto.randomUUID(),
50
+ role: params.role,
51
+ token: params.token,
52
+ expireIn: params.expireIn,
53
+ isAuth: !!params.state?.user && !!params.token,
54
+ _v: undefined,
55
+ };
56
+ store.set(SESSION_STORAGE_PREFIX, data);
57
+ },
58
+ get(): SessionData | undefined {
59
+ return asyncContextStorage.getStore()?.get(SESSION_STORAGE_PREFIX) as SessionData | undefined;
60
+ },
61
+ clear(): void {
62
+ asyncContextStorage.getStore()?.delete(SESSION_STORAGE_PREFIX);
63
+ }
64
+ };
65
+
66
+
67
+ export { asyncContextStorage, requestCtxStorage, sessionCtxStorage };
68
+