Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
nytamin committed Nov 20, 2023
1 parent 3e81a6e commit bbd52d5
Show file tree
Hide file tree
Showing 12 changed files with 288 additions and 36 deletions.
Binary file modified .yarn/install-state.gz
Binary file not shown.
27 changes: 27 additions & 0 deletions jest.config.base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module.exports = {
roots: ['<rootDir>/src'],
projects: ['<rootDir>'],
preset: 'ts-jest',
moduleFileExtensions: ['js', 'ts'],
transform: {
'^.+\\.(ts|tsx)$': [
'ts-jest',
{
tsconfig: 'tsconfig.json',
},
],
},
testMatch: ['**/__tests__/**/*.spec.(ts|js)'],
testEnvironment: 'node',
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
coverageDirectory: '<rootDir>/coverage/',
collectCoverage: false,
// verbose: true,
}
4 changes: 4 additions & 0 deletions packages/apps/backend/src/api-server/PublishChannels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ export const PublishChannels = {
AllPlaylists: (): string => {
return `playlists`
},

OneSpecificPlaylist: (playlistId: string): string => {
return `playlists/${playlistId}`
},
}
53 changes: 43 additions & 10 deletions packages/apps/backend/src/api-server/services/PlaylistService.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import EventEmitter from 'eventemitter3'
import { Application, PaginationParams, Params } from '@feathersjs/feathers'
import { ServiceTypes, Services, PlaylistServiceDefinition as Definition } from '@sofie-prompter-editor/shared-model'
import {
ServiceTypes,
Services,
PlaylistServiceDefinition as Definition,
diff,
} from '@sofie-prompter-editor/shared-model'
export { PlaylistServiceDefinition } from '@sofie-prompter-editor/shared-model'
import { PublishChannels } from '../PublishChannels.js'
import { CustomFeathersService } from './lib.js'
import { Store } from '../../data-stores/Store.js'
import { Lambda, observe } from 'mobx'
import { LoggerInstance } from '../../lib/logger.js'
import { Forbidden, NotFound } from '@feathersjs/errors'

export type PlaylistFeathersService = CustomFeathersService<Definition.Service, Definition.Events>

