mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-27 07:14:56 +00:00
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:
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -30,6 +30,7 @@ export function NoSsr({ children, defer = false, fallback = null }: NoSsrProps)
|
||||
}
|
||||
} else {
|
||||
setMounted(true)
|
||||
return undefined
|
||||
}
|
||||
}, [defer])
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 || '' }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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[] =
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitReturns": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
|
||||
Reference in New Issue
Block a user