9 Commits

Author SHA1 Message Date
65583b1564 channels 2026-01-21 22:39:08 +06:00
595354b7f0 Merge pull request 'shareFps' (#9) from shareFps into master
Reviewed-on: #9
2026-01-12 07:23:51 +00:00
Nadar
d08b011596 shareFps 2026-01-12 10:22:56 +03:00
12ce381abd minor update
All checks were successful
Deploy / publish-web (push) Successful in 46s
2025-12-27 02:52:17 +06:00
2d30ac2863 minor update
All checks were successful
Deploy / publish-web (push) Successful in 44s
2025-12-27 02:49:39 +06:00
0f218c1519 button colors 2025-12-27 01:58:01 +06:00
4b1a563850 screen sharing
All checks were successful
Deploy / publish-web (push) Successful in 1m25s
2025-12-27 01:49:25 +06:00
169d43f0db screen sharing 2025-12-27 01:48:49 +06:00
47a464f08f screen sharing
All checks were successful
Deploy / publish-web (push) Successful in 48s
2025-12-26 18:22:22 +06:00
66 changed files with 20480 additions and 305 deletions

Binary file not shown.

View File

@@ -2,9 +2,6 @@ FROM node:lts-alpine AS builder
WORKDIR /app WORKDIR /app
RUN corepack enable
RUN yarn set version stable
COPY package.json yarn.lock .yarnrc.yml ./ COPY package.json yarn.lock .yarnrc.yml ./
COPY .yarn ./.yarn COPY .yarn ./.yarn

View File

@@ -3,8 +3,6 @@ FROM node:lts AS builder
WORKDIR /app WORKDIR /app
# RUN corepack enable yarn && yarn set version stable
COPY package.json yarn.lock .yarnrc.yml ./ COPY package.json yarn.lock .yarnrc.yml ./
COPY .yarn ./.yarn COPY .yarn ./.yarn

View File

@@ -27,4 +27,21 @@ body {
.p-scrollpanel-bar-y { .p-scrollpanel-bar-y {
translate: -0.25rem; translate: -0.25rem;
}
.p-select-overlay {
/* Force dropdown width to match computed min-width from PrimeVue internals. */
width: 0 !important;
}
.p-select-label {
width: 0 !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
.p-select-option-label {
min-width: 0 !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
} }

View File

@@ -16,9 +16,7 @@ declare module 'vue' {
PrimeDivider: typeof import('primevue/divider')['default'] PrimeDivider: typeof import('primevue/divider')['default']
PrimeFloatLabel: typeof import('primevue/floatlabel')['default'] PrimeFloatLabel: typeof import('primevue/floatlabel')['default']
PrimeInputText: typeof import('primevue/inputtext')['default'] PrimeInputText: typeof import('primevue/inputtext')['default']
PrimeMenu: typeof import('primevue/menu')['default']
PrimePassword: typeof import('primevue/password')['default'] PrimePassword: typeof import('primevue/password')['default']
PrimeProgressBar: typeof import('primevue/progressbar')['default']
PrimeScrollPanel: typeof import('primevue/scrollpanel')['default'] PrimeScrollPanel: typeof import('primevue/scrollpanel')['default']
PrimeSelect: typeof import('primevue/select')['default'] PrimeSelect: typeof import('primevue/select')['default']
PrimeSelectButton: typeof import('primevue/selectbutton')['default'] PrimeSelectButton: typeof import('primevue/selectbutton')['default']
@@ -28,4 +26,7 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
} }
export interface ComponentCustomProperties {
Tooltip: typeof import('primevue/tooltip')['default']
}
} }

View File

@@ -1,110 +1,129 @@
<template> <template>
<div class="py-3"> <div
<div class="flex items-center gap-3"> class="overflow-hidden rounded-xl transition-[background-color]"
:class="{
'hover:bg-surface-800 cursor-pointer': !isMe,
'bg-surface-800': expanded,
}"
>
<div class="p-3 flex items-center gap-3" @click="toggleExpand">
<PrimeAvatar size="small"> <PrimeAvatar size="small">
<template #icon> <template #icon>
<User :size="20" /> <User :size="20" />
</template> </template>
</PrimeAvatar> </PrimeAvatar>
<div class="flex-1 overflow-hidden"> <div class="flex-1 overflow-hidden text-sm leading-5 font-medium text-color whitespace-nowrap overflow-ellipsis">
<div class="overflow-hidden text-sm leading-5 font-medium text-color whitespace-nowrap overflow-ellipsis"> {{ client.displayName || client.username }}
{{ client.displayName }}
</div>
<div v-if="client.username !== client.displayName" class="overflow-hidden mt-1 text-xs leading-5 text-muted-color">
{{ client.username }}
</div>
</div> </div>
<PrimeBadge v-if="client.outputMuted" severity="info" value="No sound" /> <div class="flex align-center gap-1">
<PrimeBadge v-else-if="inputMuted" severity="info" value="Muted" /> <PrimeBadge v-if="!!shareConsumer" v-tooltip.top="'Watch'" severity="success" value="Streaming" size="small" @click.stop="watchStream" />
<PrimeBadge v-if="isMe" severity="secondary" value="You" />
<template v-if="!isMe"> <PrimeBadge v-if="premuted" severity="danger" value="Muted" size="small" />
<PrimeButton icon="pi pi-ellipsis-h" text size="small" severity="contrast" @click="menuRef?.toggle" /> <PrimeBadge v-else-if="client.outputMuted" severity="info" value="No sound" size="small" />
<PrimeBadge v-else-if="inputMuted" severity="info" value="Muted" size="small" />
<PrimeMenu ref="menu" popup :model="menuItems" style="translate: calc(-100% + 2rem) 0.5rem"> <PrimeBadge v-if="isMe" severity="secondary" value="You" size="small" class="shrink-0" />
<template #start> </div>
<div class="px-4 py-3">
<div class="flex justify-between"> <Component :is="expanded ? ChevronUp : ChevronDown" v-if="!isMe" :size="20" class="text-muted-color" />
<span>Volume</span>
<span>{{ volume }}</span>
</div>
<PrimeSlider v-model="volume" class="mt-4" :min="0" :max="1000" :step="volume < 200 ? 5 : 25" />
</div>
</template>
</PrimeMenu>
</template>
</div> </div>
<CollapseTransition v-if="!isMe">
<div v-if="expanded">
<div class="px-3 pb-3">
<div class="flex justify-between text-sm mb-3">
<span>Volume</span>
<span>{{ volume }}</span>
</div>
<PrimeSlider v-model="volume" :min="0" :max="1000" :step="volume < 200 ? 5 : 25" />
<div class="mt-3 flex gap-1 justify-end">
<PrimeButton size="small" variant="text" @click="premuted = !premuted">
{{ premuted ? 'Unmute' : 'Mute' }}
</PrimeButton>
</div>
</div>
</div>
</CollapseTransition>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { ChadClient } from '#shared/types' import type { ChadClient } from '#shared/types'
import type { MenuItem } from 'primevue/menuitem'
import { useLocalStorage } from '@vueuse/core' import { useLocalStorage } from '@vueuse/core'
import { User } from 'lucide-vue-next' import { ChevronDown, ChevronUp, User } from 'lucide-vue-next'
import CollapseTransition from '~/components/CollapseTransition.vue'
const props = defineProps<{ const props = defineProps<{
client: ChadClient client: ChadClient
}>() }>()
const { outputMuted } = useApp() const { outputMuted } = useApp()
const { getClientConsumers, micProducer } = useMediasoup() const { consumers: allConsumers, micProducer } = useMediasoup()
const { me } = useClients() const { me } = useClients()
const { show } = useFullscreenVideo()
const expanded = ref(false)
const volume = useLocalStorage<number>(computed(() => `CLIENT_VOLUME_${props.client.userId}`), 100, { writeDefaults: false }) const volume = useLocalStorage<number>(computed(() => `CLIENT_VOLUME_${props.client.userId}`), 100, { writeDefaults: false })
const premuted = useLocalStorage<boolean>(computed(() => `CLIENT_PREMUTED_${props.client.userId}`), false, { writeDefaults: false })
const menuRef = useTemplateRef<HTMLAudioElement>('menu')
const menuItems: MenuItem[] = [
{
label: 'Mute',
icon: 'pi pi-headphones',
},
{
label: 'DM',
icon: 'pi pi-comment',
disabled: true,
},
]
const isMe = computed(() => { const isMe = computed(() => {
return me.value && props.client.userId === me.value.userId return me.value && props.client.userId === me.value.userId
}) })
const audioConsumer = computed(() => { const consumers = computed(() => {
if (isMe.value) return allConsumers.value.values().filter(consumer => consumer.appData.socketId === props.client.socketId).toArray()
return undefined
const consumers = getClientConsumers(props.client.socketId)
return consumers.find(consumer => consumer.track.kind === 'audio')
}) })
const inputMuted = computed(() => { const audioConsumer = computed(() => {
if (isMe.value) return consumers.value.find(consumer => consumer.track.kind === 'audio')
return micProducer.value?.paused ?? false })
const consumers = getClientConsumers(props.client.socketId) const videoConsumers = computed(() => {
return consumers.value.filter(consumer => consumer.track.kind === 'video')
})
return consumers.find(consumer => consumer.track.kind === 'audio')?.paused const shareConsumer = computed(() => {
return videoConsumers.value.find(consumer => consumer.appData.source === 'share')
}) })
const audioTrack = computed(() => { const audioTrack = computed(() => {
return audioConsumer.value?.track return audioConsumer.value?.track
}) })
const audioConsumerPaused = ref(false)
const inputMuted = computed(() => {
if (isMe.value)
return micProducer.value?.paused ?? false
return premuted.value || audioConsumerPaused.value
})
watch(allConsumers, () => {
audioConsumerPaused.value = audioConsumer.value?.paused ?? false
})
const { setGain } = useAudioContext(audioTrack) const { setGain } = useAudioContext(audioTrack)
watch(volume, (volume) => { watchEffect(() => {
if (outputMuted.value) setGain((outputMuted.value || premuted.value) ? 0 : (volume.value * 0.01))
})
function toggleExpand() {
if (isMe.value)
return return
setGain(volume * 0.01) expanded.value = !expanded.value
}, { immediate: true }) }
watch(outputMuted, (outputMuted) => { function watchStream() {
setGain(outputMuted ? 0 : (volume.value * 0.01)) if (!shareConsumer.value)
}) return
show(new MediaStream([shareConsumer.value.track]))
}
</script> </script>

View File

@@ -0,0 +1,118 @@
<template>
<Transition name="collapse-transition" v-on="bindings">
<slot />
</Transition>
</template>
<script lang="ts" setup>
import type { RendererElement } from 'vue'
defineOptions({
name: 'CollapseTransition',
})
const emit = defineEmits<{
expanded: []
collapsed: []
}>()
const bindings = {
beforeEnter(el: RendererElement) {
if (!el.dataset)
el.dataset = {}
el.dataset.oldPaddingTop = el.style.paddingTop
el.dataset.oldPaddingBottom = el.style.paddingBottom
el.dataset.elExistsHeight = el.style.height ?? undefined
el.style.maxHeight = 0
el.style.paddingTop = 0
el.style.paddingBottom = 0
},
enter(el: RendererElement) {
requestAnimationFrame(() => {
el.dataset.oldOverflow = el.style.overflow
if (el.dataset.elExistsHeight) {
el.style.maxHeight = el.dataset.elExistsHeight
}
else if (el.scrollHeight !== 0) {
el.style.maxHeight = `${el.scrollHeight}px`
}
else {
el.style.maxHeight = 0
}
el.style.paddingTop = el.dataset.oldPaddingTop
el.style.paddingBottom = el.dataset.oldPaddingBottom
el.style.overflow = 'hidden'
})
},
afterEnter(el: RendererElement) {
el.style.maxHeight = ''
el.style.overflow = el.dataset.oldOverflow
emit('expanded')
},
enterCancelled(el: RendererElement) {
reset(el)
},
beforeLeave(el: RendererElement) {
if (!el.dataset)
el.dataset = {}
el.dataset.oldPaddingTop = el.style.paddingTop
el.dataset.oldPaddingBottom = el.style.paddingBottom
el.dataset.oldOverflow = el.style.overflow
el.style.maxHeight = `${el.scrollHeight}px`
el.style.overflow = 'hidden'
},
leave(el: RendererElement) {
if (el.scrollHeight !== 0) {
el.style.maxHeight = 0
el.style.paddingTop = 0
el.style.paddingBottom = 0
}
},
afterLeave(el: RendererElement) {
reset(el)
emit('collapsed')
},
leaveCancelled(el: RendererElement) {
reset(el)
},
}
function reset(el: RendererElement) {
el.style.maxHeight = ''
el.style.overflow = el.dataset.oldOverflow
el.style.paddingTop = el.dataset.oldPaddingTop
el.style.paddingBottom = el.dataset.oldPaddingBottom
}
</script>
<style lang="scss">
.collapse-transition {
transition-property: height, padding-top, padding-bottom;
transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
transition-duration: var(--default-transition-duration, 150ms);
}
.collapse-transition-leave-active,
.collapse-transition-enter-active {
transition-property: opacity, max-height, padding-top, padding-bottom;
transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
transition-duration: var(--default-transition-duration, 150ms);
}
.collapse-transition-leave-to,
.collapse-transition-enter-from {
opacity: 0;
}
</style>

