mirror of
https://github.com/pockethost/pockethost.git
synced 2025-03-30 15:08:30 +00:00
refactor: rpc commands
This commit is contained in:
parent
c0b50bef94
commit
f20bd11f91
@ -10,9 +10,9 @@ Therefore, PocketHost uses an RPC pattern instead.
|
||||
|
||||
## Creating a new RPC Call
|
||||
|
||||
1. Create a new RPC call in `./packages/common/schema/Rpc`
|
||||
2. Add frontend support in `./packages/pockethost.io/src/pocketbase/PocketbaseClient.ts` using the `mkRpc` command
|
||||
3. Add backend support in (for example) `./packages/daemon/src/services/InstanceService/InstanceService.ts` using `registerCommand`
|
||||
1. From the command line, run `npx hygen rpc new <FunctionName>`. This will generate all necessary files for both frontend and backend support of your RPC call.
|
||||
2. Edit `./packages/common/src/schema/Rpc/<FunctionName>.ts` to suit the schema you want.
|
||||
3. Edit `./packages/daemon/services/RpcService/commands.ts` to respond to the RPC command
|
||||
|
||||
## Getting the result from an RPC call
|
||||
|
||||
|
@ -102,6 +102,7 @@ global.EventSource = require('eventsource')
|
||||
pbService.shutdown()
|
||||
}
|
||||
|
||||
await (await rpcService()).initRpcs()
|
||||
process.on('SIGTERM', shutdown)
|
||||
process.on('SIGINT', shutdown)
|
||||
process.on('SIGHUP', shutdown)
|
||||
|
@ -6,26 +6,16 @@ import {
|
||||
PUBLIC_APP_DOMAIN,
|
||||
PUBLIC_APP_PROTOCOL,
|
||||
} from '$constants'
|
||||
import { clientService, proxyService, rpcService } from '$services'
|
||||
import { clientService, proxyService } from '$services'
|
||||
import { mkInternalUrl, now } from '$util'
|
||||
import {
|
||||
assertTruthy,
|
||||
createCleanupManager,
|
||||
CreateInstancePayload,
|
||||
CreateInstancePayloadSchema,
|
||||
CreateInstanceResult,
|
||||
createTimerManager,
|
||||
InstanceId,
|
||||
InstanceStatus,
|
||||
mkSingleton,
|
||||
RpcCommands,
|
||||
safeCatch,
|
||||
SaveSecretsPayload,
|
||||
SaveSecretsPayloadSchema,
|
||||
SaveSecretsResult,
|
||||
SaveVersionPayload,
|
||||
SaveVersionPayloadSchema,
|
||||
SaveVersionResult,
|
||||
SingletonBaseConfig,
|
||||
} from '@pockethost/common'
|
||||
import { forEachRight, map } from '@s-libs/micro-dash'
|
||||
@ -33,7 +23,6 @@ import Bottleneck from 'bottleneck'
|
||||
import { existsSync } from 'fs'
|
||||
import getPort from 'get-port'
|
||||
import { join } from 'path'
|
||||
import { valid, validRange } from 'semver'
|
||||
import { AsyncReturnType } from 'type-fest'
|
||||
import { instanceLoggerService } from '../InstanceLoggerService'
|
||||
import { pocketbase, PocketbaseProcess } from '../PocketBaseService'
|
||||
@ -57,59 +46,8 @@ export const instanceService = mkSingleton(
|
||||
const { dbg, raw, error, warn } = _instanceLogger
|
||||
const { client } = await clientService()
|
||||
|
||||
const { registerCommand } = await rpcService()
|
||||
|
||||
const pbService = await pocketbase()
|
||||
|
||||
registerCommand<CreateInstancePayload, CreateInstanceResult>(
|
||||
RpcCommands.CreateInstance,
|
||||
CreateInstancePayloadSchema,
|
||||
async (rpc) => {
|
||||
const { payload } = rpc
|
||||
const { subdomain } = payload
|
||||
const instance = await client.createInstance({
|
||||
subdomain,
|
||||
uid: rpc.userId,
|
||||
version: (await pocketbase()).getLatestVersion(),
|
||||
status: InstanceStatus.Idle,
|
||||
secondsThisMonth: 0,
|
||||
isBackupAllowed: false,
|
||||
secrets: {},
|
||||
})
|
||||
return { instance }
|
||||
}
|
||||
)
|
||||
|
||||
const SEMVER_RE =
|
||||
/^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$/
|
||||
registerCommand<SaveVersionPayload, SaveVersionResult>(
|
||||
RpcCommands.SaveVersion,
|
||||
SaveVersionPayloadSchema,
|
||||
async (rpc) => {
|
||||
const { payload } = rpc
|
||||
const { instanceId, version } = payload
|
||||
if (valid(version) === null && validRange(version) === null) {
|
||||
return {
|
||||
status: `error`,
|
||||
message: `Version must be a valid semver or semver range`,
|
||||
}
|
||||
}
|
||||
await client.updateInstance(instanceId, { version })
|
||||
return { status: 'ok' }
|
||||
}
|
||||
)
|
||||
|
||||
registerCommand<SaveSecretsPayload, SaveSecretsResult>(
|
||||
RpcCommands.SaveSecrets,
|
||||
SaveSecretsPayloadSchema,
|
||||
async (job) => {
|
||||
const { payload } = job
|
||||
const { instanceId, secrets } = payload
|
||||
await client.updateInstance(instanceId, { secrets })
|
||||
return { status: 'ok' }
|
||||
}
|
||||
)
|
||||
|
||||
const instances: { [_: string]: InstanceApi } = {}
|
||||
|
||||
const instanceLimiter = new Bottleneck({ maxConcurrent: 1 })
|
||||
|
@ -14,6 +14,7 @@ import Bottleneck from 'bottleneck'
|
||||
import { default as knexFactory } from 'knex'
|
||||
import pocketbaseEs from 'pocketbase'
|
||||
import { AsyncReturnType, JsonObject } from 'type-fest'
|
||||
import { registerRpcCommands } from './commands'
|
||||
|
||||
export type RpcServiceApi = AsyncReturnType<typeof rpcService>
|
||||
|
||||
@ -33,7 +34,8 @@ export type RpcServiceConfig = SingletonBaseConfig & {}
|
||||
|
||||
export const rpcService = mkSingleton(async (config: RpcServiceConfig) => {
|
||||
const { logger } = config
|
||||
const { dbg, error } = logger.create('RpcService')
|
||||
const rpcServiceLogger = logger.create('RpcService')
|
||||
const { dbg, error } = rpcServiceLogger
|
||||
const { client } = await clientService()
|
||||
|
||||
const limiter = new Bottleneck({ maxConcurrent: 1 })
|
||||
@ -93,10 +95,17 @@ export const rpcService = mkSingleton(async (config: RpcServiceConfig) => {
|
||||
})
|
||||
}
|
||||
|
||||
dbg(`Starting RPC service...`)
|
||||
|
||||
const initRpcs = async () => {
|
||||
dbg(`Initializing RPCs...`)
|
||||
await registerRpcCommands(rpcServiceLogger)
|
||||
await client.resetRpcs()
|
||||
const rpcs = await client.incompleteRpcs()
|
||||
rpcs.forEach(run)
|
||||
}
|
||||
|
||||
const unsub = await client.onNewRpc(run)
|
||||
await client.resetRpcs()
|
||||
const rpcs = await client.incompleteRpcs()
|
||||
rpcs.forEach(run)
|
||||
|
||||
const shutdown = () => {
|
||||
unsub()
|
||||
@ -123,6 +132,7 @@ export const rpcService = mkSingleton(async (config: RpcServiceConfig) => {
|
||||
|
||||
return {
|
||||
registerCommand,
|
||||
initRpcs,
|
||||
shutdown,
|
||||
}
|
||||
})
|
||||
|
124
packages/daemon/src/services/RpcService/commands.ts
Normal file
124
packages/daemon/src/services/RpcService/commands.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import {
|
||||
CreateInstancePayload,
|
||||
CreateInstancePayloadSchema,
|
||||
CreateInstanceResult,
|
||||
InstanceStatus,
|
||||
Logger,
|
||||
RenameInstancePayloadSchema,
|
||||
RpcCommands,
|
||||
SaveSecretsPayload,
|
||||
SaveSecretsPayloadSchema,
|
||||
SaveSecretsResult,
|
||||
SaveVersionPayload,
|
||||
SaveVersionPayloadSchema,
|
||||
// gen:import
|
||||
SaveVersionResult,
|
||||
SetInstanceMaintenancePayloadSchema,
|
||||
type RenameInstancePayload,
|
||||
type RenameInstanceResult,
|
||||
type SetInstanceMaintenancePayload,
|
||||
type SetInstanceMaintenanceResult,
|
||||
} from '@pockethost/common'
|
||||
import { valid, validRange } from 'semver'
|
||||
import { clientService } from '../clientService/clientService'
|
||||
import { instanceService } from '../InstanceService/InstanceService'
|
||||
import { pocketbase } from '../PocketBaseService'
|
||||
import { rpcService } from './RpcService'
|
||||
|
||||
export const registerRpcCommands = async (logger: Logger) => {
|
||||
const { client } = await clientService()
|
||||
const _rpcCommandLogger = logger.create(`RpcCommands`)
|
||||
const { dbg, warn } = _rpcCommandLogger
|
||||
|
||||
const { registerCommand } = await rpcService()
|
||||
|
||||
registerCommand<CreateInstancePayload, CreateInstanceResult>(
|
||||
RpcCommands.CreateInstance,
|
||||
CreateInstancePayloadSchema,
|
||||
async (rpc) => {
|
||||
const { payload } = rpc
|
||||
const { subdomain } = payload
|
||||
const instance = await client.createInstance({
|
||||
subdomain,
|
||||
uid: rpc.userId,
|
||||
version: (await pocketbase()).getLatestVersion(),
|
||||
status: InstanceStatus.Idle,
|
||||
secondsThisMonth: 0,
|
||||
secrets: {},
|
||||
maintenance: false,
|
||||
})
|
||||
return { instance }
|
||||
}
|
||||
)
|
||||
|
||||
registerCommand<SaveVersionPayload, SaveVersionResult>(
|
||||
RpcCommands.SaveVersion,
|
||||
SaveVersionPayloadSchema,
|
||||
async (rpc) => {
|
||||
const { payload } = rpc
|
||||
const { instanceId, version } = payload
|
||||
if (valid(version) === null && validRange(version) === null) {
|
||||
return {
|
||||
status: `error`,
|
||||
message: `Version must be a valid semver or semver range`,
|
||||
}
|
||||
}
|
||||
await client.updateInstance(instanceId, { version })
|
||||
return { status: 'ok' }
|
||||
}
|
||||
)
|
||||
|
||||
registerCommand<SaveSecretsPayload, SaveSecretsResult>(
|
||||
RpcCommands.SaveSecrets,
|
||||
SaveSecretsPayloadSchema,
|
||||
async (job) => {
|
||||
const { payload } = job
|
||||
const { instanceId, secrets } = payload
|
||||
await client.updateInstance(instanceId, { secrets })
|
||||
return { status: 'ok' }
|
||||
}
|
||||
)
|
||||
|
||||
registerCommand<RenameInstancePayload, RenameInstanceResult>(
|
||||
RpcCommands.RenameInstance,
|
||||
RenameInstancePayloadSchema,
|
||||
async (job) => {
|
||||
const { payload } = job
|
||||
const { instanceId, subdomain } = payload
|
||||
try {
|
||||
await client.updateInstance(instanceId, { subdomain })
|
||||
return { status: 'ok' }
|
||||
} catch (e) {
|
||||
return { status: 'error', message: `${e}` }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
registerCommand<SetInstanceMaintenancePayload, SetInstanceMaintenanceResult>(
|
||||
RpcCommands.SetInstanceMaintenance,
|
||||
SetInstanceMaintenancePayloadSchema,
|
||||
async (job) => {
|
||||
const { payload } = job
|
||||
const { instanceId, maintenance } = payload
|
||||
try {
|
||||
dbg(`Updating to maintenance mode ${instanceId}`)
|
||||
await client.updateInstance(instanceId, { maintenance })
|
||||
if (maintenance) {
|
||||
try {
|
||||
dbg(`Shutting down instance ${instanceId}`)
|
||||
const is = await instanceService()
|
||||
const api = is.getInstanceApiIfExistsById(instanceId)
|
||||
await api?.shutdown()
|
||||
} catch (e) {
|
||||
warn(e)
|
||||
}
|
||||
}
|
||||
return { status: 'ok' }
|
||||
} catch (e) {
|
||||
return { status: 'error', message: `${e}` }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// gen:command
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user