Skip to content
This repository has been archived by the owner on Jan 24, 2025. It is now read-only.

Commit

Permalink
chore(docz-core): split socket from data server
Browse files Browse the repository at this point in the history
  • Loading branch information
pedronauck committed Jan 23, 2019
1 parent f500971 commit 2d63e83
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 117 deletions.
1 change: 1 addition & 0 deletions core/docz-core/src/bundler/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import chalk from 'chalk'
import logger from 'signale'
import WebpackDevServer from 'webpack-dev-server'

import { Configuration as Config } from 'webpack'
import PrettyError from 'pretty-error'

Expand Down
5 changes: 3 additions & 2 deletions core/docz-core/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ export const build = async (args: Arguments<any>) => {

try {
await Entries.writeApp(config, true)
await dataServer.init()
await dataServer.start()

await run('onPreBuild', config)
await bundler.build(bundlerConfig)

await run('onPostBuild', config)
await dataServer.close()
dataServer.close()
} catch (err) {
pe.render(err)
process.exit(1)
dataServer.close()
}
}
28 changes: 17 additions & 11 deletions core/docz-core/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import PrettyError from 'pretty-error'

import { Entries } from '../lib/Entries'
import { DataServer } from '../lib/DataServer'
import { Socket } from '../lib/Socket'
import { parseConfig } from '../config/docz'
import { onSignal } from '../utils/on-signal'
import { bundler as webpack } from '../bundler'
Expand All @@ -18,6 +19,7 @@ export const dev = async (args: Arguments<any>) => {
const config = await parseConfig(args)
const bundler = webpack(config, env)
const entries = new Entries(config)
const { websocketHost, websocketPort } = config

const bundlerConfig = await bundler.mountConfig(env)
const app = await bundler.createApp(bundlerConfig)
Expand All @@ -31,31 +33,35 @@ export const dev = async (args: Arguments<any>) => {
}

const server = await app.start()
const dataServer = new DataServer(
server,
config.websocketPort,
config.websocketHost
)
const dataServer = new DataServer()
const socket = new Socket(server, websocketHost, websocketPort)

if (args.propsParser) dataServer.register([states.props(config)])
dataServer.register([states.config(config), states.entries(entries, config)])

try {
await dataServer.init()
await dataServer.listen()
await dataServer.start()
} catch (err) {
logger.fatal('Failed to process data server')
pe.render(err)
await dataServer.close()
logger.error(err)
dataServer.close()
process.exit(1)
}

socket.onConnection((_, emit) => {
const subscribe = dataServer.onStateChange(action => {
emit(action.type, action.payload)
})

return () => subscribe()
})

onSignal(async () => {
await dataServer.close()
dataServer.close()
server.close()
})

server.on('close', async () => {
await dataServer.close()
dataServer.close()
})
}
126 changes: 40 additions & 86 deletions core/docz-core/src/lib/DataServer.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,7 @@
import * as fs from 'fs-extra'
import WS from 'ws'
import { isFunction } from 'lodash/fp'

import * as paths from '../config/paths'
import { onSignal } from '../utils/on-signal'

export type Send = (type: string, payload: any) => void
export type On = (type: string) => Promise<any>

const isSocketOpened = (socket: WS) => socket.readyState === WS.OPEN
const sender = (socket?: WS) => (type: string, payload: any) => {
if (socket && isSocketOpened(socket)) {
socket.send(JSON.stringify({ type, payload }))
}
}

export interface Action {
type: string
payload: any
}

