@overlaysymphony/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/package.json +33 -0
- package/src/libs/broadcast/broadcast.ts +16 -0
- package/src/libs/broadcast/index.ts +1 -0
- package/src/libs/defer/defer.ts +19 -0
- package/src/libs/defer/index.ts +1 -0
- package/src/libs/pubsub/index.ts +2 -0
- package/src/libs/pubsub/pubsub.ts +36 -0
- package/src/libs/querystring/index.ts +2 -0
- package/src/libs/querystring/querystring.ts +37 -0
- package/src/libs/queue/index.ts +2 -0
- package/src/libs/queue/queue.ts +71 -0
- package/src/setupTests.ts +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# `@overlaysymphony/core`
|
|
2
|
+
|
|
3
|
+
The is the core module for the [OverlaySymphony interactive streaming framework](https://github.com/OverlaySymphony/overlaysymphony), though at the moment it only includes a few utility helpers.
|
|
4
|
+
|
|
5
|
+
**You should not need to use this package directly.**
|
|
6
|
+
|
|
7
|
+
# OverlaySymphony
|
|
8
|
+
|
|
9
|
+
An interactive streaming framework for orchestrating overlays, bots, and other services.
|
|
10
|
+
|
|
11
|
+
> Note: This project is still very much under development. I use it actively on my own stream, so all releases should be fully functional. I will do my best to follow semver, though I cannot make any guarantees about release timelines or stability at this time.
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@overlaysymphony/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Core module for the OverlaySymphony interactive streaming framework.",
|
|
5
|
+
"homepage": "https://github.com/OverlaySymphony/overlaysymphony",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
"./libs/*": "./src/libs/*/index.js",
|
|
9
|
+
"./ui/*": "./src/ui/*.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"lint": "pnpm run \"/^lint-.*/\"",
|
|
14
|
+
"lint-typecheck": "tsc --noEmit",
|
|
15
|
+
"lint-eslint": "eslint --ext .js --ext .jsx --ext .ts --ext .tsx .",
|
|
16
|
+
"lint-prettier": "prettier --check .",
|
|
17
|
+
"lint-depcheck": "depcheck .",
|
|
18
|
+
"test": "vitest",
|
|
19
|
+
"dev": "echo TODO",
|
|
20
|
+
"clean": "rm -rf node_modules/.cache tsconfig.tsbuildinfo dist",
|
|
21
|
+
"build": "echo TODO"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@overlaysymphony/eslint-config": "workspace:*",
|
|
25
|
+
"@overlaysymphony/tooling": "workspace:*",
|
|
26
|
+
"@vitest/coverage-v8": "^2.1.2",
|
|
27
|
+
"depcheck": "catalog:",
|
|
28
|
+
"eslint": "catalog:",
|
|
29
|
+
"prettier": "catalog:",
|
|
30
|
+
"typescript": "catalog:",
|
|
31
|
+
"vitest": "^2.1.2"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default function createBroadcaster<Data>(
|
|
2
|
+
name: string,
|
|
3
|
+
handler?: (data: Data) => void,
|
|
4
|
+
): (data: Data) => void {
|
|
5
|
+
const channel = new BroadcastChannel(name)
|
|
6
|
+
|
|
7
|
+
if (handler) {
|
|
8
|
+
channel.onmessage = (_event) => {
|
|
9
|
+
handler(_event.data as Data)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return (data: Data) => {
|
|
14
|
+
channel.postMessage(data)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./broadcast.js"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default function createDefer<Data = void>(): {
|
|
2
|
+
promise: Promise<Data>
|
|
3
|
+
resolve: (value: Data) => void
|
|
4
|
+
reject: (reason?: string) => void
|
|
5
|
+
} {
|
|
6
|
+
let resolve: ((value: Data) => void) | undefined = undefined
|
|
7
|
+
let reject: ((reason?: string) => void) | undefined = undefined
|
|
8
|
+
|
|
9
|
+
const promise = new Promise<Data>((resolve_, reject_) => {
|
|
10
|
+
resolve = resolve_
|
|
11
|
+
reject = reject_
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
promise,
|
|
16
|
+
resolve: resolve as Exclude<typeof resolve, undefined>,
|
|
17
|
+
reject: reject as Exclude<typeof reject, undefined>,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./defer.js"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
type Handler<Data> = (data: Data) => void
|
|
2
|
+
|
|
3
|
+
type Subscriber<Data> = (handler: (data: Data) => void) => () => void
|
|
4
|
+
type Dispatcher<Data> = (data: Data) => void
|
|
5
|
+
|
|
6
|
+
export interface PubSub<Data> {
|
|
7
|
+
subscribe: Subscriber<Data>
|
|
8
|
+
dispatch: Dispatcher<Data>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function createPubSub<Data>(): PubSub<Data> {
|
|
12
|
+
const handlers: Array<Handler<Data>> = []
|
|
13
|
+
|
|
14
|
+
function subscribe(handler: Handler<Data>) {
|
|
15
|
+
handlers.push(handler)
|
|
16
|
+
|
|
17
|
+
return () => {
|
|
18
|
+
const index = handlers.indexOf(handler)
|
|
19
|
+
if (index === -1) return false
|
|
20
|
+
|
|
21
|
+
handlers.splice(index, 1)
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function dispatch(data: Data) {
|
|
27
|
+
for (const handler of handlers) {
|
|
28
|
+
handler(data)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
subscribe,
|
|
34
|
+
dispatch,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
type ParsedQuerystring = Record<
|
|
2
|
+
string,
|
|
3
|
+
string | number | boolean | null | undefined
|
|
4
|
+
>
|
|
5
|
+
|
|
6
|
+
export function stringify(input: ParsedQuerystring): string {
|
|
7
|
+
const output: string[] = []
|
|
8
|
+
|
|
9
|
+
for (const [key, value] of Object.entries(input)) {
|
|
10
|
+
if (typeof value !== "undefined" && value !== null) {
|
|
11
|
+
output.push(`${key}=${value}`)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return output.join("&")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parse(input: string): ParsedQuerystring {
|
|
19
|
+
input = decodeURIComponent(input)
|
|
20
|
+
if (input[0] === "#") input = input.slice(1)
|
|
21
|
+
if (input[0] === "?") input = input.slice(1)
|
|
22
|
+
|
|
23
|
+
if (input.length === 0) {
|
|
24
|
+
return {}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const output: ParsedQuerystring = {}
|
|
28
|
+
|
|
29
|
+
for (const pair of input.split("&")) {
|
|
30
|
+
const [key, value] = pair.split("=")
|
|
31
|
+
output[key] = value
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return output
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default { stringify, parse }
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
type Handler<Data> = (data: Data) => void
|
|
2
|
+
|
|
3
|
+
type Listener<Data> = (handler: Handler<Data>) => () => void
|
|
4
|
+
type Enqueuer<Data> = (priority: number, data: Data) => void
|
|
5
|
+
type Dismisser = () => void
|
|
6
|
+
|
|
7
|
+
export interface Queue<Data> {
|
|
8
|
+
listen: Listener<Data>
|
|
9
|
+
enqueue: Enqueuer<Data>
|
|
10
|
+
dismiss: Dismisser
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface QueueItem<Data> {
|
|
14
|
+
priority: number
|
|
15
|
+
data: Data
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function createQueue<Data>(): Queue<Data> {
|
|
19
|
+
const handlers: Array<Handler<Data>> = []
|
|
20
|
+
const queue: Array<QueueItem<Data>> = []
|
|
21
|
+
let current: { data: Data } | undefined = undefined
|
|
22
|
+
|
|
23
|
+
function dispatch(data: Data | undefined) {
|
|
24
|
+
if (typeof data === "undefined") {
|
|
25
|
+
current = undefined
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
current = { data }
|
|
30
|
+
for (const handler of handlers) {
|
|
31
|
+
handler(current.data)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function listen(handler: Handler<Data>) {
|
|
36
|
+
handlers.push(handler)
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
const index = handlers.indexOf(handler)
|
|
40
|
+
if (index === -1) return false
|
|
41
|
+
|
|
42
|
+
handlers.splice(index, 1)
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function enqueue(priority: number, data: Data) {
|
|
48
|
+
if (!current) {
|
|
49
|
+
return dispatch(data)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const after = queue.findIndex((item) => item.priority < priority)
|
|
53
|
+
|
|
54
|
+
if (after === -1) {
|
|
55
|
+
queue.push({ priority, data })
|
|
56
|
+
} else {
|
|
57
|
+
queue.splice(after, 0, { priority, data })
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function dismiss() {
|
|
62
|
+
const { data } = queue.shift() || {}
|
|
63
|
+
return dispatch(data)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
listen,
|
|
68
|
+
enqueue,
|
|
69
|
+
dismiss,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
File without changes
|