mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-09-11 15:26:19 +03:00
Rewrite internal jobs
API (#1722)
This commit is contained in:
parent
03d234fd7d
commit
b873df960d
@ -74,7 +74,7 @@
|
||||
"./server/jobs/*": "./dist/server/jobs/*.js",
|
||||
{=! Used by our code, uncodumented (but accessible) for users. =}
|
||||
{=! Todo(filip): This export becomes problematic once we start supporting different executors =}
|
||||
"./server/jobs/pgBoss/types": "./dist/server/jobs/pgBoss/types.js",
|
||||
"./server/jobs/core/pgBoss": "./dist/server/jobs/core/pgBoss/index.js",
|
||||
|
||||
{=! Used by the framework client code, reconsider during refactoring. =}
|
||||
"./client/webSocket/WebSocketProvider": "./dist/client/webSocket/WebSocketProvider.jsx",
|
||||
|
@ -1,14 +1,21 @@
|
||||
{{={= =}=}}
|
||||
import { prisma } from 'wasp/server'
|
||||
import type { JSONValue, JSONObject } from 'wasp/server/_types/serialization'
|
||||
import { type JobFn } from '{= jobExecutorTypesImportPath =}'
|
||||
import { type JobFn, createJobDefinition } from '{= jobExecutorImportPath =}'
|
||||
|
||||
// PRIVATE API
|
||||
export const entities = {
|
||||
const entities = {
|
||||
{=# entities =}
|
||||
{= name =}: prisma.{= prismaIdentifier =},
|
||||
{=/ entities =}
|
||||
};
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
export type {= typeName =}<Input extends JSONObject, Output extends JSONValue | void> = JobFn<Input, Output, typeof entities>
|
||||
|
||||
// PUBLIC API
|
||||
export const {= jobName =} = createJobDefinition({
|
||||
jobName: '{= jobName =}',
|
||||
defaultJobOptions: {=& jobPerformOptions =},
|
||||
jobSchedule: {=& jobSchedule =},
|
||||
entities,
|
||||
})
|
@ -0,0 +1,3 @@
|
||||
export { type JobFn } from './types'
|
||||
export { registerJob, createJobDefinition } from './pgBossJob.js'
|
||||
export { startPgBoss } from './pgBoss.js'
|
@ -24,6 +24,7 @@ function createPgBoss() {
|
||||
|
||||
let resolvePgBossStarted: (boss: PgBoss) => void
|
||||
let rejectPgBossStarted: (boss: PgBoss) => void
|
||||
// PRIVATE API
|
||||
// Code that wants to access pg-boss must wait until it has been started.
|
||||
export const pgBossStarted = new Promise<PgBoss>((resolve, reject) => {
|
||||
resolvePgBossStarted = resolve
|
||||
@ -39,6 +40,7 @@ enum PgBossStatus {
|
||||
|
||||
let pgBossStatus: PgBossStatus = PgBossStatus.Unstarted
|
||||
|
||||
// PRIVATE API
|
||||
/**
|
||||
* Prepares the target PostgreSQL database and begins job monitoring.
|
||||
* If the required database objects do not exist in the specified database,
|
@ -3,40 +3,61 @@ import { pgBossStarted } from './pgBoss.js'
|
||||
import { Job, SubmittedJob } from '../job.js'
|
||||
import type { JSONValue, JSONObject } from 'wasp/server/_types/serialization'
|
||||
import { PrismaDelegate } from 'wasp/server/_types'
|
||||
import type { JobFn } from 'wasp/server/jobs/pgBoss/types'
|
||||
import type { JobFn } from 'wasp/server/jobs/core/pgBoss'
|
||||
|
||||
export const PG_BOSS_EXECUTOR_NAME = Symbol('PgBoss')
|
||||
|
||||
type JobSchedule = {
|
||||
cron: Parameters<PgBoss['schedule']>[1]
|
||||
args: Parameters<PgBoss['schedule']>[2]
|
||||
options: Parameters<PgBoss['schedule']>[3]
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
/**
|
||||
* Creates an instance of PgBossJob and initializes the PgBoss executor by registering this job function.
|
||||
* We expect this to be called once per job name. If called multiple times with the same name and different
|
||||
* functions, we will override the previous calls.
|
||||
* Creates an instance of PgBossJob which contains all of the necessary
|
||||
* information to submit a job to pg-boss.
|
||||
*/
|
||||
export function createJob<
|
||||
export function createJobDefinition<
|
||||
Input extends JSONObject,
|
||||
Output extends JSONValue | void,
|
||||
Entities extends Partial<PrismaDelegate>
|
||||
>({
|
||||
jobName,
|
||||
jobFn,
|
||||
defaultJobOptions,
|
||||
jobSchedule,
|
||||
entities,
|
||||
}: {
|
||||
// jobName - The user-defined job name in their .wasp file.
|
||||
jobName: Parameters<PgBoss['schedule']>[0]
|
||||
// jobFn - The user-defined async job callback function.
|
||||
jobFn: JobFn<Input, Output, Entities>
|
||||
// defaultJobOptions - pg-boss specific options for `boss.send()` applied to every `submit()` invocation,
|
||||
// which can overriden in that call.
|
||||
defaultJobOptions: PgBoss.Schedule['options']
|
||||
jobSchedule?: {
|
||||
cron: Parameters<PgBoss['schedule']>[1]
|
||||
args: Parameters<PgBoss['schedule']>[2]
|
||||
options: Parameters<PgBoss['schedule']>[3]
|
||||
}
|
||||
jobSchedule: JobSchedule | null
|
||||
// Entities used by job, passed into callback context.
|
||||
entities: Entities
|
||||
}) {
|
||||
return new PgBossJob<Input, Output, Entities>(
|
||||
jobName,
|
||||
defaultJobOptions,
|
||||
entities,
|
||||
jobSchedule,
|
||||
)
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
/**
|
||||
* Uses the info about a job in PgBossJob to register a user defined job handler with pg-boss.
|
||||
* We expect this to be called once per job name. If called multiple times with the same name and different
|
||||
* functions, we will override the previous calls.
|
||||
*/
|
||||
export function registerJob<
|
||||
Input extends JSONObject,
|
||||
Output extends JSONValue | void,
|
||||
Entities extends Partial<PrismaDelegate>
|
||||
>({ job, jobFn }: {
|
||||
job: PgBossJob<Input, Output, Entities>,
|
||||
jobFn: JobFn<Input, Output, Entities>,
|
||||
}) {
|
||||
// NOTE(shayne): We are not awaiting `pgBossStarted` here since we need to return an instance to the job
|
||||
// template, or else the NodeJS module bootstrapping process will block and fail as it would then depend
|
||||
@ -49,33 +70,31 @@ export function createJob<
|
||||
pgBossStarted.then(async (boss) => {
|
||||
// As a safety precaution against undefined behavior of registering different
|
||||
// functions for the same job name, remove all registered functions first.
|
||||
await boss.offWork(jobName)
|
||||
await boss.offWork(job.jobName)
|
||||
|
||||
// This tells pg-boss to run given worker function when job with that name is submitted.
|
||||
// Ref: https://github.com/timgit/pg-boss/blob/master/docs/readme.md#work
|
||||
await boss.work<Input, Output>(
|
||||
jobName,
|
||||
pgBossCallbackWrapper<Input, Output, Entities>(jobFn, entities)
|
||||
job.jobName,
|
||||
pgBossCallbackWrapper<Input, Output, Entities>(jobFn, job.entities)
|
||||
)
|
||||
|
||||
// If a job schedule is provided, we should schedule the recurring job.
|
||||
// If the schedule name already exists, it's updated to the provided cron expression, arguments, and options.
|
||||
// Ref: https://github.com/timgit/pg-boss/blob/master/docs/readme.md#scheduling
|
||||
if (jobSchedule) {
|
||||
if (job.jobSchedule) {
|
||||
const options: PgBoss.ScheduleOptions = {
|
||||
...defaultJobOptions,
|
||||
...jobSchedule.options,
|
||||
...job.defaultJobOptions,
|
||||
...job.jobSchedule.options,
|
||||
}
|
||||
await boss.schedule(
|
||||
jobName,
|
||||
jobSchedule.cron,
|
||||
jobSchedule.args || null,
|
||||
job.jobName,
|
||||
job.jobSchedule.cron,
|
||||
job.jobSchedule.args || null,
|
||||
options
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return new PgBossJob<Input, Output>(jobName, defaultJobOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,24 +104,33 @@ export function createJob<
|
||||
*/
|
||||
class PgBossJob<
|
||||
Input extends JSONObject,
|
||||
Output extends JSONValue | void
|
||||
Output extends JSONValue | void,
|
||||
Entities extends Partial<PrismaDelegate>
|
||||
> extends Job {
|
||||
public readonly defaultJobOptions: Parameters<PgBoss['send']>[2]
|
||||
public readonly startAfter: number | string | Date
|
||||
public readonly entities: Entities
|
||||
public readonly jobSchedule: JobSchedule | null
|
||||
|
||||
constructor(
|
||||
jobName: string,
|
||||
defaultJobOptions: Parameters<PgBoss['send']>[2],
|
||||
entities: Entities,
|
||||
jobSchedule: JobSchedule | null,
|
||||
startAfter?: number | string | Date
|
||||
) {
|
||||
super(jobName, PG_BOSS_EXECUTOR_NAME)
|
||||
this.defaultJobOptions = defaultJobOptions
|
||||
this.entities = entities
|
||||
this.jobSchedule = jobSchedule
|
||||
this.startAfter = startAfter
|
||||
}
|
||||
delay(startAfter: number | string | Date) {
|
||||
return new PgBossJob<Input, Output>(
|
||||
return new PgBossJob<Input, Output, Entities>(
|
||||
this.jobName,
|
||||
this.defaultJobOptions,
|
||||
this.entities,
|
||||
this.jobSchedule,
|
||||
startAfter
|
||||
)
|
||||
}
|
||||
@ -113,7 +141,7 @@ class PgBossJob<
|
||||
...(this.startAfter && { startAfter: this.startAfter }),
|
||||
...jobOptions,
|
||||
})
|
||||
return new PgBossSubmittedJob<Input, Output>(boss, this, jobId)
|
||||
return new PgBossSubmittedJob<Input, Output, Entities>(boss, this, jobId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,7 +150,8 @@ class PgBossJob<
|
||||
*/
|
||||
class PgBossSubmittedJob<
|
||||
Input extends JSONObject,
|
||||
Output extends JSONValue | void
|
||||
Output extends JSONValue | void,
|
||||
Entities extends Partial<PrismaDelegate>
|
||||
> extends SubmittedJob {
|
||||
public readonly pgBoss: {
|
||||
readonly cancel: () => ReturnType<PgBoss['cancel']>
|
||||
@ -132,7 +161,7 @@ class PgBossSubmittedJob<
|
||||
|
||||
constructor(
|
||||
boss: PgBoss,
|
||||
job: PgBossJob<Input, Output>,
|
||||
job: PgBossJob<Input, Output, Entities>,
|
||||
jobId: SubmittedJob['jobId']
|
||||
) {
|
||||
super(job, jobId)
|
@ -1,5 +1,5 @@
|
||||
{{={= =}=}}
|
||||
|
||||
{=# jobs =}
|
||||
export type { {= typeName =} } from './{= jobName =}'
|
||||
export { type {= typeName =}, {= jobName =} } from './{= jobName =}.js'
|
||||
{=/ jobs =}
|
||||
|
@ -1,12 +1,9 @@
|
||||
{{={= =}=}}
|
||||
import { createJob } from './{= jobExecutorRelativePath =}'
|
||||
{=& jobEntitiesImportStatement =}
|
||||
{=& jobPerformFnImportStatement =}
|
||||
import { registerJob } from '{= jobExecutorImportPath =}'
|
||||
{=& jobPerformFn.importStatement =}
|
||||
{=& jobDefinition.importStatement =}
|
||||
|
||||
export const {= jobName =} = createJob({
|
||||
jobName: "{= jobName =}",
|
||||
jobFn: {= jobPerformFnName =},
|
||||
defaultJobOptions: {=& jobPerformOptions =},
|
||||
jobSchedule: {=& jobSchedule =},
|
||||
{= jobEntitiesIdentifier =},
|
||||
registerJob({
|
||||
job: {= jobDefinition.importIdentifier =},
|
||||
jobFn: {= jobPerformFn.importIdentifier =},
|
||||
})
|
||||
|
@ -1,8 +1,8 @@
|
||||
{{={= =}=}}
|
||||
|
||||
// This module exports all jobs and is imported by the server to ensure
|
||||
// This module imports all jobs and is imported by the server to ensure
|
||||
// any schedules that are not referenced are still loaded by NodeJS.
|
||||
|
||||
{=# jobs =}
|
||||
export { {= name =} } from '../{= name =}.js'
|
||||
import '../{= name =}.js'
|
||||
{=/ jobs =}
|
@ -11,7 +11,7 @@ import { ServerSetupFnContext } from 'wasp/server/types'
|
||||
{=/ setupFn.isDefined =}
|
||||
|
||||
{=# isPgBossJobExecutorUsed =}
|
||||
import { startPgBoss } from './jobs/core/pgBoss/pgBoss.js'
|
||||
import { startPgBoss } from 'wasp/server/jobs/core/pgBoss'
|
||||
import './jobs/core/allJobs.js'
|
||||
{=/ isPgBossJobExecutorUsed =}
|
||||
|
||||
|
@ -151,7 +151,7 @@ action deleteTasks {
|
||||
entities: [Task],
|
||||
}
|
||||
|
||||
job PrintTimeAndNumberOfTasks {
|
||||
job printTimeAndNumberOfTasks {
|
||||
executor: PgBoss,
|
||||
perform: {
|
||||
fn: import { printTimeAndNumberOfTasks } from "@src/jobs/print"
|
||||
|
271
waspc/examples/todo-typescript/package-lock.json
generated
271
waspc/examples/todo-typescript/package-lock.json
generated
@ -40,6 +40,7 @@
|
||||
"lucia": "^3.0.0-beta.14",
|
||||
"mitt": "3.0.0",
|
||||
"msw": "^1.1.0",
|
||||
"pg-boss": "^8.4.2",
|
||||
"prisma": "4.16.2",
|
||||
"react": "^18.2.0",
|
||||
"react-hook-form": "^7.45.4",
|
||||
@ -1425,6 +1426,18 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/aggregate-error": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
|
||||
"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
|
||||
"dependencies": {
|
||||
"clean-stack": "^2.0.0",
|
||||
"indent-string": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||
@ -1645,6 +1658,14 @@
|
||||
"version": "1.0.1",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/buffer-writer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"license": "MIT",
|
||||
@ -1743,6 +1764,14 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-stack": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
|
||||
@ -1888,6 +1917,17 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/cron-parser": {
|
||||
"version": "4.9.0",
|
||||
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
|
||||
"integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
|
||||
"dependencies": {
|
||||
"luxon": "^3.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@ -2032,6 +2072,17 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delay": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz",
|
||||
"integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
@ -3824,6 +3875,11 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"license": "MIT"
|
||||
@ -3911,6 +3967,14 @@
|
||||
"oslo": "^0.27.0"
|
||||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
||||
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/lz-string": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
@ -4383,6 +4447,25 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-map": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
|
||||
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
|
||||
"dependencies": {
|
||||
"aggregate-error": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/packet-reader": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
|
||||
@ -4426,6 +4509,106 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/pg": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
|
||||
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
|
||||
"dependencies": {
|
||||
"buffer-writer": "2.0.0",
|
||||
"packet-reader": "1.0.0",
|
||||
"pg-connection-string": "^2.6.2",
|
||||
"pg-pool": "^3.6.1",
|
||||
"pg-protocol": "^1.6.0",
|
||||
"pg-types": "^2.1.0",
|
||||
"pgpass": "1.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"pg-cloudflare": "^1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pg-native": ">=3.0.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"pg-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pg-boss": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/pg-boss/-/pg-boss-8.4.2.tgz",
|
||||
"integrity": "sha512-xcl/G8C7qlCyrcvlQvgLVBIe68zO0XfZc6K86/G9fq/mL+YQMEo1spW6lHqsPpNi2KGlpXwBEL/XZxkMa19eRA==",
|
||||
"dependencies": {
|
||||
"cron-parser": "^4.0.0",
|
||||
"delay": "^5.0.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"p-map": "^4.0.0",
|
||||
"pg": "^8.5.1",
|
||||
"serialize-error": "^8.1.0",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-cloudflare": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
||||
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/pg-connection-string": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
|
||||
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA=="
|
||||
},
|
||||
"node_modules/pg-int8": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-pool": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
|
||||
"integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==",
|
||||
"peerDependencies": {
|
||||
"pg": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-protocol": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
|
||||
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
|
||||
},
|
||||
"node_modules/pg-types": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||
"dependencies": {
|
||||
"pg-int8": "1.0.1",
|
||||
"postgres-array": "~2.0.0",
|
||||
"postgres-bytea": "~1.0.0",
|
||||
"postgres-date": "~1.0.4",
|
||||
"postgres-interval": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pgpass": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||
"dependencies": {
|
||||
"split2": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"license": "ISC"
|
||||
@ -4477,6 +4660,41 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-array": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-bytea": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-date": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-interval": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||
"dependencies": {
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-format": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
@ -4933,6 +5151,31 @@
|
||||
"version": "2.1.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/serialize-error": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz",
|
||||
"integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==",
|
||||
"dependencies": {
|
||||
"type-fest": "^0.20.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/serialize-error/node_modules/type-fest": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
||||
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.15.0",
|
||||
"license": "MIT",
|
||||
@ -5203,6 +5446,14 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
@ -5535,6 +5786,18 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/value-equal": {
|
||||
"version": "1.0.1",
|
||||
"license": "MIT"
|
||||
@ -6186,6 +6449,14 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
@ -6,6 +6,7 @@ import type {
|
||||
} from 'wasp/server/operations'
|
||||
import type { Task } from 'wasp/entities'
|
||||
import { emailSender } from 'wasp/server/email'
|
||||
import { printTimeAndNumberOfTasks } from 'wasp/server/jobs'
|
||||
|
||||
type CreateArgs = Pick<Task, 'description'>
|
||||
|
||||
@ -17,6 +18,9 @@ export const createTask: CreateTask<CreateArgs, Task> = async (
|
||||
throw new HttpError(401)
|
||||
}
|
||||
|
||||
console.log("Executing 'printTimeAndNumberOfTasks' task.")
|
||||
await printTimeAndNumberOfTasks.submit({})
|
||||
|
||||
emailSender.send({
|
||||
to: 'test@test.com',
|
||||
from: {
|
||||
|
@ -47,7 +47,7 @@ import Wasp.Generator.SdkGenerator.CrudG (genCrud)
|
||||
import Wasp.Generator.SdkGenerator.Server.AuthG (genNewServerApi)
|
||||
import Wasp.Generator.SdkGenerator.Server.CrudG (genNewServerCrudApi)
|
||||
import Wasp.Generator.SdkGenerator.Server.EmailSenderG (depsRequiredByEmail, genNewEmailSenderApi)
|
||||
import Wasp.Generator.SdkGenerator.Server.JobGenerator (genNewJobsApi)
|
||||
import Wasp.Generator.SdkGenerator.Server.JobGenerator (depsRequiredByJobs, genNewJobsApi)
|
||||
import qualified Wasp.Generator.SdkGenerator.Server.OperationsGenerator as ServerOpsGen
|
||||
import Wasp.Generator.SdkGenerator.ServerApiG (genServerApi)
|
||||
import Wasp.Generator.SdkGenerator.WebSocketGenerator (depsRequiredByWebSockets, genWebSockets)
|
||||
@ -86,7 +86,6 @@ genSdkReal spec =
|
||||
genFileCopy [relfile|server/index.ts|],
|
||||
genFileCopy [relfile|server/HttpError.ts|],
|
||||
genFileCopy [relfile|types/index.ts|],
|
||||
genFileCopy [relfile|server/jobs/pgBoss/types.ts|],
|
||||
genFileCopy [relfile|client/test/vitest/helpers.tsx|],
|
||||
genFileCopy [relfile|client/test/index.ts|],
|
||||
genServerConfigFile spec,
|
||||
@ -224,7 +223,8 @@ npmDepsForSdk spec =
|
||||
++ ServerAuthG.depsRequiredByAuth spec
|
||||
++ depsRequiredByEmail spec
|
||||
++ depsRequiredByWebSockets spec
|
||||
++ depsRequiredForTesting,
|
||||
++ depsRequiredForTesting
|
||||
++ depsRequiredByJobs spec,
|
||||
N.devDependencies =
|
||||
AS.Dependency.fromList
|
||||
[ ("@tsconfig/node" <> majorNodeVersionStr, "latest")
|
||||
|
@ -1,24 +1,33 @@
|
||||
module Wasp.Generator.SdkGenerator.Server.JobGenerator
|
||||
( genNewJobsApi,
|
||||
getImportPathForJobName,
|
||||
getJobExecutorTypesImportPath,
|
||||
genJobExecutors,
|
||||
depsRequiredByJobs,
|
||||
getJobExecutorImportPath,
|
||||
getImportJsonForJobDefinition,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson (object, (.=))
|
||||
import Data.Maybe (fromJust)
|
||||
import qualified Data.Aeson as Aeson
|
||||
import qualified Data.Aeson.Text as Aeson.Text
|
||||
import Data.Maybe (fromJust, fromMaybe)
|
||||
import StrongPath (File', Path, Posix, Rel, reldir, relfile, relfileP, (</>))
|
||||
import qualified StrongPath as SP
|
||||
import StrongPath.TH (reldirP)
|
||||
import Wasp.AppSpec (AppSpec, getJobs)
|
||||
import qualified Wasp.AppSpec as AS
|
||||
import Wasp.AppSpec.Job (Job, JobExecutor (PgBoss))
|
||||
import qualified Wasp.AppSpec.App.Dependency as AS.Dependency
|
||||
import qualified Wasp.AppSpec.JSON as AS.JSON
|
||||
import Wasp.AppSpec.Job (Job, JobExecutor (PgBoss), jobExecutors)
|
||||
import qualified Wasp.AppSpec.Job as J
|
||||
import Wasp.AppSpec.Util (isPgBossJobExecutorUsed)
|
||||
import Wasp.Generator.Common (makeJsonWithEntityData)
|
||||
import Wasp.Generator.FileDraft (FileDraft)
|
||||
import qualified Wasp.Generator.JsImport as GJI
|
||||
import Wasp.Generator.Monad (Generator)
|
||||
import Wasp.Generator.SdkGenerator.Common (makeSdkImportPath)
|
||||
import qualified Wasp.Generator.SdkGenerator.Common as C
|
||||
import qualified Wasp.JsImport as JI
|
||||
import qualified Wasp.SemanticVersion as SV
|
||||
import Wasp.Util
|
||||
|
||||
genNewJobsApi :: AppSpec -> Generator [FileDraft]
|
||||
@ -29,7 +38,8 @@ genNewJobsApi spec =
|
||||
sequence
|
||||
[ genIndexTs jobs
|
||||
]
|
||||
<++> mapM genJobType jobs
|
||||
<++> mapM genJob jobs
|
||||
<++> genJobExecutors spec
|
||||
|
||||
genIndexTs :: [(String, Job)] -> Generator FileDraft
|
||||
genIndexTs jobs = return $ C.mkTmplFdWithData tmplFile tmplData
|
||||
@ -42,29 +52,81 @@ genIndexTs jobs = return $ C.mkTmplFdWithData tmplFile tmplData
|
||||
"jobName" .= jobName
|
||||
]
|
||||
|
||||
genJobType :: (String, Job) -> Generator FileDraft
|
||||
genJobType (jobName, job) =
|
||||
genJob :: (String, Job) -> Generator FileDraft
|
||||
genJob (jobName, job) =
|
||||
return $
|
||||
C.mkTmplFdWithDstAndData
|
||||
tmplFile
|
||||
dstFile
|
||||
$ Just tmplData
|
||||
where
|
||||
tmplFile = [relfile|server/jobs/_jobTypes.ts|]
|
||||
tmplFile = [relfile|server/jobs/_job.ts|]
|
||||
dstFile = [reldir|server/jobs|] </> fromJust (SP.parseRelFile $ jobName ++ ".ts")
|
||||
tmplData =
|
||||
object
|
||||
[ "typeName" .= toUpperFirst jobName,
|
||||
"jobExecutorTypesImportPath" .= SP.fromRelFileP jobExecutorTypesImportPath,
|
||||
"entities" .= maybe [] (map (makeJsonWithEntityData . AS.refName)) (J.entities job)
|
||||
[ "jobName" .= jobName,
|
||||
"typeName" .= toUpperFirst jobName,
|
||||
"jobExecutorImportPath" .= SP.fromRelFileP jobExecutorImportPath,
|
||||
"entities" .= maybe [] (map (makeJsonWithEntityData . AS.refName)) (J.entities job),
|
||||
-- NOTE: You cannot directly input an Aeson.object for Mustache to substitute.
|
||||
-- This is why we must get a text representation of the object, either by
|
||||
-- `Aeson.Text.encodeToLazyText` on an Aeson.Object, or `show` on an AS.JSON.
|
||||
"jobSchedule" .= Aeson.Text.encodeToLazyText (fromMaybe Aeson.Null maybeJobSchedule),
|
||||
"jobPerformOptions" .= show (fromMaybe AS.JSON.emptyObject maybeJobPerformOptions)
|
||||
]
|
||||
|
||||
jobExecutorTypesImportPath = getJobExecutorTypesImportPath (J.executor job)
|
||||
jobExecutorImportPath = getJobExecutorImportPath (J.executor job)
|
||||
|
||||
getImportPathForJobName :: String -> Path Posix (Rel d) File'
|
||||
getImportPathForJobName jobName = makeSdkImportPath $ [reldirP|server/jobs|] </> fromJust (SP.parseRelFileP jobName)
|
||||
maybeJobPerformOptions = J.performExecutorOptionsJson job
|
||||
jobScheduleTmplData s =
|
||||
object
|
||||
[ "cron" .= J.cron s,
|
||||
"args" .= J.args s,
|
||||
"options" .= fromMaybe AS.JSON.emptyObject (J.scheduleExecutorOptionsJson job)
|
||||
]
|
||||
maybeJobSchedule = jobScheduleTmplData <$> J.schedule job
|
||||
|
||||
-- | We are importing relevant types per executor e.g. JobFn, this functions maps
|
||||
-- the executor to the import path of the relevant types.
|
||||
getJobExecutorTypesImportPath :: JobExecutor -> Path Posix (Rel r) File'
|
||||
getJobExecutorTypesImportPath PgBoss = makeSdkImportPath [relfileP|server/jobs/pgBoss/types|]
|
||||
-- | We are importing relevant functions and types per executor e.g. JobFn or registerJob,
|
||||
-- this functions maps the executor to the import path from SDK.
|
||||
getJobExecutorImportPath :: JobExecutor -> Path Posix (Rel r) File'
|
||||
getJobExecutorImportPath PgBoss = makeSdkImportPath [relfileP|server/jobs/core/pgBoss|]
|
||||
|
||||
getImportJsonForJobDefinition :: String -> Aeson.Value
|
||||
getImportJsonForJobDefinition jobName =
|
||||
GJI.jsImportToImportJson $
|
||||
Just $
|
||||
JI.JsImport
|
||||
{ JI._path = JI.ModuleImportPath $ makeSdkImportPath [relfileP|server/jobs|],
|
||||
JI._name = JI.JsImportField jobName,
|
||||
-- NOTE: We are using alias to avoid name conflicts with user defined imports.
|
||||
JI._importAlias = Just "_waspJobDefinition"
|
||||
}
|
||||
|
||||
genJobExecutors :: AppSpec -> Generator [FileDraft]
|
||||
genJobExecutors spec = case getJobs spec of
|
||||
[] -> return []
|
||||
_someJobs ->
|
||||
return $
|
||||
C.mkTmplFd [relfile|server/jobs/core/job.ts|] : genAllJobExecutors
|
||||
where
|
||||
genAllJobExecutors = concatMap genJobExecutor jobExecutors
|
||||
|
||||
-- Per each defined job executor, we generate the needed files.
|
||||
genJobExecutor :: JobExecutor -> [FileDraft]
|
||||
genJobExecutor PgBoss =
|
||||
[ C.mkTmplFd [relfile|server/jobs/core/pgBoss/pgBoss.ts|],
|
||||
C.mkTmplFd [relfile|server/jobs/core/pgBoss/pgBossJob.ts|],
|
||||
C.mkTmplFd [relfile|server/jobs/core/pgBoss/types.ts|],
|
||||
C.mkTmplFd [relfile|server/jobs/core/pgBoss/index.ts|]
|
||||
]
|
||||
|
||||
-- NOTE: Our pg-boss related documentation references this version in URLs.
|
||||
-- Please update the docs when this changes (until we solve: https://github.com/wasp-lang/wasp/issues/596).
|
||||
pgBossVersionRange :: SV.Range
|
||||
pgBossVersionRange = SV.Range [SV.backwardsCompatibleWith (SV.Version 8 4 2)]
|
||||
|
||||
pgBossDependency :: AS.Dependency.Dependency
|
||||
pgBossDependency = AS.Dependency.make ("pg-boss", show pgBossVersionRange)
|
||||
|
||||
depsRequiredByJobs :: AppSpec -> [AS.Dependency.Dependency]
|
||||
depsRequiredByJobs spec = [pgBossDependency | isPgBossJobExecutorUsed spec]
|
||||
|
@ -51,7 +51,7 @@ import Wasp.Generator.ServerGenerator.AuthG (genAuth)
|
||||
import qualified Wasp.Generator.ServerGenerator.Common as C
|
||||
import Wasp.Generator.ServerGenerator.CrudG (genCrud)
|
||||
import Wasp.Generator.ServerGenerator.Db.Seed (genDbSeed, getDbSeeds, getPackageJsonPrismaSeedField)
|
||||
import Wasp.Generator.ServerGenerator.JobGenerator (depsRequiredByJobs, genJobExecutors, genJobs)
|
||||
import Wasp.Generator.ServerGenerator.JobGenerator (genJobs)
|
||||
import Wasp.Generator.ServerGenerator.JsImport (extImportToImportJson, getAliasedJsImportStmtAndIdentifier)
|
||||
import Wasp.Generator.ServerGenerator.OperationsG (genOperations)
|
||||
import Wasp.Generator.ServerGenerator.OperationsRoutesG (genOperationsRoutes)
|
||||
@ -75,7 +75,6 @@ genServer spec =
|
||||
<++> genSrcDir spec
|
||||
<++> genDotEnv spec
|
||||
<++> genJobs spec
|
||||
<++> genJobExecutors spec
|
||||
<++> genPatches spec
|
||||
<++> genEnvValidationScript
|
||||
<++> genApis spec
|
||||
@ -164,7 +163,6 @@ npmDepsForWasp spec =
|
||||
("superjson", "^1.12.2")
|
||||
]
|
||||
++ depsRequiredByPassport spec
|
||||
++ depsRequiredByJobs spec
|
||||
++ depsRequiredByWebSockets spec,
|
||||
N.waspDevDependencies =
|
||||
AS.Dependency.fromList
|
||||
|
@ -1,19 +1,13 @@
|
||||
module Wasp.Generator.ServerGenerator.JobGenerator
|
||||
( genJobs,
|
||||
genJobExecutors,
|
||||
pgBossVersionRange,
|
||||
pgBossDependency,
|
||||
depsRequiredByJobs,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson (object, (.=))
|
||||
import qualified Data.Aeson as Aeson
|
||||
import qualified Data.Aeson.Text as Aeson.Text
|
||||
import Data.Maybe (fromJust, fromMaybe)
|
||||
import Data.Maybe (fromJust)
|
||||
import StrongPath
|
||||
( Dir,
|
||||
File',
|
||||
Path,
|
||||
Path',
|
||||
Posix,
|
||||
@ -21,81 +15,50 @@ import StrongPath
|
||||
reldir,
|
||||
reldirP,
|
||||
relfile,
|
||||
toFilePath,
|
||||
(</>),
|
||||
)
|
||||
import qualified StrongPath as SP
|
||||
import Wasp.AppSpec (AppSpec, getJobs)
|
||||
import qualified Wasp.AppSpec.App.Dependency as AS.Dependency
|
||||
import qualified Wasp.AppSpec.JSON as AS.JSON
|
||||
import Wasp.AppSpec.Job (Job, JobExecutor (PgBoss), jobExecutors)
|
||||
import Wasp.AppSpec.Job (Job)
|
||||
import qualified Wasp.AppSpec.Job as J
|
||||
import Wasp.AppSpec.Util (isPgBossJobExecutorUsed)
|
||||
import Wasp.Generator.Common (ServerRootDir)
|
||||
import Wasp.Generator.FileDraft (FileDraft)
|
||||
import Wasp.Generator.Monad (Generator)
|
||||
import Wasp.Generator.SdkGenerator.Server.JobGenerator (getImportPathForJobName, getJobExecutorTypesImportPath)
|
||||
import Wasp.Generator.SdkGenerator.Server.JobGenerator
|
||||
( getImportJsonForJobDefinition,
|
||||
getJobExecutorImportPath,
|
||||
)
|
||||
import Wasp.Generator.ServerGenerator.Common
|
||||
( ServerTemplatesDir,
|
||||
srcDirInServerTemplatesDir,
|
||||
)
|
||||
import qualified Wasp.Generator.ServerGenerator.Common as C
|
||||
import qualified Wasp.Generator.ServerGenerator.JsImport as SJI
|
||||
import Wasp.JsImport (JsImportName (JsImportField), JsImportPath (ModuleImportPath), makeJsImport)
|
||||
import qualified Wasp.JsImport as JI
|
||||
import qualified Wasp.SemanticVersion as SV
|
||||
import Wasp.Util (toUpperFirst)
|
||||
|
||||
genJobs :: AppSpec -> Generator [FileDraft]
|
||||
genJobs spec = case getJobs spec of
|
||||
[] -> return []
|
||||
jobs -> return $ genAllJobImports spec : (genJob <$> jobs)
|
||||
jobs -> return $ genAllJobImports spec : (genRegisterJob <$> jobs)
|
||||
|
||||
genJob :: (String, Job) -> FileDraft
|
||||
genJob (jobName, job) =
|
||||
genRegisterJob :: (String, Job) -> FileDraft
|
||||
genRegisterJob (jobName, job) =
|
||||
C.mkTmplFdWithDstAndData
|
||||
tmplFile
|
||||
dstFile
|
||||
( Just $
|
||||
object
|
||||
[ "jobName" .= jobName,
|
||||
"typeName" .= toUpperFirst jobName,
|
||||
"jobPerformFnName" .= jobPerformFnName,
|
||||
"jobPerformFnImportStatement" .= jobPerformFnImportStatement,
|
||||
-- NOTE: You cannot directly input an Aeson.object for Mustache to substitute.
|
||||
-- This is why we must get a text representation of the object, either by
|
||||
-- `Aeson.Text.encodeToLazyText` on an Aeson.Object, or `show` on an AS.JSON.
|
||||
"jobSchedule" .= Aeson.Text.encodeToLazyText (fromMaybe Aeson.Null maybeJobSchedule),
|
||||
"jobPerformOptions" .= show (fromMaybe AS.JSON.emptyObject maybeJobPerformOptions),
|
||||
"jobExecutorRelativePath" .= toFilePath (executorJobTemplateInJobsDir "js" (J.executor job)),
|
||||
"jobExecutorTypesImportPath" .= SP.fromRelFileP jobExecutorTypesImportPath,
|
||||
"jobEntitiesImportStatement" .= jobEntitiesImportStatement,
|
||||
"jobEntitiesIdentifier" .= jobEntitiesIdentifier
|
||||
[ "jobPerformFn" .= jobPerformFn,
|
||||
"jobExecutorImportPath" .= SP.fromRelFileP (getJobExecutorImportPath (J.executor job)),
|
||||
"jobDefinition" .= getImportJsonForJobDefinition jobName
|
||||
]
|
||||
)
|
||||
where
|
||||
tmplFile = C.asTmplFile $ jobsDirInServerTemplatesDir </> [relfile|_job.ts|]
|
||||
dstFile = jobsDirInServerRootDir </> fromJust (SP.parseRelFile $ jobName ++ ".ts")
|
||||
|
||||
-- Users import job types from the SDK, so the types for each job are generated
|
||||
-- separately and imported from the SDK.
|
||||
(jobEntitiesImportStatement, jobEntitiesIdentifier) =
|
||||
JI.getJsImportStmtAndIdentifier $
|
||||
makeJsImport (ModuleImportPath $ getImportPathForJobName jobName) (JsImportField "entities")
|
||||
|
||||
(jobPerformFnImportStatement, jobPerformFnName) =
|
||||
SJI.getJsImportStmtAndIdentifier relPathFromJobsDirToServerSrcDir $ (J.fn . J.perform) job
|
||||
|
||||
maybeJobPerformOptions = J.performExecutorOptionsJson job
|
||||
jobScheduleTmplData s =
|
||||
object
|
||||
[ "cron" .= J.cron s,
|
||||
"args" .= J.args s,
|
||||
"options" .= fromMaybe AS.JSON.emptyObject (J.scheduleExecutorOptionsJson job)
|
||||
]
|
||||
maybeJobSchedule = jobScheduleTmplData <$> J.schedule job
|
||||
|
||||
jobExecutorTypesImportPath = getJobExecutorTypesImportPath (J.executor job)
|
||||
jobPerformFn =
|
||||
SJI.extImportToImportJson relPathFromJobsDirToServerSrcDir $
|
||||
Just $ (J.fn . J.perform) job
|
||||
|
||||
relPathFromJobsDirToServerSrcDir :: Path Posix (Rel importLocation) (Dir C.ServerSrcDir)
|
||||
relPathFromJobsDirToServerSrcDir = [reldirP|../|]
|
||||
@ -104,7 +67,7 @@ genJob (jobName, job) =
|
||||
-- even if they are not referenced by user code. This ensures schedules are started, etc.
|
||||
genAllJobImports :: AppSpec -> FileDraft
|
||||
genAllJobImports spec =
|
||||
let tmplFile = C.asTmplFile $ jobsDirInServerTemplatesDir </> [relfile|core/_allJobs.ts|]
|
||||
let tmplFile = C.asTmplFile $ jobsDirInServerTemplatesDir </> [relfile|core/allJobs.ts|]
|
||||
dstFile = jobsDirInServerRootDir </> [relfile|core/allJobs.ts|]
|
||||
in C.mkTmplFdWithDstAndData
|
||||
tmplFile
|
||||
@ -115,50 +78,13 @@ genAllJobImports spec =
|
||||
)
|
||||
where
|
||||
buildJobInfo :: String -> Aeson.Value
|
||||
buildJobInfo jobName =
|
||||
object
|
||||
[ "name" .= jobName
|
||||
]
|
||||
|
||||
genJobExecutors :: AppSpec -> Generator [FileDraft]
|
||||
genJobExecutors spec = case getJobs spec of
|
||||
[] -> return []
|
||||
_someJobs -> return $ jobExecutorFds ++ jobExecutorHelperFds
|
||||
where
|
||||
jobExecutorFds :: [FileDraft]
|
||||
jobExecutorFds = genJobExecutor <$> jobExecutors
|
||||
|
||||
genJobExecutor :: JobExecutor -> FileDraft
|
||||
genJobExecutor jobExecutor = C.mkTmplFd $ executorJobTemplateInServerTemplatesDir jobExecutor
|
||||
|
||||
jobExecutorHelperFds :: [FileDraft]
|
||||
jobExecutorHelperFds =
|
||||
[ C.mkTmplFd $ jobsDirInServerTemplatesDir </> [relfile|core/pgBoss/pgBoss.ts|],
|
||||
C.mkTmplFd $ jobsDirInServerTemplatesDir </> [relfile|core/job.ts|]
|
||||
]
|
||||
|
||||
executorJobTemplateInServerTemplatesDir :: JobExecutor -> Path SP.System (Rel ServerTemplatesDir) File'
|
||||
executorJobTemplateInServerTemplatesDir = (jobsDirInServerTemplatesDir </>) . executorJobTemplateInJobsDir "ts"
|
||||
buildJobInfo jobName = object ["name" .= jobName]
|
||||
|
||||
data JobsDir
|
||||
|
||||
jobsDirInServerTemplatesDir :: Path' (Rel ServerTemplatesDir) (Dir JobsDir)
|
||||
jobsDirInServerTemplatesDir = srcDirInServerTemplatesDir </> [reldir|jobs|]
|
||||
|
||||
executorJobTemplateInJobsDir :: String -> JobExecutor -> Path' (Rel JobsDir) File'
|
||||
executorJobTemplateInJobsDir ext PgBoss = fromJust $ SP.parseRelFile $ "core/pgBoss/pgBossJob" <> "." <> ext
|
||||
|
||||
-- Path to destination files are the same as in templates dir.
|
||||
jobsDirInServerRootDir :: Path' (Rel ServerRootDir) (Dir JobsDir)
|
||||
jobsDirInServerRootDir = SP.castRel jobsDirInServerTemplatesDir
|
||||
|
||||
-- NOTE: Our pg-boss related documentation references this version in URLs.
|
||||
-- Please update the docs when this changes (until we solve: https://github.com/wasp-lang/wasp/issues/596).
|
||||
pgBossVersionRange :: SV.Range
|
||||
pgBossVersionRange = SV.Range [SV.backwardsCompatibleWith (SV.Version 8 4 2)]
|
||||
|
||||
pgBossDependency :: AS.Dependency.Dependency
|
||||
pgBossDependency = AS.Dependency.make ("pg-boss", show pgBossVersionRange)
|
||||
|
||||
depsRequiredByJobs :: AppSpec -> [AS.Dependency.Dependency]
|
||||
depsRequiredByJobs spec = [pgBossDependency | isPgBossJobExecutorUsed spec]
|
||||
|
Loading…
Reference in New Issue
Block a user