View File

@@ -10,78 +10,104 @@ export const useApp = createGlobalState(() => {
const toast = useToast() const toast = useToast()
const ready = ref(false) const ready = ref(false)
const isTauri = computed(() => '__TAURI_INTERNALS__' in window)
const commitSha = __COMMIT_SHA__
const version = computedAsync(() => {
if (import.meta.dev) {
return 'dev'
}
else if (isTauri.value) {
return getVersion()
}
else {
return 'web'
}
}, '-')
const inputMuted = ref(false) const inputMuted = computed(() => {
const outputMuted = ref(false) return !!mediasoup.micProducer.value?.paused
})
const previousInputMuted = ref(inputMuted.value) const previousInputMuted = ref(inputMuted.value)
const isTauri = computed(() => '__TAURI_INTERNALS__' in window) const outputMuted = ref(false)
const commitSha = __COMMIT_SHA__ const sharingEnabled = computed(() => {
return !!mediasoup.shareProducer.value
const version = computedAsync(() => isTauri.value ? getVersion() : 'web', '-')
watch(inputMuted, async (inputMuted) => {
if (inputMuted) {
await mediasoup.pauseProducer('microphone')
}
else {
if (outputMuted.value) {
outputMuted.value = false
}
await mediasoup.resumeProducer('microphone')
}
const toastText = inputMuted ? 'Microphone muted' : 'Microphone activated'
toast.add({ severity: 'info', summary: toastText, closable: false, life: 1000 })
}) })
watch(outputMuted, async (outputMuted) => { async function muteInput() {
if (outputMuted) { if (inputMuted.value)
previousInputMuted.value = inputMuted.value return
muteInput()
} await mediasoup.pauseProducer('microphone')
else {
inputMuted.value = previousInputMuted.value toast.add({ severity: 'info', summary: 'Microphone muted', closable: false, life: 1000 })
}
async function unmuteInput() {
if (!inputMuted.value)
return
if (outputMuted.value) {
await unmuteOutput()
} }
const updatedMe = await signaling.socket.value?.emitWithAck('updateClient', { await mediasoup.resumeProducer('microphone')
outputMuted,
toast.add({ severity: 'info', summary: 'Microphone activated', closable: false, life: 1000 })
}
async function toggleInput() {
if (inputMuted.value)
await unmuteInput()
else
await muteInput()
}
async function muteOutput() {
if (outputMuted.value)
return
outputMuted.value = true
previousInputMuted.value = inputMuted.value
await muteInput()
await signaling.socket.value?.emitWithAck('updateClient', {
outputMuted: true,
}) })
const toastText = outputMuted ? 'Sound muted' : 'Sound resumed' toast.add({ severity: 'info', summary: 'Sound muted', closable: false, life: 1000 })
toast.add({ severity: 'info', summary: toastText, closable: false, life: 1000 })
})
function muteInput() {
inputMuted.value = true
} }
function unmuteInput() { async function unmuteOutput() {
inputMuted.value = false
}
function toggleInput() {
if (inputMuted.value)
unmuteInput()
else
muteInput()
}
function muteOutput() {
outputMuted.value = true
}
function unmuteOutput() {
outputMuted.value = false outputMuted.value = false
if (!previousInputMuted.value)
await unmuteInput()
await signaling.socket.value?.emitWithAck('updateClient', {
outputMuted: false,
})
toast.add({ severity: 'info', summary: 'Sound resumed', closable: false, life: 1000 })
} }
function toggleOutput() { async function toggleOutput() {
if (outputMuted.value) if (outputMuted.value)
unmuteOutput() await unmuteOutput()
else else
muteOutput() await muteOutput()
}
async function toggleShare() {
if (!mediasoup.shareProducer.value) {
await mediasoup.enableShare()
}
else {
await mediasoup.disableProducer('share')
}
} }
return { return {
@@ -98,5 +124,7 @@ export const useApp = createGlobalState(() => {
version, version,
isTauri, isTauri,
commitSha, commitSha,
toggleShare,
sharingEnabled,
} }
}) })

View File

@@ -0,0 +1,28 @@
import { createGlobalState, useDevicesList } from '@vueuse/core'
export const useDevices = createGlobalState(() => {
const {
ensurePermissions,
permissionGranted,
videoInputs,
audioInputs,
audioOutputs,
} = useDevicesList()
async function getShareStream(fps = 30) {
return navigator.mediaDevices.getDisplayMedia({
audio: false,
video: {
displaySurface: 'monitor',
frameRate: { max: fps },
},
})
}
return {
videoInputs: computed<MediaDeviceInfo[]>(() => JSON.parse(JSON.stringify(videoInputs.value))),
audioInputs: computed<MediaDeviceInfo[]>(() => JSON.parse(JSON.stringify(audioInputs.value))),
audioOutputs: computed<MediaDeviceInfo[]>(() => JSON.parse(JSON.stringify(audioOutputs.value))),
getShareStream,
}
})

View File

@@ -0,0 +1,65 @@
import { createGlobalState, useEventListener } from '@vueuse/core'
export const useFullscreenVideo = createGlobalState(() => {
const videoEl = shallowRef<HTMLVideoElement>()
const visible = computed(() => !!videoEl.value)
async function show(stream: MediaStream) {
if (videoEl.value) {
videoEl.value.srcObject = stream
}
else {
const el = document.createElement('video')
el.srcObject = stream
el.autoplay = true
el.playsInline = true
el.controls = false
el.muted = true
// el.style.position = 'fixed'
// el.style.top = '0'
// el.style.left = '0'
// el.style.width = '1px'
// el.style.height = '1px'
// el.style.opacity = '0'
// el.style.pointerEvents = 'none'
document.body.appendChild(el)
videoEl.value = el
}
stream.getTracks().forEach(t =>
t.addEventListener('ended', hide),
)
videoEl.value.addEventListener('ended', hide)
await videoEl.value.requestFullscreen()
}
function hide() {
if (!videoEl.value)
return
(videoEl.value.srcObject as MediaStream).getTracks().forEach(t =>
t.removeEventListener('ended', hide),
)
videoEl.value.removeEventListener('ended', hide)
videoEl.value?.remove()
videoEl.value = undefined
}
useEventListener(document, 'fullscreenchange', () => {
if (!document.fullscreenElement && videoEl.value) {
videoEl.value?.remove()
videoEl.value = undefined
}
})
return {
visible,
show,
hide,
}
})

View File

