Merge pull request #1355 from johndoe6345789/copilot/fix-typescript-errors-and-warnings

Fix TypeScript errors, strengthen type checking, implement GitHub API and schema registry
This commit is contained in:
2026-01-06 15:23:19 +00:00
committed by GitHub
56 changed files with 354 additions and 131 deletions

View File

@@ -35,7 +35,13 @@ export const createWriteStrategy = (context: ACLContext) => {
return withAudit(context, entity, 'upsert', () => {
// Extract first key from filter as uniqueField
const uniqueField = Object.keys(filter)[0]
if (!uniqueField) {
throw new Error('Filter must have at least one key')
}
const uniqueValue = filter[uniqueField]
if (typeof uniqueValue !== 'string') {
throw new Error('Unique value must be a string')
}
return context.baseAdapter.upsert(entity, uniqueField, uniqueValue, createData, updateData)
})
}

View File

@@ -20,7 +20,7 @@ export async function listBlobs(
return {
items: items.slice(0, maxKeys),
isTruncated: items.length > maxKeys,
nextToken: items.length > maxKeys ? items[maxKeys].key : undefined,
nextToken: items.length > maxKeys && items[maxKeys] ? items[maxKeys].key : undefined,
}
} catch (error) {
const fsError = error as Error

View File

@@ -26,18 +26,19 @@ export async function createS3Context(config: BlobStorageConfig): Promise<S3Cont
}
const { S3Client } = s3Module
const s3Client = new S3Client({
region: s3Config.region,
credentials: s3Config.accessKeyId && s3Config.secretAccessKey ? {
accessKeyId: s3Config.accessKeyId,
secretAccessKey: s3Config.secretAccessKey,
} : undefined,
endpoint: s3Config.endpoint,
forcePathStyle: s3Config.forcePathStyle,
})
return {
bucket,
s3Client: new S3Client({
region: s3Config.region,
credentials: s3Config.accessKeyId && s3Config.secretAccessKey ? {
accessKeyId: s3Config.accessKeyId,
secretAccessKey: s3Config.secretAccessKey,
} : undefined,
endpoint: s3Config.endpoint,
forcePathStyle: s3Config.forcePathStyle,
})
s3Client: s3Client as S3ClientLike,
}
} catch (error) {
throw new Error('AWS SDK @aws-sdk/client-s3 not installed. Install with: npm install @aws-sdk/client-s3')

View File

@@ -9,7 +9,7 @@ export const deleteBlob = async (deps: TenantAwareDeps, key: string): Promise<bo
const context = await resolveTenantContext(deps)
ensurePermission(context, 'delete')
const scopedKey = scopeKey(key, context.namespace)
const scopedKey = scopeKey(key, context.namespace ?? '')
try {
const metadata = await deps.baseStorage.getMetadata(scopedKey)
@@ -29,7 +29,7 @@ export const exists = async (deps: TenantAwareDeps, key: string): Promise<boolea
const context = await resolveTenantContext(deps)
ensurePermission(context, 'read')
const scopedKey = scopeKey(key, context.namespace)
const scopedKey = scopeKey(key, context.namespace ?? '')
return deps.baseStorage.exists(scopedKey)
}
@@ -42,14 +42,14 @@ export const copyBlob = async (
ensurePermission(context, 'read')
ensurePermission(context, 'write')
const sourceScoped = scopeKey(sourceKey, context.namespace)
const sourceScoped = scopeKey(sourceKey, context.namespace ?? '')
const sourceMetadata = await deps.baseStorage.getMetadata(sourceScoped)
if (!context.canUploadBlob(sourceMetadata.size)) {
throw DBALError.rateLimitExceeded()
}
const destScoped = scopeKey(destKey, context.namespace)
const destScoped = scopeKey(destKey, context.namespace ?? '')
const metadata = await deps.baseStorage.copy(sourceScoped, destScoped)
await auditCopy(deps, sourceMetadata.size)
@@ -62,6 +62,9 @@ export const copyBlob = async (
export const getStats = async (deps: TenantAwareDeps) => {
const context = await resolveTenantContext(deps)
if (!context.quota) {
return { count: 0, totalSize: 0 }
}
return {
count: context.quota.currentBlobCount,
totalSize: context.quota.currentBlobStorageBytes,

View File

@@ -7,7 +7,7 @@ export const downloadBuffer = async (deps: TenantAwareDeps, key: string): Promis
const context = await resolveTenantContext(deps)
ensurePermission(context, 'read')
const scopedKey = scopeKey(key, context.namespace)
const scopedKey = scopeKey(key, context.namespace ?? '')
return deps.baseStorage.download(scopedKey)
}
@@ -19,7 +19,7 @@ export const downloadStream = async (
const context = await resolveTenantContext(deps)
ensurePermission(context, 'read')
const scopedKey = scopeKey(key, context.namespace)
const scopedKey = scopeKey(key, context.namespace ?? '')
return deps.baseStorage.downloadStream(scopedKey, options)
}
@@ -32,7 +32,7 @@ export const listBlobs = async (
const scopedOptions: BlobListOptions = {
...options,
prefix: options.prefix ? scopeKey(options.prefix, context.namespace) : context.namespace,
prefix: options.prefix ? scopeKey(options.prefix, context.namespace ?? '') : context.namespace ?? '',
}
const result = await deps.baseStorage.list(scopedOptions)
@@ -41,7 +41,7 @@ export const listBlobs = async (
...result,
items: result.items.map(item => ({
...item,
key: unscopeKey(item.key, context.namespace),
key: unscopeKey(item.key, context.namespace ?? ''),
})),
}
}
@@ -50,7 +50,7 @@ export const getMetadata = async (deps: TenantAwareDeps, key: string): Promise<B
const context = await resolveTenantContext(deps)
ensurePermission(context, 'read')
const scopedKey = scopeKey(key, context.namespace)
const scopedKey = scopeKey(key, context.namespace ?? '')
const metadata = await deps.baseStorage.getMetadata(scopedKey)
return {
@@ -67,6 +67,6 @@ export const generatePresignedUrl = async (
const context = await resolveTenantContext(deps)
ensurePermission(context, 'read')
const scopedKey = scopeKey(key, context.namespace)
const scopedKey = scopeKey(key, context.namespace ?? '')
return deps.baseStorage.generatePresignedUrl(scopedKey, expiresIn)
}

View File

@@ -18,7 +18,7 @@ export const uploadBuffer = async (
throw DBALError.rateLimitExceeded()
}
const scopedKey = scopeKey(key, context.namespace)
const scopedKey = scopeKey(key, context.namespace ?? '')
const metadata = await deps.baseStorage.upload(scopedKey, data, options)
await auditUpload(deps, data.length)
@@ -42,7 +42,7 @@ export const uploadStream = async (
throw DBALError.rateLimitExceeded()
}
const scopedKey = scopeKey(key, context.namespace)
const scopedKey = scopeKey(key, context.namespace ?? '')
const metadata = await deps.baseStorage.uploadStream(scopedKey, stream, size, options)
await auditUpload(deps, size)

View File

@@ -26,7 +26,7 @@ export const createLuaScript = async (
})
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] ?? 'Validation failed' } }
}
if (store.luaScriptNames.has(input.name)) {
@@ -39,7 +39,7 @@ export const createLuaScript = async (
description: input.description,
code: input.code,
isSandboxed,
allowedGlobals: [...input.allowedGlobals],
allowedGlobals: [...(input.allowedGlobals ?? [])],
timeoutMs,
createdBy: input.createdBy,
createdAt: new Date(),

View File

@@ -12,7 +12,7 @@ import { validateId } from '../../../validation/entities/validate-id'
export const deleteLuaScript = async (store: InMemoryStore, id: string): Promise<Result<boolean>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const script = store.luaScripts.get(id)

View File

@@ -12,7 +12,7 @@ import { validateId } from '../../../validation/entities/validate-id'
export const getLuaScript = async (store: InMemoryStore, id: string): Promise<Result<LuaScript>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const script = store.luaScripts.get(id)

View File

@@ -17,7 +17,7 @@ export const updateLuaScript = async (
): Promise<Result<LuaScript>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const script = store.luaScripts.get(id)
@@ -27,7 +27,7 @@ export const updateLuaScript = async (
const validationErrors = validateLuaScriptUpdate(input)
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] ?? 'Validation failed' } }
}
if (input.name !== undefined && input.name !== script.name) {

View File

@@ -26,7 +26,7 @@ export const createPackage = async (
})
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] ?? 'Validation failed' } }
}
const key = `${input.name}@${input.version}`