Expand All @@ -33,6 +39,9 @@ export class PlaylistService extends EventEmitter<Definition.Events> implements
service.publish('updated', (_data, _context) => {
return app.channel(PublishChannels.AllPlaylists())
})
service.publish('patched', (_data, _context) => {
return app.channel(PublishChannels.AllPlaylists())
})
service.publish('removed', (_data, _context) => {
return app.channel(PublishChannels.AllPlaylists())
})
Expand All @@ -45,10 +54,13 @@ export class PlaylistService extends EventEmitter<Definition.Events> implements
this.observers.push(
observe(this.store.playlists.playlists, (change) => {
this.log.info('observed change', change)

if (change.type === 'add') {
this.emit('created', change.newValue)
} else if (change.type === 'update') {
this.emit('updated', change.newValue)
const patch = diff(change.oldValue, change.newValue)
if (patch) this.emit('patched', patch)
// this.emit('updated', change.newValue)
} else if (change.type === 'delete') {
this.emit('removed', change.oldValue._id)
}
Expand All @@ -65,20 +77,41 @@ export class PlaylistService extends EventEmitter<Definition.Events> implements
public async find(_params?: Params & { paginate?: PaginationParams }): Promise<Data[]> {
return Array.from(this.store.playlists.playlists.values())
}
public async get(_id: Id, _params?: Params): Promise<Data> {
throw new Error('Not implemented')
public async get(id: Id, _params?: Params): Promise<Data> {
const data = this.store.playlists.playlists.get(id)
if (!data) throw new NotFound(`Playlist "${id}" not found`)
return data
}
public async create(_data: Data, _params?: Params): Promise<Result> {
throw new Error('Not implemented')
throw new Forbidden(`Not supported`)
// this.store.playlists.create(data)
// return this.get(data._id)
}
public async update(_id: NullId, _data: Data, _params?: Params): Promise<Result> {
throw new Error('Not implemented')
throw new Forbidden(`Not supported`)
// if (id === null) throw new BadRequest(`id must not be null`)
// if (id !== data._id) throw new BadRequest(`Cannot change id of playlist`)

// this.store.playlists.update(data)
// return this.get(data._id)
}
public async patch(_id: NullId, _data: PartialData, _params?: Params): Promise<Result> {
throw new Error('Not implemented')
public async patch(_id: NullId, _data: PatchData, _params?: Params): Promise<Result> {
throw new Forbidden(`Not supported`)
// if (id === null) throw new BadRequest(`id must not be null`)
// const existing = await this.get(id)
// const newData: RundownPlaylist = {
// ...existing,
// ...data,
// }
// this.store.playlists.update(newData)
// return newData
}
public async remove(_id: NullId, _params?: Params): Promise<Result> {
throw new Error('Not implemented')
throw new Forbidden(`Not supported`)
// if (id === null) throw new BadRequest(`id must not be null`)
// const existing = await this.get(id)
// this.store.playlists.remove(id)
// return existing
}
// public async setup?(app: Application, path: string): Promise<void> {
// throw new Error('Not implemented')
Expand All @@ -105,4 +138,4 @@ type Result = Definition.Result
type Id = Definition.Id
type NullId = Definition.NullId
type Data = Definition.Data
type PartialData = Definition.PartialData
type PatchData = Definition.PatchData
56 changes: 38 additions & 18 deletions packages/apps/client/src/TestPlaylists.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
import React, { useEffect } from 'react'
import { APIConnection } from './api/ApiConnection.ts'
import { RundownPlaylist, RundownPlaylistId } from '@sofie-prompter-editor/shared-model'
import { RundownPlaylist, RundownPlaylistId, patch } from '@sofie-prompter-editor/shared-model'

export const TestPlaylists: React.FC<{ api: APIConnection }> = ({ api }) => {
const [ready, setReady] = React.useState(false)
const [connected, setConnected] = React.useState(false)
const [playlists, setPlaylists] = React.useState<Record<RundownPlaylistId, RundownPlaylist>>({})

const updatePlaylists = React.useCallback((id: RundownPlaylistId, data: RundownPlaylist | null) => {
if (data === null) {
const updatePlaylists = React.useCallback(
(id: RundownPlaylistId, data: (prev: RundownPlaylist | undefined) => RundownPlaylist | null) => {
setPlaylists((prev) => {
const d = { ...prev }
delete d[id]
return d
})
} else {
setPlaylists((prev) => {
return {
...prev,
[id]: data,
const newData = data(prev[id])
if (newData === null) {
const d = { ...prev }
delete d[id]
return d
} else {
return {
...prev,
[id]: newData,
}
}
})
}
}, [])
},
[]
)

useApiConnection(
(connected) => {
Expand All @@ -41,20 +43,38 @@ export const TestPlaylists: React.FC<{ api: APIConnection }> = ({ api }) => {
.catch(console.error)

api.playlist.on('created', (data) => {
updatePlaylists(data._id, data)
updatePlaylists(data._id, () => data)
})
api.playlist.on('updated', (data) => {
updatePlaylists(data._id, data)
updatePlaylists(data._id, () => data)
})
api.playlist.on('patched', (data) => {
updatePlaylists(data._id, (prev) => {
if (!prev) {
// We need to do a resync:
api.playlist
.get(data._id)
.then((playlist) => {
updatePlaylists(playlist._id, () => playlist)
})
.catch(console.error)

return patch({} as any, data)
} else {
return patch(prev, data)
}
})
})
api.playlist.on('removed', (id) => {
updatePlaylists(id, null)
updatePlaylists(id, () => null)
})

// Also fetch initial list:
api.playlist
.find()
.then((list) => {
console.log('list', list)
list.forEach((playlist) => updatePlaylists(playlist._id, playlist))
list.forEach((playlist) => updatePlaylists(playlist._id, () => playlist))
})
.catch(console.error)
},
Expand Down
2 changes: 1 addition & 1 deletion packages/apps/client/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ api.playlist.on('created', (payload) => {
console.log(`playlist created: "${JSON.stringify(payload)}"`)
})
api.playlist.on('patched', (payload) => {
assertType<Partial<RundownPlaylist>>(payload)
// assertType<Partial<RundownPlaylist>>(payload)
console.log(`playlist patched: "${JSON.stringify(payload)}"`)
})
api.playlist.on('updated', (payload) => {
Expand Down
6 changes: 3 additions & 3 deletions packages/shared/model/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const base = require('../../../jest.config.base')
const packageJson = require('./package')
import base from '../../../jest.config.base.js'
import packageJson from './package.json' assert { type: "json" }

module.exports = {
export default {
...base,
displayName: packageJson.name,
}
2 changes: 1 addition & 1 deletion packages/shared/model/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"watch": "run build:main -w --preserveWatchOutput",
"build:main": "tsc -p tsconfig.json",
"lint": "run -T eslint --ext .ts --ext .tsx --ext .js --ignore-pattern dist",
"__test": "jest"
"test": "jest"
},
"engines": {
"node": ">=20.9.0"
Expand Down
63 changes: 63 additions & 0 deletions packages/shared/model/src/__tests__/patch.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { diff, patch } from '../patch'

test('patch', () => {
const a: any = {
a: 1,
b: 'b',
c: null,
d: false,
e: true,
f: {
a: 1,
},
g: [1, { a: 1, b: 2 }],
}

{
// No change
const b0 = clone(a)
const d = diff(a, b0)
expect(Object.keys(d)).toHaveLength(0)
const b1 = patch(a, d)
expect(b1).toEqual(b0)
}
{
const b0 = clone(a)
b0.b = 'c'
const d = diff(a, b0)
const b1 = patch(a, d)
expect(b1).toEqual(b0)
}
{
const b0 = clone(a)
delete b0.b
const d = diff(a, b0)
const b1 = patch(a, d)
expect(b1).toEqual(b0)
}
{
const b0 = clone(a)
b0.g[1].a = 5
const d = diff(a, b0)
const b1 = patch(a, d)
expect(b1).toEqual(b0)
}
{
const b0 = clone(a)
delete b0.g[1].b
const d = diff(a, b0)
const b1 = patch(a, d)
expect(b1).toEqual(b0)
}
{
const b0 = clone(a)
b0.f = [1, 2, 3]
const d = diff(a, b0)
const b1 = patch(a, d)
expect(b1).toEqual(b0)
}
})

function clone<T>(o: T): T {
return JSON.parse(JSON.stringify(o))
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
assertConstIncludesAllMethods,
} from './lib.js'
import { RundownPlaylist } from '../model.js'
import { Diff } from '../patch.js'

/** List of all method names */
export const ALL_METHODS = [
Expand All @@ -27,7 +28,7 @@ interface Methods extends ServiceMethods {
get(id: Id, params?: Params): Promise<Data>
create(data: Data, params?: Params): Promise<Result>
update(id: NullId, data: Data, params?: Params): Promise<Result>
patch(id: NullId, data: PartialData, params?: Params): Promise<Result>
patch(id: NullId, data: PatchData, params?: Params): Promise<Result>
remove(id: NullId, params?: Params): Promise<Result>
//
subscribeToPlaylists(_?: unknown, params?: Params): Promise<void>
Expand All @@ -51,15 +52,15 @@ export const ALL_EVENTS = [
export interface Events {
created: [data: Data]
updated: [data: Data]
patched: [data: PartialData]
patched: [data: PatchData]
removed: [id: Id]
//
tmpPong: [payload: string]
}

// Helper types for the default service methods:
export type Data = RundownPlaylist
export type PartialData = Partial<Data>
export type PatchData = Diff<Data>
export type Result = Pick<Data, '_id'>
export type Id = Data['_id']
export type NullId = Id | null
Expand Down
1 change: 1 addition & 0 deletions packages/shared/model/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './model.js'
export * from './patch.js'
export * from './ProtectedString.js'
export * from './client-server-api/index.js'
Loading

0 comments on commit bbd52d5

Please sign in to comment.