@@ -1,6 +1,7 @@
import type { ChadClient } from '#shared/types' import type { MediaKind, ProducerOptions } from 'mediasoup-client/types'
import { createSharedComposable } from '@vueuse/core' import { createSharedComposable } from '@vueuse/core'
import * as mediasoupClient from 'mediasoup-client' import * as mediasoupClient from 'mediasoup-client'
import { useDevices } from '~/composables/use-devices'
import { usePreferences } from '~/composables/use-preferences' import { usePreferences } from '~/composables/use-preferences'
import { useSignaling } from '~/composables/use-signaling' import { useSignaling } from '~/composables/use-signaling'
@@ -25,7 +26,7 @@ export const useMediasoup = createSharedComposable(() => {
const signaling = useSignaling() const signaling = useSignaling()
const { addClient, removeClient } = useClients() const { addClient, removeClient } = useClients()
const preferences = usePreferences() const preferences = usePreferences()
const { me } = useAuth() const { getShareStream } = useDevices()
const device = shallowRef<mediasoupClient.Device>() const device = shallowRef<mediasoupClient.Device>()
const rtpCapabilities = shallowRef<mediasoupClient.types.RtpCapabilities>() const rtpCapabilities = shallowRef<mediasoupClient.types.RtpCapabilities>()
@@ -158,7 +159,7 @@ export const useMediasoup = createSharedComposable(() => {
producerId, producerId,
kind, kind,
rtpParameters, rtpParameters,
streamId: `${socketId}-${appData.share ? 'share' : 'mic-webcam'}`, streamId: `${socketId}-${appData.source === 'share' ? 'share' : 'mic-webcam'}`,
appData: { ...appData, socketId }, appData: { ...appData, socketId },
}) })
@@ -213,8 +214,6 @@ export const useMediasoup = createSharedComposable(() => {
consumer.pause() consumer.pause()
console.log(consumerId)
triggerRef(consumers) triggerRef(consumers)
}) })
@@ -230,20 +229,62 @@ export const useMediasoup = createSharedComposable(() => {
}) })
}, { immediate: true, flush: 'sync' }) }, { immediate: true, flush: 'sync' })
function getClientConsumers(socketId: ChadClient['socketId']) { async function enableProducer(type: ProducerType, options: ProducerOptions) {
return consumers.value.values().filter(consumer => consumer.appData.socketId === socketId) const producer = getProducerByType(type)
}
async function enableMic() { if (producer.value)
if (micProducer.value)
return return
if (!device.value || !sendTransport.value) if (!device.value || !sendTransport.value)
return return
if (!device.value.canProduce('audio')) if (!options.track)
return return
if (!device.value.canProduce(options.track.kind as MediaKind))
return
producer.value = await sendTransport.value.produce(options)
producers.value.set(producer.value.id, producer.value)
triggerRef(producers)
triggerRef(producer)
producer.value.on('transportclose', () => {
micProducer.value = undefined
})
producer.value.on('trackended', () => {
disableProducer(type)
})
}
async function disableProducer(type: ProducerType) {
const producer = getProducerByType(type)
if (!signaling.socket.value || !producer.value)
return
producers.value.delete(producer.value.id)
try {
producer.value.close()
await signaling.socket.value.emitWithAck('closeProducer', {
producerId: producer.value.id,
})
}
catch {
}
finally {
triggerRef(producers)
triggerRef(producer)
}
producer.value = undefined
}
async function enableMic() {
const stream = await navigator.mediaDevices.getUserMedia({ const stream = await navigator.mediaDevices.getUserMedia({
audio: { audio: {
deviceId: { exact: preferences.inputDeviceId.value }, deviceId: { exact: preferences.inputDeviceId.value },
@@ -258,7 +299,7 @@ export const useMediasoup = createSharedComposable(() => {
if (!track) if (!track)
return return
micProducer.value = await sendTransport.value.produce({ await enableProducer('microphone', {
track, track,
codecOptions: { codecOptions: {
opusStereo: true, opusStereo: true,
@@ -266,41 +307,35 @@ export const useMediasoup = createSharedComposable(() => {
opusFec: false, // Фиксит пакет лос opusFec: false, // Фиксит пакет лос
}, },
}) })
producers.value.set(micProducer.value.id, micProducer.value)
triggerRef(producers)
triggerRef(micProducer)
micProducer.value.on('transportclose', () => {
micProducer.value = undefined
})
micProducer.value.on('trackended', () => {
disableMic()
})
} }
async function disableMic() { async function disableMic() {
if (!signaling.socket.value || !micProducer.value) await disableProducer('microphone')
}
async function enableShare() {
if (!device.value)
return return
producers.value.delete(micProducer.value.id) const stream = await getShareStream(preferences.shareFps.value)
try { const track = stream.getVideoTracks()[0]
micProducer.value.close()
await signaling.socket.value.emitWithAck('closeProducer', { if (!track)
producerId: micProducer.value.id, return
})
}
catch {
}
finally {
triggerRef(producers)
triggerRef(micProducer)
}
micProducer.value = undefined await enableProducer('share', {
track,
codec: device.value.rtpCapabilities.codecs?.find(
c => c.mimeType.toLowerCase() === 'video/h264',
),
codecOptions: {
videoGoogleStartBitrate: 1000,
},
appData: {
source: 'share',
},
})
} }
async function pauseProducer(type: ProducerType) { async function pauseProducer(type: ProducerType) {
@@ -371,18 +406,6 @@ export const useMediasoup = createSharedComposable(() => {
} }
} }
watch(
preferences.inputDeviceId,
async (inputDeviceId) => {
await disableMic()
if (!inputDeviceId)
return
await enableMic()
},
)
watch([ watch([
preferences.inputDeviceId, preferences.inputDeviceId,
preferences.echoCancellation, preferences.echoCancellation,
@@ -408,8 +431,9 @@ export const useMediasoup = createSharedComposable(() => {
micProducer, micProducer,
cameraProducer, cameraProducer,
shareProducer, shareProducer,
getClientConsumers,
pauseProducer, pauseProducer,
resumeProducer, resumeProducer,
enableShare,
disableProducer,
} }
}) })

View File

@@ -1,5 +1,5 @@
import chadApi from '#shared/chad-api' import chadApi from '#shared/chad-api'
import { createGlobalState, useDevicesList, useLocalStorage, watchDebounced } from '@vueuse/core' import { createGlobalState, useLocalStorage, watchDebounced } from '@vueuse/core'
export interface SyncedPreferences { export interface SyncedPreferences {
toggleInputHotkey: string toggleInputHotkey: string
@@ -8,7 +8,9 @@ export interface SyncedPreferences {
} }
export const usePreferences = createGlobalState(() => { export const usePreferences = createGlobalState(() => {
const synced = ref(false) const { videoInputs, audioInputs, audioOutputs } = useDevices()
const fetched = ref(false)
const inputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('INPUT_DEVICE_ID', 'default') const inputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('INPUT_DEVICE_ID', 'default')
const outputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('OUTPUT_DEVICE_ID', 'default') const outputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('OUTPUT_DEVICE_ID', 'default')
@@ -17,17 +19,11 @@ export const usePreferences = createGlobalState(() => {
const noiseSuppression = useLocalStorage('NOISE_SUPPRESSION', true) const noiseSuppression = useLocalStorage('NOISE_SUPPRESSION', true)
const echoCancellation = useLocalStorage('ECHO_CANCELLATION', true) const echoCancellation = useLocalStorage('ECHO_CANCELLATION', true)
const shareFps = useLocalStorage('SHARE_FPS', 30)
const toggleInputHotkey = ref<SyncedPreferences['toggleInputHotkey']>('') const toggleInputHotkey = ref<SyncedPreferences['toggleInputHotkey']>('')
const toggleOutputHotkey = ref<SyncedPreferences['toggleOutputHotkey']>('') const toggleOutputHotkey = ref<SyncedPreferences['toggleOutputHotkey']>('')
const {
ensurePermissions,
permissionGranted,
videoInputs,
audioInputs,
audioOutputs,
} = useDevicesList()
const inputDeviceExist = computed(() => { const inputDeviceExist = computed(() => {
return audioInputs.value.some(device => device.deviceId === inputDeviceId.value) return audioInputs.value.some(device => device.deviceId === inputDeviceId.value)
}) })
@@ -57,18 +53,16 @@ export const usePreferences = createGlobalState(() => {
) )
return { return {
synced, fetched,
inputDeviceId, inputDeviceId,
outputDeviceId, outputDeviceId,
autoGainControl, autoGainControl,
noiseSuppression, noiseSuppression,
echoCancellation, echoCancellation,
shareFps,
toggleInputHotkey, toggleInputHotkey,
toggleOutputHotkey, toggleOutputHotkey,
inputDeviceExist, inputDeviceExist,
outputDeviceExist, outputDeviceExist,
videoInputs: computed(() => JSON.parse(JSON.stringify(videoInputs.value))),
audioInputs: computed(() => JSON.parse(JSON.stringify(audioInputs.value))),
audioOutputs: computed(() => JSON.parse(JSON.stringify(audioOutputs.value))),
} }
}) })

View File

@@ -1,25 +1,31 @@
<template> <template>
<div class="grid grid-cols-2 gap-2 p-2 h-screen grid-rows-[auto_1fr]"> <div class="grid grid-cols-[1fr_1.25fr] gap-2 p-2 h-screen grid-rows-[auto_1fr]">
<div <div
class="flex items-center justify-between gap-2 rounded-xl p-3 bg-surface-950" class="flex items-center justify-between gap-2 rounded-xl p-3 bg-surface-950"
> >
<div class="inline-flex items-center gap-3"> <div class="inline-flex items-center gap-3">
<PrimeBadge v-if="isTauri" class="opacity-50" severity="secondary" :value="version" size="small" /> <PrimeBadge class="opacity-50" severity="secondary" :value="version" size="small" />
<PrimeBadge :severity="connected ? 'success' : 'danger' " /> <PrimeBadge :severity="connected ? 'success' : 'danger' " />
</div> </div>
<PrimeButtonGroup class="ml-auto"> <PrimeButtonGroup class="ml-auto">
<PrimeButton outlined @click="toggleInput"> <PrimeButton :severity="inputMuted ? 'info' : undefined" outlined @click="toggleInput">
<template #icon> <template #icon>
<Component :is="inputMuted ? MicOff : Mic" /> <Component :is="inputMuted ? MicOff : Mic" />
</template> </template>
</PrimeButton> </PrimeButton>
<PrimeButton outlined @click="toggleOutput"> <PrimeButton :severity="outputMuted ? 'info' : undefined" outlined @click="toggleOutput">
<template #icon> <template #icon>
<Component :is="outputMuted ? VolumeOff : Volume2" /> <Component :is="outputMuted ? VolumeOff : Volume2" />
</template> </template>
</PrimeButton> </PrimeButton>
</PrimeButtonGroup> </PrimeButtonGroup>
<PrimeButton :severity="sharingEnabled ? 'success' : undefined" outlined @click="toggleShare">
<template #icon>
<Component :is="sharingEnabled ? ScreenShareOff : ScreenShare" />
</template>
</PrimeButton>
</div> </div>
<div <div
@@ -39,8 +45,8 @@
</div> </div>
<PrimeScrollPanel class="bg-surface-900 rounded-xl" style="min-height: 0"> <PrimeScrollPanel class="bg-surface-900 rounded-xl" style="min-height: 0">
<div v-auto-animate class="p-3 divide-y divide-surface-800"> <div v-auto-animate class="p-3 space-y-1">
<ClientRow v-for="client of clients" :key="client.id" :client="client" /> <ClientRow v-for="client of clients" :key="client.userId" :client="client" />
</div> </div>
</PrimeScrollPanel> </PrimeScrollPanel>
@@ -53,9 +59,28 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { MessageCircle, Mic, MicOff, Settings, UserPen, Volume2, VolumeOff } from 'lucide-vue-next' import {
MessageCircle,
Mic,
MicOff,
ScreenShare,
ScreenShareOff,
Settings,
UserPen,
Volume2,
VolumeOff,
} from 'lucide-vue-next'
const { clients, inputMuted, toggleInput, outputMuted, toggleOutput, version, isTauri } = useApp() const {
version,
clients,
inputMuted,
outputMuted,
sharingEnabled,
toggleInput,
toggleOutput,
toggleShare,
} = useApp()
const { connect, connected } = useSignaling() const { connect, connected } = useSignaling()
interface Tab { interface Tab {

View File

@@ -18,6 +18,6 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
} }
if (me.value && to.meta.auth === 'guest') { if (me.value && to.meta.auth === 'guest') {
return navigateTo('/') return navigateTo({ name: 'Index' })
} }
}) })

View File

@@ -7,9 +7,9 @@ export default defineNuxtRouteMiddleware(async () => {
if (!me.value) if (!me.value)
return return
const { synced, toggleInputHotkey, toggleOutputHotkey } = usePreferences() const { fetched, toggleInputHotkey, toggleOutputHotkey } = usePreferences()
if (synced.value) if (fetched.value)
return return
try { try {
@@ -20,7 +20,7 @@ export default defineNuxtRouteMiddleware(async () => {
toggleInputHotkey.value = preferences.toggleInputHotkey ?? toggleInputHotkey.value toggleInputHotkey.value = preferences.toggleInputHotkey ?? toggleInputHotkey.value
toggleOutputHotkey.value = preferences.toggleOutputHotkey ?? toggleOutputHotkey.value toggleOutputHotkey.value = preferences.toggleOutputHotkey ?? toggleOutputHotkey.value
synced.value = true fetched.value = true
} }
catch {} catch {}
}) })

View File

@@ -1,10 +1,12 @@
<template> <template>
<div class="flex items-center justify-center"> <div>
<PrimeCard> <div class="flex items-center justify-center">
<template #content> <PrimeCard>
The chat is under development. <template #content>
</template> The chat is under development.
</PrimeCard> </template>
</PrimeCard>
</div>
</div> </div>
</template> </template>

View File

@@ -47,6 +47,18 @@
<!-- <label for="outputDevice">Output device</label> --> <!-- <label for="outputDevice">Output device</label> -->
<!-- </PrimeFloatLabel> --> <!-- </PrimeFloatLabel> -->
<PrimeDivider align="left">
Video
</PrimeDivider>
<div>
<div class="flex justify-between text-sm mb-3">
<span>FPS</span>
<span>{{ shareFps }}</span>
</div>
<PrimeSlider v-model="shareFps" class="mx-[10px]" :min="30" :max="60" :step="5" />
</div>
<template v-if="isTauri"> <template v-if="isTauri">
<PrimeDivider align="left"> <PrimeDivider align="left">
Hotkeys Hotkeys
@@ -106,6 +118,7 @@ definePageMeta({
}) })
const { isTauri, version, commitSha } = useApp() const { isTauri, version, commitSha } = useApp()
const { checking, checkForUpdates, lastUpdate } = useUpdater() const { checking, checkForUpdates, lastUpdate } = useUpdater()
const { audioInputs, audioOutputs } = useDevices()
const { const {
inputDeviceId, inputDeviceId,
outputDeviceId, outputDeviceId,
@@ -116,8 +129,7 @@ const {
toggleOutputHotkey, toggleOutputHotkey,
inputDeviceExist, inputDeviceExist,
outputDeviceExist, outputDeviceExist,
audioInputs, shareFps,
audioOutputs,
} = usePreferences() } = usePreferences()
const setupToggleInputHotkey = (event: KeyboardEvent) => setupHotkey(event, toggleInputHotkey) const setupToggleInputHotkey = (event: KeyboardEvent) => setupHotkey(event, toggleInputHotkey)

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "chad", "productName": "chad",
"version": "0.2.15", "version": "0.2.19",
"identifier": "xyz.koptilnya.chad", "identifier": "xyz.koptilnya.chad",
"build": { "build": {
"frontendDist": "../.output/public", "frontendDist": "../.output/public",

14
server/dto/channel.dto.ts Normal file
View File

@@ -0,0 +1,14 @@
// dto/channel.dto.ts
import type { Prisma } from '../prisma/generated/client'
export const channelPublicSelect = {
id: true,
name: true,
owner_id: true,
persistent: true,
maxClients: true,
} satisfies Prisma.ChannelSelect
export type ChannelPublicDTO = Prisma.ChannelGetPayload<{
select: typeof channelPublicSelect
}>

View File

@@ -2,7 +2,7 @@
"name": "server", "name": "server",
"scripts": { "scripts": {
"start": "ts-node --transpile-only server.ts", "start": "ts-node --transpile-only server.ts",
"db:deploy": "npx prisma migrate deploy && npx prisma generate" "db:deploy": "npx prisma migrate deploy && npx prisma db seed && npx prisma generate"
}, },
"type": "module", "type": "module",
"packageManager": "yarn@4.10.3", "packageManager": "yarn@4.10.3",
@@ -11,7 +11,8 @@
"@fastify/cookie": "^11.0.2", "@fastify/cookie": "^11.0.2",
"@fastify/cors": "^11.1.0", "@fastify/cors": "^11.1.0",
"@lucia-auth/adapter-prisma": "^4.0.1", "@lucia-auth/adapter-prisma": "^4.0.1",
"@prisma/client": "^6.17.0", "@prisma/adapter-better-sqlite3": "^7.2.0",
"@prisma/client": "7",
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"consola": "^3.4.2", "consola": "^3.4.2",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
@@ -19,7 +20,7 @@
"fastify-plugin": "^5.1.0", "fastify-plugin": "^5.1.0",
"lucia": "^3.2.2", "lucia": "^3.2.2",
"mediasoup": "^3.19.3", "mediasoup": "^3.19.3",
"prisma": "^6.17.0", "prisma": "7",
"socket.io": "^4.8.1", "socket.io": "^4.8.1",
"ws": "^8.18.3", "ws": "^8.18.3",
"zod": "^4.1.12" "zod": "^4.1.12"

View File

@@ -24,6 +24,7 @@ export default fp<Partial<ServerOptions>>(
fastify.ready(() => { fastify.ready(() => {
registerWebrtcSocket(fastify.io, fastify.mediasoupRouter) registerWebrtcSocket(fastify.io, fastify.mediasoupRouter)
registerChannelSocket(fastify.io, fastify.mediasoupRouter)
}) })
}, },
{ name: 'socket-io', dependencies: ['mediasoup-worker', 'mediasoup-router'] }, { name: 'socket-io', dependencies: ['mediasoup-worker', 'mediasoup-router'] },

13
server/prisma.config.ts Normal file
View File

@@ -0,0 +1,13 @@
import { defineConfig, env } from 'prisma/config'
import 'dotenv/config'
export default defineConfig({
schema: './prisma/schema.prisma',
migrations: {
path: './prisma/migrations',
seed: 'ts-node ./prisma/seed.ts',
},
datasource: {
url: env('DATABASE_URL'),
},
})

View File

@@ -1,6 +1,14 @@
import { PrismaClient } from '@prisma/client' import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3'
import { env } from 'prisma/config'
import { PrismaClient } from './generated/client/index.js'
import 'dotenv/config'
const client = new PrismaClient({ const client = new PrismaClient({
adapter: new PrismaBetterSqlite3({
url: env('DATABASE_URL'),
}, {
timestampFormat: 'unixepoch-ms',
}),
log: ['query', 'error', 'warn'], log: ['query', 'error', 'warn'],
}) })

View File

@@ -0,0 +1,39 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* This file should be your main import to use Prisma-related types and utilities in a browser.
* Use it to get access to models, enums, and input types.
*
* This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only.
* See `client.ts` for the standard, server-side entry point.
*
* 🟢 You can import this file directly.
*/
import * as Prisma from './internal/prismaNamespaceBrowser.ts'
export { Prisma }
export * as $Enums from './enums.ts'
export * from './enums.ts';
/**
* Model User
*
*/
export type User = Prisma.UserModel
/**
* Model Session
*
*/
export type Session = Prisma.SessionModel
/**
* Model UserPreferences
*
*/
export type UserPreferences = Prisma.UserPreferencesModel
/**
* Model Channel
*
*/
export type Channel = Prisma.ChannelModel

View File

@@ -0,0 +1 @@
export * from "./index"

View File

@@ -0,0 +1,5 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('.') }

View File

@@ -0,0 +1,61 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
* If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead.
*
* 🟢 You can import this file directly.
*/
import * as process from 'node:process'
import * as path from 'node:path'
import { fileURLToPath } from 'node:url'
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url))
import * as runtime from "@prisma/client/runtime/client"
import * as $Enums from "./enums.ts"
import * as $Class from "./internal/class.ts"
import * as Prisma from "./internal/prismaNamespace.ts"
export * as $Enums from './enums.ts'
export * from "./enums.ts"
/**
* ## Prisma Client
*
* Type-safe database client for TypeScript
* @example
* ```
* const prisma = new PrismaClient()
* // Fetch zero or more Users
* const users = await prisma.user.findMany()
* ```
*
* Read more in our [docs](https://pris.ly/d/client).
*/
export const PrismaClient = $Class.getPrismaClientClass()
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
export { Prisma }
/**
* Model User
*
*/
export type User = Prisma.UserModel
/**
* Model Session
*
*/
export type Session = Prisma.SessionModel
/**
* Model UserPreferences
*
*/
export type UserPreferences = Prisma.UserPreferencesModel
/**
* Model Channel
*
*/
export type Channel = Prisma.ChannelModel

View File

@@ -0,0 +1,298 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* This file exports various common sort, input & filter types that are not directly linked to a particular model.
*
* 🟢 You can import this file directly.
*/
import type * as runtime from "@prisma/client/runtime/client"
import * as $Enums from "./enums.ts"
import type * as Prisma from "./internal/prismaNamespace.ts"
export type StringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]
notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringFilter<$PrismaModel> | string
}
export type DateTimeFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[]
notIn?: Date[] | string[]
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
}
export type StringWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]
notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedStringFilter<$PrismaModel>
_max?: Prisma.NestedStringFilter<$PrismaModel>
}
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[]
notIn?: Date[] | string[]
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
}
export type StringNullableFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null
notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
}
export type SortOrderInput = {
sort: Prisma.SortOrder
nulls?: Prisma.NullsOrder
}
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null
notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
}
export type IntNullableFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null
notIn?: number[] | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
}
export type BoolFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
}
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null
notIn?: number[] | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
}
export type BoolWithAggregatesFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedBoolFilter<$PrismaModel>
_max?: Prisma.NestedBoolFilter<$PrismaModel>
}
export type NestedStringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]
notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringFilter<$PrismaModel> | string
}
export type NestedDateTimeFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[]
notIn?: Date[] | string[]
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
}
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]
notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedStringFilter<$PrismaModel>
_max?: Prisma.NestedStringFilter<$PrismaModel>
}
export type NestedIntFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[]
notIn?: number[]
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntFilter<$PrismaModel> | number
}
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[]
notIn?: Date[] | string[]
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
}
export type NestedStringNullableFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null
notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
}
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null
notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
}
export type NestedIntNullableFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null
notIn?: number[] | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
}
export type NestedBoolFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
}
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null
notIn?: number[] | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
}
export type NestedFloatNullableFilter<$PrismaModel = never> = {
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
in?: number[] | null
notIn?: number[] | null
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
}
export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedBoolFilter<$PrismaModel>
_max?: Prisma.NestedBoolFilter<$PrismaModel>
}

