@bejibun/core 0.1.73 → 0.2.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/CHANGELOG.md +64 -1
- package/README.md +21 -10
- package/ace.js +2 -0
- package/bases/BaseJob.d.ts +7 -0
- package/bases/BaseJob.js +17 -0
- package/bases/BaseModel.d.ts +8 -11
- package/bases/BaseModel.js +22 -17
- package/bases/index.d.ts +1 -0
- package/bases/index.js +1 -0
- package/bootstrap.js +4 -0
- package/builders/JobBuilder.d.ts +11 -0
- package/builders/JobBuilder.js +33 -0
- package/builders/NamespaceBuilder.d.ts +5 -0
- package/builders/NamespaceBuilder.js +37 -0
- package/builders/RouterBuilder.d.ts +3 -3
- package/builders/RouterBuilder.js +1 -2
- package/commands/Kernel.js +7 -1
- package/commands/make/MakeJobCommand.d.ts +27 -0
- package/commands/make/MakeJobCommand.js +51 -0
- package/commands/queue/QueueFlushCommand.d.ts +27 -0
- package/commands/queue/QueueFlushCommand.js +32 -0
- package/commands/queue/QueueRetryCommand.d.ts +27 -0
- package/commands/queue/QueueRetryCommand.js +74 -0
- package/commands/queue/QueueWorkCommand.d.ts +27 -0
- package/commands/queue/QueueWorkCommand.js +70 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/loader/NamespaceLoader.d.ts +3 -0
- package/loader/NamespaceLoader.js +6 -0
- package/models/EpochTimestamps.d.ts +2 -0
- package/models/EpochTimestamps.js +20 -0
- package/models/JobModel.d.ts +18 -0
- package/models/JobModel.js +13 -0
- package/models/index.d.ts +1 -0
- package/models/index.js +1 -0
- package/package.json +2 -2
- package/server.js +1 -1
- package/stubs/jobs/TemplateJob.ts +12 -0
- package/stubs/models/TemplateModel.ts +6 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,69 @@ All notable changes to this project will be documented in this file.
|
|
|
3
3
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
## [v0.2.1](https://github.com/Bejibun-Framework/bejibun-core/compare/v0.2.0...v0.2.1) - 2026-02-13
|
|
7
|
+
|
|
8
|
+
### 🩹 Fixes
|
|
9
|
+
|
|
10
|
+
### 📖 Changes
|
|
11
|
+
- Added `queue:flush` to flush all failed queue jobs.
|
|
12
|
+
- Added `queue:retry` to retry a failed queue job.
|
|
13
|
+
- Close database connection after using command.
|
|
14
|
+
|
|
15
|
+
### ❤️Contributors
|
|
16
|
+
- Havea Crenata ([@crenata](https://github.com/crenata))
|
|
17
|
+
|
|
18
|
+
**Full Changelog**: https://github.com/Bejibun-Framework/bejibun-core/blob/master/CHANGELOG.md
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## [v0.2.0](https://github.com/Bejibun-Framework/bejibun-core/compare/v0.1.73...v0.2.0) - 2026-02-12
|
|
23
|
+
|
|
24
|
+
### 🩹 Fixes
|
|
25
|
+
- Invalid namespace for model - [#15](https://github.com/Bejibun-Framework/bejibun-core/issues/15)
|
|
26
|
+
|
|
27
|
+
### 📖 Changes
|
|
28
|
+
- Added dynamic model timestamps for `created_at`, `updated_at`, and `deleted_at`
|
|
29
|
+
|
|
30
|
+
#### Breaking Changes :
|
|
31
|
+
- No longer `BaseColumns` on `Model`
|
|
32
|
+
|
|
33
|
+
#### What's New :
|
|
34
|
+
- Epoch Timestamp Trait
|
|
35
|
+
- Job Dispatch
|
|
36
|
+
- Queue Worker
|
|
37
|
+
|
|
38
|
+
#### What is Epoch Timestamp Trait?
|
|
39
|
+
Override default model timestamps for `created_at`, `updated_at`, and `deleted_at` to unix timestamp.
|
|
40
|
+
|
|
41
|
+
Example :
|
|
42
|
+
```text
|
|
43
|
+
2026-01-01 12:00:00.000 +0000 -> 1767948151
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### How does Queue work?
|
|
47
|
+
When you dispatch job, system will add the job to database and save its params.
|
|
48
|
+
|
|
49
|
+
Then worker will run jobs on the database and called `handle` function with params from the database which saved before.
|
|
50
|
+
|
|
51
|
+
Worker only run jobs when the job is available by using `available_at`. You can also delay the job by passing `.delay(seconds)`
|
|
52
|
+
|
|
53
|
+
Example :
|
|
54
|
+
```ts
|
|
55
|
+
// Immediately
|
|
56
|
+
await TestJob.dispatch(/*any params here*/).send();
|
|
57
|
+
|
|
58
|
+
// With delay
|
|
59
|
+
await TestJob.dispatch(/*any params here*/).delay(60 * 10 /*10 minutes*/).send();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### ❤️Contributors
|
|
63
|
+
- Havea Crenata ([@crenata](https://github.com/crenata))
|
|
64
|
+
|
|
65
|
+
**Full Changelog**: https://github.com/Bejibun-Framework/bejibun-core/blob/master/CHANGELOG.md
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
6
69
|
## [v0.1.73](https://github.com/Bejibun-Framework/bejibun-core/compare/v0.1.72...v0.1.73) - 2026-02-02
|
|
7
70
|
|
|
8
71
|
### 🩹 Fixes
|
|
@@ -81,7 +144,7 @@ All notable changes to this project will be documented in this file.
|
|
|
81
144
|
### 📖 Changes
|
|
82
145
|
#### What's New :
|
|
83
146
|
- Added `Router.resource()`
|
|
84
|
-
|
|
147
|
+
|
|
85
148
|
Single line code that automatically generates a full set of CRUD.
|
|
86
149
|
|
|
87
150
|
#### How to use?
|
package/README.md
CHANGED
|
@@ -274,22 +274,18 @@ Database table model
|
|
|
274
274
|
Example :
|
|
275
275
|
|
|
276
276
|
```ts
|
|
277
|
-
import
|
|
278
|
-
import
|
|
277
|
+
import type {Timestamp, NullableTimestamp} from "@bejibun/core/bases/BaseModel";
|
|
278
|
+
import BaseModel from "@bejibun/core/bases/BaseModel";
|
|
279
279
|
|
|
280
|
-
export
|
|
281
|
-
name: string;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export default class TestModel extends BaseModel implements TestColumns {
|
|
280
|
+
export default class TestModel extends BaseModel {
|
|
285
281
|
public static tableName: string = "tests";
|
|
286
282
|
public static idColumn: string = "id";
|
|
287
283
|
|
|
288
284
|
declare id: bigint;
|
|
289
285
|
declare name: string;
|
|
290
|
-
declare created_at:
|
|
291
|
-
declare updated_at:
|
|
292
|
-
declare deleted_at:
|
|
286
|
+
declare created_at: Timestamp;
|
|
287
|
+
declare updated_at: Timestamp;
|
|
288
|
+
declare deleted_at: NullableTimestamp;
|
|
293
289
|
}
|
|
294
290
|
```
|
|
295
291
|
|
|
@@ -566,6 +562,17 @@ await Storage.build({
|
|
|
566
562
|
}).delete("path/to/your/file.ext");
|
|
567
563
|
```
|
|
568
564
|
|
|
565
|
+
### Queue
|
|
566
|
+
Run processes at background.
|
|
567
|
+
|
|
568
|
+
```ts
|
|
569
|
+
// Immediately
|
|
570
|
+
await TestJob.dispatch(/*any params here*/).send();
|
|
571
|
+
|
|
572
|
+
// With delay
|
|
573
|
+
await TestJob.dispatch(/*any params here*/).delay(60 * 10 /*10 minutes*/).send();
|
|
574
|
+
```
|
|
575
|
+
|
|
569
576
|
### Cors
|
|
570
577
|
Documentation : [@bejibun/cors](https://github.com/Bejibun-Framework/bejibun-cors/blob/master/README.md)
|
|
571
578
|
|
|
@@ -593,6 +600,7 @@ Commands:
|
|
|
593
600
|
maintenance:up Turn app into live mode
|
|
594
601
|
make:command <file> Create a new command file
|
|
595
602
|
make:controller <file> Create a new controller file
|
|
603
|
+
make:job <file> Create a new job file
|
|
596
604
|
make:middleware <file> Create a new middleware file
|
|
597
605
|
make:migration <file> Create a new migration file
|
|
598
606
|
make:model <file> Create a new model file
|
|
@@ -603,6 +611,9 @@ Commands:
|
|
|
603
611
|
migrate:rollback [options] Rollback the latest migrations
|
|
604
612
|
migrate:status [options] List migrations status
|
|
605
613
|
package:configure [options] Configure package after installation
|
|
614
|
+
queue:flush Flush all of the failed queue jobs
|
|
615
|
+
queue:retry Retry a failed queue job
|
|
616
|
+
queue:work Start processing jobs on the queue as a daemon
|
|
606
617
|
help [command] display help for command
|
|
607
618
|
|
|
608
619
|
Examples:
|
package/ace.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import App from "@bejibun/app";
|
|
1
2
|
import Str from "@bejibun/utils/facades/Str";
|
|
2
3
|
import { program } from "commander";
|
|
3
4
|
import os from "os";
|
|
4
5
|
import Kernel from "./commands/Kernel";
|
|
5
6
|
import { version } from "package.json";
|
|
7
|
+
await import(App.Path.rootPath("bootstrap.ts"));
|
|
6
8
|
const commandExec = "ace";
|
|
7
9
|
program
|
|
8
10
|
.name(commandExec)
|
package/bases/BaseJob.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { isEmpty } from "@bejibun/utils";
|
|
2
|
+
import JobBuilder from "../builders/JobBuilder";
|
|
3
|
+
import RuntimeException from "../exceptions/RuntimeException";
|
|
4
|
+
export default class BaseJob {
|
|
5
|
+
static _namespace;
|
|
6
|
+
static get namespace() {
|
|
7
|
+
if (isEmpty(this._namespace))
|
|
8
|
+
throw new RuntimeException(`Job namespace not registered for [${this.name}].`);
|
|
9
|
+
return this._namespace;
|
|
10
|
+
}
|
|
11
|
+
static setNamespace(namespace) {
|
|
12
|
+
this._namespace = namespace;
|
|
13
|
+
}
|
|
14
|
+
static dispatch(...args) {
|
|
15
|
+
return new JobBuilder().setQueue(this.namespace).dispatch(...args);
|
|
16
|
+
}
|
|
17
|
+
}
|
package/bases/BaseModel.d.ts
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Luxon from "@bejibun/utils/facades/Luxon";
|
|
2
2
|
import { Constructor, Model, ModelOptions, PartialModelObject, QueryBuilder, QueryBuilderType, QueryContext, TransactionOrKnex } from "objection";
|
|
3
3
|
import SoftDeletes from "../facades/SoftDeletes";
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
created_at: DateTime | string;
|
|
7
|
-
updated_at: DateTime | string;
|
|
8
|
-
deleted_at: DateTime | string | null;
|
|
9
|
-
}
|
|
4
|
+
export type Timestamp = typeof Luxon.DateTime | Date | string;
|
|
5
|
+
export type NullableTimestamp = Timestamp | null;
|
|
10
6
|
declare class BunQueryBuilder<M extends Model, R = M[]> extends SoftDeletes<M, R> {
|
|
11
7
|
update(payload: PartialModelObject<M>): Promise<QueryBuilder<M, R>>;
|
|
12
8
|
}
|
|
13
|
-
export default class BaseModel extends Model
|
|
9
|
+
export default class BaseModel extends Model {
|
|
10
|
+
protected static _namespace: string;
|
|
14
11
|
static tableName: string;
|
|
15
12
|
static idColumn: string;
|
|
13
|
+
static createdColumn: string;
|
|
14
|
+
static updatedColumn: string;
|
|
16
15
|
static deletedColumn: string;
|
|
17
16
|
static QueryBuilder: typeof BunQueryBuilder;
|
|
18
17
|
id: number | bigint;
|
|
19
|
-
created_at: DateTime | string;
|
|
20
|
-
updated_at: DateTime | string;
|
|
21
|
-
deleted_at: DateTime | string | null;
|
|
22
18
|
static get namespace(): string;
|
|
23
19
|
$beforeInsert(queryContext: QueryContext): void;
|
|
24
20
|
$beforeUpdate(opt: ModelOptions, queryContext: QueryContext): void;
|
|
21
|
+
static setNamespace(namespace: string): void;
|
|
25
22
|
static query<T extends Model>(this: Constructor<T>, trxOrKnex?: TransactionOrKnex): QueryBuilderType<T>;
|
|
26
23
|
static withTrashed<T extends Model>(this: T): QueryBuilderType<T>;
|
|
27
24
|
static onlyTrashed<T extends Model>(this: T): QueryBuilderType<T>;
|
package/bases/BaseModel.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import Str from "@bejibun/utils/facades/Str";
|
|
4
|
-
import { DateTime } from "luxon";
|
|
1
|
+
import { defineValue, isEmpty, isNotEmpty } from "@bejibun/utils";
|
|
2
|
+
import Luxon from "@bejibun/utils/facades/Luxon";
|
|
5
3
|
import { Model } from "objection";
|
|
6
|
-
import { relative, sep } from "path";
|
|
7
|
-
import { fileURLToPath } from "url";
|
|
8
4
|
import ModelNotFoundException from "../exceptions/ModelNotFoundException";
|
|
9
5
|
import SoftDeletes from "../facades/SoftDeletes";
|
|
6
|
+
import RuntimeException from "../exceptions/RuntimeException";
|
|
10
7
|
class BunQueryBuilder extends SoftDeletes {
|
|
11
8
|
// @ts-ignore
|
|
12
9
|
async update(payload) {
|
|
@@ -20,26 +17,34 @@ class BunQueryBuilder extends SoftDeletes {
|
|
|
20
17
|
}
|
|
21
18
|
// @ts-ignore
|
|
22
19
|
export default class BaseModel extends Model {
|
|
20
|
+
static _namespace;
|
|
23
21
|
static tableName;
|
|
24
22
|
static idColumn;
|
|
23
|
+
static createdColumn = "created_at";
|
|
24
|
+
static updatedColumn = "updated_at";
|
|
25
25
|
static deletedColumn = "deleted_at";
|
|
26
26
|
static QueryBuilder = BunQueryBuilder;
|
|
27
27
|
static get namespace() {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const namespaces = withoutExt.split(sep);
|
|
32
|
-
namespaces.pop();
|
|
33
|
-
namespaces.push(this.name);
|
|
34
|
-
return namespaces.map(part => Str.toPascalCase(part)).join("/");
|
|
28
|
+
if (isEmpty(this._namespace))
|
|
29
|
+
throw new RuntimeException(`Model namespace not registered for [${this.name}]`);
|
|
30
|
+
return this._namespace;
|
|
35
31
|
}
|
|
36
32
|
$beforeInsert(queryContext) {
|
|
37
|
-
const now = DateTime.now();
|
|
38
|
-
this.
|
|
39
|
-
|
|
33
|
+
const now = Luxon.DateTime.now();
|
|
34
|
+
if (isNotEmpty(this[this.constructor.createdColumn])) {
|
|
35
|
+
this[this.constructor.createdColumn] = now;
|
|
36
|
+
}
|
|
37
|
+
if (isNotEmpty(this[this.constructor.updatedColumn])) {
|
|
38
|
+
this[this.constructor.updatedColumn] = now;
|
|
39
|
+
}
|
|
40
40
|
}
|
|
41
41
|
$beforeUpdate(opt, queryContext) {
|
|
42
|
-
this.
|
|
42
|
+
if (isNotEmpty(this[this.constructor.updatedColumn])) {
|
|
43
|
+
this[this.constructor.updatedColumn] = Luxon.DateTime.now();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
static setNamespace(namespace) {
|
|
47
|
+
this._namespace = namespace;
|
|
43
48
|
}
|
|
44
49
|
static query(trxOrKnex) {
|
|
45
50
|
return super.query(trxOrKnex);
|
package/bases/index.d.ts
CHANGED
package/bases/index.js
CHANGED
package/bootstrap.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import App from "@bejibun/app";
|
|
1
2
|
import Database from "@bejibun/database";
|
|
2
3
|
import BaseModel from "./bases/BaseModel";
|
|
4
|
+
import NamespaceLoader from "./loader/NamespaceLoader";
|
|
3
5
|
BaseModel.knex(Database.knex());
|
|
6
|
+
await NamespaceLoader.load(App.Path.jobsPath());
|
|
7
|
+
await NamespaceLoader.load(App.Path.modelsPath());
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default class JobBuilder {
|
|
2
|
+
protected queue?: string;
|
|
3
|
+
protected now: number;
|
|
4
|
+
protected availableAt: number;
|
|
5
|
+
protected args: Array<any>;
|
|
6
|
+
constructor();
|
|
7
|
+
setQueue(queue: string): JobBuilder;
|
|
8
|
+
dispatch(...args: any): JobBuilder;
|
|
9
|
+
delay(delay: number): JobBuilder;
|
|
10
|
+
send(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Luxon from "@bejibun/utils/facades/Luxon";
|
|
2
|
+
import JobModel from "../models/JobModel";
|
|
3
|
+
export default class JobBuilder {
|
|
4
|
+
queue;
|
|
5
|
+
now;
|
|
6
|
+
availableAt;
|
|
7
|
+
args;
|
|
8
|
+
constructor() {
|
|
9
|
+
this.now = Luxon.DateTime.now().toUnixInteger();
|
|
10
|
+
this.availableAt = this.now;
|
|
11
|
+
this.args = [];
|
|
12
|
+
}
|
|
13
|
+
setQueue(queue) {
|
|
14
|
+
this.queue = queue;
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
dispatch(...args) {
|
|
18
|
+
this.args.push(...args);
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
delay(delay) {
|
|
22
|
+
this.availableAt = this.now + delay;
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
async send() {
|
|
26
|
+
await JobModel.create({
|
|
27
|
+
queue: this.queue,
|
|
28
|
+
payload: JSON.stringify(this.args),
|
|
29
|
+
available_at: this.availableAt,
|
|
30
|
+
created_at: this.now
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import App from "@bejibun/app";
|
|
2
|
+
import { isEmpty } from "@bejibun/utils";
|
|
3
|
+
import { readdirSync } from "fs";
|
|
4
|
+
import { join, relative, sep } from "path";
|
|
5
|
+
import { pathToFileURL } from "url";
|
|
6
|
+
export default class NamespaceBuilder {
|
|
7
|
+
computeNamespace(filePath) {
|
|
8
|
+
const rel = relative(App.Path.rootPath(), filePath);
|
|
9
|
+
const withoutExt = rel.replace(/\.[tj]s$/, "");
|
|
10
|
+
const parts = withoutExt.split(sep);
|
|
11
|
+
return parts.join("/");
|
|
12
|
+
}
|
|
13
|
+
async walk(directory) {
|
|
14
|
+
const entries = readdirSync(directory, { withFileTypes: true });
|
|
15
|
+
const files = await Promise.all(entries.map((entry) => {
|
|
16
|
+
const fullPath = join(directory, entry.name);
|
|
17
|
+
return entry.isDirectory() ?
|
|
18
|
+
this.walk(fullPath) :
|
|
19
|
+
((fullPath.endsWith(".ts") ||
|
|
20
|
+
fullPath.endsWith(".js")) ? [fullPath] : []);
|
|
21
|
+
}));
|
|
22
|
+
return files.flat();
|
|
23
|
+
}
|
|
24
|
+
async load(directory) {
|
|
25
|
+
const files = await this.walk(directory);
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
const fileUrl = pathToFileURL(file).href;
|
|
28
|
+
const module = await import(fileUrl);
|
|
29
|
+
const Class = module.default;
|
|
30
|
+
if (isEmpty(Class))
|
|
31
|
+
continue;
|
|
32
|
+
const namespace = this.computeNamespace(file);
|
|
33
|
+
if (typeof Class.setNamespace === "function")
|
|
34
|
+
Class.setNamespace(namespace);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -8,9 +8,9 @@ export interface ResourceOptions {
|
|
|
8
8
|
except?: Array<ResourceAction>;
|
|
9
9
|
}
|
|
10
10
|
export default class RouterBuilder {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
protected basePath: string;
|
|
12
|
+
protected middlewares: Array<IMiddleware>;
|
|
13
|
+
protected baseNamespace: string;
|
|
14
14
|
prefix(basePath: string): RouterBuilder;
|
|
15
15
|
middleware(...middlewares: Array<IMiddleware>): RouterBuilder;
|
|
16
16
|
namespace(baseNamespace: string): RouterBuilder;
|
|
@@ -3,7 +3,6 @@ import Logger from "@bejibun/logger";
|
|
|
3
3
|
import { defineValue, isEmpty, isModuleExists, isNotEmpty } from "@bejibun/utils";
|
|
4
4
|
import HttpMethodEnum from "@bejibun/utils/enums/HttpMethodEnum";
|
|
5
5
|
import Enum from "@bejibun/utils/facades/Enum";
|
|
6
|
-
import path from "path";
|
|
7
6
|
import RouterException from "../exceptions/RouterException";
|
|
8
7
|
export default class RouterBuilder {
|
|
9
8
|
basePath = "";
|
|
@@ -267,7 +266,7 @@ export default class RouterBuilder {
|
|
|
267
266
|
if (isEmpty(controllerName) || isEmpty(methodName)) {
|
|
268
267
|
throw new RouterException(`Invalid router controller definition: ${definition}.`);
|
|
269
268
|
}
|
|
270
|
-
const controllerPath =
|
|
269
|
+
const controllerPath = App.Path.rootPath(defineValue(overrideNamespace, this.baseNamespace));
|
|
271
270
|
let location = null;
|
|
272
271
|
try {
|
|
273
272
|
location = Bun.resolveSync(`./${controllerName}.ts`, controllerPath);
|
package/commands/Kernel.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import App from "@bejibun/app";
|
|
2
2
|
import { defineValue, isEmpty } from "@bejibun/utils";
|
|
3
|
+
import BaseModel from "../bases/BaseModel";
|
|
3
4
|
export default class Kernel {
|
|
4
5
|
static registerCommands(program) {
|
|
5
6
|
const rootCommands = require(App.Path.configPath("command.ts")).default;
|
|
@@ -55,7 +56,12 @@ export default class Kernel {
|
|
|
55
56
|
const commandObj = args[args.length - 1];
|
|
56
57
|
const options = typeof commandObj.opts === "function" ? commandObj.opts() : commandObj;
|
|
57
58
|
const positionalArgs = args[0];
|
|
58
|
-
|
|
59
|
+
try {
|
|
60
|
+
await instance.handle(options, positionalArgs);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await BaseModel.knex().destroy();
|
|
64
|
+
}
|
|
59
65
|
});
|
|
60
66
|
}
|
|
61
67
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default class MakeJobCommand {
|
|
2
|
+
/**
|
|
3
|
+
* The name and signature of the console command.
|
|
4
|
+
*
|
|
5
|
+
* @var $signature string
|
|
6
|
+
*/
|
|
7
|
+
protected $signature: string;
|
|
8
|
+
/**
|
|
9
|
+
* The console command description.
|
|
10
|
+
*
|
|
11
|
+
* @var $description string
|
|
12
|
+
*/
|
|
13
|
+
protected $description: string;
|
|
14
|
+
/**
|
|
15
|
+
* The options or optional flag of the console command.
|
|
16
|
+
*
|
|
17
|
+
* @var $options Array<Array<any>>
|
|
18
|
+
*/
|
|
19
|
+
protected $options: Array<Array<any>>;
|
|
20
|
+
/**
|
|
21
|
+
* The arguments of the console command.
|
|
22
|
+
*
|
|
23
|
+
* @var $arguments Array<Array<string>>
|
|
24
|
+
*/
|
|
25
|
+
protected $arguments: Array<Array<string>>;
|
|
26
|
+
handle(options: any, args: string): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import App from "@bejibun/app";
|
|
2
|
+
import Logger from "@bejibun/logger";
|
|
3
|
+
import { isEmpty } from "@bejibun/utils";
|
|
4
|
+
import Str from "@bejibun/utils/facades/Str";
|
|
5
|
+
import path from "path";
|
|
6
|
+
export default class MakeJobCommand {
|
|
7
|
+
/**
|
|
8
|
+
* The name and signature of the console command.
|
|
9
|
+
*
|
|
10
|
+
* @var $signature string
|
|
11
|
+
*/
|
|
12
|
+
$signature = "make:job";
|
|
13
|
+
/**
|
|
14
|
+
* The console command description.
|
|
15
|
+
*
|
|
16
|
+
* @var $description string
|
|
17
|
+
*/
|
|
18
|
+
$description = "Create a new job file";
|
|
19
|
+
/**
|
|
20
|
+
* The options or optional flag of the console command.
|
|
21
|
+
*
|
|
22
|
+
* @var $options Array<Array<any>>
|
|
23
|
+
*/
|
|
24
|
+
$options = [];
|
|
25
|
+
/**
|
|
26
|
+
* The arguments of the console command.
|
|
27
|
+
*
|
|
28
|
+
* @var $arguments Array<Array<string>>
|
|
29
|
+
*/
|
|
30
|
+
$arguments = [
|
|
31
|
+
["<file>", "The name of the job file"]
|
|
32
|
+
];
|
|
33
|
+
async handle(options, args) {
|
|
34
|
+
if (isEmpty(args)) {
|
|
35
|
+
Logger.setContext("APP").error("There is no filename provided.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const file = args;
|
|
39
|
+
const jobsDirectory = "jobs";
|
|
40
|
+
const template = Bun.file(path.resolve(__dirname, `../../stubs/${jobsDirectory}/TemplateJob.ts`));
|
|
41
|
+
if (!await template.exists()) {
|
|
42
|
+
Logger.setContext("APP").error("Whoops, something went wrong, the job template not found.");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const name = Str.toPascalCase(file.replace(/\s+/g, "").replace(/job/gi, ""));
|
|
46
|
+
const destination = `${name}Job.ts`;
|
|
47
|
+
const content = await template.text();
|
|
48
|
+
await Bun.write(App.Path.jobsPath(destination), content.replace(/template/gi, name));
|
|
49
|
+
Logger.setContext("APP").info(`Job [app/jobs/${destination}] created successfully.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default class QueueFlushCommand {
|
|
2
|
+
/**
|
|
3
|
+
* The name and signature of the console command.
|
|
4
|
+
*
|
|
5
|
+
* @var $signature string
|
|
6
|
+
*/
|
|
7
|
+
protected $signature: string;
|
|
8
|
+
/**
|
|
9
|
+
* The console command description.
|
|
10
|
+
*
|
|
11
|
+
* @var $description string
|
|
12
|
+
*/
|
|
13
|
+
protected $description: string;
|
|
14
|
+
/**
|
|
15
|
+
* The options or optional flag of the console command.
|
|
16
|
+
*
|
|
17
|
+
* @var $options Array<Array<any>>
|
|
18
|
+
*/
|
|
19
|
+
protected $options: Array<Array<any>>;
|
|
20
|
+
/**
|
|
21
|
+
* The arguments of the console command.
|
|
22
|
+
*
|
|
23
|
+
* @var $arguments Array<Array<string>>
|
|
24
|
+
*/
|
|
25
|
+
protected $arguments: Array<Array<string>>;
|
|
26
|
+
handle(options: any, args: string): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Logger from "@bejibun/logger";
|
|
2
|
+
import JobModel from "../../models/JobModel";
|
|
3
|
+
export default class QueueFlushCommand {
|
|
4
|
+
/**
|
|
5
|
+
* The name and signature of the console command.
|
|
6
|
+
*
|
|
7
|
+
* @var $signature string
|
|
8
|
+
*/
|
|
9
|
+
$signature = "queue:flush";
|
|
10
|
+
/**
|
|
11
|
+
* The console command description.
|
|
12
|
+
*
|
|
13
|
+
* @var $description string
|
|
14
|
+
*/
|
|
15
|
+
$description = "Flush all of the failed queue jobs";
|
|
16
|
+
/**
|
|
17
|
+
* The options or optional flag of the console command.
|
|
18
|
+
*
|
|
19
|
+
* @var $options Array<Array<any>>
|
|
20
|
+
*/
|
|
21
|
+
$options = [];
|
|
22
|
+
/**
|
|
23
|
+
* The arguments of the console command.
|
|
24
|
+
*
|
|
25
|
+
* @var $arguments Array<Array<string>>
|
|
26
|
+
*/
|
|
27
|
+
$arguments = [];
|
|
28
|
+
async handle(options, args) {
|
|
29
|
+
await JobModel.query().where("attempts", ">=", 3).delete();
|
|
30
|
+
Logger.setContext("Queue").info("All failed jobs deleted successfully.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default class QueueRetryCommand {
|
|
2
|
+
/**
|
|
3
|
+
* The name and signature of the console command.
|
|
4
|
+
*
|
|
5
|
+
* @var $signature string
|
|
6
|
+
*/
|
|
7
|
+
protected $signature: string;
|
|
8
|
+
/**
|
|
9
|
+
* The console command description.
|
|
10
|
+
*
|
|
11
|
+
* @var $description string
|
|
12
|
+
*/
|
|
13
|
+
protected $description: string;
|
|
14
|
+
/**
|
|
15
|
+
* The options or optional flag of the console command.
|
|
16
|
+
*
|
|
17
|
+
* @var $options Array<Array<any>>
|
|
18
|
+
*/
|
|
19
|
+
protected $options: Array<Array<any>>;
|
|
20
|
+
/**
|
|
21
|
+
* The arguments of the console command.
|
|
22
|
+
*
|
|
23
|
+
* @var $arguments Array<Array<string>>
|
|
24
|
+
*/
|
|
25
|
+
protected $arguments: Array<Array<string>>;
|
|
26
|
+
handle(options: any, args: string): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import App from "@bejibun/app";
|
|
2
|
+
import Logger from "@bejibun/logger";
|
|
3
|
+
import { defineValue, isEmpty } from "@bejibun/utils";
|
|
4
|
+
import RuntimeException from "../../exceptions/RuntimeException";
|
|
5
|
+
import JobModel from "../../models/JobModel";
|
|
6
|
+
export default class QueueRetryCommand {
|
|
7
|
+
/**
|
|
8
|
+
* The name and signature of the console command.
|
|
9
|
+
*
|
|
10
|
+
* @var $signature string
|
|
11
|
+
*/
|
|
12
|
+
$signature = "queue:retry";
|
|
13
|
+
/**
|
|
14
|
+
* The console command description.
|
|
15
|
+
*
|
|
16
|
+
* @var $description string
|
|
17
|
+
*/
|
|
18
|
+
$description = "Retry a failed queue job";
|
|
19
|
+
/**
|
|
20
|
+
* The options or optional flag of the console command.
|
|
21
|
+
*
|
|
22
|
+
* @var $options Array<Array<any>>
|
|
23
|
+
*/
|
|
24
|
+
$options = [];
|
|
25
|
+
/**
|
|
26
|
+
* The arguments of the console command.
|
|
27
|
+
*
|
|
28
|
+
* @var $arguments Array<Array<string>>
|
|
29
|
+
*/
|
|
30
|
+
$arguments = [];
|
|
31
|
+
async handle(options, args) {
|
|
32
|
+
let running = true;
|
|
33
|
+
process.on("exit", async () => {
|
|
34
|
+
running = false;
|
|
35
|
+
Logger.setContext("Queue").info("Queue worker stopped.");
|
|
36
|
+
});
|
|
37
|
+
process.on("SIGINT", async () => {
|
|
38
|
+
running = false;
|
|
39
|
+
Logger.setContext("Queue").info("Stopping queue worker, SIGINT sent.");
|
|
40
|
+
});
|
|
41
|
+
process.on("SIGTERM", async () => {
|
|
42
|
+
running = false;
|
|
43
|
+
Logger.setContext("Queue").info("Stopping queue worker, SIGTERM sent.");
|
|
44
|
+
});
|
|
45
|
+
while (running) {
|
|
46
|
+
const job = await JobModel.query().where("attempts", ">=", 3).orderBy("id", "asc").first();
|
|
47
|
+
if (isEmpty(job?.id)) {
|
|
48
|
+
running = false;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const handler = async () => {
|
|
52
|
+
const module = await import(App.Path.rootPath(job.queue));
|
|
53
|
+
const Class = module.default;
|
|
54
|
+
if (isEmpty(Class))
|
|
55
|
+
throw new RuntimeException(`Job class not found [${job.queue}].`);
|
|
56
|
+
const instance = new Class();
|
|
57
|
+
if (typeof instance.handle !== "function")
|
|
58
|
+
throw new RuntimeException(`Job class has no handle function in [${job.queue}].`);
|
|
59
|
+
instance.handle(JSON.parse(job.payload));
|
|
60
|
+
};
|
|
61
|
+
try {
|
|
62
|
+
await handler();
|
|
63
|
+
await JobModel.query().findById(job.id).delete();
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
await JobModel.query().findById(job.id).update({
|
|
67
|
+
attempts: defineValue(Number(job.attempts), 0) + 1
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
Logger.setContext("Queue").info("All failed jobs retried successfully.");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default class QueueWorkCommand {
|
|
2
|
+
/**
|
|
3
|
+
* The name and signature of the console command.
|
|
4
|
+
*
|
|
5
|
+
* @var $signature string
|
|
6
|
+
*/
|
|
7
|
+
protected $signature: string;
|
|
8
|
+
/**
|
|
9
|
+
* The console command description.
|
|
10
|
+
*
|
|
11
|
+
* @var $description string
|
|
12
|
+
*/
|
|
13
|
+
protected $description: string;
|
|
14
|
+
/**
|
|
15
|
+
* The options or optional flag of the console command.
|
|
16
|
+
*
|
|
17
|
+
* @var $options Array<Array<any>>
|
|
18
|
+
*/
|
|
19
|
+
protected $options: Array<Array<any>>;
|
|
20
|
+
/**
|
|
21
|
+
* The arguments of the console command.
|
|
22
|
+
*
|
|
23
|
+
* @var $arguments Array<Array<string>>
|
|
24
|
+
*/
|
|
25
|
+
protected $arguments: Array<Array<string>>;
|
|
26
|
+
handle(options: any, args: string): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import App from "@bejibun/app";
|
|
2
|
+
import Logger from "@bejibun/logger";
|
|
3
|
+
import { defineValue, isEmpty, isNotEmpty } from "@bejibun/utils";
|
|
4
|
+
import RuntimeException from "../../exceptions/RuntimeException";
|
|
5
|
+
import JobModel from "../../models/JobModel";
|
|
6
|
+
export default class QueueWorkCommand {
|
|
7
|
+
/**
|
|
8
|
+
* The name and signature of the console command.
|
|
9
|
+
*
|
|
10
|
+
* @var $signature string
|
|
11
|
+
*/
|
|
12
|
+
$signature = "queue:work";
|
|
13
|
+
/**
|
|
14
|
+
* The console command description.
|
|
15
|
+
*
|
|
16
|
+
* @var $description string
|
|
17
|
+
*/
|
|
18
|
+
$description = "Start processing jobs on the queue as a daemon";
|
|
19
|
+
/**
|
|
20
|
+
* The options or optional flag of the console command.
|
|
21
|
+
*
|
|
22
|
+
* @var $options Array<Array<any>>
|
|
23
|
+
*/
|
|
24
|
+
$options = [];
|
|
25
|
+
/**
|
|
26
|
+
* The arguments of the console command.
|
|
27
|
+
*
|
|
28
|
+
* @var $arguments Array<Array<string>>
|
|
29
|
+
*/
|
|
30
|
+
$arguments = [];
|
|
31
|
+
async handle(options, args) {
|
|
32
|
+
let running = true;
|
|
33
|
+
process.on("exit", async () => {
|
|
34
|
+
running = false;
|
|
35
|
+
Logger.setContext("Queue").info("Queue worker stopped.");
|
|
36
|
+
});
|
|
37
|
+
process.on("SIGINT", async () => {
|
|
38
|
+
running = false;
|
|
39
|
+
Logger.setContext("Queue").info("Stopping queue worker, SIGINT sent.");
|
|
40
|
+
});
|
|
41
|
+
process.on("SIGTERM", async () => {
|
|
42
|
+
running = false;
|
|
43
|
+
Logger.setContext("Queue").info("Stopping queue worker, SIGTERM sent.");
|
|
44
|
+
});
|
|
45
|
+
while (running) {
|
|
46
|
+
const job = await JobModel.query().where("attempts", "<", 3).orderBy("id", "asc").first();
|
|
47
|
+
if (isNotEmpty(job?.id)) {
|
|
48
|
+
const handler = async () => {
|
|
49
|
+
const module = await import(App.Path.rootPath(job.queue));
|
|
50
|
+
const Class = module.default;
|
|
51
|
+
if (isEmpty(Class))
|
|
52
|
+
throw new RuntimeException(`Job class not found [${job.queue}].`);
|
|
53
|
+
const instance = new Class();
|
|
54
|
+
if (typeof instance.handle !== "function")
|
|
55
|
+
throw new RuntimeException(`Job class has no handle function in [${job.queue}].`);
|
|
56
|
+
instance.handle(JSON.parse(job.payload));
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
await handler();
|
|
60
|
+
await JobModel.query().findById(job.id).delete();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
await JobModel.query().findById(job.id).update({
|
|
64
|
+
attempts: defineValue(Number(job.attempts), 0) + 1
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { isNotEmpty } from "@bejibun/utils";
|
|
2
|
+
import Luxon from "@bejibun/utils/facades/Luxon";
|
|
3
|
+
const EpochTimestamps = (Base) => class extends Base {
|
|
4
|
+
$beforeInsert(queryContext) {
|
|
5
|
+
const now = Luxon.DateTime.now().toUnixInteger();
|
|
6
|
+
if (isNotEmpty(this[this.constructor.createdColumn])) {
|
|
7
|
+
this[this.constructor.createdColumn] = now;
|
|
8
|
+
}
|
|
9
|
+
if (isNotEmpty(this[this.constructor.updatedColumn])) {
|
|
10
|
+
this[this.constructor.updatedColumn] = now;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
$beforeUpdate(opt, queryContext) {
|
|
14
|
+
this.updated_at = Luxon.DateTime.now().toUnixInteger();
|
|
15
|
+
if (isNotEmpty(this[this.constructor.updatedColumn])) {
|
|
16
|
+
this[this.constructor.updatedColumn] = Luxon.DateTime.now().toUnixInteger();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export default EpochTimestamps;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ModelOptions, QueryBuilder, QueryContext } from "objection";
|
|
2
|
+
declare const JobModel_base: any;
|
|
3
|
+
export default class JobModel extends JobModel_base {
|
|
4
|
+
static tableName: string;
|
|
5
|
+
static idColumn: string;
|
|
6
|
+
static updatedColumn: null;
|
|
7
|
+
static deletedColumn: null;
|
|
8
|
+
static QueryBuilder: typeof QueryBuilder;
|
|
9
|
+
$beforeUpdate(opt: ModelOptions, queryContext: QueryContext): void;
|
|
10
|
+
id: bigint;
|
|
11
|
+
queue: string;
|
|
12
|
+
payload: string;
|
|
13
|
+
attempts: bigint;
|
|
14
|
+
reserved_at: bigint | null;
|
|
15
|
+
available_at: bigint;
|
|
16
|
+
created_at: bigint;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { QueryBuilder } from "objection";
|
|
2
|
+
import BaseModel from "../bases/BaseModel";
|
|
3
|
+
import EpochTimestamps from "../models/EpochTimestamps";
|
|
4
|
+
export default class JobModel extends EpochTimestamps(BaseModel) {
|
|
5
|
+
static tableName = "jobs";
|
|
6
|
+
static idColumn = "id";
|
|
7
|
+
static updatedColumn = null;
|
|
8
|
+
static deletedColumn = null;
|
|
9
|
+
static QueryBuilder = QueryBuilder;
|
|
10
|
+
$beforeUpdate(opt, queryContext) {
|
|
11
|
+
// do nothing
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../models/JobModel";
|
package/models/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../models/JobModel";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bejibun/core",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"author": "Havea Crenata <havea.crenata@gmail.com>",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"@bejibun/database": "^0.1.19",
|
|
16
16
|
"@bejibun/logger": "^0.1.22",
|
|
17
17
|
"@bejibun/utils": "^0.1.28",
|
|
18
|
-
"@vinejs/vine": "^4.
|
|
18
|
+
"@vinejs/vine": "^4.3.0",
|
|
19
19
|
"commander": "^14.0.3",
|
|
20
20
|
"luxon": "^3.7.2",
|
|
21
21
|
"objection": "^3.1.5"
|
package/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import RuntimeException from "./exceptions/RuntimeException";
|
|
|
5
5
|
import Router from "./facades/Router";
|
|
6
6
|
import MaintenanceMiddleware from "./middlewares/MaintenanceMiddleware";
|
|
7
7
|
import RateLimiterMiddleware from "./middlewares/RateLimiterMiddleware";
|
|
8
|
-
import(App.Path.rootPath("bootstrap.ts"));
|
|
8
|
+
await import(App.Path.rootPath("bootstrap.ts"));
|
|
9
9
|
export default class Server {
|
|
10
10
|
get exceptionHandler() {
|
|
11
11
|
const exceptionHandlerPath = App.Path.appPath("exceptions/handler.ts");
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import type {Timestamp, NullableTimestamp} from "@bejibun/core/bases/BaseModel";
|
|
2
|
+
import BaseModel from "@bejibun/core/bases/BaseModel";
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
name: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default class TemplateModel extends BaseModel implements TemplateColumns {
|
|
4
|
+
export default class TemplateModel extends BaseModel {
|
|
9
5
|
public static tableName: string = "templates";
|
|
10
6
|
public static idColumn: string = "id";
|
|
11
7
|
|
|
12
8
|
declare id: bigint;
|
|
13
9
|
declare name: string;
|
|
14
|
-
declare created_at:
|
|
15
|
-
declare updated_at:
|
|
16
|
-
declare deleted_at:
|
|
10
|
+
declare created_at: Timestamp;
|
|
11
|
+
declare updated_at: Timestamp;
|
|
12
|
+
declare deleted_at: NullableTimestamp;
|
|
17
13
|
}
|