export interface Params {
state: Record<string, any>
Expand All @@ -27,103 +10,74 @@ export interface Params {

export interface State {
id: string
init: (params: Params) => Promise<any>
update: (params: Params) => any
close: (params: Params) => any
start: (params: Params) => Promise<void>
close: () => void
}

export interface Action {
type: string
payload: any
}

export type Listener = (action: Action) => void

export class DataServer {
private client?: WS.Server
private states: Set<State>
private state: Record<string, any>
private state: Map<string, any>
private listeners: Set<Listener>

constructor(server?: any, port?: number, host?: string) {
constructor() {
this.states = new Set()
this.state = {}

if (server) {
this.client = new WS.Server({
server,
port,
host,
})
}
this.state = new Map()
this.listeners = new Set<Listener>()
}

public register(states: State[]): DataServer {
for (const state of states) this.states.add(state)
return this
}

public async init(): Promise<void> {
public async start(): Promise<void> {
const setState = (key: string, val: any) => this.setState(key, val)

await Promise.all(
Array.from(this.states).map(async state => {
if (!isFunction(state.init)) return
return state.init({
state: { ...this.state },
setState: this.setState(),
if (!isFunction(state.start)) return
return state.start({
setState,
state: this.mapToObject(this.state),
})
})
)

this.updateStateFile()
}

public async listen(): Promise<void> {
if (this.client) {
this.client.on('connection', socket => {
const close = this.handleConnection(socket)
const handleClose = async () => {
await close()
socket.terminate()
}

this.client!.on('close', handleClose)
onSignal(handleClose)
})
public close(): void {
for (const state of this.states) {
isFunction(state.close) && state.close()
}
}

public async close(): Promise<void> {
await Promise.all(
Array.from(this.states).map(
async state =>
isFunction(state.close) &&
state.close({
state: { ...this.state },
setState: this.setState(),
})
)
)
public onStateChange(listener: Listener): () => void {
this.listeners.add(listener)
return () => this.listeners.clear()
}

private handleConnection(socket: WS): () => void {
const states = Array.from(this.states).map(
async state =>
isFunction(state.update) &&
state.update({
state: this.state,
setState: this.setState(socket),
})
)

return async () => {
const fns = await Promise.all(states.filter(Boolean))
for (const fn of fns) isFunction(fn) && fn()
}
private setState(key: string, val: any): void {
this.state.set(key, val)
this.writeDbFile()
this.listeners.forEach(listener => {
listener({ type: `state.${key}`, payload: val })
})
}

private setState(socket?: WS): (key: string, val: any) => void {
const send = sender(socket)

return (key: string, val: any): void => {
this.state[key] = val
send(`state.${key}`, val)
this.updateStateFile()
}
private async writeDbFile(): Promise<void> {
fs.outputJSONSync(paths.db, this.mapToObject(this.state), { spaces: 2 })
}

private async updateStateFile(): Promise<void> {
await fs.outputJSON(paths.db, this.state, { spaces: 2 })
private mapToObject<T>(map: Map<string, any>): T {
return Array.from(map.entries()).reduce(
(obj, [key, val]) => ({ ...obj, [key]: val }),
{} as T
)
}
}
43 changes: 43 additions & 0 deletions core/docz-core/src/lib/Socket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import WS from 'ws'
import { onSignal } from '../utils/on-signal'

export type Send = (type: string, payload: any) => void
export type On = (type: string) => Promise<any>

const isSocketOpened = (socket: WS) => socket.readyState === WS.OPEN
const sender = (socket?: WS) => (type: string, payload: any) => {
if (socket && isSocketOpened(socket)) {
socket.send(JSON.stringify({ type, payload }))
}
}

export class Socket {
private client?: WS.Server

constructor(server?: any, host?: string, port?: number) {
if (server) {
this.client = new WS.Server({
server,
host,
port,
})
}
}

public onConnection(listener: (socket: WS, emit: Send) => () => void): void {
if (!this.client) return

this.client.on('connection', socket => {
const emit = sender(socket)
const subs = listener(socket, emit)

const handleClose = async () => {
subs()
socket.terminate()
}

this.client!.on('close', handleClose)
onSignal(handleClose)
})
}
}
10 changes: 5 additions & 5 deletions core/docz-core/src/states/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,17 @@ export const state = (config: Config): State => {

return {
id: 'config',
init: updateConfig(config),
close: () => watcher.close(),
update: async params => {
start: async params => {
const update = updateConfig(config)
const fn = async () => update(params)

await update(params)
watcher.on('add', fn)
watcher.on('change', fn)
watcher.on('unlink', fn)

return () => watcher.close()
},
close: () => {
watcher.close()
},
}
}
10 changes: 5 additions & 5 deletions core/docz-core/src/states/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,20 @@ export const state = (entries: Entries, config: Config): State => {

return {
id: 'entries',
init: updateEntries(entries),
close: () => watcher.close(),
update: async params => {
start: async params => {
const update = updateEntries(entries)

watcher.on('add', async () => update(params))
watcher.on('change', async () => update(params))
watcher.on('unlink', async () => update(params))
watcher.on('raw', async (event: string, path: string, details: any) => {
if (details.event === 'moved' && details.type === 'directory') {
await update(params)
}
})

return () => watcher.close()
},
close: () => {
watcher.close()
},
}
}
16 changes: 8 additions & 8 deletions core/docz-core/src/states/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const add = (config: Config) => (p: Params) => async (filepath: string) => {
}
}

const remove = (config: Config) => (p: Params) => async (filepath: string) =>
const remove = (p: Params) => async (filepath: string) =>
p.setState('props', omit(filepath, get('state.props', p)))

export const state = (config: Config): State => {
Expand All @@ -48,17 +48,17 @@ export const state = (config: Config): State => {

return {
id: 'props',
init: initial(config),
close: () => watcher.close(),
update: async params => {
start: async params => {
const addFilepath = add(config)
const removeFilepath = remove(config)
const addInitial = initial(config)

await addInitial(params)
watcher.on('add', addFilepath(params))
watcher.on('change', addFilepath(params))
watcher.on('unlink', removeFilepath(params))

return () => watcher.close()
watcher.on('unlink', remove(params))
},
close: () => {
watcher.close()
},
}
}

0 comments on commit 2d63e83

Please sign in to comment.