View File

@@ -0,0 +1 @@
export * from "./index"

View File

@@ -0,0 +1,5 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('#main-entry-point') }

View File

@@ -0,0 +1 @@
export * from "./default"

View File

@@ -0,0 +1,165 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
Object.defineProperty(exports, "__esModule", { value: true });
const {
PrismaClientKnownRequestError,
PrismaClientUnknownRequestError,
PrismaClientRustPanicError,
PrismaClientInitializationError,
PrismaClientValidationError,
getPrismaClient,
sqltag,
empty,
join,
raw,
skip,
Decimal,
Debug,
DbNull,
JsonNull,
AnyNull,
NullTypes,
makeStrictEnum,
Extensions,
warnOnce,
defineDmmfProperty,
Public,
getRuntime,
createParam,
} = require('./runtime/wasm-compiler-edge.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 7.2.0
* Query Engine version: 0c8ef2ce45c83248ab3df073180d5eda9e8be7a3
*/
Prisma.prismaVersion = {
client: "7.2.0",
engine: "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
}
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
Prisma.PrismaClientUnknownRequestError = PrismaClientUnknownRequestError
Prisma.PrismaClientRustPanicError = PrismaClientRustPanicError
Prisma.PrismaClientInitializationError = PrismaClientInitializationError
Prisma.PrismaClientValidationError = PrismaClientValidationError
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = sqltag
Prisma.empty = empty
Prisma.join = join
Prisma.raw = raw
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = Extensions.getExtensionContext
Prisma.defineExtension = Extensions.defineExtension
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = DbNull
Prisma.JsonNull = JsonNull
Prisma.AnyNull = AnyNull
Prisma.NullTypes = NullTypes
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
Serializable: 'Serializable'
});
exports.Prisma.UserScalarFieldEnum = {
id: 'id',
username: 'username',
password: 'password',
displayName: 'displayName',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.SessionScalarFieldEnum = {
id: 'id',
userId: 'userId',
expiresAt: 'expiresAt'
};
exports.Prisma.UserPreferencesScalarFieldEnum = {
userId: 'userId',
toggleInputHotkey: 'toggleInputHotkey',
toggleOutputHotkey: 'toggleOutputHotkey'
};
exports.Prisma.ChannelScalarFieldEnum = {
id: 'id',
name: 'name',
maxClients: 'maxClients',
persistent: 'persistent',
owner_id: 'owner_id'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.Prisma.ModelName = {
User: 'User',
Session: 'Session',
UserPreferences: 'UserPreferences',
Channel: 'Channel'
};
/**
* Create the Client
*/
const config = {
"previewFeatures": [],
"clientVersion": "7.2.0",
"engineVersion": "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
"activeProvider": "sqlite",
"inlineSchema": "datasource db {\n provider = \"sqlite\"\n}\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"./generated/client\"\n}\n\nmodel User {\n id String @id @default(cuid())\n username String @unique\n password String\n displayName String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n Session Session[]\n UserPreferences UserPreferences?\n channels Channel[]\n}\n\nmodel Session {\n id String @id\n userId String\n expiresAt DateTime\n user User @relation(references: [id], fields: [userId], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel UserPreferences {\n userId String @id\n toggleInputHotkey String? @default(\"\")\n toggleOutputHotkey String? @default(\"\")\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Channel {\n id String @id\n name String\n maxClients Int?\n persistent Boolean @default(false)\n owner_id String?\n owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)\n}\n"
}
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"Session\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"UserPreferences\",\"kind\":\"object\",\"type\":\"UserPreferences\",\"relationName\":\"UserToUserPreferences\"},{\"name\":\"channels\",\"kind\":\"object\",\"type\":\"Channel\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"UserPreferences\":{\"fields\":[{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleInputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleOutputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserPreferences\"}],\"dbName\":null},\"Channel\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"maxClients\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"persistent\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"owner_id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
config.compilerWasm = {
getRuntime: async () => require('./query_compiler_bg.js'),
getQueryCompilerWasmModule: async () => {
const loader = (await import('#wasm-compiler-loader')).default
const compiler = (await loader).default
return compiler
}
}
if (typeof globalThis !== 'undefined' && globalThis['DEBUG'] || (typeof process !== 'undefined' && process.env && process.env.DEBUG) || undefined) {
Debug.enable(typeof globalThis !== 'undefined' && globalThis['DEBUG'] || (typeof process !== 'undefined' && process.env && process.env.DEBUG) || undefined)
}
const PrismaClient = getPrismaClient(config)
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)

View File

@@ -0,0 +1,15 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* This file exports all enum related types from the schema.
*
* 🟢 You can import this file directly.
*/
// This file is empty because there are no enums in the schema.
export {}

View File

@@ -0,0 +1,196 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
Object.defineProperty(exports, "__esModule", { value: true });
const {
Decimal,
DbNull,
JsonNull,
AnyNull,
NullTypes,
makeStrictEnum,
Public,
getRuntime,
skip
} = require('./runtime/index-browser.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 7.2.0
* Query Engine version: 0c8ef2ce45c83248ab3df073180d5eda9e8be7a3
*/
Prisma.prismaVersion = {
client: "7.2.0",
engine: "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
}
Prisma.PrismaClientKnownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)};
Prisma.PrismaClientUnknownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientRustPanicError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientInitializationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientValidationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.empty = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.join = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.raw = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.defineExtension = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = DbNull
Prisma.JsonNull = JsonNull
Prisma.AnyNull = AnyNull
Prisma.NullTypes = NullTypes
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
Serializable: 'Serializable'
});
exports.Prisma.UserScalarFieldEnum = {
id: 'id',
username: 'username',
password: 'password',
displayName: 'displayName',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.SessionScalarFieldEnum = {
id: 'id',
userId: 'userId',
expiresAt: 'expiresAt'
};
exports.Prisma.UserPreferencesScalarFieldEnum = {
userId: 'userId',
toggleInputHotkey: 'toggleInputHotkey',
toggleOutputHotkey: 'toggleOutputHotkey'
};
exports.Prisma.ChannelScalarFieldEnum = {
id: 'id',
name: 'name',
maxClients: 'maxClients',
persistent: 'persistent',
owner_id: 'owner_id'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.Prisma.ModelName = {
User: 'User',
Session: 'Session',
UserPreferences: 'UserPreferences',
Channel: 'Channel'
};
/**
* This is a stub Prisma Client that will error at runtime if called.
*/
class PrismaClient {
constructor() {
return new Proxy(this, {
get(target, prop) {
let message
const runtime = getRuntime()
if (runtime.isEdge) {
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
- Use Prisma Accelerate: https://pris.ly/d/accelerate
- Use Driver Adapters: https://pris.ly/d/driver-adapters
`;
} else {
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
}
message += `
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
throw new Error(message)
}
})
}
}
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)

6981
server/prisma/generated/client/index.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,165 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
Object.defineProperty(exports, "__esModule", { value: true });
const {
PrismaClientKnownRequestError,
PrismaClientUnknownRequestError,
PrismaClientRustPanicError,
PrismaClientInitializationError,
PrismaClientValidationError,
getPrismaClient,
sqltag,
empty,
join,
raw,
skip,
Decimal,
Debug,
DbNull,
JsonNull,
AnyNull,
NullTypes,
makeStrictEnum,
Extensions,
warnOnce,
defineDmmfProperty,
Public,
getRuntime,
createParam,
} = require('./runtime/client.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 7.2.0
* Query Engine version: 0c8ef2ce45c83248ab3df073180d5eda9e8be7a3
*/
Prisma.prismaVersion = {
client: "7.2.0",
engine: "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
}
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
Prisma.PrismaClientUnknownRequestError = PrismaClientUnknownRequestError
Prisma.PrismaClientRustPanicError = PrismaClientRustPanicError
Prisma.PrismaClientInitializationError = PrismaClientInitializationError
Prisma.PrismaClientValidationError = PrismaClientValidationError
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = sqltag
Prisma.empty = empty
Prisma.join = join
Prisma.raw = raw
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = Extensions.getExtensionContext
Prisma.defineExtension = Extensions.defineExtension
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = DbNull
Prisma.JsonNull = JsonNull
Prisma.AnyNull = AnyNull
Prisma.NullTypes = NullTypes
const path = require('path')
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
Serializable: 'Serializable'
});
exports.Prisma.UserScalarFieldEnum = {
id: 'id',
username: 'username',
password: 'password',
displayName: 'displayName',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.SessionScalarFieldEnum = {
id: 'id',
userId: 'userId',
expiresAt: 'expiresAt'
};
exports.Prisma.UserPreferencesScalarFieldEnum = {
userId: 'userId',
toggleInputHotkey: 'toggleInputHotkey',
toggleOutputHotkey: 'toggleOutputHotkey'
};
exports.Prisma.ChannelScalarFieldEnum = {
id: 'id',
name: 'name',
maxClients: 'maxClients',
persistent: 'persistent',
owner_id: 'owner_id'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.Prisma.ModelName = {
User: 'User',
Session: 'Session',
UserPreferences: 'UserPreferences',
Channel: 'Channel'
};
/**
* Create the Client
*/
const config = {
"previewFeatures": [],
"clientVersion": "7.2.0",
"engineVersion": "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
"activeProvider": "sqlite",
"inlineSchema": "datasource db {\n provider = \"sqlite\"\n}\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"./generated/client\"\n}\n\nmodel User {\n id String @id @default(cuid())\n username String @unique\n password String\n displayName String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n Session Session[]\n UserPreferences UserPreferences?\n channels Channel[]\n}\n\nmodel Session {\n id String @id\n userId String\n expiresAt DateTime\n user User @relation(references: [id], fields: [userId], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel UserPreferences {\n userId String @id\n toggleInputHotkey String? @default(\"\")\n toggleOutputHotkey String? @default(\"\")\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Channel {\n id String @id\n name String\n maxClients Int?\n persistent Boolean @default(false)\n owner_id String?\n owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)\n}\n"
}
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"Session\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"UserPreferences\",\"kind\":\"object\",\"type\":\"UserPreferences\",\"relationName\":\"UserToUserPreferences\"},{\"name\":\"channels\",\"kind\":\"object\",\"type\":\"Channel\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"UserPreferences\":{\"fields\":[{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleInputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleOutputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserPreferences\"}],\"dbName\":null},\"Channel\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"maxClients\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"persistent\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"owner_id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
config.compilerWasm = {
getRuntime: async () => require('./query_compiler_bg.js'),
getQueryCompilerWasmModule: async () => {
const { Buffer } = require('node:buffer')
const { wasm } = require('./query_compiler_bg.wasm-base64.js')
const queryCompilerWasmFileBytes = Buffer.from(wasm, 'base64')
return new WebAssembly.Module(queryCompilerWasmFileBytes)
}
}
const PrismaClient = getPrismaClient(config)
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)

View File

@@ -0,0 +1,220 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* WARNING: This is an internal file that is subject to change!
*
* 🛑 Under no circumstances should you import this file directly! 🛑
*
* Please import the `PrismaClient` class from the `client.ts` file instead.
*/
import * as runtime from "@prisma/client/runtime/client"
import type * as Prisma from "./prismaNamespace.ts"
const config: runtime.GetPrismaClientConfig = {
"previewFeatures": [],
"clientVersion": "7.2.0",
"engineVersion": "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
"activeProvider": "sqlite",
"inlineSchema": "datasource db {\n provider = \"sqlite\"\n // url = env(\"DATABASE_URL\")\n}\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"./generated/client\"\n}\n\nmodel User {\n id String @id @default(cuid())\n username String @unique\n password String\n displayName String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n Session Session[]\n UserPreferences UserPreferences?\n channels Channel[]\n}\n\nmodel Session {\n id String @id\n userId String\n expiresAt DateTime\n user User @relation(references: [id], fields: [userId], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel UserPreferences {\n userId String @id\n toggleInputHotkey String? @default(\"\")\n toggleOutputHotkey String? @default(\"\")\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Channel {\n id String @id @default(cuid())\n name String\n maxClients Int?\n persistent Boolean @default(false)\n owner_id String?\n owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)\n}\n",
"runtimeDataModel": {
"models": {},
"enums": {},
"types": {}
}
}
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"Session\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"UserPreferences\",\"kind\":\"object\",\"type\":\"UserPreferences\",\"relationName\":\"UserToUserPreferences\"},{\"name\":\"channels\",\"kind\":\"object\",\"type\":\"Channel\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"UserPreferences\":{\"fields\":[{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleInputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleOutputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserPreferences\"}],\"dbName\":null},\"Channel\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"maxClients\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"persistent\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"owner_id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> {
const { Buffer } = await import('node:buffer')
const wasmArray = Buffer.from(wasmBase64, 'base64')
return new WebAssembly.Module(wasmArray)
}
config.compilerWasm = {
getRuntime: async () => await import("@prisma/client/runtime/query_compiler_bg.sqlite.mjs"),
getQueryCompilerWasmModule: async () => {
const { wasm } = await import("@prisma/client/runtime/query_compiler_bg.sqlite.wasm-base64.mjs")
return await decodeBase64AsWasm(wasm)
}
}
export type LogOptions<ClientOptions extends Prisma.PrismaClientOptions> =
'log' extends keyof ClientOptions ? ClientOptions['log'] extends Array<Prisma.LogLevel | Prisma.LogDefinition> ? Prisma.GetEvents<ClientOptions['log']> : never : never
export interface PrismaClientConstructor {
/**
* ## Prisma Client
*
* Type-safe database client for TypeScript
* @example
* ```
* const prisma = new PrismaClient()
* // Fetch zero or more Users
* const users = await prisma.user.findMany()
* ```
*
* Read more in our [docs](https://pris.ly/d/client).
*/
new <
Options extends Prisma.PrismaClientOptions = Prisma.PrismaClientOptions,
LogOpts extends LogOptions<Options> = LogOptions<Options>,
OmitOpts extends Prisma.PrismaClientOptions['omit'] = Options extends { omit: infer U } ? U : Prisma.PrismaClientOptions['omit'],
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
>(options: Prisma.Subset<Options, Prisma.PrismaClientOptions> ): PrismaClient<LogOpts, OmitOpts, ExtArgs>
}
/**
* ## Prisma Client
*
* Type-safe database client for TypeScript
* @example
* ```
* const prisma = new PrismaClient()
* // Fetch zero or more Users
* const users = await prisma.user.findMany()
* ```
*
* Read more in our [docs](https://pris.ly/d/client).
*/
export interface PrismaClient<
in LogOpts extends Prisma.LogLevel = never,
in out OmitOpts extends Prisma.PrismaClientOptions['omit'] = undefined,
in out ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
> {
[K: symbol]: { types: Prisma.TypeMap<ExtArgs>['other'] }
$on<V extends LogOpts>(eventType: V, callback: (event: V extends 'query' ? Prisma.QueryEvent : Prisma.LogEvent) => void): PrismaClient;
/**
* Connect with the database
*/
$connect(): runtime.Types.Utils.JsPromise<void>;
/**
* Disconnect from the database
*/
$disconnect(): runtime.Types.Utils.JsPromise<void>;
/**
* Executes a prepared raw query and returns the number of affected rows.
* @example
* ```
* const result = await prisma.$executeRaw`UPDATE User SET cool = ${true} WHERE email = ${'user@email.com'};`
* ```
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$executeRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<number>;
/**
* Executes a raw query and returns the number of affected rows.
* Susceptible to SQL injections, see documentation.
* @example
* ```
* const result = await prisma.$executeRawUnsafe('UPDATE User SET cool = $1 WHERE email = $2 ;', true, 'user@email.com')
* ```
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$executeRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<number>;
/**
* Performs a prepared raw query and returns the `SELECT` data.
* @example
* ```
* const result = await prisma.$queryRaw`SELECT * FROM User WHERE id = ${1} OR email = ${'user@email.com'};`
* ```
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$queryRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<T>;
/**
* Performs a raw query and returns the `SELECT` data.
* Susceptible to SQL injections, see documentation.
* @example
* ```
* const result = await prisma.$queryRawUnsafe('SELECT * FROM User WHERE id = $1 OR email = $2;', 1, 'user@email.com')
* ```
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$queryRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<T>;
/**
* Allows the running of a sequence of read/write operations that are guaranteed to either succeed or fail as a whole.
* @example
* ```
* const [george, bob, alice] = await prisma.$transaction([
* prisma.user.create({ data: { name: 'George' } }),
* prisma.user.create({ data: { name: 'Bob' } }),
* prisma.user.create({ data: { name: 'Alice' } }),
* ])
* ```
*
* Read more in our [docs](https://www.prisma.io/docs/concepts/components/prisma-client/transactions).
*/
$transaction<P extends Prisma.PrismaPromise<any>[]>(arg: [...P], options?: { isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>>
$transaction<R>(fn: (prisma: Omit<PrismaClient, runtime.ITXClientDenyList>) => runtime.Types.Utils.JsPromise<R>, options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<R>
$extends: runtime.Types.Extensions.ExtendsHook<"extends", Prisma.TypeMapCb<OmitOpts>, ExtArgs, runtime.Types.Utils.Call<Prisma.TypeMapCb<OmitOpts>, {
extArgs: ExtArgs
}>>
/**
* `prisma.user`: Exposes CRUD operations for the **User** model.
* Example usage:
* ```ts
* // Fetch zero or more Users
* const users = await prisma.user.findMany()
* ```
*/
get user(): Prisma.UserDelegate<ExtArgs, { omit: OmitOpts }>;
/**
* `prisma.session`: Exposes CRUD operations for the **Session** model.
* Example usage:
* ```ts
* // Fetch zero or more Sessions
* const sessions = await prisma.session.findMany()
* ```
*/
get session(): Prisma.SessionDelegate<ExtArgs, { omit: OmitOpts }>;
/**
* `prisma.userPreferences`: Exposes CRUD operations for the **UserPreferences** model.
* Example usage:
* ```ts
* // Fetch zero or more UserPreferences
* const userPreferences = await prisma.userPreferences.findMany()
* ```
*/
get userPreferences(): Prisma.UserPreferencesDelegate<ExtArgs, { omit: OmitOpts }>;
/**
* `prisma.channel`: Exposes CRUD operations for the **Channel** model.
* Example usage:
* ```ts
* // Fetch zero or more Channels
* const channels = await prisma.channel.findMany()
* ```
*/
get channel(): Prisma.ChannelDelegate<ExtArgs, { omit: OmitOpts }>;
}
export function getPrismaClientClass(): PrismaClientConstructor {
return runtime.getPrismaClient(config) as unknown as PrismaClientConstructor
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,128 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* WARNING: This is an internal file that is subject to change!
*
* 🛑 Under no circumstances should you import this file directly! 🛑
*
* All exports from this file are wrapped under a `Prisma` namespace object in the browser.ts file.
* While this enables partial backward compatibility, it is not part of the stable public API.
*
* If you are looking for your Models, Enums, and Input Types, please import them from the respective
* model files in the `model` directory!
*/
import * as runtime from "@prisma/client/runtime/index-browser"
export type * from '../models.ts'
export type * from './prismaNamespace.ts'
export const Decimal = runtime.Decimal
export const NullTypes = {
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
}
/**
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
*
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/
export const DbNull = runtime.DbNull
/**
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
*
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/
export const JsonNull = runtime.JsonNull
/**
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
*
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/
export const AnyNull = runtime.AnyNull
export const ModelName = {
User: 'User',
Session: 'Session',
UserPreferences: 'UserPreferences',
Channel: 'Channel'
} as const
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
/*
* Enums
*/
export const TransactionIsolationLevel = {
Serializable: 'Serializable'
} as const
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
export const UserScalarFieldEnum = {
id: 'id',
username: 'username',
password: 'password',
displayName: 'displayName',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
export const SessionScalarFieldEnum = {
id: 'id',
userId: 'userId',
expiresAt: 'expiresAt'
} as const
export type SessionScalarFieldEnum = (typeof SessionScalarFieldEnum)[keyof typeof SessionScalarFieldEnum]
export const UserPreferencesScalarFieldEnum = {
userId: 'userId',
toggleInputHotkey: 'toggleInputHotkey',
toggleOutputHotkey: 'toggleOutputHotkey'
} as const
export type UserPreferencesScalarFieldEnum = (typeof UserPreferencesScalarFieldEnum)[keyof typeof UserPreferencesScalarFieldEnum]
export const ChannelScalarFieldEnum = {
id: 'id',
name: 'name',
maxClients: 'maxClients',
persistent: 'persistent',
owner_id: 'owner_id'
} as const
export type ChannelScalarFieldEnum = (typeof ChannelScalarFieldEnum)[keyof typeof ChannelScalarFieldEnum]
export const SortOrder = {
asc: 'asc',
desc: 'desc'
} as const
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export const NullsOrder = {
first: 'first',
last: 'last'
} as const
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]

View File

@@ -0,0 +1,15 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* This is a barrel export file for all models and their related types.
*
* 🟢 You can import this file directly.
*/
export type * from './models/User.ts'
export type * from './models/Session.ts'
export type * from './models/UserPreferences.ts'
export type * from './models/Channel.ts'
export type * from './commonInputTypes.ts'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,144 @@
{
"name": "prisma-client-bc59e606b3f744a8b15c18a26f89a0eb5328cd0b7e5ee2c8265028eef68b0317",
"main": "index.js",
"types": "index.d.ts",
"browser": "default.js",
"exports": {
"./client": {
"require": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./package.json": "./package.json",
".": {
"require": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./extension": {
"types": "./extension.d.ts",
"require": "./extension.js",
"import": "./extension.js",
"default": "./extension.js"
},
"./index-browser": {
"types": "./index.d.ts",
"require": "./index-browser.js",
"import": "./index-browser.js",
"default": "./index-browser.js"
},
"./index": {
"types": "./index.d.ts",
"require": "./index.js",
"import": "./index.js",
"default": "./index.js"
},
"./edge": {
"types": "./edge.d.ts",
"require": "./edge.js",
"import": "./edge.js",
"default": "./edge.js"
},
"./runtime/client": {
"types": "./runtime/client.d.ts",
"node": {
"require": "./runtime/client.js",
"default": "./runtime/client.js"
},
"require": "./runtime/client.js",
"import": "./runtime/client.mjs",
"default": "./runtime/client.mjs"
},
"./runtime/wasm-compiler-edge": {
"types": "./runtime/wasm-compiler-edge.d.ts",
"require": "./runtime/wasm-compiler-edge.js",
"import": "./runtime/wasm-compiler-edge.mjs",
"default": "./runtime/wasm-compiler-edge.mjs"
},
"./runtime/index-browser": {
"types": "./runtime/index-browser.d.ts",
"require": "./runtime/index-browser.js",
"import": "./runtime/index-browser.mjs",
"default": "./runtime/index-browser.mjs"
},
"./generator-build": {
"require": "./generator-build/index.js",
"import": "./generator-build/index.js",
"default": "./generator-build/index.js"
},
"./sql": {
"require": {
"types": "./sql.d.ts",
"node": "./sql.js",
"default": "./sql.js"
},
"import": {
"types": "./sql.d.ts",
"node": "./sql.mjs",
"default": "./sql.mjs"
},
"default": "./sql.js"
},
"./*": "./*"
},
"version": "7.2.0",
"sideEffects": false,
"dependencies": {
"@prisma/client-runtime-utils": "7.2.0"
},
"imports": {
"#wasm-compiler-loader": {
"edge-light": "./wasm-edge-light-loader.mjs",
"workerd": "./wasm-worker-loader.mjs",
"worker": "./wasm-worker-loader.mjs",
"default": "./wasm-worker-loader.mjs"
},
"#main-entry-point": {
"require": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
}
}
}

View File

@@ -0,0 +1,2 @@
"use strict";var h=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var D=(e,t)=>{for(var n in t)h(e,n,{get:t[n],enumerable:!0})},O=(e,t,n,_)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of M(t))!j.call(e,r)&&r!==n&&h(e,r,{get:()=>t[r],enumerable:!(_=T(t,r))||_.enumerable});return e};var B=e=>O(h({},"__esModule",{value:!0}),e);var xe={};D(xe,{QueryCompiler:()=>F,__wbg_Error_e83987f665cf5504:()=>q,__wbg_Number_bb48ca12f395cd08:()=>C,__wbg_String_8f0eb39a4a4c2f66:()=>k,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68:()=>W,__wbg___wbindgen_debug_string_df47ffb5e35e6763:()=>V,__wbg___wbindgen_in_bb933bd9e1b3bc0f:()=>z,__wbg___wbindgen_is_object_c818261d21f283a4:()=>L,__wbg___wbindgen_is_string_fbb76cb2940daafd:()=>P,__wbg___wbindgen_is_undefined_2d472862bd29a478:()=>Q,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147:()=>Y,__wbg___wbindgen_number_get_a20bf9b85341449d:()=>G,__wbg___wbindgen_string_get_e4f06c90489ad01b:()=>J,__wbg___wbindgen_throw_b855445ff6a94295:()=>X,__wbg_entries_e171b586f8f6bdbf:()=>H,__wbg_getTime_14776bfb48a1bff9:()=>K,__wbg_get_7bed016f185add81:()=>Z,__wbg_get_with_ref_key_1dc361bd10053bfe:()=>v,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38:()=>ee,__wbg_instanceof_Uint8Array_20c8e73002f7af98:()=>te,__wbg_isSafeInteger_d216eda7911dde36:()=>ne,__wbg_length_69bca3cb64fc8748:()=>re,__wbg_length_cdd215e10d9dd507:()=>_e,__wbg_new_0_f9740686d739025c:()=>oe,__wbg_new_1acc0b6eea89d040:()=>ce,__wbg_new_5a79be3ab53b8aa5:()=>ie,__wbg_new_68651c719dcda04e:()=>se,__wbg_new_e17d9f43105b08be:()=>ue,__wbg_prototypesetcall_2a6620b6922694b2:()=>fe,__wbg_set_3f1d0b984ed272ed:()=>be,__wbg_set_907fb406c34a251d:()=>de,__wbg_set_c213c871859d6500:()=>ae,__wbg_set_message_82ae475bb413aa5c:()=>ge,__wbg_set_wasm:()=>N,__wbindgen_cast_2241b6af4c4b2941:()=>le,__wbindgen_cast_4625c577ab2ec9ee:()=>we,__wbindgen_cast_9ae0607507abb057:()=>pe,__wbindgen_cast_d6cd19b81560fd6e:()=>ye,__wbindgen_init_externref_table:()=>me});module.exports=B(xe);var A=()=>{};A.prototype=A;let o;function N(e){o=e}let p=null;function a(){return(p===null||p.byteLength===0)&&(p=new Uint8Array(o.memory.buffer)),p}let y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0});y.decode();const U=2146435072;let S=0;function R(e,t){return S+=t,S>=U&&(y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}),y.decode(),S=t),y.decode(a().subarray(e,e+t))}function m(e,t){return e=e>>>0,R(e,t)}let f=0;const g=new TextEncoder;"encodeInto"in g||(g.encodeInto=function(e,t){const n=g.encode(e);return t.set(n),{read:e.length,written:n.length}});function l(e,t,n){if(n===void 0){const i=g.encode(e),d=t(i.length,1)>>>0;return a().subarray(d,d+i.length).set(i),f=i.length,d}let _=e.length,r=t(_,1)>>>0;const s=a();let c=0;for(;c<_;c++){const i=e.charCodeAt(c);if(i>127)break;s[r+c]=i}if(c!==_){c!==0&&(e=e.slice(c)),r=n(r,_,_=c+e.length*3,1)>>>0;const i=a().subarray(r+c,r+_),d=g.encodeInto(e,i);c+=d.written,r=n(r,_,c,1)>>>0}return f=c,r}let b=null;function u(){return(b===null||b.buffer.detached===!0||b.buffer.detached===void 0&&b.buffer!==o.memory.buffer)&&(b=new DataView(o.memory.buffer)),b}function x(e){return e==null}function I(e){const t=typeof e;if(t=="number"||t=="boolean"||e==null)return`${e}`;if(t=="string")return`"${e}"`;if(t=="symbol"){const r=e.description;return r==null?"Symbol":`Symbol(${r})`}if(t=="function"){const r=e.name;return typeof r=="string"&&r.length>0?`Function(${r})`:"Function"}if(Array.isArray(e)){const r=e.length;let s="[";r>0&&(s+=I(e[0]));for(let c=1;c<r;c++)s+=", "+I(e[c]);return s+="]",s}const n=/\[object ([^\]]+)\]/.exec(toString.call(e));let _;if(n&&n.length>1)_=n[1];else return toString.call(e);if(_=="Object")try{return"Object("+JSON.stringify(e)+")"}catch{return"Object"}return e instanceof Error?`${e.name}: ${e.message}
${e.stack}`:_}function $(e,t){return e=e>>>0,a().subarray(e/1,e/1+t)}function w(e){const t=o.__wbindgen_externrefs.get(e);return o.__externref_table_dealloc(e),t}const E=typeof FinalizationRegistry>"u"?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry(e=>o.__wbg_querycompiler_free(e>>>0,1));class F{__destroy_into_raw(){const t=this.__wbg_ptr;return this.__wbg_ptr=0,E.unregister(this),t}free(){const t=this.__destroy_into_raw();o.__wbg_querycompiler_free(t,0)}compileBatch(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compileBatch(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}constructor(t){const n=o.querycompiler_new(t);if(n[2])throw w(n[1]);return this.__wbg_ptr=n[0]>>>0,E.register(this,this.__wbg_ptr,this),this}compile(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compile(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}}Symbol.dispose&&(F.prototype[Symbol.dispose]=F.prototype.free);function q(e,t){return Error(m(e,t))}function C(e){return Number(e)}function k(e,t){const n=String(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function W(e){const t=e,n=typeof t=="boolean"?t:void 0;return x(n)?16777215:n?1:0}function V(e,t){const n=I(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function z(e,t){return e in t}function L(e){const t=e;return typeof t=="object"&&t!==null}function P(e){return typeof e=="string"}function Q(e){return e===void 0}function Y(e,t){return e==t}function G(e,t){const n=t,_=typeof n=="number"?n:void 0;u().setFloat64(e+8*1,x(_)?0:_,!0),u().setInt32(e+4*0,!x(_),!0)}function J(e,t){const n=t,_=typeof n=="string"?n:void 0;var r=x(_)?0:l(_,o.__wbindgen_malloc,o.__wbindgen_realloc),s=f;u().setInt32(e+4*1,s,!0),u().setInt32(e+4*0,r,!0)}function X(e,t){throw new Error(m(e,t))}function H(e){return Object.entries(e)}function K(e){return e.getTime()}function Z(e,t){return e[t>>>0]}function v(e,t){return e[t]}function ee(e){let t;try{t=e instanceof ArrayBuffer}catch{t=!1}return t}function te(e){let t;try{t=e instanceof Uint8Array}catch{t=!1}return t}function ne(e){return Number.isSafeInteger(e)}function re(e){return e.length}function _e(e){return e.length}function oe(){return new Date}function ce(){return new Object}function ie(e){return new Uint8Array(e)}function se(){return new Map}function ue(){return new Array}function fe(e,t,n){Uint8Array.prototype.set.call($(e,t),n)}function be(e,t,n){e[t]=n}function de(e,t,n){return e.set(t,n)}function ae(e,t,n){e[t>>>0]=n}function ge(e,t){global.PRISMA_WASM_PANIC_REGISTRY.set_message(m(e,t))}function le(e,t){return m(e,t)}function we(e){return BigInt.asUintN(64,e)}function pe(e){return e}function ye(e){return e}function me(){const e=o.__wbindgen_externrefs,t=e.grow(4);e.set(0,void 0),e.set(t+0,void 0),e.set(t+1,null),e.set(t+2,!0),e.set(t+3,!1)}0&&(module.exports={QueryCompiler,__wbg_Error_e83987f665cf5504,__wbg_Number_bb48ca12f395cd08,__wbg_String_8f0eb39a4a4c2f66,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68,__wbg___wbindgen_debug_string_df47ffb5e35e6763,__wbg___wbindgen_in_bb933bd9e1b3bc0f,__wbg___wbindgen_is_object_c818261d21f283a4,__wbg___wbindgen_is_string_fbb76cb2940daafd,__wbg___wbindgen_is_undefined_2d472862bd29a478,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147,__wbg___wbindgen_number_get_a20bf9b85341449d,__wbg___wbindgen_string_get_e4f06c90489ad01b,__wbg___wbindgen_throw_b855445ff6a94295,__wbg_entries_e171b586f8f6bdbf,__wbg_getTime_14776bfb48a1bff9,__wbg_get_7bed016f185add81,__wbg_get_with_ref_key_1dc361bd10053bfe,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38,__wbg_instanceof_Uint8Array_20c8e73002f7af98,__wbg_isSafeInteger_d216eda7911dde36,__wbg_length_69bca3cb64fc8748,__wbg_length_cdd215e10d9dd507,__wbg_new_0_f9740686d739025c,__wbg_new_1acc0b6eea89d040,__wbg_new_5a79be3ab53b8aa5,__wbg_new_68651c719dcda04e,__wbg_new_e17d9f43105b08be,__wbg_prototypesetcall_2a6620b6922694b2,__wbg_set_3f1d0b984ed272ed,__wbg_set_907fb406c34a251d,__wbg_set_c213c871859d6500,__wbg_set_message_82ae475bb413aa5c,__wbg_set_wasm,__wbindgen_cast_2241b6af4c4b2941,__wbindgen_cast_4625c577ab2ec9ee,__wbindgen_cast_9ae0607507abb057,__wbindgen_cast_d6cd19b81560fd6e,__wbindgen_init_externref_table});

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,87 @@
import { AnyNull } from '@prisma/client-runtime-utils';
import { DbNull } from '@prisma/client-runtime-utils';
import { Decimal } from '@prisma/client-runtime-utils';
import { isAnyNull } from '@prisma/client-runtime-utils';
import { isDbNull } from '@prisma/client-runtime-utils';
import { isJsonNull } from '@prisma/client-runtime-utils';
import { JsonNull } from '@prisma/client-runtime-utils';
import { NullTypes } from '@prisma/client-runtime-utils';
export { AnyNull }
declare type Args<T, F extends Operation> = T extends {
[K: symbol]: {
types: {
operations: {
[K in F]: {
args: any;
};
};
};
};
} ? T[symbol]['types']['operations'][F]['args'] : any;
export { DbNull }
export { Decimal }
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
[K in keyof A]: Exact<A[K], W[K]>;
} : W) : never) | (A extends Narrowable ? A : never);
export declare function getRuntime(): GetRuntimeOutput;
declare type GetRuntimeOutput = {
id: RuntimeName;
prettyName: string;
isEdge: boolean;
};
export { isAnyNull }
export { isDbNull }
export { isJsonNull }
export { JsonNull }
/**
* Generates more strict variant of an enum which, unlike regular enum,
* throws on non-existing property access. This can be useful in following situations:
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
* - enum values are generated dynamically from DMMF.
*
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
* will result in `undefined` value being used, which will be accepted. Using strict enum
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
*
* Note: if you need to check for existence of a value in the enum you can still use either
* `in` operator or `hasOwnProperty` function.
*
* @param definition
* @returns
*/
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
declare type Narrowable = string | number | bigint | boolean | [];
export { NullTypes }
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
declare namespace Public {
export {
validator
}
}
export { Public }
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
export { }

View File

@@ -0,0 +1,6 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
"use strict";var s=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var a=(e,t)=>{for(var n in t)s(e,n,{get:t[n],enumerable:!0})},y=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of p(t))!f.call(e,i)&&i!==n&&s(e,i,{get:()=>t[i],enumerable:!(r=g(t,i))||r.enumerable});return e};var x=e=>y(s({},"__esModule",{value:!0}),e);var O={};a(O,{AnyNull:()=>o.AnyNull,DbNull:()=>o.DbNull,Decimal:()=>m.Decimal,JsonNull:()=>o.JsonNull,NullTypes:()=>o.NullTypes,Public:()=>l,getRuntime:()=>c,isAnyNull:()=>o.isAnyNull,isDbNull:()=>o.isDbNull,isJsonNull:()=>o.isJsonNull,makeStrictEnum:()=>u});module.exports=x(O);var l={};a(l,{validator:()=>d});function d(...e){return t=>t}var b=new Set(["toJSON","$$typeof","asymmetricMatch",Symbol.iterator,Symbol.toStringTag,Symbol.isConcatSpreadable,Symbol.toPrimitive]);function u(e){return new Proxy(e,{get(t,n){if(n in t)return t[n];if(!b.has(n))throw new TypeError("Invalid enum value: ".concat(String(n)))}})}var N=()=>{var e,t;return((t=(e=globalThis.process)==null?void 0:e.release)==null?void 0:t.name)==="node"},S=()=>{var e,t;return!!globalThis.Bun||!!((t=(e=globalThis.process)==null?void 0:e.versions)!=null&&t.bun)},E=()=>!!globalThis.Deno,R=()=>typeof globalThis.Netlify=="object",h=()=>typeof globalThis.EdgeRuntime=="object",C=()=>{var e;return((e=globalThis.navigator)==null?void 0:e.userAgent)==="Cloudflare-Workers"};function k(){var n;return(n=[[R,"netlify"],[h,"edge-light"],[C,"workerd"],[E,"deno"],[S,"bun"],[N,"node"]].flatMap(r=>r[0]()?[r[1]]:[]).at(0))!=null?n:""}var M={node:"Node.js",workerd:"Cloudflare Workers",deno:"Deno and Deno Deploy",netlify:"Netlify Edge Functions","edge-light":"Edge Runtime (Vercel Edge Functions, Vercel Edge Middleware, Next.js (Pages Router) Edge API Routes, Next.js (App Router) Edge Route Handlers or Next.js Middleware)"};function c(){let e=k();return{id:e,prettyName:M[e]||e,isEdge:["workerd","deno","netlify","edge-light"].includes(e)}}var o=require("@prisma/client-runtime-utils"),m=require("@prisma/client-runtime-utils");0&&(module.exports={AnyNull,DbNull,Decimal,JsonNull,NullTypes,Public,getRuntime,isAnyNull,isDbNull,isJsonNull,makeStrictEnum});
//# sourceMappingURL=index-browser.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,45 @@
datasource db {
provider = "sqlite"
}
generator client {
provider = "prisma-client-js"
output = "./generated/client"
}
model User {
id String @id @default(cuid())
username String @unique
password String
displayName String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Session Session[]
UserPreferences UserPreferences?
channels Channel[]
}
model Session {
id String @id
userId String
expiresAt DateTime
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
@@index([userId])
}
model UserPreferences {
userId String @id
toggleInputHotkey String? @default("")
toggleOutputHotkey String? @default("")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Channel {
id String @id
name String
maxClients Int?
persistent Boolean @default(false)
owner_id String?
owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)
}

View File

@@ -0,0 +1,5 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
export default import('./query_compiler_bg.wasm?module')

View File

@@ -0,0 +1,5 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
export default import('./query_compiler_bg.wasm')

View File

@@ -0,0 +1,30 @@
/*
Warnings:
- You are about to drop the column `volumes` on the `UserPreferences` table. All the data in the column will be lost.
*/
-- CreateTable
CREATE TABLE "Channel" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"maxClients" INTEGER,
"persistent" BOOLEAN NOT NULL DEFAULT false,
"owner_id" TEXT,
CONSTRAINT "Channel_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_UserPreferences" (
"userId" TEXT NOT NULL PRIMARY KEY,
"toggleInputHotkey" TEXT DEFAULT '',
"toggleOutputHotkey" TEXT DEFAULT '',
CONSTRAINT "UserPreferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO "new_UserPreferences" ("toggleInputHotkey", "toggleOutputHotkey", "userId") SELECT "toggleInputHotkey", "toggleOutputHotkey", "userId" FROM "UserPreferences";
DROP TABLE "UserPreferences";
ALTER TABLE "new_UserPreferences" RENAME TO "UserPreferences";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -1,11 +1,11 @@
datasource db { datasource db {
provider = "sqlite" provider = "sqlite"
url = env("DATABASE_URL") // url = env("DATABASE_URL")
} }
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client"
// output = "./generated/client" output = "./generated/client"
} }
model User { model User {
@@ -15,16 +15,15 @@ model User {
displayName String displayName String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
Session Session[] Session Session[]
UserPreferences UserPreferences? UserPreferences UserPreferences?
channels Channel[]
} }
model Session { model Session {
id String @id id String @id
userId String userId String
expiresAt DateTime expiresAt DateTime
user User @relation(references: [id], fields: [userId], onDelete: Cascade) user User @relation(references: [id], fields: [userId], onDelete: Cascade)
@@index([userId]) @@index([userId])
@@ -34,7 +33,14 @@ model UserPreferences {
userId String @id userId String @id
toggleInputHotkey String? @default("") toggleInputHotkey String? @default("")
toggleOutputHotkey String? @default("") toggleOutputHotkey String? @default("")
volumes Json? @default("{}")
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Channel {
id String @id @default(cuid())
name String
maxClients Int?
persistent Boolean @default(false)
owner_id String?
owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)
} }

23
server/prisma/seed.ts Normal file
View File

@@ -0,0 +1,23 @@
import prisma from '../prisma/client.ts'
async function main() {
await prisma.channel.upsert({
where: { id: 'default' },
update: {},
create: {
id: 'default',
name: 'Default',
persistent: true,
},
})
}
main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})