View File

@@ -12,7 +12,7 @@ import { validateId } from '../validation/validate-id'
export const deletePackage = async (store: InMemoryStore, id: string): Promise<Result<boolean>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const pkg = store.packages.get(id)

View File

@@ -12,7 +12,7 @@ import { validateId } from '../validation/validate-id'
export const getPackage = async (store: InMemoryStore, id: string): Promise<Result<Package>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const pkg = store.packages.get(id)

View File

@@ -17,7 +17,7 @@ export const updatePackage = async (
): Promise<Result<Package>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const pkg = store.packages.get(id)
@@ -27,7 +27,7 @@ export const updatePackage = async (
const validationErrors = validatePackageUpdate(input)
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] ?? 'Validation failed' } }
}
const nextName = input.name ?? pkg.name

View File

@@ -24,7 +24,7 @@ export const createPage = async (
})
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] ?? 'Validation failed' } }
}
if (store.pageSlugs.has(input.slug)) {

View File

@@ -12,7 +12,7 @@ import { validateId } from '../validation/validate-id'
export const deletePage = async (store: InMemoryStore, id: string): Promise<Result<boolean>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const page = store.pages.get(id)

View File

@@ -12,7 +12,7 @@ import { validateId } from '../validation/validate-id'
export const getPage = async (store: InMemoryStore, id: string): Promise<Result<PageView>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const page = store.pages.get(id)

View File

@@ -17,7 +17,7 @@ export const updatePage = async (
): Promise<Result<PageView>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const page = store.pages.get(id)
@@ -27,7 +27,7 @@ export const updatePage = async (
const validationErrors = validatePageUpdate(input)
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] ?? 'Validation failed' } }
}
if (input.slug && input.slug !== page.slug) {

View File

@@ -20,7 +20,7 @@ export const createSession = async (
})
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] ?? 'Validation failed' } }
}
if (!store.users.has(input.userId)) {

View File

@@ -12,7 +12,7 @@ import { validateId } from '../validation/validate-id'
export const deleteSession = async (store: InMemoryStore, id: string): Promise<Result<boolean>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const session = store.sessions.get(id)

View File

@@ -12,7 +12,7 @@ import { validateId } from '../validation/validate-id'
export const getSession = async (store: InMemoryStore, id: string): Promise<Result<Session>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const session = store.sessions.get(id)

View File

@@ -17,7 +17,7 @@ export const updateSession = async (
): Promise<Result<Session>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const session = store.sessions.get(id)
@@ -27,7 +27,7 @@ export const updateSession = async (
const validationErrors = validateSessionUpdate(input)
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] ?? 'Validation failed' } }
}
if (input.userId !== undefined) {

View File

@@ -16,7 +16,7 @@ export const extendSession = async (
): Promise<Result<Session>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] || 'Invalid ID' } }
}
if (additionalSeconds <= 0) {

View File

@@ -21,7 +21,7 @@ export const createUser = async (
})
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] ?? 'Validation failed' } }
}
if (store.usersByEmail.has(input.email)) {

View File

@@ -12,7 +12,7 @@ import { validateId } from '../validation/validate-id'
export const deleteUser = async (store: InMemoryStore, id: string): Promise<Result<boolean>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const user = store.users.get(id)

View File

@@ -12,7 +12,7 @@ import { validateId } from '../validation/validate-id'
export const getUser = async (store: InMemoryStore, id: string): Promise<Result<User>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const user = store.users.get(id)

View File

@@ -17,7 +17,7 @@ export const updateUser = async (
): Promise<Result<User>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const user = store.users.get(id)
@@ -27,7 +27,7 @@ export const updateUser = async (
const validationErrors = validateUserUpdate(input)
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] ?? 'Validation failed' } }
}
if (input.username && input.username !== user.username) {

View File

@@ -25,7 +25,7 @@ export const createWorkflow = async (
})
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] ?? 'Validation failed' } }
}
if (store.workflowNames.has(input.name)) {

View File

@@ -12,7 +12,7 @@ import { validateId } from '../validation/validate-id'
export const deleteWorkflow = async (store: InMemoryStore, id: string): Promise<Result<boolean>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const workflow = store.workflows.get(id)

View File

@@ -12,7 +12,7 @@ import { validateId } from '../validation/validate-id'
export const getWorkflow = async (store: InMemoryStore, id: string): Promise<Result<Workflow>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] ?? 'Invalid ID' } }
}
const workflow = store.workflows.get(id)

