startx 1.0.5 → 1.0.8
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/apps/core-server/Dockerfile +7 -2
- package/apps/core-server/src/middlewares/auth-middleware.ts +74 -30
- package/apps/queue-worker/Dockerfile +15 -8
- package/apps/queue-worker/package.json +5 -1
- package/apps/queue-worker/src/bullmq/board.ts +28 -0
- package/apps/queue-worker/src/index.ts +2 -0
- package/apps/startx-cli/dist/index.mjs +2 -2
- package/apps/startx-cli/src/configs/scripts.ts +40 -0
- package/package.json +8 -2
- package/packages/@db/drizzle/drizzle.config.ts +1 -1
- package/packages/@db/drizzle/src/index.ts +4 -15
- package/packages/@repo/lib/src/cookie-module/cookie-module.ts +94 -38
- package/packages/@repo/lib/src/extra/index.ts +1 -0
- package/packages/@repo/lib/src/extra/token-module.ts +50 -21
- package/packages/@repo/lib/src/mail-module/nodemailer.ts +33 -21
- package/packages/@repo/lib/src/session-module/i-session.ts +132 -59
- package/packages/@repo/lib/src/session-module/index.ts +8 -2
- package/packages/@repo/lib/src/session-module/redis-session.ts +53 -23
- package/packages/@repo/lib/src/validation-module/index.ts +50 -78
- package/packages/@repo/model/eslint.config.ts +4 -0
- package/packages/@repo/model/package.json +41 -0
- package/packages/@repo/model/src/index.ts +0 -0
- package/packages/@repo/model/tsconfig.json +7 -0
- package/packages/@repo/model/vitest.config.ts +3 -0
- package/packages/common/src/time.ts +95 -22
- package/packages/queue/src/adapter/bullmq-adapter.ts +138 -47
- package/packages/queue/src/index.ts +3 -0
- package/packages/queue/src/queue-interface.ts +12 -5
- package/packages/queue/src/registry.ts +2 -2
- package/packages/queue/tsconfig.json +1 -1
- package/packages/ui/src/api/use-api/react-query/types.ts +3 -3
- package/packages/ui/src/api/use-api/react-query/use-api.ts +10 -11
- package/pnpm-workspace.yaml +4 -2
- package/turbo.json +20 -0
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { logger } from "@repo/logger";
|
|
2
|
-
import { Queue, Worker, QueueEvents, type ConnectionOptions, type JobsOptions } from "bullmq";
|
|
3
|
-
|
|
2
|
+
import { Queue, Worker, QueueEvents, type ConnectionOptions, type JobsOptions, type WorkerOptions } from "bullmq";
|
|
3
|
+
|
|
4
|
+
import type { IQueueProvider, RegisterCronConfig } from "../queue-interface.js";
|
|
4
5
|
import { JobSchemas, type JobRegistry } from "../registry.js";
|
|
5
|
-
import type {
|
|
6
|
+
import type { JobContext, JobHandler, JobOptions } from "../types.js";
|
|
6
7
|
|
|
7
8
|
export class BullMQProvider implements IQueueProvider {
|
|
8
9
|
private queues: Map<string, Queue> = new Map();
|
|
9
|
-
private workers: Worker
|
|
10
|
+
private workers: Map<string, Worker> = new Map();
|
|
10
11
|
private events: Map<string, QueueEvents> = new Map();
|
|
11
12
|
|
|
12
|
-
constructor(
|
|
13
|
+
constructor(
|
|
14
|
+
private connection: ConnectionOptions = {
|
|
15
|
+
host: "localhost",
|
|
16
|
+
port: 6379,
|
|
17
|
+
}
|
|
18
|
+
) {}
|
|
13
19
|
|
|
14
20
|
private mapOptions(options?: JobOptions): JobsOptions {
|
|
15
21
|
if (!options) return {};
|
|
22
|
+
|
|
16
23
|
return {
|
|
17
24
|
jobId: options.jobId,
|
|
18
25
|
delay: options.delay,
|
|
@@ -21,10 +28,15 @@ export class BullMQProvider implements IQueueProvider {
|
|
|
21
28
|
};
|
|
22
29
|
}
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
getQueue(queueName: string): Queue {
|
|
25
32
|
if (!this.queues.has(queueName)) {
|
|
26
|
-
|
|
33
|
+
const queue = new Queue(queueName, {
|
|
34
|
+
connection: this.connection,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
this.queues.set(queueName, queue);
|
|
27
38
|
}
|
|
39
|
+
|
|
28
40
|
return this.queues.get(queueName)!;
|
|
29
41
|
}
|
|
30
42
|
|
|
@@ -34,9 +46,10 @@ export class BullMQProvider implements IQueueProvider {
|
|
|
34
46
|
options?: JobOptions
|
|
35
47
|
): Promise<string> {
|
|
36
48
|
const validated = JobSchemas[queueName].params.parse(params);
|
|
37
|
-
const queue = this.getQueue(queueName
|
|
49
|
+
const queue = this.getQueue(queueName);
|
|
38
50
|
const job = await queue.add("job", validated, this.mapOptions(options));
|
|
39
|
-
|
|
51
|
+
|
|
52
|
+
return String(job.id);
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
async enqueueMany<K extends keyof JobRegistry>(
|
|
@@ -44,52 +57,115 @@ export class BullMQProvider implements IQueueProvider {
|
|
|
44
57
|
paramsList: Array<JobRegistry[K]["params"]>,
|
|
45
58
|
options?: JobOptions
|
|
46
59
|
): Promise<string[]> {
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
if (options?.jobId && paramsList.length > 1) {
|
|
61
|
+
throw new Error("`jobId` cannot be shared across multiple jobs in enqueueMany()");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const queue = this.getQueue(queueName);
|
|
65
|
+
const jobs = await queue.addBulk(
|
|
66
|
+
paramsList.map(params => ({
|
|
67
|
+
name: "job",
|
|
68
|
+
data: JobSchemas[queueName].params.parse(params),
|
|
69
|
+
opts: this.mapOptions(options),
|
|
70
|
+
}))
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return jobs.map(job => String(job.id));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async registerCron<K extends keyof JobRegistry>(config: RegisterCronConfig<K>): Promise<void> {
|
|
77
|
+
const { queueName, schedulerId, cronExpression, params, handler, options } = config;
|
|
49
78
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
79
|
+
const validated = JobSchemas[queueName].params.parse(params);
|
|
80
|
+
const queue = this.getQueue(queueName);
|
|
81
|
+
|
|
82
|
+
await queue.upsertJobScheduler(
|
|
83
|
+
schedulerId,
|
|
84
|
+
{ pattern: cronExpression },
|
|
85
|
+
{
|
|
86
|
+
name: "cron-job",
|
|
87
|
+
data: validated,
|
|
88
|
+
opts: this.mapOptions(options),
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
this.registerWorker(queueName, handler, options);
|
|
93
|
+
}
|
|
55
94
|
|
|
56
|
-
|
|
57
|
-
|
|
95
|
+
async removeCron<K extends keyof JobRegistry>(queueName: K, schedulerId: string): Promise<boolean> {
|
|
96
|
+
const queue = this.getQueue(queueName);
|
|
97
|
+
return await queue.removeJobScheduler(schedulerId);
|
|
58
98
|
}
|
|
59
99
|
|
|
60
100
|
registerWorker<K extends keyof JobRegistry>(
|
|
61
101
|
queueName: K,
|
|
62
102
|
handler: JobHandler<JobRegistry[K]["params"], JobRegistry[K]["result"]>,
|
|
63
|
-
options
|
|
64
|
-
concurrency?: number;
|
|
65
|
-
}
|
|
103
|
+
options?: Omit<WorkerOptions, "connection">
|
|
66
104
|
): void {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.join(", ")}`
|
|
71
|
-
);
|
|
72
|
-
const worker = new Worker(
|
|
73
|
-
queueName as string,
|
|
74
|
-
async job => {
|
|
75
|
-
const validData = JobSchemas[queueName].params.parse(job.data) as JobRegistry[K]["params"];
|
|
105
|
+
if (this.workers.has(queueName)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
76
108
|
|
|
77
|
-
|
|
78
|
-
jobId: job.id!,
|
|
79
|
-
attemptsMade: job.attemptsMade,
|
|
80
|
-
};
|
|
109
|
+
logger.info(`Registering worker`, { queue: queueName, options });
|
|
81
110
|
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
const worker = new Worker(
|
|
112
|
+
queueName,
|
|
113
|
+
async job => {
|
|
114
|
+
try {
|
|
115
|
+
const validData = JobSchemas[queueName].params.parse(job.data) as JobRegistry[K]["params"];
|
|
116
|
+
const context: JobContext = {
|
|
117
|
+
jobId: String(job.id),
|
|
118
|
+
attemptsMade: job.attemptsMade,
|
|
119
|
+
};
|
|
120
|
+
return await handler(validData, context);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
logger.error(`Job execution failed`, {
|
|
123
|
+
queue: queueName,
|
|
124
|
+
jobId: job.id,
|
|
125
|
+
attemptsMade: job.attemptsMade,
|
|
126
|
+
data: job.data,
|
|
127
|
+
error,
|
|
128
|
+
});
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
84
131
|
},
|
|
85
|
-
{
|
|
132
|
+
{
|
|
133
|
+
connection: this.connection,
|
|
134
|
+
...options,
|
|
135
|
+
}
|
|
86
136
|
);
|
|
87
|
-
|
|
137
|
+
|
|
138
|
+
worker.on("ready", () => logger.info(`Worker ready`, { queue: queueName }));
|
|
139
|
+
worker.on("active", job => logger.debug(`Job started`, { queue: queueName, jobId: job.id }));
|
|
140
|
+
worker.on("completed", job =>
|
|
141
|
+
logger.info(`Job completed`, { queue: queueName, jobId: job.id, attemptsMade: job.attemptsMade })
|
|
142
|
+
);
|
|
143
|
+
worker.on("failed", (job, error) =>
|
|
144
|
+
logger.error(`Job failed`, { queue: queueName, jobId: job?.id, attemptsMade: job?.attemptsMade, error })
|
|
145
|
+
);
|
|
146
|
+
worker.on("stalled", jobId => logger.warn(`Job stalled`, { queue: queueName, jobId }));
|
|
147
|
+
worker.on("closing", () => logger.warn(`Worker closing`, { queue: queueName }));
|
|
148
|
+
worker.on("closed", () => logger.warn(`Worker closed`, { queue: queueName }));
|
|
149
|
+
worker.on("error", error => logger.error(`Worker error`, { queue: queueName, error }));
|
|
150
|
+
|
|
151
|
+
void worker.client.then(client => {
|
|
152
|
+
client.on("ready", () => logger.info(`Redis ready`, { queue: queueName }));
|
|
153
|
+
client.on("reconnecting", () => logger.warn(`Redis reconnecting`, { queue: queueName }));
|
|
154
|
+
client.on("end", () => logger.error(`Redis connection ended`, { queue: queueName }));
|
|
155
|
+
client.on("error", error => logger.error(`Redis connection error`, { queue: queueName, error }));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
this.workers.set(queueName, worker);
|
|
88
159
|
}
|
|
89
160
|
|
|
90
161
|
private getQueueEvents(queueName: string): QueueEvents {
|
|
91
162
|
if (!this.events.has(queueName)) {
|
|
92
|
-
|
|
163
|
+
const events = new QueueEvents(queueName, {
|
|
164
|
+
connection: this.connection,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
events.on("error", error => logger.error(`QueueEvents error`, { queue: queueName, error }));
|
|
168
|
+
this.events.set(queueName, events);
|
|
93
169
|
}
|
|
94
170
|
return this.events.get(queueName)!;
|
|
95
171
|
}
|
|
@@ -98,24 +174,39 @@ export class BullMQProvider implements IQueueProvider {
|
|
|
98
174
|
queueName: K,
|
|
99
175
|
callback: (jobId: string, result: JobRegistry[K]["result"]) => void
|
|
100
176
|
): void {
|
|
101
|
-
const events = this.getQueueEvents(queueName
|
|
177
|
+
const events = this.getQueueEvents(queueName);
|
|
102
178
|
events.on("completed", ({ jobId, returnvalue }) => {
|
|
103
|
-
|
|
104
|
-
|
|
179
|
+
try {
|
|
180
|
+
callback(jobId, returnvalue as JobRegistry[K]["result"]);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
logger.error("Job completion callback failed", { queue: queueName, jobId, error });
|
|
183
|
+
}
|
|
105
184
|
});
|
|
106
185
|
}
|
|
107
186
|
|
|
108
187
|
onJobFailed<K extends keyof JobRegistry>(queueName: K, callback: (jobId: string, error: Error) => void): void {
|
|
109
|
-
const events = this.getQueueEvents(queueName
|
|
110
|
-
events.on("failed", ({ jobId, failedReason }) =>
|
|
188
|
+
const events = this.getQueueEvents(queueName);
|
|
189
|
+
events.on("failed", ({ jobId, failedReason }) => {
|
|
190
|
+
try {
|
|
191
|
+
callback(jobId, new Error(failedReason));
|
|
192
|
+
} catch (error) {
|
|
193
|
+
logger.error("Job failure callback failed", { queue: queueName, jobId, error });
|
|
194
|
+
}
|
|
195
|
+
});
|
|
111
196
|
}
|
|
112
197
|
|
|
113
198
|
async close(): Promise<void> {
|
|
114
199
|
logger.warn("Shutting down queue system...");
|
|
115
200
|
|
|
116
|
-
await Promise.
|
|
117
|
-
await Promise.
|
|
118
|
-
await Promise.
|
|
201
|
+
const workerResults = await Promise.allSettled(Array.from(this.workers.values()).map(worker => worker.close()));
|
|
202
|
+
const eventResults = await Promise.allSettled(Array.from(this.events.values()).map(event => event.close()));
|
|
203
|
+
const queueResults = await Promise.allSettled(Array.from(this.queues.values()).map(queue => queue.close()));
|
|
204
|
+
|
|
205
|
+
[...workerResults, ...eventResults, ...queueResults]
|
|
206
|
+
.filter((result): result is PromiseRejectedResult => result.status === "rejected")
|
|
207
|
+
.forEach(result => {
|
|
208
|
+
logger.error("Error during shutdown", result.reason);
|
|
209
|
+
});
|
|
119
210
|
|
|
120
211
|
logger.warn("Queue system shut down safely.");
|
|
121
212
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getRedis } from "@repo/redis";
|
|
2
2
|
import { BullMQProvider } from "./adapter/bullmq-adapter.js";
|
|
3
|
+
import { JobSchemas } from "./registry.js";
|
|
3
4
|
|
|
4
5
|
const redisConnection = getRedis({ db: 10 });
|
|
6
|
+
export const queueList = Object.keys(JobSchemas) as Array<keyof typeof JobSchemas>;
|
|
7
|
+
|
|
5
8
|
export const BullQueue = new BullMQProvider(redisConnection);
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import type { WorkerOptions } from "bullmq";
|
|
1
2
|
import type { JobRegistry } from "./registry.js";
|
|
2
3
|
import type { JobOptions, JobHandler } from "./types.js";
|
|
3
|
-
|
|
4
|
+
export interface RegisterCronConfig<K extends keyof JobRegistry> {
|
|
5
|
+
queueName: K;
|
|
6
|
+
schedulerId: string;
|
|
7
|
+
cronExpression: string;
|
|
8
|
+
params: JobRegistry[K]["params"];
|
|
9
|
+
handler: JobHandler<JobRegistry[K]["params"], JobRegistry[K]["result"]>;
|
|
10
|
+
options?: Omit<JobOptions, "delay" | "jobId"> & Omit<WorkerOptions, "connection">;
|
|
11
|
+
}
|
|
4
12
|
export interface IQueueProvider {
|
|
5
13
|
enqueue<K extends keyof JobRegistry>(
|
|
6
14
|
queueName: K,
|
|
@@ -17,11 +25,10 @@ export interface IQueueProvider {
|
|
|
17
25
|
registerWorker<K extends keyof JobRegistry>(
|
|
18
26
|
queueName: K,
|
|
19
27
|
handler: JobHandler<JobRegistry[K]["params"], JobRegistry[K]["result"]>,
|
|
20
|
-
options
|
|
21
|
-
concurrency?: number;
|
|
22
|
-
}
|
|
28
|
+
options?: unknown
|
|
23
29
|
): void;
|
|
24
|
-
|
|
30
|
+
registerCron<K extends keyof JobRegistry>(config: RegisterCronConfig<K>): Promise<void>;
|
|
31
|
+
removeCron<K extends keyof JobRegistry>(queueName: K, schedulerId: string): Promise<boolean>;
|
|
25
32
|
onJobComplete<K extends keyof JobRegistry>(
|
|
26
33
|
queueName: K,
|
|
27
34
|
callback: (jobId: string, result: JobRegistry[K]["result"]) => void
|
|
@@ -2,9 +2,9 @@ import type { UseQueryResult, UseMutationResult } from "@tanstack/react-query";
|
|
|
2
2
|
import type { z } from "zod";
|
|
3
3
|
import type { IPaginatedData, TimeString } from "../api-types";
|
|
4
4
|
|
|
5
|
-
export type ExtractData<E> = "data" extends keyof E ? E["data"] : unknown;
|
|
6
|
-
export type ExtractZQuery<E> = "zQuery" extends keyof E ? E["zQuery"] : never;
|
|
7
|
-
export type ExtractZParams<E> = "zParams" extends keyof E ? E["zParams"] : never;
|
|
5
|
+
export type ExtractData<E> = "data" extends keyof E ? Exclude<E["data"], undefined> : unknown;
|
|
6
|
+
export type ExtractZQuery<E> = "zQuery" extends keyof E ? NonNullable<E["zQuery"]> : never;
|
|
7
|
+
export type ExtractZParams<E> = "zParams" extends keyof E ? NonNullable<E["zParams"]> : never;
|
|
8
8
|
export type ExtractZBody<E> = "zBody" extends keyof E ? NonNullable<E["zBody"]> : never;
|
|
9
9
|
|
|
10
10
|
export type WithAbort<T> = T & { abort: () => void };
|
|
@@ -126,7 +126,6 @@ function useFetchApi<ID, ZQ extends ZQuery, ZP extends ZParams>(
|
|
|
126
126
|
abort: () => queryClient.cancelQueries({ queryKey }),
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
|
-
|
|
130
129
|
function usePaginatedFetchApi<ID, IO, ZQ extends ZQuery, ZP extends ZParams>(
|
|
131
130
|
key: keyof RawSchema,
|
|
132
131
|
endpoint: IPaginatedFetchOptions<ID, IO, ZQ, ZP>,
|
|
@@ -139,7 +138,7 @@ function usePaginatedFetchApi<ID, IO, ZQ extends ZQuery, ZP extends ZParams>(
|
|
|
139
138
|
staleTime?: number;
|
|
140
139
|
enabled?: boolean;
|
|
141
140
|
}
|
|
142
|
-
): UseQueryResult<
|
|
141
|
+
): UseQueryResult<IPaginatedData<ID, IO>> & { abort: () => void } {
|
|
143
142
|
const queryClient = useQueryClient();
|
|
144
143
|
const mergedQuery = useMemo(
|
|
145
144
|
() =>
|
|
@@ -161,10 +160,13 @@ function usePaginatedFetchApi<ID, IO, ZQ extends ZQuery, ZP extends ZParams>(
|
|
|
161
160
|
|
|
162
161
|
const staleTime = ApiHelper.parseTime(ApiHelper.merge(options.staleTime, endpoint.staleTime));
|
|
163
162
|
|
|
164
|
-
const query = useQuery<
|
|
163
|
+
const query = useQuery<IPaginatedData<ID, IO>>({
|
|
165
164
|
queryKey,
|
|
166
165
|
queryFn: async ({ signal }) => {
|
|
167
|
-
const
|
|
166
|
+
const resp = await axiosClient.request<{
|
|
167
|
+
message: string;
|
|
168
|
+
data: IPaginatedData<ID, IO>;
|
|
169
|
+
}>({
|
|
168
170
|
method: endpoint.method || "GET",
|
|
169
171
|
url: ApiHelper.buildUrl({
|
|
170
172
|
route: endpoint.route,
|
|
@@ -172,13 +174,9 @@ function usePaginatedFetchApi<ID, IO, ZQ extends ZQuery, ZP extends ZParams>(
|
|
|
172
174
|
searchParams: mergedQuery,
|
|
173
175
|
}),
|
|
174
176
|
signal,
|
|
175
|
-
};
|
|
177
|
+
});
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
data: IPaginatedData<ID, IO>;
|
|
179
|
-
}>(config);
|
|
180
|
-
|
|
181
|
-
return resp.data;
|
|
179
|
+
return resp.data.data;
|
|
182
180
|
},
|
|
183
181
|
staleTime,
|
|
184
182
|
enabled: options.enabled ?? endpoint.enable?.isEnable ?? true,
|
|
@@ -188,13 +186,14 @@ function usePaginatedFetchApi<ID, IO, ZQ extends ZQuery, ZP extends ZParams>(
|
|
|
188
186
|
refetchIntervalInBackground: endpoint.refetch?.interval?.inBackground,
|
|
189
187
|
refetchOnMount: endpoint.refetch?.onMount ? "always" : false,
|
|
190
188
|
refetchOnWindowFocus: endpoint.refetch?.onFocus ? "always" : false,
|
|
191
|
-
}
|
|
189
|
+
});
|
|
192
190
|
|
|
193
191
|
return {
|
|
194
192
|
...query,
|
|
195
193
|
abort: () => queryClient.cancelQueries({ queryKey }),
|
|
196
194
|
};
|
|
197
195
|
}
|
|
196
|
+
|
|
198
197
|
function useMutationApi<Schema extends RawSchema, K extends keyof Schema & string>(
|
|
199
198
|
endpoint: IFetchMutationOptions<K>,
|
|
200
199
|
axiosClient: AxiosInstance,
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -75,8 +75,8 @@ catalog:
|
|
|
75
75
|
"@types/morgan": "^1.9.10"
|
|
76
76
|
|
|
77
77
|
# db
|
|
78
|
-
drizzle-orm: "^0.
|
|
79
|
-
drizzle-kit: "^0.
|
|
78
|
+
drizzle-orm: "^1.0.0-beta.22"
|
|
79
|
+
drizzle-kit: "^1.0.0-beta.22"
|
|
80
80
|
pg: "^8.19.0"
|
|
81
81
|
better-sqlite3: "^12.9.0"
|
|
82
82
|
|
|
@@ -102,6 +102,8 @@ catalog:
|
|
|
102
102
|
|
|
103
103
|
# bullmq
|
|
104
104
|
bullmq: "^5.76.4"
|
|
105
|
+
"@bull-board/express": "^7.1.5"
|
|
106
|
+
"@bull-board/api": "^7.1.5"
|
|
105
107
|
|
|
106
108
|
# redis
|
|
107
109
|
ioredis: "^5.10.1"
|
package/turbo.json
CHANGED
|
@@ -69,6 +69,26 @@
|
|
|
69
69
|
"cache": false,
|
|
70
70
|
"interactive": true
|
|
71
71
|
},
|
|
72
|
+
"db:migrate": {
|
|
73
|
+
"cache": false,
|
|
74
|
+
"interactive": true
|
|
75
|
+
},
|
|
76
|
+
"db:up": {
|
|
77
|
+
"cache": false,
|
|
78
|
+
"interactive": true
|
|
79
|
+
},
|
|
80
|
+
"db:check": {
|
|
81
|
+
"cache": false,
|
|
82
|
+
"interactive": true
|
|
83
|
+
},
|
|
84
|
+
"db:pull": {
|
|
85
|
+
"cache": false,
|
|
86
|
+
"interactive": true
|
|
87
|
+
},
|
|
88
|
+
"db:generate": {
|
|
89
|
+
"cache": false,
|
|
90
|
+
"interactive": true
|
|
91
|
+
},
|
|
72
92
|
"db:studio": {
|
|
73
93
|
"cache": false,
|
|
74
94
|
"interactive": false,
|