View File

@@ -1,5 +1,5 @@
import type { FastifyInstance } from 'fastify' import type { FastifyInstance } from 'fastify'
import type { Namespace } from '../types/webrtc.ts' import type { Namespace } from '../types/socket.ts'
import { z } from 'zod' import { z } from 'zod'
import prisma from '../prisma/client.ts' import prisma from '../prisma/client.ts'
import { socketToClient } from '../utils/socket-to-client.ts' import { socketToClient } from '../utils/socket-to-client.ts'

449
server/socket/channel.ts Normal file
View File

@@ -0,0 +1,449 @@
import type { types } from 'mediasoup'
import type { Server as SocketServer } from 'socket.io'
import type {
Namespace,
SomeSocket,
} from '../types/socket.ts'
import { consola } from 'consola'
import prisma from '../prisma/client.ts'
import { socketToClient } from '../utils/socket-to-client.ts'
export default function (io: SocketServer<Namespace>) {
io.on('connection', async (socket) => {
consola.info('[WebRtc]', 'Client connected', socket.id)
socket.data.joined = false
socket.data.inputMuted = false
socket.data.outputMuted = false
socket.data.transports = new Map()
socket.data.producers = new Map()
socket.data.consumers = new Map()
const user = await prisma.user.findUnique({
where: {
id: socket.handshake.auth.userId,
},
select: {
id: true,
username: true,
displayName: true,
},
})
if (!user) {
socket.disconnect()
return
}
const { id, username, displayName } = user
socket.data.userId = id
socket.data.username = username
socket.data.displayName = displayName
socket.emit('authenticated', { channels })
socket.on('join', async ({ rtpCapabilities }, cb) => {
if (socket.data.joined) {
consola.error('[WebRtc]', 'Already joined')
cb({ error: 'Already joined' })
}
socket.data.joined = true
socket.data.rtpCapabilities = rtpCapabilities
const joinedSockets = await getJoinedSockets()
cb(joinedSockets.map(socketToClient))
for (const joinedSocket of joinedSockets.filter(joinedSocket => joinedSocket.id !== socket.id)) {
for (const producer of joinedSocket.data.producers.values()) {
createConsumer(
socket,
joinedSocket,
producer,
)
}
}
socket.broadcast.emit('newPeer', socketToClient(socket))
})
socket.on('getRtpCapabilities', (cb) => {
cb(router.rtpCapabilities)
})
socket.on('createTransport', async ({ producing, consuming }, cb) => {
try {
const transport = await router.createWebRtcTransport({
listenInfos: [
{
protocol: 'udp',
ip: '0.0.0.0',
announcedAddress: process.env.ANNOUNCED_ADDRESS || '127.0.0.1',
portRange: {
min: 40000,
max: 40100,
},
},
],
enableUdp: true,
preferUdp: true,
appData: {
producing,
consuming,
},
})
socket.data.transports.set(transport.id, transport)
cb({
id: transport.id,
iceParameters: transport.iceParameters,
iceCandidates: transport.iceCandidates,
dtlsParameters: transport.dtlsParameters,
})
transport.on('icestatechange', (iceState: types.IceState) => {
if (iceState === 'disconnected' || iceState === 'closed') {
consola.info('[WebRtc]', '[WebRtcTransport]', `"icestatechange" event [iceState:${iceState}], closing peer`, transport.id)
socket.disconnect()
}
})
transport.on('dtlsstatechange', (dtlsState: types.DtlsState) => {
if (dtlsState === 'failed' || dtlsState === 'closed') {
consola.warn('WebRtcTransport "dtlsstatechange" event [dtlsState:%s], closing peer', dtlsState)
socket.disconnect()
}
})
}
catch (error) {
if (error instanceof Error) {
consola.error('[WebRtc]', '[createTransport]', error.message)
cb({ error: error.message })
}
}
})
socket.on('connectTransport', async ({ transportId, dtlsParameters }, cb) => {
const transport = socket.data.transports.get(transportId)
if (!transport) {
consola.error('[WebRtc]', '[connectTransport]', `Transport with id ${transportId} not found`)
cb({ error: 'Transport not found' })
return
}
try {
await transport.connect({ dtlsParameters })
cb({ ok: true })
}
catch (error) {
if (error instanceof Error) {
consola.error('[WebRtc]', '[connectTransport]', error.message)
cb({ error: error.message })
}
}
})
socket.on('produce', async ({ transportId, kind, rtpParameters, appData }, cb) => {
if (!socket.data.joined) {
consola.error('Peer not joined yet')
cb({ error: 'Peer not joined yet' })
return
}
const transport = socket.data.transports.get(transportId)
if (!transport) {
consola.error('[WebRtc]', '[produce]', `Transport with id ${transportId} not found`)
cb({ error: 'Transport not found' })
return
}
try {
const producer = await transport.produce({ kind, rtpParameters, appData: { ...appData, socketId: socket.id } })
socket.data.producers.set(producer.id, producer)
cb({ id: producer.id })
const otherSockets = await getJoinedSockets(socket.id)
for (const otherSocket of otherSockets) {
createConsumer(
otherSocket,
socket,
producer,
)
}
// TODO: Add into the AudioLevelObserver and ActiveSpeakerObserver.
// https://github.com/versatica/mediasoup-demo/blob/v3/server/lib/Room.js#L1276
}
catch (error) {
if (error instanceof Error) {
consola.error('[WebRtc]', '[produce]', error.message)
cb({ error: error.message })
}
}
})
socket.on('closeProducer', async ({ producerId }, cb) => {
if (!socket.data.joined) {
consola.error('Peer not joined yet')
cb({ error: 'Peer not joined yet' })
return
}
const producer = socket.data.producers.get(producerId)
if (!producer) {
consola.error(`producer with id "${producerId}" not found`)
cb({ error: `producer with id "${producerId}" not found` })
return
}
producer.close()
socket.data.producers.delete(producerId)
cb({ ok: true })
})
socket.on('pauseProducer', async ({ producerId }, cb) => {
if (!socket.data.joined) {
consola.error('Peer not joined yet')
cb({ error: 'Peer not joined yet' })
return
}
const producer = socket.data.producers.get(producerId)
if (!producer) {
consola.error(`producer with id "${producerId}" not found`)
cb({ error: `producer with id "${producerId}" not found` })
return
}
if (producer.paused)
return
await producer.pause()
cb({ ok: true })
})
socket.on('resumeProducer', async ({ producerId }, cb) => {
if (!socket.data.joined) {
consola.error('Peer not joined yet')
cb({ error: 'Peer not joined yet' })
return
}
const producer = socket.data.producers.get(producerId)
if (!producer) {
consola.error(`producer with id "${producerId}" not found`)
cb({ error: `producer with id "${producerId}" not found` })
return
}
await producer.resume()
cb({ ok: true })
})
socket.on('pauseConsumer', async ({ consumerId }, cb) => {
if (!socket.data.joined) {
consola.error('Peer not joined yet')
cb({ error: 'Peer not joined yet' })
return
}
const consumer = socket.data.consumers.get(consumerId)
if (!consumer) {
consola.error(`consumer with id "${consumerId}" not found`)
cb({ error: `consumer with id "${consumerId}" not found` })
return
}
await consumer.pause()
cb({ ok: true })
})
socket.on('resumeConsumer', async ({ consumerId }, cb) => {
if (!socket.data.joined) {
consola.error('Peer not joined yet')
cb({ error: 'Peer not joined yet' })
return
}
const consumer = socket.data.consumers.get(consumerId)
if (!consumer) {
consola.error(`consumer with id "${consumerId}" not found`)
cb({ error: `consumer with id "${consumerId}" not found` })
return
}
await consumer.resume()
cb({ ok: true })
})
socket.on('updateClient', async (updatedClient, cb) => {
if (typeof updatedClient.inputMuted === 'boolean') {
socket.data.inputMuted = updatedClient.inputMuted
}
if (typeof updatedClient.outputMuted === 'boolean') {
socket.data.outputMuted = updatedClient.outputMuted
}
cb(socketToClient(socket))
namespace.emit('clientChanged', socket.id, socketToClient(socket))
})
socket.on('disconnect', () => {
consola.info('Client disconnected:', socket.id)
if (socket.data.joined) {
socket.broadcast.emit('peerClosed', socket.id)
}
for (const transport of socket.data.transports.values()) {
transport.close()
}
})
})
async function getJoinedSockets(excludeId?: string) {
const sockets = await namespace.fetchSockets()
return sockets.filter(socket => socket.data.joined && (excludeId ? excludeId !== socket.id : true))
}
async function createConsumer(
consumerSocket: SomeSocket,
producerSocket: SomeSocket,
producer: types.Producer,
) {
if (
!consumerSocket.data.rtpCapabilities
|| !router.canConsume(
{
producerId: producer.id,
rtpCapabilities: consumerSocket.data.rtpCapabilities,
},
)
) {
return
}
const transport = Array.from(consumerSocket.data.transports.values())
.find(t => t.appData.consuming)
if (!transport) {
consola.error('createConsumer() | Transport for consuming not found')
return
}
let consumer: types.Consumer
try {
consumer = await transport.consume(
{
producerId: producer.id,
rtpCapabilities: consumerSocket.data.rtpCapabilities,
// Enable NACK for OPUS.
enableRtx: true,
paused: true,
ignoreDtx: true,
},
)
}
catch (error) {
consola.error('_createConsumer() | transport.consume():%o', error)
return
}
consumerSocket.data.consumers.set(consumer.id, consumer)
consumer.on('transportclose', () => {
consumerSocket.data.consumers.delete(consumer.id)
})
consumer.on('producerclose', () => {
consumerSocket.data.consumers.delete(consumer.id)
consumerSocket.emit('consumerClosed', { consumerId: consumer.id })
})
consumer.on('producerpause', () => {
consumerSocket.emit('consumerPaused', { consumerId: consumer.id })
})
consumer.on('producerresume', () => {
consumerSocket.emit('consumerResumed', { consumerId: consumer.id })
})
consumer.on('score', (score: types.ConsumerScore) => {
consumerSocket.emit('consumerScore', { consumerId: consumer.id, score })
})
try {
await consumerSocket.emitWithAck(
'newConsumer',
{
socketId: producerSocket.id,
producerId: producer.id,
id: consumer.id,
kind: consumer.kind,
rtpParameters: consumer.rtpParameters,
type: consumer.type,
appData: producer.appData,
producerPaused: consumer.producerPaused,
},
)
await consumer.resume()
consumerSocket.emit(
'consumerScore',
{
consumerId: consumer.id,
score: consumer.score,
},
)
}
catch (error) {
consola.error('_createConsumer() | failed:%o', error)
}
}
}