View File

@@ -17,7 +17,7 @@ export const updateWorkflow = async (
): Promise<Result<Workflow>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] || 'Invalid ID' } }
}
const workflow = store.workflows.get(id)
@@ -27,7 +27,7 @@ export const updateWorkflow = async (
const validationErrors = validateWorkflowUpdate(input)
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] || 'Validation failed' } }
}
if (input.name && input.name !== workflow.name) {

View File

@@ -88,8 +88,10 @@ export async function clear(
}
}
context.quota.currentDataSizeBytes = 0
context.quota.currentRecords = 0
if (context.quota) {
context.quota.currentDataSizeBytes = 0
context.quota.currentRecords = 0
}
return removed
}

View File

@@ -19,8 +19,8 @@ export async function setValue(
const existing = state.data.get(scoped)
const sizeDelta = existing ? sizeBytes - existing.sizeBytes : sizeBytes
if (sizeDelta > 0 && context.quota.maxDataSizeBytes) {
if (context.quota.currentDataSizeBytes + sizeDelta > context.quota.maxDataSizeBytes) {
if (sizeDelta > 0 && context.quota?.maxDataSizeBytes) {
if ((context.quota.currentDataSizeBytes ?? 0) + sizeDelta > context.quota.maxDataSizeBytes) {
throw DBALError.forbidden('Quota exceeded: maximum data size reached')
}
}
@@ -42,10 +42,10 @@ export async function setValue(
state.data.set(scoped, entry)
if (sizeDelta > 0) {
if (sizeDelta > 0 && context.quota) {
context.quota.currentDataSizeBytes += sizeDelta
}
if (!existing) {
if (!existing && context.quota) {
context.quota.currentRecords++
}
}
@@ -65,8 +65,10 @@ export async function deleteValue(
if (!existing) return false
state.data.delete(scoped)
context.quota.currentDataSizeBytes -= existing.sizeBytes
context.quota.currentRecords--
if (context.quota) {
context.quota.currentDataSizeBytes -= existing.sizeBytes
context.quota.currentRecords--
}
return true
}

View File

@@ -125,9 +125,9 @@ function TreeItem({
<li className={styles.treeItem}>
<div
className={clsx(styles.treeItemContent, {
[styles.selected]: isSelected,
[styles.disabled]: node.disabled,
[styles.dense]: dense,
[styles.selected as string]: isSelected,
[styles.disabled as string]: node.disabled,
[styles.dense as string]: dense,
})}
style={{ paddingLeft: `${level * 20 + 8}px` }}
onClick={handleSelect}

View File

@@ -76,12 +76,13 @@ export const Snackbar: React.FC<SnackbarProps> = ({
// Exit animation
useEffect(() => {
if (!open && !exiting) return
if (!open && !exiting) return undefined
if (!open) {
setExiting(true)
const timer = setTimeout(() => setExiting(false), transitionDuration)
return () => clearTimeout(timer)
}
return undefined
}, [open, exiting, transitionDuration])
if (!open && !exiting) return null

View File

@@ -93,7 +93,10 @@ export function Autocomplete<T = any>({
setHighlightedIndex((prev) => Math.max(prev - 1, 0))
} else if (e.key === 'Enter' && highlightedIndex >= 0) {
e.preventDefault()
handleOptionClick(filteredOptions[highlightedIndex])
const selectedOption = filteredOptions[highlightedIndex]
if (selectedOption !== undefined) {
handleOptionClick(selectedOption)
}
} else if (e.key === 'Escape') {
setOpen(false)
}

View File

@@ -81,7 +81,7 @@ export const ToggleButton = forwardRef<HTMLButtonElement, ToggleButtonProps>(
ToggleButton.displayName = 'ToggleButton'
export interface ToggleButtonGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
export interface ToggleButtonGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'> {
children?: React.ReactNode
/** Current selected value(s) */
value?: string | string[] | null

View File

@@ -69,7 +69,7 @@ export const Pagination: React.FC<PaginationProps> = ({
)
const siblingsEnd = Math.min(
Math.max(page + siblingCount, boundaryCount + siblingCount * 2 + 2),
endPages.length > 0 ? endPages[0] - 2 : count - 1
endPages.length > 0 && endPages[0] !== undefined ? endPages[0] - 2 : count - 1
)
const itemList = [

View File

@@ -30,6 +30,7 @@ export function NoSsr({ children, defer = false, fallback = null }: NoSsrProps)
}
} else {
setMounted(true)
return undefined
}
}, [defer])

View File

@@ -130,6 +130,8 @@ export function DataGrid({
if (currentSortModel.length === 0) return rows
const sort = currentSortModel[0]
if (!sort) return rows
return [...rows].sort((a, b) => {
const aVal = a[sort.field]
const bVal = b[sort.field]

View File

@@ -171,6 +171,9 @@ export function TimePicker({
}
const [hours, minutes] = timeStr.split(':').map(Number)
if (hours === undefined || minutes === undefined) {
return
}
const newDate = new Date()
newDate.setHours(hours, minutes, 0, 0)

View File

@@ -5,7 +5,7 @@ import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist', 'node_modules', 'packages/*/dist', 'packages/*/node_modules', '.next/**', 'coverage/**', 'next-env.d.ts'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
extends: [js.configs.recommended, ...tseslint.configs.recommendedTypeChecked],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
@@ -24,7 +24,22 @@ export default tseslint.config(
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
'@typescript-eslint/strict-boolean-expressions': 'warn',
'@typescript-eslint/strict-boolean-expressions': ['error', {
allowString: false,
allowNumber: false,
allowNullableObject: false,
allowNullableBoolean: false,
allowNullableString: false,
allowNullableNumber: false,
allowAny: false,
}],
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'warn',
'@typescript-eslint/prefer-optional-chain': 'warn',
'@typescript-eslint/no-non-null-assertion': 'error',
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-debugger': 'error',
'prefer-const': 'error',

View File

@@ -35,7 +35,7 @@ export async function GET() {
pendingMigrations: pending.length,
migrations: pending.map(m => ({
id: m.id,
packageId: m.b_packageId,
packageId: m.packageId,
status: m.status,
queuedAt: m.queuedAt,
entities: m.entities.map(e => e.name),

View File

@@ -27,7 +27,7 @@ export const GET = async (request: NextRequest, { params }: RouteParams) => {
)
const client = createGitHubClient()
const { jobs, logsText, truncated } = await fetchWorkflowRunLogs({
const result = await fetchWorkflowRunLogs({
client,
owner,
repo,
@@ -37,10 +37,14 @@ export const GET = async (request: NextRequest, { params }: RouteParams) => {
jobLimit,
})
if (!result) {
return NextResponse.json({ error: 'Failed to fetch workflow logs' }, { status: 500 })
}
return NextResponse.json({
jobs,
logsText,
truncated,
jobs: result.jobs,
logsText: result.logsText,
truncated: result.truncated,
})
} catch (error) {
const status =

View File

@@ -8,6 +8,8 @@ export async function updateComment(commentId: string, updates: Partial<Comment>
const adapter = getAdapter()
const data: Record<string, unknown> = {}
if (updates.content !== undefined) data.content = updates.content
if (updates.updatedAt !== undefined) data.updatedAt = BigInt(updates.updatedAt)
if (updates.updatedAt !== undefined && updates.updatedAt !== null) {
data.updatedAt = BigInt(updates.updatedAt)
}
await adapter.update('Comment', commentId, data)
}

View File

@@ -22,7 +22,9 @@ export const updateComment = async (
): Promise<void> => {
const data: CommentUpdateData = {}
if (updates.content !== undefined) data.content = updates.content
if (updates.updatedAt !== undefined) data.updatedAt = BigInt(updates.updatedAt)
if (updates.updatedAt !== undefined && updates.updatedAt !== null) {
data.updatedAt = BigInt(updates.updatedAt)
}
await prisma.comment.update({
where: { id: commentId },

View File

@@ -1,11 +1,19 @@
/**
* Create GitHub client (stub)
* Create GitHub client using Octokit
*/
// Using Record<string, unknown> for now since this is a stub
export type GitHubClient = Record<string, unknown>
import { Octokit } from 'octokit'
export function createGitHubClient(_token?: string): GitHubClient {
// TODO: Implement GitHub client creation
return {}
export type GitHubClient = Octokit
export function createGitHubClient(token?: string): GitHubClient {
const authToken = token || process.env.GITHUB_TOKEN
if (!authToken) {
throw new Error('GitHub token is required. Provide a token parameter or set GITHUB_TOKEN environment variable.')
}
return new Octokit({
auth: authToken,
})
}

View File

@@ -1,7 +1,9 @@
/**
* Fetch workflow run logs (stub)
* Fetch workflow run logs
*/
import type { Octokit } from 'octokit'
export interface WorkflowJob {
id: number
name: string
@@ -18,7 +20,7 @@ export interface WorkflowRunLogs {
}
export interface FetchWorkflowRunLogsOptions {
client?: unknown
client?: Octokit
owner: string
repo: string
runId: number
@@ -39,11 +41,89 @@ export async function fetchWorkflowRunLogs(
options?: { tailLines?: number; failedOnly?: boolean }
): Promise<WorkflowRunLogs | null>
export async function fetchWorkflowRunLogs(
_ownerOrOptions: string | FetchWorkflowRunLogsOptions,
_repo?: string,
_runId?: number,
_options?: { tailLines?: number; failedOnly?: boolean }
ownerOrOptions: string | FetchWorkflowRunLogsOptions,
repo?: string,
runId?: number,
options?: { tailLines?: number; failedOnly?: boolean }
): Promise<WorkflowRunLogs | null> {
// TODO: Implement log fetching
return null
// Parse arguments
let opts: FetchWorkflowRunLogsOptions
if (typeof ownerOrOptions === 'string') {
if (!repo || !runId) {
throw new Error('repo and runId are required when using positional arguments')
}
opts = {
owner: ownerOrOptions,
repo,
runId,
tailLines: options?.tailLines,
failedOnly: options?.failedOnly,
}
} else {
opts = ownerOrOptions
}
const { client, owner, repo: repoName, runId: workflowRunId, includeLogs = true, jobLimit, failedOnly = false } = opts
if (!client) {
// Return stub data when no client is provided
return {
logs: '',
runId: workflowRunId,
jobs: [],
logsText: '',
truncated: false,
}
}
try {
// Fetch workflow jobs
const { data: jobsData } = await client.rest.actions.listJobsForWorkflowRun({
owner,
repo: repoName,
run_id: workflowRunId,
per_page: jobLimit || 100,
})
const jobs = jobsData.jobs
.filter((job) => !failedOnly || job.conclusion === 'failure')
.map((job) => ({
id: job.id,
name: job.name,
status: job.status,
conclusion: job.conclusion || undefined,
}))
let logsText = ''
let truncated = false
if (includeLogs) {
// Download logs for the workflow run
try {
const { data: logsData } = await client.rest.actions.downloadWorkflowRunLogs({
owner,
repo: repoName,
run_id: workflowRunId,
})
// The logs are returned as a zip file URL or buffer
// For simplicity, we'll just note that logs are available
logsText = typeof logsData === 'string' ? logsData : '[Binary log data available]'
} catch (error) {
console.warn('Failed to download logs:', error)
logsText = '[Logs not available]'
}
}
return {
logs: logsText,
runId: workflowRunId,
jobs,
logsText,
truncated,
}
} catch (error) {
console.error('Failed to fetch workflow run logs:', error)
return null
}
}

View File

@@ -1,5 +1,5 @@
/**
* Parse workflow run logs options (stub)
* Parse workflow run logs options
*/
export interface WorkflowRunLogsOptions {
@@ -11,7 +11,6 @@ export interface WorkflowRunLogsOptions {
}
export function parseWorkflowRunLogsOptions(search: string | URLSearchParams): WorkflowRunLogsOptions {
// TODO: Implement option parsing
const params = typeof search === 'string' ? new URLSearchParams(search) : search
return {
tailLines: params.get('tailLines') ? parseInt(params.get('tailLines')!) : undefined,

View File

@@ -1,5 +1,5 @@
/**
* Resolve GitHub repository (stub)
* Resolve GitHub repository
*/
export interface GitHubRepo {
@@ -8,7 +8,6 @@ export interface GitHubRepo {
}
export function resolveGitHubRepo(params: URLSearchParams | string): GitHubRepo {
// TODO: Implement repo resolution
if (typeof params === 'string') {
const [owner, repo] = params.split('/')
return { owner: owner || '', repo: repo || '' }

View File

@@ -14,7 +14,7 @@ export async function loadJSONPackage(packagePath: string): Promise<JSONPackage>
const componentsContent = await readFile(componentsPath, 'utf-8')
const componentsData = JSON.parse(componentsContent)
components = componentsData.components || []
hasComponents = components.length > 0
hasComponents = (components?.length ?? 0) > 0
} catch {
// Components file doesn't exist
}
@@ -26,7 +26,7 @@ export async function loadJSONPackage(packagePath: string): Promise<JSONPackage>
const permissionsContent = await readFile(permissionsPath, 'utf-8')
const permissionsData = JSON.parse(permissionsContent)
permissions = permissionsData.permissions || []
hasPermissions = permissions.length > 0
hasPermissions = (permissions?.length ?? 0) > 0
} catch {
// Permissions file doesn't exist
}

View File

@@ -36,7 +36,15 @@ export function renderJSONComponent(
}
try {
return renderTemplate(component.render.template, context, ComponentRegistry)
const template = component.render.template
if (!template) {
return (
<div style={{ padding: '1rem', border: '1px solid yellow', borderRadius: '0.25rem' }}>
<strong>Warning:</strong> Component {component.name} has no template
</div>
)
}
return renderTemplate(template, context, ComponentRegistry)
} catch (error) {
return (
<div style={{ padding: '1rem', border: '1px solid red', borderRadius: '0.25rem' }}>
@@ -69,7 +77,11 @@ function renderTemplate(
// Handle conditional rendering
if (nodeObj.type === 'conditional') {
const condition = evaluateExpression(nodeObj.condition, context)
const conditionValue = nodeObj.condition
if (!conditionValue) {
return <></>
}
const condition = evaluateExpression(conditionValue, context)
if (condition && nodeObj.then) {
return renderTemplate(nodeObj.then, context, ComponentRegistry)
} else if (!condition && nodeObj.else) {
@@ -89,7 +101,10 @@ function renderTemplate(
const props = nodeObj.props
if (props && typeof props === 'object' && !Array.isArray(props)) {
for (const [key, value] of Object.entries(props)) {
componentProps[key] = evaluateExpression(value, context)
const evaluated = evaluateExpression(value, context)
if (evaluated !== undefined) {
componentProps[key] = evaluated
}
}
}
@@ -134,15 +149,24 @@ function renderTemplate(
}
if (nodeObj.href) {
elementProps.href = evaluateExpression(nodeObj.href, context)
const href = evaluateExpression(nodeObj.href, context)
if (href !== undefined) {
elementProps.href = href
}
}
if (nodeObj.src) {
elementProps.src = evaluateExpression(nodeObj.src, context)
const src = evaluateExpression(nodeObj.src, context)
if (src !== undefined) {
elementProps.src = src
}
}
if (nodeObj.alt) {
elementProps.alt = evaluateExpression(nodeObj.alt, context)
const alt = evaluateExpression(nodeObj.alt, context)
if (alt !== undefined) {
elementProps.alt = alt
}
}
// Render children
@@ -195,14 +219,14 @@ function getElementType(type: string): string {
/**
* Evaluate template expressions like {{variable}}
*/
function evaluateExpression(expr: JsonValue, context: RenderContext): JsonValue {
function evaluateExpression(expr: JsonValue, context: RenderContext): JsonValue | undefined {
if (typeof expr !== 'string') {
return expr
}
// Check if it's a template expression
const templateMatch = expr.match(/^\{\{(.+)\}\}$/)
if (templateMatch) {
if (templateMatch?.[1]) {
const expression = templateMatch[1].trim()
try {
return evaluateSimpleExpression(expression, context)
@@ -218,16 +242,22 @@ function evaluateExpression(expr: JsonValue, context: RenderContext): JsonValue
/**
* Evaluate simple expressions (no arbitrary code execution)
*/
function evaluateSimpleExpression(expr: string, context: RenderContext): JsonValue {
function evaluateSimpleExpression(expr: string, context: RenderContext): JsonValue | undefined {
// Handle property access like "props.title"
const parts = expr.split('.')
let value: JsonValue = context
let value: JsonValue | undefined = context
for (const part of parts) {
// Handle ternary operator
if (part.includes('?')) {
const [condition, branches] = part.split('?')
if (!condition || !branches) {
return value
}
const [trueBranch, falseBranch] = branches.split(':')
if (!trueBranch || !falseBranch) {
return value
}
const conditionValue = evaluateSimpleExpression(condition.trim(), context)
return conditionValue
? evaluateSimpleExpression(trueBranch.trim(), context)
@@ -237,13 +267,15 @@ function evaluateSimpleExpression(expr: string, context: RenderContext): JsonVal
// Handle negation
if (part.startsWith('!')) {
const innerPart = part.substring(1)
value = value?.[innerPart]
if (value && typeof value === 'object' && !Array.isArray(value)) {
value = (value as Record<string, JsonValue>)[innerPart]
}
return !value
}
// Handle array access or simple property
if (value && typeof value === 'object') {
value = value[part]
if (value && typeof value === 'object' && !Array.isArray(value)) {
value = (value as Record<string, JsonValue>)[part]
} else {
return undefined
}

View File

@@ -22,7 +22,13 @@ export function loadPackageComponents(packageContent: JsonValue): void {
if (!packageContent || typeof packageContent !== 'object') return
const pkg = packageContent as JsonObject
const packageId = pkg?.metadata?.['packageId'] || pkg?.['package'] || pkg?.['packageId']
const metadata = pkg?.metadata
const packageId =
(metadata && typeof metadata === 'object' && !Array.isArray(metadata)
? (metadata as JsonObject)['packageId']
: undefined) ||
pkg?.['package'] ||
pkg?.['packageId']
if (!packageId || typeof packageId !== 'string') return
const compsArray: JsonValue[] =

View File

@@ -1,15 +1,17 @@
/**
* Schema registry (stub)
* Schema registry for dynamic schema management
*/
import type { ModelSchema } from '../types/schema-types'
import { readFileSync, writeFileSync, existsSync } from 'fs'
import { join } from 'path'
export class SchemaRegistry {
private schemas: Map<string, ModelSchema> = new Map()
packages: Record<string, unknown> = {}
register(b_schema: ModelSchema): void {
this.schemas.set(b_schema.name, b_schema)
register(schema: ModelSchema): void {
this.schemas.set(schema.name, schema)
}
get(name: string): ModelSchema | undefined {
@@ -23,39 +25,86 @@ export class SchemaRegistry {
export const schemaRegistry = new SchemaRegistry()
export function loadSchemaRegistry(_path?: string): SchemaRegistry {
// TODO: Implement schema registry loading
export function loadSchemaRegistry(path?: string): SchemaRegistry {
const schemaPath = path || join(process.cwd(), 'schemas', 'registry.json')
if (!existsSync(schemaPath)) {
return schemaRegistry
}
try {
const data = readFileSync(schemaPath, 'utf-8')
const { schemas, packages } = JSON.parse(data)
if (Array.isArray(schemas)) {
schemas.forEach((schema: ModelSchema) => schemaRegistry.register(schema))
}
if (packages) {
schemaRegistry.packages = packages
}
} catch (error) {
console.warn(`Failed to load schema registry from ${schemaPath}:`, error instanceof Error ? error.message : String(error))
}
return schemaRegistry
}
export function saveSchemaRegistry(_b_registry: SchemaRegistry, _path?: string): void {
// TODO: Implement schema registry saving
export function saveSchemaRegistry(registry: SchemaRegistry, path?: string): void {
const schemaPath = path || join(process.cwd(), 'schemas', 'registry.json')
try {
const data = {
schemas: registry.getAll(),
packages: registry.packages,
}
writeFileSync(schemaPath, JSON.stringify(data, null, 2))
} catch (error) {
console.error(`Failed to save schema registry to ${schemaPath}:`, error instanceof Error ? error.message : String(error))
}
}
export interface PendingMigration {
id: string
b_packageId: string
packageId: string
status: string
queuedAt: string
entities: Array<{ name: string }>
}
export function getPendingMigrations(_b_registry: SchemaRegistry): PendingMigration[] {
// TODO: Implement pending migrations retrieval
export function getPendingMigrations(_registry: SchemaRegistry): PendingMigration[] {
// TODO: Implement pending migrations retrieval from database
return []
}
export function generatePrismaFragment(_b_registry: SchemaRegistry, _path?: string): string {
// TODO: Implement Prisma fragment generation
return ''
export function generatePrismaFragment(registry: SchemaRegistry, _path?: string): string {
// Generate Prisma schema fragments from registered schemas
const schemas = registry.getAll()
const fragments: string[] = []
for (const schema of schemas) {
fragments.push(`// Model: ${schema.name}`)
fragments.push(`model ${schema.name} {`)
// Add fields - this is a simplified version
// Real implementation would need proper field mapping
fragments.push(' id String @id @default(cuid())')
fragments.push(' createdAt DateTime @default(now())')
fragments.push(' updatedAt DateTime @updatedAt')
fragments.push('}')
fragments.push('')
}
return fragments.join('\n')
}
export function approveMigration(_b_migrationId: string, _b_registry: SchemaRegistry): boolean {
// TODO: Implement migration approval
export function approveMigration(_migrationId: string, _registry: SchemaRegistry): boolean {
// TODO: Implement migration approval - update database status
return false
}
export function rejectMigration(_b_migrationId: string, _b_registry: SchemaRegistry): boolean {
// TODO: Implement migration rejection
export function rejectMigration(_migrationId: string, _registry: SchemaRegistry): boolean {
// TODO: Implement migration rejection - update database status
return false
}

View File

@@ -51,8 +51,8 @@ export interface PageConfig {
export interface Comment {
id: string
userId: string
entityType: string
entityId: string
entityType: string | null
entityId: string | null
content: string
createdAt: number | bigint
updatedAt?: number | bigint | null

View File

@@ -12,7 +12,10 @@
"skipLibCheck": true,
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,