Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Realtime setup and generator commands out of experimental and into main cli #9342

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 133 additions & 35 deletions docs/docs/cli-commands.md

Large diffs are not rendered by default.

105 changes: 56 additions & 49 deletions packages/cli/src/commands/experimental/setupServerFileHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const { version } = JSON.parse(
fs.readFileSync(path.resolve(__dirname, '../../../package.json'), 'utf-8')
)

export async function handler({ force, verbose }) {
export const setupServerFileTasks = (force = false) => {
const redwoodPaths = getPaths()
const ts = isTypeScriptProject()

Expand All @@ -27,6 +27,60 @@ export async function handler({ force, verbose }) {
`server.${isTypeScriptProject() ? 'ts' : 'js'}`
)

return [
{
title: 'Adding the experimental server files...',
task: () => {
const serverFileTemplateContent = fs.readFileSync(
path.resolve(__dirname, 'templates', 'server.ts.template'),
'utf-8'
)

const setupScriptContent = ts
? serverFileTemplateContent
: transformTSToJS(serverFilePath, serverFileTemplateContent)

return [
writeFile(serverFilePath, setupScriptContent, {
overwriteExisting: force,
}),
]
},
},
{
title: 'Adding config to redwood.toml...',
task: (_ctx, task) => {
//
const redwoodTomlPath = getConfigPath()
const configContent = fs.readFileSync(redwoodTomlPath, 'utf-8')
if (!configContent.includes('[experimental.serverFile]')) {
// Use string replace to preserve comments and formatting
writeFile(
redwoodTomlPath,
configContent.concat(
`\n[experimental.serverFile]\n\tenabled = true\n`
),
{
overwriteExisting: true, // redwood.toml always exists
}
)
} else {
task.skip(
`The [experimental.serverFile] config block already exists in your 'redwood.toml' file.`
)
}
},
},
addApiPackages([
'fastify',
'chalk@4.1.2',
`@redwoodjs/fastify@${version}`,
`@redwoodjs/project-config@${version}`,
]),
]
}

export async function handler({ force, verbose }) {
const tasks = new Listr(
[
{
Expand All @@ -42,54 +96,7 @@ export async function handler({ force, verbose }) {
}
},
},
{
title: 'Adding the experimental server file...',
task: () => {
const serverFileTemplateContent = fs.readFileSync(
path.resolve(__dirname, 'templates', 'server.ts.template'),
'utf-8'
)

const setupScriptContent = ts
? serverFileTemplateContent
: transformTSToJS(serverFilePath, serverFileTemplateContent)

return [
writeFile(serverFilePath, setupScriptContent, {
overwriteExisting: force,
}),
]
},
},
{
title: 'Adding config to redwood.toml...',
task: (_ctx, task) => {
const redwoodTomlPath = getConfigPath()
const configContent = fs.readFileSync(redwoodTomlPath, 'utf-8')
if (!configContent.includes('[experimental.serverFile]')) {
// Use string replace to preserve comments and formatting
writeFile(
redwoodTomlPath,
configContent.concat(
`\n[experimental.serverFile]\n\tenabled = true\n`
),
{
overwriteExisting: true, // redwood.toml always exists
}
)
} else {
task.skip(
`The [experimental.serverFile] config block already exists in your 'redwood.toml' file.`
)
}
},
},
addApiPackages([
'fastify',
'chalk@4.1.2',
`@redwoodjs/fastify@${version}`,
`@redwoodjs/project-config@${version}`,
]),
...setupServerFileTasks(force),
{
task: () => {
printTaskEpilogue(command, description, EXPERIMENTAL_TOPIC_ID)
Expand Down
17 changes: 12 additions & 5 deletions packages/cli/src/commands/experimental/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,17 @@ export const printTaskEpilogue = (command, description, topicId) => {
)
}

export const isServerFileSetup = () => {
export const serverFileExists = () => {
const serverFilePath = path.join(
getPaths().api.src,
`server.${isTypeScriptProject() ? 'ts' : 'js'}`
)

if (!fs.existsSync(serverFilePath)) {
return fs.existsSync(serverFilePath)
}

export const isServerFileSetup = () => {
if (!serverFileExists) {
throw new Error(
'RedwoodJS Realtime requires a serverful environment. Please run `yarn rw exp setup-server-file` first.'
)
Expand All @@ -59,15 +63,18 @@ export const isServerFileSetup = () => {
return true
}

export const isRealtimeSetup = () => {
export const realtimeExists = () => {
const realtimePath = path.join(
getPaths().api.lib,
`realtime.${isTypeScriptProject() ? 'ts' : 'js'}`
)
return fs.existsSync(realtimePath)
}

if (!fs.existsSync(realtimePath)) {
export const isRealtimeSetup = () => {
if (!realtimeExists) {
throw new Error(
'Adding realtime events to requires that RedwoodJS Realtime be setup. Please run `yarn rw exp setup-realtime` first.'
'Adding realtime events requires that RedwoodJS Realtime be setup. Please run `yarn setup realtime` first.'
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers'

import { getEpilogue } from './util'

export const EXPERIMENTAL_TOPIC_ID = 5002

export const command = 'generate-realtime <name>'
export const command = 'realtime <name>'

export const description =
'Generate a subscription or live query used with the experimental RedwoodJS Realtime feature'
'Generate a subscription or live query used with RedwoodJS Realtime'

export function builder(yargs) {
yargs
// .scriptName('rw exp generate-realtime')
.positional('name', {
type: 'string',
description:
Expand All @@ -36,16 +31,15 @@ export function builder(yargs) {
description: 'Print more logs',
type: 'boolean',
})
.epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true))
}

export async function handler(options) {
recordTelemetryAttributes({
command: 'experimental generate-realtime',
command: 'generate realtime',
type: options.type,
force: options.force,
verbose: options.verbose,
})
const { handler } = await import('./generateRealtimeHandler.js')
const { handler } = await import('./realtimeHandler.js')
return handler(options)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@ import prompts from 'prompts'
import { generate as generateTypes } from '@redwoodjs/internal/dist/generate/generate'
import { errorTelemetry } from '@redwoodjs/telemetry'

// Move this check out of experimental when server file is moved as well
import {
generateTemplate,
getPaths,
transformTSToJS,
writeFile,
} from '../../lib'
import c from '../../lib/colors'
import { isTypeScriptProject } from '../../lib/project'

import { command, description, EXPERIMENTAL_TOPIC_ID } from './setupRealtime'
import { printTaskEpilogue, isServerFileSetup, isRealtimeSetup } from './util'
} from '../../../lib'
import c from '../../../lib/colors'
import { isTypeScriptProject } from '../../../lib/project'
import { isRealtimeSetup, isServerFileSetup } from '../../experimental/util.js'

const templateVariables = (name) => {
name = singular(name.toLowerCase())
Expand Down Expand Up @@ -74,20 +73,6 @@ export async function handler({ name, type, force, verbose }) {

const tasks = new Listr(
[
{
title: 'Confirmation',
task: async (_ctx, task) => {
const confirmation = await task.prompt({
type: 'Confirm',
message:
'Realtime is currently an experimental RedwoodJS feature. Continue?',
})

if (!confirmation) {
throw new Error('User aborted')
}
},
},
{
title: 'Checking for realtime environment prerequisites ...',
task: () => {
Expand Down Expand Up @@ -248,11 +233,6 @@ export async function handler({ name, type, force, verbose }) {
)
},
},
{
task: () => {
printTaskEpilogue(command, description, EXPERIMENTAL_TOPIC_ID)
},
},
],
{
rendererOptions: { collapseSubtasks: false, persistentOutput: true },
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// api/src/services/${name}s/${name}s.ts
import type { LiveQueryStorageMechanism } from '@redwoodjs/graphql-server'
import type { LiveQueryStorageMechanism } from '@redwoodjs/realtime'

import { logger } from 'src/lib/logger'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { RedwoodRealtimeOptions } from '@redwoodjs/realtime'

import subscriptions from 'src/subscriptions/**/*.{js,ts}'

// if using a Redis store
// import { Redis } from 'ioredis'
// const publishClient = new Redis()
// const subscribeClient = new Redis()

/**
* Configure RedwoodJS Realtime
*
* See https://redwoodjs.com/docs/realtime
*
* Realtime supports Live Queries and Subscriptions over GraphQL SSE.
*
* Live Queries are GraphQL queries that are automatically re-run when the data they depend on changes.
*
* Subscriptions are GraphQL queries that are run when a client subscribes to a channel.
*
* Redwood Realtime
* - uses a publish/subscribe model to broadcast data to clients.
* - uses a store to persist Live Query and Subscription data.
*
* Redwood Realtime supports in-memory and Redis stores:
* - In-memory stores are useful for development and testing.
* - Redis stores are useful for production.
*
*/
export const realtime: RedwoodRealtimeOptions = {
subscriptions: {
subscriptions,
store: 'in-memory',
// if using a Redis store
// store: { redis: { publishClient, subscribeClient } },
},
liveQueries: {
store: 'in-memory',
// if using a Redis store
// store: { redis: { publishClient, subscribeClient } },
},
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import gql from 'graphql-tag'

import type { PubSub } from '@redwoodjs/graphql-server'
import type { PubSub } from '@redwoodjs/realtime'

import { logger } from 'src/lib/logger'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers'

import { getEpilogue } from './util'
export const command = 'realtime'

export const EXPERIMENTAL_TOPIC_ID = 5002

export const command = 'setup-realtime'

export const description = 'Setup the experimental RedwoodJS Realtime feature'
export const description = 'Setup RedwoodJS Realtime'

export function builder(yargs) {
yargs
.option('includeExamples', {
alias: ['e', 'examples'],
default: true,
description:
'Include examples how to implement liveQueries and subscriptions',
'Include examples of how to implement liveQueries and subscriptions',
type: 'boolean',
})
.option('force', {
Expand All @@ -29,16 +25,15 @@ export function builder(yargs) {
description: 'Print more logs',
type: 'boolean',
})
.epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true))
}

export async function handler(options) {
recordTelemetryAttributes({
command: 'experimental setup-realtime',
command: 'setup realtime',
includeExamples: options.includeExamples,
force: options.force,
verbose: options.verbose,
})
const { handler } = await import('./setupRealtimeHandler.js')
const { handler } = await import('./realtimeHandler.js')
return handler(options)
}
Loading
Loading