View File

@@ -1,17 +1,14 @@
import type { types } from 'mediasoup' import type { types } from 'mediasoup'
import type { Server as SocketServer } from 'socket.io' import type { Server as SocketServer } from 'socket.io'
import type { import type {
Namespace,
SomeSocket, SomeSocket,
} from '../types/webrtc.ts' } from '../types/socket.ts'
import { consola } from 'consola' import { consola } from 'consola'
import prisma from '../prisma/client.ts' import prisma from '../prisma/client.ts'
import { socketToClient } from '../utils/socket-to-client.ts' import { socketToClient } from '../utils/socket-to-client.ts'
export default function (io: SocketServer, router: types.Router) { export default function (io: SocketServer, router: types.Router) {
const namespace: Namespace = io.of('/webrtc') io.on('connection', async (socket) => {
namespace.on('connection', async (socket) => {
consola.info('[WebRtc]', 'Client connected', socket.id) consola.info('[WebRtc]', 'Client connected', socket.id)
socket.data.joined = false socket.data.joined = false
@@ -23,7 +20,7 @@ export default function (io: SocketServer, router: types.Router) {
socket.data.producers = new Map() socket.data.producers = new Map()
socket.data.consumers = new Map() socket.data.consumers = new Map()
const { id, username, displayName } = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { where: {
id: socket.handshake.auth.userId, id: socket.handshake.auth.userId,
}, },
@@ -34,11 +31,19 @@ export default function (io: SocketServer, router: types.Router) {
}, },
}) })
if (!user) {
socket.disconnect()
return
}
const { id, username, displayName } = user
socket.data.userId = id socket.data.userId = id
socket.data.username = username socket.data.username = username
socket.data.displayName = displayName socket.data.displayName = displayName
socket.emit('authenticated') socket.emit('authenticated', { channels })
socket.on('join', async ({ rtpCapabilities }, cb) => { socket.on('join', async ({ rtpCapabilities }, cb) => {
if (socket.data.joined) { if (socket.data.joined) {
@@ -319,7 +324,7 @@ export default function (io: SocketServer, router: types.Router) {
cb(socketToClient(socket)) cb(socketToClient(socket))
namespace.emit('clientChanged', socket.id, socketToClient(socket)) io.emit('clientChanged', socket.id, socketToClient(socket))
}) })
socket.on('disconnect', () => { socket.on('disconnect', () => {
@@ -336,7 +341,7 @@ export default function (io: SocketServer, router: types.Router) {
}) })
async function getJoinedSockets(excludeId?: string) { async function getJoinedSockets(excludeId?: string) {
const sockets = await namespace.fetchSockets() const sockets = await io.fetchSockets()
return sockets.filter(socket => socket.data.joined && (excludeId ? excludeId !== socket.id : true)) return sockets.filter(socket => socket.data.joined && (excludeId ? excludeId !== socket.id : true))
} }

View File

@@ -1,12 +1,12 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es2016",
"module": "ESNext",
"moduleResolution": "nodenext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"allowImportingTsExtensions": true "allowImportingTsExtensions": true,
"module": "ESNext",
"moduleResolution": "node",
"target": "ES2023",
"strict": true,
"esModuleInterop": true
} }
} }

View File

@@ -1,12 +1,18 @@
import type { types } from 'mediasoup' import type { types } from 'mediasoup'
import type { RemoteSocket, Socket, Namespace as SocketNamespace } from 'socket.io' import type { RemoteSocket, Server, Socket } from 'socket.io'
import type { User } from '../prisma/client' import type { ChannelModel, UserModel } from '../prisma/generated/client/models.ts'
export interface ServerInfo {
owner_id: UserModel['id']
channels: ChannelModel[]
rtpCapabilities: types.RtpCapabilities
}
export interface ChadClient { export interface ChadClient {
socketId: string socketId: string
userId: User['id'] userId: UserModel['id']
username: User['username'] username: UserModel['username']
displayName: User['displayName'] displayName: UserModel['displayName']
inputMuted: boolean inputMuted: boolean
outputMuted: boolean outputMuted: boolean
} }
@@ -96,7 +102,7 @@ export interface ClientToServerEvents {
} }
export interface ServerToClientEvents { export interface ServerToClientEvents {
authenticated: () => void authenticated: (arg: ServerInfo) => void
newPeer: (arg: ChadClient) => void newPeer: (arg: ChadClient) => void
producers: (arg: ProducerShort[]) => void producers: (arg: ProducerShort[]) => void
newConsumer: ( newConsumer: (
@@ -124,9 +130,9 @@ export interface InterServerEvent {}
export interface SocketData { export interface SocketData {
joined: boolean joined: boolean
userId: User['id'] userId: UserModel['id']
username: User['username'] username: UserModel['username']
displayName: User['displayName'] displayName: UserModel['displayName']
inputMuted: boolean inputMuted: boolean
outputMuted: boolean outputMuted: boolean
rtpCapabilities: types.RtpCapabilities rtpCapabilities: types.RtpCapabilities
@@ -135,6 +141,7 @@ export interface SocketData {
consumers: Map<types.Consumer['id'], types.Consumer> consumers: Map<types.Consumer['id'], types.Consumer>
} }
export type SomeSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData> | RemoteSocket<ServerToClientEvents, SocketData> export type SomeSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>
| RemoteSocket<ServerToClientEvents, SocketData>
export type Namespace = SocketNamespace<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData> export type SocketServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>

View File

@@ -1,4 +1,4 @@
import type { ChadClient, SomeSocket } from '../types/webrtc.ts' import type { ChadClient, SomeSocket } from '../types/socket.ts'
export function socketToClient(socket: SomeSocket): ChadClient { export function socketToClient(socket: SomeSocket): ChadClient {
return { return {

File diff suppressed because it is too large Load Diff