diff --git a/dbal/development/src/adapters/acl-adapter/bulk.ts b/dbal/development/src/adapters/acl-adapter/bulk.ts index 9c618cc4b..89a91a2bd 100644 --- a/dbal/development/src/adapters/acl-adapter/bulk.ts +++ b/dbal/development/src/adapters/acl-adapter/bulk.ts @@ -25,11 +25,12 @@ export const findByField = (context: ACLContext) => async (entity: string, field export const upsert = (context: ACLContext) => async ( entity: string, - filter: Record, + uniqueField: string, + uniqueValue: unknown, createData: Record, updateData: Record, ) => { - return withAudit(context, entity, 'upsert', () => context.baseAdapter.upsert(entity, filter, createData, updateData)) + return withAudit(context, entity, 'upsert', () => context.baseAdapter.upsert(entity, uniqueField, uniqueValue, createData, updateData)) } export const updateByField = (context: ACLContext) => async ( diff --git a/dbal/development/src/adapters/acl-adapter/context.ts b/dbal/development/src/adapters/acl-adapter/context.ts index 8213926b9..0c60da3a5 100644 --- a/dbal/development/src/adapters/acl-adapter/context.ts +++ b/dbal/development/src/adapters/acl-adapter/context.ts @@ -3,6 +3,8 @@ import type { ACLAdapterOptions, ACLContext, ACLRule, User } from './types' import { logAudit } from '../acl/audit-logger' import { defaultACLRules } from '../acl/default-rules' +export type { ACLContext } from './types' + export const createContext = ( baseAdapter: DBALAdapter, user: User, diff --git a/dbal/development/src/adapters/prisma/context.ts b/dbal/development/src/adapters/prisma/context.ts index 948c9c256..d9916b393 100644 --- a/dbal/development/src/adapters/prisma/context.ts +++ b/dbal/development/src/adapters/prisma/context.ts @@ -6,9 +6,13 @@ export function createPrismaContext( options?: PrismaAdapterOptions ): PrismaContext { const inferredDialect = options?.dialect ?? inferDialectFromUrl(databaseUrl) - const prisma = new PrismaClient({ - datasources: databaseUrl ? { db: { url: databaseUrl } } : undefined, - }) + const prisma = new PrismaClient( + databaseUrl + ? { + datasources: { db: { url: databaseUrl } }, + } as any + : undefined + ) return { prisma, diff --git a/dbal/development/src/adapters/prisma/operations/utils.ts b/dbal/development/src/adapters/prisma/operations/utils.ts index dae05eb2a..8e69b454e 100644 --- a/dbal/development/src/adapters/prisma/operations/utils.ts +++ b/dbal/development/src/adapters/prisma/operations/utils.ts @@ -12,11 +12,12 @@ type PrismaModelDelegate = { delete: (...args: unknown[]) => Promise deleteMany: (...args: unknown[]) => Promise<{ count: number }> upsert: (...args: unknown[]) => Promise + count: (...args: unknown[]) => Promise } export function getModel(context: PrismaContext, entity: string): PrismaModelDelegate { const modelName = entity.charAt(0).toLowerCase() + entity.slice(1) - const model = (context.prisma as Record)[modelName] + const model = (context.prisma as unknown as Record)[modelName] if (!model) { throw DBALError.notFound(`Entity ${entity} not found`) diff --git a/dbal/development/src/blob/providers/s3/operations/downloads.ts b/dbal/development/src/blob/providers/s3/operations/downloads.ts index 32116f9b5..c67c41775 100644 --- a/dbal/development/src/blob/providers/s3/operations/downloads.ts +++ b/dbal/development/src/blob/providers/s3/operations/downloads.ts @@ -17,11 +17,16 @@ export async function downloadBuffer( Range: buildRangeHeader(options), }) - const response = await context.s3Client.send(command) + const response = await context.s3Client.send(command) as { + Body?: AsyncIterable + } const chunks: Uint8Array[] = [] - for await (const chunk of response.Body as any) { - chunks.push(chunk) + const body = response.Body + if (body) { + for await (const chunk of body) { + chunks.push(chunk) + } } return Buffer.concat(chunks) diff --git a/dbal/development/src/blob/providers/s3/operations/listing.ts b/dbal/development/src/blob/providers/s3/operations/listing.ts index c284cc79d..d0944b0f7 100644 --- a/dbal/development/src/blob/providers/s3/operations/listing.ts +++ b/dbal/development/src/blob/providers/s3/operations/listing.ts @@ -16,9 +16,20 @@ export async function listBlobs( MaxKeys: options.maxKeys || 1000, }) - const response = await context.s3Client.send(command) + const response = await context.s3Client.send(command) as { + Contents?: Array<{ + Key?: string + Size?: number + ETag?: string + LastModified?: Date + }> + NextContinuationToken?: string + IsTruncated?: boolean + } - const items: BlobMetadata[] = (response.Contents || []).map(obj => ({ + const contents = response.Contents + + const items: BlobMetadata[] = (contents || []).map(obj => ({ key: obj.Key || '', size: obj.Size || 0, contentType: 'application/octet-stream', diff --git a/dbal/development/src/blob/providers/s3/operations/metadata.ts b/dbal/development/src/blob/providers/s3/operations/metadata.ts index fb04ea20b..79e1e7e48 100644 --- a/dbal/development/src/blob/providers/s3/operations/metadata.ts +++ b/dbal/development/src/blob/providers/s3/operations/metadata.ts @@ -14,7 +14,13 @@ export async function getMetadata( Key: key, }) - const response = await context.s3Client.send(command) + const response = await context.s3Client.send(command) as { + ContentLength?: number + ContentType?: string + ETag?: string + LastModified?: Date + Metadata?: Record + } return { key, diff --git a/dbal/development/src/blob/providers/s3/operations/uploads.ts b/dbal/development/src/blob/providers/s3/operations/uploads.ts index d951bb3ae..e38b1a55d 100644 --- a/dbal/development/src/blob/providers/s3/operations/uploads.ts +++ b/dbal/development/src/blob/providers/s3/operations/uploads.ts @@ -19,7 +19,9 @@ export async function uploadBuffer( Metadata: options.metadata, }) - const response = await context.s3Client.send(command) + const response = await context.s3Client.send(command) as { + ETag?: string + } return { key, diff --git a/dbal/development/src/blob/providers/tenant-aware-storage/tenant-context.ts b/dbal/development/src/blob/providers/tenant-aware-storage/tenant-context.ts index 114c27452..b96a1364b 100644 --- a/dbal/development/src/blob/providers/tenant-aware-storage/tenant-context.ts +++ b/dbal/development/src/blob/providers/tenant-aware-storage/tenant-context.ts @@ -3,7 +3,11 @@ import type { TenantContext } from '../../../core/foundation/tenant-context' import type { TenantAwareDeps } from './context' export const resolveTenantContext = async ({ tenantManager, tenantId, userId }: TenantAwareDeps): Promise => { - return tenantManager.getTenantContext(tenantId, userId) + const hasAccess = await tenantManager.validateTenantAccess(tenantId, userId) + if (!hasAccess) { + throw DBALError.forbidden(`User ${userId} does not have access to tenant ${tenantId}`) + } + return tenantManager.getTenantContext(tenantId) } export const ensurePermission = (context: TenantContext, action: 'read' | 'write' | 'delete'): void => { diff --git a/dbal/development/src/bridges/websocket-bridge/index.ts b/dbal/development/src/bridges/websocket-bridge/index.ts index b6f27cbad..566d0ec7e 100644 --- a/dbal/development/src/bridges/websocket-bridge/index.ts +++ b/dbal/development/src/bridges/websocket-bridge/index.ts @@ -1,5 +1,5 @@ import type { DBALAdapter, AdapterCapabilities } from '../../adapters/adapter' -import type { ListOptions, ListResult } from '../../core/types' +import type { ListOptions, ListResult } from '../../core/foundation/types' import { createConnectionManager } from './connection-manager' import { createMessageRouter } from './message-router' import { createOperations } from './operations' @@ -47,10 +47,13 @@ export class WebSocketBridge implements DBALAdapter { upsert( entity: string, - filter: Record, + uniqueField: string, + uniqueValue: unknown, createData: Record, updateData: Record, ): Promise { + // Convert the new signature to the old one for compatibility + const filter = { [uniqueField]: uniqueValue } return this.operations.upsert(entity, filter, createData, updateData) } diff --git a/dbal/development/src/bridges/websocket-bridge/message-router.ts b/dbal/development/src/bridges/websocket-bridge/message-router.ts index 0603f2a2a..19f607ab7 100644 --- a/dbal/development/src/bridges/websocket-bridge/message-router.ts +++ b/dbal/development/src/bridges/websocket-bridge/message-router.ts @@ -56,7 +56,7 @@ export const createMessageRouter = (state: BridgeState): MessageRouter => ({ state.pendingRequests.delete(response.id) if (response.error) { - const error = new DBALError(response.error.message, response.error.code, response.error.details) + const error = new DBALError(response.error.code, response.error.message, response.error.details) pending.reject(error) } else { pending.resolve(response.result) diff --git a/dbal/development/src/bridges/websocket-bridge/operations.ts b/dbal/development/src/bridges/websocket-bridge/operations.ts index 8519082fe..74eeccfd3 100644 --- a/dbal/development/src/bridges/websocket-bridge/operations.ts +++ b/dbal/development/src/bridges/websocket-bridge/operations.ts @@ -1,5 +1,5 @@ import type { AdapterCapabilities } from '../../adapters/adapter' -import type { ListOptions, ListResult } from '../../core/types' +import type { ListOptions, ListResult } from '../../core/foundation/types' import type { ConnectionManager } from './connection-manager' import type { BridgeState } from './state' import { rpcCall } from './rpc' diff --git a/dbal/development/src/core/entities/lua-script/types.ts b/dbal/development/src/core/entities/lua-script/types.ts index 58f5a7e1e..8d919190b 100644 --- a/dbal/development/src/core/entities/lua-script/types.ts +++ b/dbal/development/src/core/entities/lua-script/types.ts @@ -10,6 +10,8 @@ export interface CreateLuaScriptInput { isActive?: boolean; isSandboxed?: boolean; timeoutMs?: number; + allowedGlobals?: string[]; + createdBy?: string; } export interface UpdateLuaScriptInput { @@ -19,6 +21,8 @@ export interface UpdateLuaScriptInput { isActive?: boolean; isSandboxed?: boolean; timeoutMs?: number; + allowedGlobals?: string[]; + createdBy?: string; } export interface LuaScript { @@ -26,9 +30,11 @@ export interface LuaScript { name: string; code: string; description?: string; - isActive: boolean; + isActive?: boolean; isSandboxed: boolean; timeoutMs: number; + allowedGlobals?: string[]; + createdBy?: string; createdAt: Date; updatedAt: Date; } diff --git a/dbal/development/src/core/entities/package/types.ts b/dbal/development/src/core/entities/package/types.ts index 383b1e331..4e6da6bdc 100644 --- a/dbal/development/src/core/entities/package/types.ts +++ b/dbal/development/src/core/entities/package/types.ts @@ -4,11 +4,16 @@ */ export interface CreatePackageInput { - packageId: string; + packageId?: string; name: string; version?: string; description?: string; isPublished?: boolean; + author?: string; + manifest?: any; + isInstalled?: boolean; + installedAt?: Date; + installedBy?: string; } export interface UpdatePackageInput { @@ -16,15 +21,25 @@ export interface UpdatePackageInput { version?: string; description?: string; isPublished?: boolean; + author?: string; + manifest?: any; + isInstalled?: boolean; + installedAt?: Date; + installedBy?: string; } export interface Package { id: string; - packageId: string; + packageId?: string; name: string; version?: string; description?: string; - isPublished: boolean; + isPublished?: boolean; + author?: string; + manifest?: any; + isInstalled?: boolean; + installedAt?: Date; + installedBy?: string; createdAt: Date; updatedAt: Date; } @@ -37,3 +52,22 @@ export interface Result { message: string; }; } + +export interface ListOptions { + filter?: Record; + sort?: Record; + page?: number; + limit?: number; + skip?: number; + take?: number; + where?: Record; + orderBy?: Record; +} + +export interface ListResult { + items?: T[]; + data?: T[]; + total: number; + skip?: number; + take?: number; +} diff --git a/dbal/development/src/core/entities/session/crud/list-sessions.ts b/dbal/development/src/core/entities/session/crud/list-sessions.ts index 85f3ce24e..ad65d7fef 100644 --- a/dbal/development/src/core/entities/session/crud/list-sessions.ts +++ b/dbal/development/src/core/entities/session/crud/list-sessions.ts @@ -4,7 +4,7 @@ */ import type { ListOptions, Result, Session } from '../types' import type { InMemoryStore } from '../store/in-memory-store' -import { cleanExpiredSessions } from './clean-expired' +import { cleanExpiredSessions } from '../lifecycle/clean-expired' /** * List sessions with filtering and pagination diff --git a/dbal/development/src/core/entities/session/lifecycle/clean-expired.ts b/dbal/development/src/core/entities/session/lifecycle/clean-expired.ts index 631809c34..70dd1f0a3 100644 --- a/dbal/development/src/core/entities/session/lifecycle/clean-expired.ts +++ b/dbal/development/src/core/entities/session/lifecycle/clean-expired.ts @@ -2,8 +2,8 @@ * @file clean-expired.ts * @description Clean expired sessions operation */ -import type { Result } from '../../types' -import type { InMemoryStore } from '../../store/in-memory-store' +import type { Result } from '../types' +import type { InMemoryStore } from '../store/in-memory-store' /** * Clean up expired sessions diff --git a/dbal/development/src/core/entities/session/lifecycle/extend-session.ts b/dbal/development/src/core/entities/session/lifecycle/extend-session.ts index a9a1d0584..d1b6c0c5d 100644 --- a/dbal/development/src/core/entities/session/lifecycle/extend-session.ts +++ b/dbal/development/src/core/entities/session/lifecycle/extend-session.ts @@ -2,9 +2,9 @@ * @file extend-session.ts * @description Extend session expiration operation */ -import type { Result, Session } from '../../types' -import type { InMemoryStore } from '../../store/in-memory-store' -import { validateId } from '../../validation/validate-id' +import type { Result, Session } from '../types' +import type { InMemoryStore } from '../store/in-memory-store' +import { validateId } from '../validation/validate-id' /** * Extend a session's expiration time diff --git a/dbal/development/src/core/entities/session/store/in-memory-store.ts b/dbal/development/src/core/entities/session/store/in-memory-store.ts index 6f027bcb7..d33f9c823 100644 --- a/dbal/development/src/core/entities/session/store/in-memory-store.ts +++ b/dbal/development/src/core/entities/session/store/in-memory-store.ts @@ -6,5 +6,6 @@ export interface InMemoryStore { sessions: Map; sessionTokens: Map; + users: Map; generateId(entityType: string): string; } diff --git a/dbal/development/src/core/entities/session/types.ts b/dbal/development/src/core/entities/session/types.ts index 312b28ff3..9a2066f12 100644 --- a/dbal/development/src/core/entities/session/types.ts +++ b/dbal/development/src/core/entities/session/types.ts @@ -7,12 +7,16 @@ export interface CreateSessionInput { userId: string; token: string; expiresAt?: Date; + isActive?: boolean; + lastActivity?: Date; } export interface UpdateSessionInput { userId?: string; token?: string; expiresAt?: Date; + isActive?: boolean; + lastActivity?: Date; } export interface Session { @@ -20,8 +24,10 @@ export interface Session { token: string; userId: string; expiresAt?: Date; + isActive?: boolean; + lastActivity?: Date; createdAt: Date; - updatedAt: Date; + updatedAt?: Date; } export interface Result { @@ -32,3 +38,22 @@ export interface Result { message: string; }; } + +export interface ListOptions { + filter?: Record; + sort?: Record; + page?: number; + limit?: number; + skip?: number; + take?: number; + where?: Record; + orderBy?: Record; +} + +export interface ListResult { + items?: T[]; + data?: T[]; + total: number; + skip?: number; + take?: number; +} diff --git a/dbal/development/src/core/entities/user/crud/create-user.ts b/dbal/development/src/core/entities/user/crud/create-user.ts index 4d81541bc..8884a8e22 100644 --- a/dbal/development/src/core/entities/user/crud/create-user.ts +++ b/dbal/development/src/core/entities/user/crud/create-user.ts @@ -4,7 +4,7 @@ */ import type { CreateUserInput, Result, User } from '../types' import type { InMemoryStore } from '../store/in-memory-store' -import { validateUserCreate } from '../../validation/validate-user-create' +import { validateUserCreate } from '../validation/validate-user-create' /** * Create a new user in the store diff --git a/dbal/development/src/core/entities/user/crud/update-user.ts b/dbal/development/src/core/entities/user/crud/update-user.ts index 29b51995c..66e7d8d0f 100644 --- a/dbal/development/src/core/entities/user/crud/update-user.ts +++ b/dbal/development/src/core/entities/user/crud/update-user.ts @@ -5,7 +5,7 @@ import type { Result, UpdateUserInput, User } from '../types' import type { InMemoryStore } from '../store/in-memory-store' import { validateId } from '../validation/validate-id' -import { validateUserUpdate } from '../../validation/validate-user-update' +import { validateUserUpdate } from '../validation/validate-user-update' /** * Update an existing user diff --git a/dbal/development/src/core/entities/user/types.ts b/dbal/development/src/core/entities/user/types.ts index 4be3c790b..5d6c456d5 100644 --- a/dbal/development/src/core/entities/user/types.ts +++ b/dbal/development/src/core/entities/user/types.ts @@ -8,6 +8,10 @@ export interface CreateUserInput { email: string; password?: string; isActive?: boolean; + role?: 'user' | 'admin' | 'god' | 'supergod'; + firstName?: string; + lastName?: string; + displayName?: string; } export interface UpdateUserInput { @@ -15,13 +19,21 @@ export interface UpdateUserInput { email?: string; password?: string; isActive?: boolean; + role?: 'user' | 'admin' | 'god' | 'supergod'; + firstName?: string; + lastName?: string; + displayName?: string; } export interface User { id: string; username: string; email: string; - isActive: boolean; + isActive?: boolean; + role?: 'user' | 'admin' | 'god' | 'supergod'; + firstName?: string; + lastName?: string; + displayName?: string; createdAt: Date; updatedAt: Date; } @@ -43,3 +55,22 @@ export interface Result { message: string; }; } + +export interface ListOptions { + filter?: Record; + sort?: Record; + page?: number; + limit?: number; + skip?: number; + take?: number; + where?: Record; + orderBy?: Record; +} + +export interface ListResult { + items?: T[]; + data?: T[]; + total: number; + skip?: number; + take?: number; +} diff --git a/dbal/development/src/core/entities/validation/validators/package-validation.ts b/dbal/development/src/core/entities/validation/validators/package-validation.ts index 5795421a3..3152e892e 100644 --- a/dbal/development/src/core/entities/validation/validators/package-validation.ts +++ b/dbal/development/src/core/entities/validation/validators/package-validation.ts @@ -2,7 +2,7 @@ * @file package-validation.ts * @description Package validation functions */ -import { isValidSemver } from '../../validation/is-valid-semver' +import { isValidSemver } from '../../../validation/is-valid-semver' const PACKAGE_ID_REGEX = /^[a-z0-9_]+$/ diff --git a/dbal/development/src/core/entities/validation/validators/page-validation.ts b/dbal/development/src/core/entities/validation/validators/page-validation.ts index 6b4881d91..d2f176059 100644 --- a/dbal/development/src/core/entities/validation/validators/page-validation.ts +++ b/dbal/development/src/core/entities/validation/validators/page-validation.ts @@ -2,6 +2,6 @@ * @file page-validation.ts * @description Page validation functions */ -import { isValidSlug } from '../../validation/is-valid-slug' +import { isValidSlug } from '../../../validation/is-valid-slug' export const validateSlug = (slug: string): boolean => isValidSlug(slug) diff --git a/dbal/development/src/core/entities/validation/validators/user-validation.ts b/dbal/development/src/core/entities/validation/validators/user-validation.ts index 7167aaad1..6ca6515e2 100644 --- a/dbal/development/src/core/entities/validation/validators/user-validation.ts +++ b/dbal/development/src/core/entities/validation/validators/user-validation.ts @@ -2,8 +2,8 @@ * @file user-validation.ts * @description User validation functions */ -import { isValidEmail } from '../../validation/is-valid-email' -import { isValidUsername } from '../../validation/is-valid-username' +import { isValidEmail } from '../../../validation/is-valid-email' +import { isValidUsername } from '../../../validation/is-valid-username' export const validateEmail = (email: string): boolean => isValidEmail(email) export const validateUsername = (username: string): boolean => isValidUsername(username) diff --git a/dbal/development/src/core/entities/workflow/crud/create-workflow.ts b/dbal/development/src/core/entities/workflow/crud/create-workflow.ts index c5785a79f..cd29ae592 100644 --- a/dbal/development/src/core/entities/workflow/crud/create-workflow.ts +++ b/dbal/development/src/core/entities/workflow/crud/create-workflow.ts @@ -4,7 +4,7 @@ */ import type { CreateWorkflowInput, Result, Workflow } from '../types' import type { InMemoryStore } from '../store/in-memory-store' -import { validateWorkflowCreate } from '../../validation/validate-workflow-create' +import { validateWorkflowCreate } from '../validation/validate-workflow-create' /** * Create a new workflow in the store diff --git a/dbal/development/src/core/entities/workflow/crud/update-workflow.ts b/dbal/development/src/core/entities/workflow/crud/update-workflow.ts index 2a6f6399e..2079dcea4 100644 --- a/dbal/development/src/core/entities/workflow/crud/update-workflow.ts +++ b/dbal/development/src/core/entities/workflow/crud/update-workflow.ts @@ -5,7 +5,7 @@ import type { Result, UpdateWorkflowInput, Workflow } from '../types' import type { InMemoryStore } from '../store/in-memory-store' import { validateId } from '../validation/validate-id' -import { validateWorkflowUpdate } from '../../validation/validate-workflow-update' +import { validateWorkflowUpdate } from '../validation/validate-workflow-update' /** * Update an existing workflow diff --git a/dbal/development/src/core/entities/workflow/types.ts b/dbal/development/src/core/entities/workflow/types.ts index 5930ff0ea..9df898782 100644 --- a/dbal/development/src/core/entities/workflow/types.ts +++ b/dbal/development/src/core/entities/workflow/types.ts @@ -10,6 +10,8 @@ export interface CreateWorkflowInput { isActive?: boolean; trigger?: string; triggerConfig?: any; + steps?: any[]; + createdBy?: string; } export interface UpdateWorkflowInput { @@ -19,6 +21,8 @@ export interface UpdateWorkflowInput { isActive?: boolean; trigger?: string; triggerConfig?: any; + steps?: any[]; + createdBy?: string; } export interface Workflow { @@ -26,9 +30,11 @@ export interface Workflow { name: string; description?: string; definition?: any; - isActive: boolean; + isActive?: boolean; trigger?: string; triggerConfig?: any; + steps?: any[]; + createdBy?: string; createdAt: Date; updatedAt: Date; } @@ -51,3 +57,22 @@ export interface Result { message: string; }; } + +export interface ListOptions { + filter?: Record; + sort?: Record; + page?: number; + limit?: number; + skip?: number; + take?: number; + where?: Record; + orderBy?: Record; +} + +export interface ListResult { + items?: T[]; + data?: T[]; + total: number; + skip?: number; + take?: number; +} diff --git a/dbal/development/src/core/foundation/tenant-context.ts b/dbal/development/src/core/foundation/tenant-context.ts index 509db4cef..129aabfe2 100644 --- a/dbal/development/src/core/foundation/tenant-context.ts +++ b/dbal/development/src/core/foundation/tenant-context.ts @@ -33,3 +33,10 @@ export interface TenantContext { canCreateRecord(): boolean canAddToList(additionalItems: number): boolean } + +export interface TenantManager { + getTenantContext(tenantId: string): Promise + updateQuota(tenantId: string, quota: Partial): Promise + validateTenantAccess(tenantId: string, userId: string): Promise + updateBlobUsage(tenantId: string, bytesChange: number, countChange: number): Promise +} diff --git a/dbal/development/src/core/validation/entities/types.ts b/dbal/development/src/core/validation/entities/types.ts index 5b5040b9c..b1e34c97e 100644 --- a/dbal/development/src/core/validation/entities/types.ts +++ b/dbal/development/src/core/validation/entities/types.ts @@ -12,6 +12,7 @@ export type User = any; export type Credential = any; export type Session = any; export type Page = any; +export type PageView = any; export type ComponentHierarchy = any; export type Workflow = any; export type LuaScript = any; diff --git a/fakemui/fakemui/data-display/Badge.tsx b/fakemui/fakemui/data-display/Badge.tsx index ccc9cfb6d..e6f78b6d1 100644 --- a/fakemui/fakemui/data-display/Badge.tsx +++ b/fakemui/fakemui/data-display/Badge.tsx @@ -1,6 +1,6 @@ import React from 'react' -export interface BadgeProps extends Omit, 'color'> { +export interface BadgeProps extends Omit, 'color' | 'content'> { children?: React.ReactNode content?: React.ReactNode dot?: boolean diff --git a/fakemui/fakemui/inputs/InputBase.tsx b/fakemui/fakemui/inputs/InputBase.tsx index 66dc91200..7cdbf5fa5 100644 --- a/fakemui/fakemui/inputs/InputBase.tsx +++ b/fakemui/fakemui/inputs/InputBase.tsx @@ -46,7 +46,7 @@ export const ButtonBase = forwardRef(function Butt ) }) -export interface InputBaseProps extends Omit, 'onChange' | 'defaultValue'> { +export interface InputBaseProps extends Omit, 'onChange' | 'defaultValue' | 'onFocus' | 'onBlur'> { disabled?: boolean error?: boolean fullWidth?: boolean diff --git a/fakemui/fakemui/inputs/TextField.tsx b/fakemui/fakemui/inputs/TextField.tsx index 0742a6837..ec2f0b289 100644 --- a/fakemui/fakemui/inputs/TextField.tsx +++ b/fakemui/fakemui/inputs/TextField.tsx @@ -33,7 +33,7 @@ export const TextField = forwardRef)} + {...(props as unknown as React.SelectHTMLAttributes)} > {children} diff --git a/fakemui/fakemui/navigation/BottomNavigation.tsx b/fakemui/fakemui/navigation/BottomNavigation.tsx index 9a58193cf..d6e29e767 100644 --- a/fakemui/fakemui/navigation/BottomNavigation.tsx +++ b/fakemui/fakemui/navigation/BottomNavigation.tsx @@ -1,6 +1,6 @@ import React, { forwardRef } from 'react' -export interface BottomNavigationProps extends React.HTMLAttributes { +export interface BottomNavigationProps extends Omit, 'onChange'> { children?: React.ReactNode value?: any onChange?: (event: React.SyntheticEvent, value: any) => void diff --git a/fakemui/fakemui/navigation/Pagination.tsx b/fakemui/fakemui/navigation/Pagination.tsx index 470e8496c..a59ba0f1e 100644 --- a/fakemui/fakemui/navigation/Pagination.tsx +++ b/fakemui/fakemui/navigation/Pagination.tsx @@ -7,7 +7,7 @@ export interface PaginationRenderItemParams { disabled: boolean } -export interface PaginationProps extends Omit, 'color'> { +export interface PaginationProps extends Omit, 'color' | 'onChange'> { count?: number page?: number onChange?: (page: number) => void diff --git a/fakemui/fakemui/navigation/SpeedDial.tsx b/fakemui/fakemui/navigation/SpeedDial.tsx index ace1532be..11bdaa663 100644 --- a/fakemui/fakemui/navigation/SpeedDial.tsx +++ b/fakemui/fakemui/navigation/SpeedDial.tsx @@ -80,7 +80,7 @@ export function SpeedDial({ ) } -export interface SpeedDialActionProps extends React.HTMLAttributes { +export interface SpeedDialActionProps extends Omit, 'onClick'> { icon?: React.ReactNode tooltipTitle?: string tooltipOpen?: boolean diff --git a/fakemui/fakemui/navigation/Tabs.tsx b/fakemui/fakemui/navigation/Tabs.tsx index 5d22763ce..c490a7f70 100644 --- a/fakemui/fakemui/navigation/Tabs.tsx +++ b/fakemui/fakemui/navigation/Tabs.tsx @@ -1,6 +1,6 @@ import React, { forwardRef } from 'react' -export interface TabsProps extends React.HTMLAttributes { +export interface TabsProps extends Omit, 'onChange'> { children?: React.ReactNode value?: any onChange?: (event: React.SyntheticEvent, value: any) => void diff --git a/frontends/dbal/package.json b/frontends/dbal/package.json index bebd3b69e..9344bde11 100644 --- a/frontends/dbal/package.json +++ b/frontends/dbal/package.json @@ -26,7 +26,7 @@ "autoprefixer": "10.4.23", "eslint": "9.39.2", "eslint-config-next": "16.1.1", - "postcss": "8.4.35", + "postcss": "8.5.6", "tailwindcss": "4.1.18", "typescript": "~5.9.3" } diff --git a/frontends/nextjs/src/app/[tenant]/[package]/layout.tsx b/frontends/nextjs/src/app/[tenant]/[package]/layout.tsx index f0e584ea9..f2b9c608d 100644 --- a/frontends/nextjs/src/app/[tenant]/[package]/layout.tsx +++ b/frontends/nextjs/src/app/[tenant]/[package]/layout.tsx @@ -25,20 +25,24 @@ interface TenantLayoutProps { /** * Load package dependencies recursively (1 level deep for now) */ -function getPackageDependencies(packageId: string): { id: string; name?: string }[] { - const metadata = loadPackageMetadata(packageId) +async function getPackageDependencies(packageId: string): Promise<{ id: string; name?: string }[]> { + const metadata = await loadPackageMetadata(packageId) as { dependencies?: string[]; name?: string; minLevel?: number } | null if (!metadata?.dependencies) { return [] } - return metadata.dependencies.map(depId => { - const depMetadata = loadPackageMetadata(depId) - return { - id: depId, - name: depMetadata?.name, - minLevel: depMetadata?.minLevel, - } - }) + const deps = await Promise.all( + metadata.dependencies.map(async depId => { + const depMetadata = await loadPackageMetadata(depId) as { name?: string; minLevel?: number } | null + return { + id: depId, + name: depMetadata?.name, + minLevel: depMetadata?.minLevel, + } + }) + ) + + return deps } export default async function TenantLayout({ @@ -62,7 +66,7 @@ export default async function TenantLayout({ } // Load dependencies that this page can also use - const additionalPackages = getPackageDependencies(pkg) + const additionalPackages = await getPackageDependencies(pkg) // TODO: Validate tenant exists against database // const tenantData = await getTenant(tenant) diff --git a/frontends/nextjs/src/app/api/github/actions/runs/[runId]/logs/route.ts b/frontends/nextjs/src/app/api/github/actions/runs/[runId]/logs/route.ts index e197e1ab7..9e17a722b 100644 --- a/frontends/nextjs/src/app/api/github/actions/runs/[runId]/logs/route.ts +++ b/frontends/nextjs/src/app/api/github/actions/runs/[runId]/logs/route.ts @@ -12,6 +12,8 @@ interface RouteParams { } } +export const dynamic = 'force-dynamic' + export const GET = async (request: NextRequest, { params }: RouteParams) => { const runId = Number(params.runId) if (!Number.isFinite(runId) || runId <= 0) { diff --git a/frontends/nextjs/src/app/api/health/route.test.tsx b/frontends/nextjs/src/app/api/health/route.test.tsx index 4db343d2b..6f3f14d67 100644 --- a/frontends/nextjs/src/app/api/health/route.test.tsx +++ b/frontends/nextjs/src/app/api/health/route.test.tsx @@ -1,10 +1,11 @@ import { describe, expect, it } from 'vitest' +import { NextRequest } from 'next/server' import { GET } from './route' describe('GET /api/health', () => { it('returns OK status and permission level count', async () => { - const response = await GET(new Request('http://example.com/api/health')) + const response = await GET(new NextRequest('http://example.com/api/health')) const payload = await response.json() expect(payload.status).toBe('ok') diff --git a/frontends/nextjs/src/app/api/packages/data/[packageId]/handlers/put-package-data.ts b/frontends/nextjs/src/app/api/packages/data/[packageId]/handlers/put-package-data.ts index af8cec1fa..6ac65a576 100644 --- a/frontends/nextjs/src/app/api/packages/data/[packageId]/handlers/put-package-data.ts +++ b/frontends/nextjs/src/app/api/packages/data/[packageId]/handlers/put-package-data.ts @@ -3,10 +3,10 @@ import { NextResponse } from 'next/server' import { readJson } from '@/lib/api/read-json' import { setPackageData } from '@/lib/db/packages/set-package-data' -import type { JsonValue } from '@/types/utility-types' +import type { PackageSeedData } from '@/lib/package-types' type PackageDataPayload = { - data?: Record + data?: PackageSeedData } interface RouteParams { diff --git a/frontends/nextjs/src/app/api/packages/data/[packageId]/route.ts b/frontends/nextjs/src/app/api/packages/data/[packageId]/route.ts index b9823fa1a..6a89cb9b5 100644 --- a/frontends/nextjs/src/app/api/packages/data/[packageId]/route.ts +++ b/frontends/nextjs/src/app/api/packages/data/[packageId]/route.ts @@ -1,3 +1,5 @@ +export const dynamic = 'force-dynamic' + export { DELETE } from './handlers/delete-package-data' export { GET } from './handlers/get-package-data' export { PUT } from './handlers/put-package-data' diff --git a/frontends/nextjs/src/app/api/v1/[...slug]/route.ts b/frontends/nextjs/src/app/api/v1/[...slug]/route.ts index e7a66e836..87e3897e3 100644 --- a/frontends/nextjs/src/app/api/v1/[...slug]/route.ts +++ b/frontends/nextjs/src/app/api/v1/[...slug]/route.ts @@ -127,7 +127,7 @@ async function handleRequest( // Build response with metadata const responseData = result.meta - ? { data: result.data, ...result.meta } + ? { data: result.data, ...(result.meta as Record) } : result.data // Map operation to appropriate status code diff --git a/frontends/nextjs/src/app/ui/[[...slug]]/page.tsx b/frontends/nextjs/src/app/ui/[[...slug]]/page.tsx index cc4080fa8..d99f67be6 100644 --- a/frontends/nextjs/src/app/ui/[[...slug]]/page.tsx +++ b/frontends/nextjs/src/app/ui/[[...slug]]/page.tsx @@ -28,12 +28,18 @@ export default async function DynamicUIPage({ params }: PageProps) { const path = '/' + slug.join('/') // Prefer Lua package-based UI pages, fallback to database-backed pages - const pageData = (await loadPageFromLuaPackages(path)) ?? (await loadPageFromDb(path)) + const rawPageData = (await loadPageFromLuaPackages(path)) ?? (await loadPageFromDb(path)) - if (!pageData) { + if (!rawPageData) { notFound() } + // Transform PageConfig to UIPageData + const pageData = { + layout: rawPageData, + actions: {}, + } + // Check authentication if required // TODO: Add auth check based on pageData.requireAuth and pageData.requiredRole diff --git a/frontends/nextjs/src/components/get-component-icon.test.tsx b/frontends/nextjs/src/components/get-component-icon.test.tsx index d4d7fd8b6..8d211b8a3 100644 --- a/frontends/nextjs/src/components/get-component-icon.test.tsx +++ b/frontends/nextjs/src/components/get-component-icon.test.tsx @@ -28,7 +28,7 @@ const knownIcons = [ describe('getComponentIcon', () => { it.each(knownIcons)('returns an icon for %s', iconName => { - expect(getComponentIcon(iconName, { sx: { fontSize: 20 } })).not.toBeNull() + expect(getComponentIcon(iconName, { style: { fontSize: 20 } })).not.toBeNull() }) it('returns null for unknown icons', () => { diff --git a/frontends/nextjs/src/hooks/__tests__/useAuth.roles.test.ts b/frontends/nextjs/src/hooks/__tests__/useAuth.roles.test.ts index f88ad4700..5ecc99dc5 100644 --- a/frontends/nextjs/src/hooks/__tests__/useAuth.roles.test.ts +++ b/frontends/nextjs/src/hooks/__tests__/useAuth.roles.test.ts @@ -1,4 +1,4 @@ -import { act, renderHook, waitFor } from '@testing-library/react' +import { act, renderHook } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { useAuth } from '@/hooks/useAuth' @@ -7,6 +7,21 @@ import { login as loginRequest } from '@/lib/auth/api/login' import { logout as logoutRequest } from '@/lib/auth/api/logout' import type { User } from '@/lib/level-types' +// Simple waitFor implementation +const waitFor = async (callback: () => boolean | void, timeout = 1000) => { + const start = Date.now() + while (Date.now() - start < timeout) { + try { + const result = callback() + if (result === false) continue + return + } catch { + await new Promise(resolve => setTimeout(resolve, 50)) + } + } + throw new Error('waitFor timed out') +} + vi.mock('@/lib/auth/api/fetch-session', () => ({ fetchSession: vi.fn(), })) diff --git a/frontends/nextjs/src/hooks/__tests__/useAuth.session.test.ts b/frontends/nextjs/src/hooks/__tests__/useAuth.session.test.ts index b8d9db2c2..18edcc476 100644 --- a/frontends/nextjs/src/hooks/__tests__/useAuth.session.test.ts +++ b/frontends/nextjs/src/hooks/__tests__/useAuth.session.test.ts @@ -1,4 +1,4 @@ -import { act, renderHook, waitFor } from '@testing-library/react' +import { act, renderHook } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { useAuth } from '@/hooks/useAuth' @@ -8,6 +8,21 @@ import { logout as logoutRequest } from '@/lib/auth/api/logout' import { register as registerRequest } from '@/lib/auth/api/register' import type { User } from '@/lib/level-types' +// Simple waitFor implementation +const waitFor = async (callback: () => boolean | void, timeout = 1000) => { + const start = Date.now() + while (Date.now() - start < timeout) { + try { + const result = callback() + if (result === false) continue + return + } catch { + await new Promise(resolve => setTimeout(resolve, 50)) + } + } + throw new Error('waitFor timed out') +} + vi.mock('@/lib/auth/api/fetch-session', () => ({ fetchSession: vi.fn(), })) diff --git a/frontends/nextjs/src/hooks/auth/utils/map-user.ts b/frontends/nextjs/src/hooks/auth/utils/map-user.ts index f66fd44ee..1506b253d 100644 --- a/frontends/nextjs/src/hooks/auth/utils/map-user.ts +++ b/frontends/nextjs/src/hooks/auth/utils/map-user.ts @@ -13,7 +13,14 @@ import { getRoleLevel } from './role-levels' */ export const mapUserToAuthUser = (user: User): AuthUser => { return { - ...user, + id: user.id, + email: user.email, + username: user.username, + role: user.role as AuthUser['role'], level: getRoleLevel(user.role), + tenantId: user.tenantId || undefined, + profilePicture: user.profilePicture || undefined, + bio: user.bio || undefined, + isInstanceOwner: user.isInstanceOwner, } } diff --git a/frontends/nextjs/src/hooks/data/useLevelRouting.ts b/frontends/nextjs/src/hooks/data/useLevelRouting.ts new file mode 100644 index 000000000..a122324db --- /dev/null +++ b/frontends/nextjs/src/hooks/data/useLevelRouting.ts @@ -0,0 +1,25 @@ +/** + * Hook for level-based routing functionality + */ + +export interface LevelRouting { + canAccessLevel: (level: number) => boolean + redirectToLevel: (level: number) => void +} + +/** + * Hook for managing level-based routing + * TODO: Implement full level routing logic + */ +export function useLevelRouting(): LevelRouting { + return { + canAccessLevel: (level: number) => { + // TODO: Implement level access check + return level >= 0 + }, + redirectToLevel: (level: number) => { + // TODO: Implement redirect logic (suppress console warning for now) + void level + }, + } +} diff --git a/frontends/nextjs/src/hooks/data/useResolvedUser.ts b/frontends/nextjs/src/hooks/data/useResolvedUser.ts new file mode 100644 index 000000000..8c4b78a9b --- /dev/null +++ b/frontends/nextjs/src/hooks/data/useResolvedUser.ts @@ -0,0 +1,22 @@ +/** + * Hook for resolved user state + */ + +export interface ResolvedUserState { + userId?: string + username?: string + level?: number + isLoading: boolean + error?: string +} + +/** + * Hook for managing resolved user state + * TODO: Implement full user resolution logic + */ +export function useResolvedUser(): ResolvedUserState { + // TODO: Implement user resolution from session/auth + return { + isLoading: false, + } +} diff --git a/frontends/nextjs/src/hooks/use-dbal/use-kv-store.ts b/frontends/nextjs/src/hooks/use-dbal/use-kv-store.ts index 6e1f50633..a2286e64d 100644 --- a/frontends/nextjs/src/hooks/use-dbal/use-kv-store.ts +++ b/frontends/nextjs/src/hooks/use-dbal/use-kv-store.ts @@ -2,6 +2,7 @@ import { useCallback } from 'react' // import { toast } from 'sonner' import { dbal } from '@/lib/dbal/core/client' +import type { JsonValue } from '@/types/utility-types' import { useDBAL } from './use-dbal' @@ -17,7 +18,7 @@ export function useKVStore(tenantId: string = 'default', userId: string = 'syste throw new Error('DBAL not ready') } try { - await dbal.kvSet(key, value, ttl, tenantId, userId) + await dbal.kvSet(key, value as JsonValue, ttl, tenantId, userId) } catch (err) { const _errorInfo = dbal.handleError(err) // toast.error(`KV Set Error: ${errorInfo.message}`) @@ -65,7 +66,7 @@ export function useKVStore(tenantId: string = 'default', userId: string = 'syste throw new Error('DBAL not ready') } try { - await dbal.kvListAdd(key, items, tenantId, userId) + await dbal.kvListAdd(key, items as JsonValue[], tenantId, userId) } catch (err) { const _errorInfo = dbal.handleError(err) // toast.error(`KV List Add Error: ${errorInfo.message}`) @@ -81,7 +82,7 @@ export function useKVStore(tenantId: string = 'default', userId: string = 'syste throw new Error('DBAL not ready') } try { - return await dbal.kvListGet(key, tenantId, userId, start, end) + return (await dbal.kvListGet(key, tenantId, userId, start, end)) as T[] } catch (err) { const _errorInfo = dbal.handleError(err) // toast.error(`KV List Get Error: ${errorInfo.message}`) diff --git a/frontends/nextjs/src/lib/compiler/index.ts b/frontends/nextjs/src/lib/compiler/index.ts index 73fc6b448..199e89d88 100644 --- a/frontends/nextjs/src/lib/compiler/index.ts +++ b/frontends/nextjs/src/lib/compiler/index.ts @@ -17,6 +17,7 @@ export async function compile(source: string, _options?: CompileOptions): Promis return { code: source } } -export async function loadAndInjectStyles(_packageId: string): Promise { +export async function loadAndInjectStyles(_packageId: string): Promise { // TODO: Implement style loading and injection + return '' } diff --git a/frontends/nextjs/src/lib/components/component-catalog.ts b/frontends/nextjs/src/lib/components/component-catalog.ts new file mode 100644 index 000000000..08f0a18ad --- /dev/null +++ b/frontends/nextjs/src/lib/components/component-catalog.ts @@ -0,0 +1,32 @@ +/** + * Component catalog for registering and retrieving components + */ + +export interface ComponentCatalogEntry { + name: string + component: React.ComponentType + description?: string +} + +/** + * Component catalog registry + * TODO: Implement full component catalog functionality + */ +export class ComponentCatalog { + private components = new Map() + + register(name: string, entry: ComponentCatalogEntry): void { + this.components.set(name, entry) + } + + get(name: string): ComponentCatalogEntry | undefined { + return this.components.get(name) + } + + getAll(): ComponentCatalogEntry[] { + return Array.from(this.components.values()) + } +} + +// Singleton instance +export const componentCatalog = new ComponentCatalog() diff --git a/frontends/nextjs/src/lib/components/component-registry.ts b/frontends/nextjs/src/lib/components/component-registry.ts new file mode 100644 index 000000000..ba715b6a9 --- /dev/null +++ b/frontends/nextjs/src/lib/components/component-registry.ts @@ -0,0 +1,33 @@ +/** + * Component registry for managing component metadata + */ + +export interface ComponentMetadata { + id: string + name: string + type: string + props?: Record +} + +/** + * Component registry + * TODO: Implement full component registry functionality + */ +export class ComponentRegistry { + private registry = new Map() + + register(id: string, metadata: ComponentMetadata): void { + this.registry.set(id, metadata) + } + + get(id: string): ComponentMetadata | undefined { + return this.registry.get(id) + } + + getAll(): ComponentMetadata[] { + return Array.from(this.registry.values()) + } +} + +// Singleton instance +export const componentRegistry = new ComponentRegistry() diff --git a/frontends/nextjs/src/lib/database-dbal/users/dbal-add-user.server.ts b/frontends/nextjs/src/lib/database-dbal/users/dbal-add-user.server.ts index 291eee8fe..fd03dab4b 100644 --- a/frontends/nextjs/src/lib/database-dbal/users/dbal-add-user.server.ts +++ b/frontends/nextjs/src/lib/database-dbal/users/dbal-add-user.server.ts @@ -7,5 +7,5 @@ import type { User } from '../../types/level-types' export async function dbalAddUser(user: User): Promise { const adapter = getAdapter() - await adapter.create('User', user) + await adapter.create('User', user as unknown as Record) } diff --git a/frontends/nextjs/src/lib/db/comments/crud/add-comment.test.ts b/frontends/nextjs/src/lib/db/comments/crud/add-comment.test.ts index bd7ed7e74..330302f0a 100644 --- a/frontends/nextjs/src/lib/db/comments/crud/add-comment.test.ts +++ b/frontends/nextjs/src/lib/db/comments/crud/add-comment.test.ts @@ -19,11 +19,11 @@ describe('addComment', () => { const cases: Array<{ name: string; comment: Comment }> = [ { name: 'basic comment', - comment: { id: 'c1', userId: 'u1', content: 'Hello', createdAt: 1000 }, + comment: { id: 'c1', userId: 'u1', entityType: 'post', entityId: 'p1', content: 'Hello', createdAt: 1000 }, }, { name: 'reply comment', - comment: { id: 'c2', userId: 'u1', content: 'Reply', createdAt: 2000, parentId: 'c1' }, + comment: { id: 'c2', userId: 'u1', entityType: 'post', entityId: 'p1', content: 'Reply', createdAt: 2000, parentId: 'c1' }, }, ] diff --git a/frontends/nextjs/src/lib/db/comments/crud/get-comments.ts b/frontends/nextjs/src/lib/db/comments/crud/get-comments.ts index 7cd334d5f..31cb3b7ba 100644 --- a/frontends/nextjs/src/lib/db/comments/crud/get-comments.ts +++ b/frontends/nextjs/src/lib/db/comments/crud/get-comments.ts @@ -4,6 +4,8 @@ import type { Comment } from '@/lib/types/level-types' type DBALCommentRecord = { id: string userId: string + entityType: string + entityId: string content: string createdAt: number | string | Date updatedAt?: number | string | Date | null @@ -19,6 +21,8 @@ export async function getComments(): Promise { return result.data.map(c => ({ id: c.id, userId: c.userId, + entityType: c.entityType, + entityId: c.entityId, content: c.content, createdAt: Number(c.createdAt), updatedAt: c.updatedAt ? Number(c.updatedAt) : undefined, diff --git a/frontends/nextjs/src/lib/db/components/config/crud/operations/add-component-config.ts b/frontends/nextjs/src/lib/db/components/config/crud/operations/add-component-config.ts index ac1cfeebf..c9b472133 100644 --- a/frontends/nextjs/src/lib/db/components/config/crud/operations/add-component-config.ts +++ b/frontends/nextjs/src/lib/db/components/config/crud/operations/add-component-config.ts @@ -1,5 +1,5 @@ import { getAdapter } from '../../../../core/dbal-client' -import type { ComponentConfig } from '../types' +import type { ComponentConfig } from '../../../types' export async function addComponentConfig(config: ComponentConfig): Promise { const adapter = getAdapter() diff --git a/frontends/nextjs/src/lib/db/components/config/crud/operations/update-component-config.ts b/frontends/nextjs/src/lib/db/components/config/crud/operations/update-component-config.ts index a3664de9b..b51c1b548 100644 --- a/frontends/nextjs/src/lib/db/components/config/crud/operations/update-component-config.ts +++ b/frontends/nextjs/src/lib/db/components/config/crud/operations/update-component-config.ts @@ -1,5 +1,5 @@ import { getAdapter } from '../../../../core/dbal-client' -import type { ComponentConfig } from '../types' +import type { ComponentConfig } from '../../../types' export async function updateComponentConfig( configId: string, diff --git a/frontends/nextjs/src/lib/db/components/node/crud/add-component-node.ts b/frontends/nextjs/src/lib/db/components/node/crud/add-component-node.ts index f78dd53c6..cd628955d 100644 --- a/frontends/nextjs/src/lib/db/components/node/crud/add-component-node.ts +++ b/frontends/nextjs/src/lib/db/components/node/crud/add-component-node.ts @@ -1,5 +1,5 @@ import { getAdapter } from '../../../core/dbal-client' -import type { ComponentNode } from '../types' +import type { ComponentNode } from '../../types' export async function addComponentNode(node: ComponentNode): Promise { const adapter = getAdapter() diff --git a/frontends/nextjs/src/lib/db/components/node/crud/update-component-node.ts b/frontends/nextjs/src/lib/db/components/node/crud/update-component-node.ts index 98a10130b..18d465c39 100644 --- a/frontends/nextjs/src/lib/db/components/node/crud/update-component-node.ts +++ b/frontends/nextjs/src/lib/db/components/node/crud/update-component-node.ts @@ -1,5 +1,5 @@ import { getAdapter } from '../../../core/dbal-client' -import type { ComponentNode } from '../types' +import type { ComponentNode } from '../../types' export async function updateComponentNode( nodeId: string, diff --git a/frontends/nextjs/src/lib/db/components/types.ts b/frontends/nextjs/src/lib/db/components/types.ts index 61c0f0953..3a5b6f5f6 100644 --- a/frontends/nextjs/src/lib/db/components/types.ts +++ b/frontends/nextjs/src/lib/db/components/types.ts @@ -12,7 +12,7 @@ export interface ComponentConfig { export interface ComponentNode { id: string - name: string + name?: string type: string parentId?: string childIds?: string[] diff --git a/frontends/nextjs/src/lib/db/core/types.ts b/frontends/nextjs/src/lib/db/core/types.ts index 992e05599..fa20595ce 100644 --- a/frontends/nextjs/src/lib/db/core/types.ts +++ b/frontends/nextjs/src/lib/db/core/types.ts @@ -21,11 +21,12 @@ export interface DropdownConfig { */ export interface ComponentNode { id: string + name?: string type: string parentId?: string - childIds: string[] - order: number - pageId: string + childIds?: string[] + order?: number + pageId?: string } /** @@ -36,7 +37,7 @@ export interface ComponentConfig { componentId: string props: Record styles: Record - events: Record + events?: Record conditionalRendering?: { condition: string luaScriptId?: string diff --git a/frontends/nextjs/src/lib/db/sessions/crud/update-session.ts b/frontends/nextjs/src/lib/db/sessions/crud/update-session.ts index 8b7d565ef..b9d6f94fa 100644 --- a/frontends/nextjs/src/lib/db/sessions/crud/update-session.ts +++ b/frontends/nextjs/src/lib/db/sessions/crud/update-session.ts @@ -1,5 +1,5 @@ import { getAdapter } from '../../core/dbal-client' -import { mapSessionRecord } from '../map-session-record' +import { mapSessionRecord, type SessionRecord } from '../map-session-record' import type { Session, UpdateSessionInput } from '../types' export async function updateSession( @@ -12,5 +12,5 @@ export async function updateSession( ...(input.expiresAt !== undefined ? { expiresAt: BigInt(input.expiresAt) } : {}), ...(input.lastActivity !== undefined ? { lastActivity: BigInt(input.lastActivity) } : {}), }) - return mapSessionRecord(record) + return mapSessionRecord(record as SessionRecord) } diff --git a/frontends/nextjs/src/lib/db/sessions/getters/get-session-by-id.ts b/frontends/nextjs/src/lib/db/sessions/getters/get-session-by-id.ts index 267773f2e..8213b9b80 100644 --- a/frontends/nextjs/src/lib/db/sessions/getters/get-session-by-id.ts +++ b/frontends/nextjs/src/lib/db/sessions/getters/get-session-by-id.ts @@ -1,10 +1,10 @@ import { getAdapter } from '../../core/dbal-client' -import { mapSessionRecord } from '../map-session-record' +import { mapSessionRecord, type SessionRecord } from '../map-session-record' import type { Session } from '../types' export async function getSessionById(sessionId: string): Promise { const adapter = getAdapter() const record = await adapter.read('Session', sessionId) if (!record) return null - return mapSessionRecord(record) + return mapSessionRecord(record as SessionRecord) } diff --git a/frontends/nextjs/src/lib/db/sessions/getters/get-session-by-token.ts b/frontends/nextjs/src/lib/db/sessions/getters/get-session-by-token.ts index 89c4c52b7..af1373ed8 100644 --- a/frontends/nextjs/src/lib/db/sessions/getters/get-session-by-token.ts +++ b/frontends/nextjs/src/lib/db/sessions/getters/get-session-by-token.ts @@ -1,6 +1,6 @@ import { getAdapter } from '../../core/dbal-client' import { deleteSession } from '../crud/delete/delete-session' -import { mapSessionRecord } from '../map-session-record' +import { mapSessionRecord, type SessionRecord } from '../map-session-record' import type { Session } from '../types' export async function getSessionByToken(token: string): Promise { @@ -8,7 +8,7 @@ export async function getSessionByToken(token: string): Promise const result = await adapter.list('Session', { filter: { token } }) if (!result.data.length) return null - const session = mapSessionRecord(result.data[0]) + const session = mapSessionRecord(result.data[0] as SessionRecord) if (session.expiresAt <= Date.now()) { await deleteSession(session.id) return null diff --git a/frontends/nextjs/src/lib/db/sessions/map-session-record.ts b/frontends/nextjs/src/lib/db/sessions/map-session-record.ts index 3ca914690..0e125d1a3 100644 --- a/frontends/nextjs/src/lib/db/sessions/map-session-record.ts +++ b/frontends/nextjs/src/lib/db/sessions/map-session-record.ts @@ -1,12 +1,12 @@ import type { Session } from './types' -type SessionRecord = { +export type SessionRecord = { id: string userId: string token: string - expiresAt: number | string | Date - createdAt: number | string | Date - lastActivity: number | string | Date + expiresAt: number | string | Date | bigint + createdAt: number | string | Date | bigint + lastActivity: number | string | Date | bigint } export function mapSessionRecord(record: SessionRecord): Session { diff --git a/frontends/nextjs/src/lib/dbal-client/adapter/get-adapter.ts b/frontends/nextjs/src/lib/dbal-client/adapter/get-adapter.ts index 9bc57337d..1fe993088 100644 --- a/frontends/nextjs/src/lib/dbal-client/adapter/get-adapter.ts +++ b/frontends/nextjs/src/lib/dbal-client/adapter/get-adapter.ts @@ -21,6 +21,20 @@ class PrismaAdapter implements DBALAdapter { return await model.findUnique({ where: { id } }) } + async read(entity: string, id: string | number): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const model = (prisma as any)[entity.toLowerCase()] + if (!model) throw new Error(`Unknown entity: ${entity}`) + return await model.findUnique({ where: { id } }) + } + + async findFirst(entity: string, options: { where: Record }): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const model = (prisma as any)[entity.toLowerCase()] + if (!model) throw new Error(`Unknown entity: ${entity}`) + return await model.findFirst({ where: options.where }) + } + async list(entity: string, options?: ListOptions): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any const model = (prisma as any)[entity.toLowerCase()] @@ -55,26 +69,42 @@ class PrismaAdapter implements DBALAdapter { async upsert( entity: string, - uniqueField: string, - uniqueValue: unknown, - createData: Record, - updateData: Record + uniqueFieldOrOptions: string | { where: Record; update: Record; create: Record }, + uniqueValue?: unknown, + createData?: Record, + updateData?: Record ): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any const model = (prisma as any)[entity.toLowerCase()] if (!model) throw new Error(`Unknown entity: ${entity}`) + + // Handle options object form + if (typeof uniqueFieldOrOptions === 'object') { + return await model.upsert({ + where: uniqueFieldOrOptions.where, + create: uniqueFieldOrOptions.create, + update: uniqueFieldOrOptions.update, + }) + } + + // Handle 5-parameter form return await model.upsert({ - where: { [uniqueField]: uniqueValue }, - create: createData, - update: updateData, + where: { [uniqueFieldOrOptions]: uniqueValue }, + create: createData!, + update: updateData!, }) } - async delete(entity: string, id: string | number): Promise { + async delete(entity: string, id: string | number): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any const model = (prisma as any)[entity.toLowerCase()] if (!model) throw new Error(`Unknown entity: ${entity}`) - await model.delete({ where: { id } }) + try { + await model.delete({ where: { id } }) + return true + } catch { + return false + } } async createMany(entity: string, data: Record[]): Promise { diff --git a/frontends/nextjs/src/lib/dbal-client/types.ts b/frontends/nextjs/src/lib/dbal-client/types.ts index 283f30949..b141353bd 100644 --- a/frontends/nextjs/src/lib/dbal-client/types.ts +++ b/frontends/nextjs/src/lib/dbal-client/types.ts @@ -16,26 +16,31 @@ export interface ListResult { hasMore: boolean } +export type UpsertOptions = { where: Record; update: Record; create: Record } + export interface DBALAdapter { // Create operations create(entity: string, data: Record): Promise // Read operations get(entity: string, id: string | number): Promise + read(entity: string, id: string | number): Promise + findFirst(entity: string, options: { where: Record }): Promise list(entity: string, options?: ListOptions): Promise // Update operations update(entity: string, id: string | number, data: Record): Promise + // Upsert has two signatures: 5-param form or options object form upsert( entity: string, - uniqueField: string, - uniqueValue: unknown, - createData: Record, - updateData: Record + uniqueFieldOrOptions: string | UpsertOptions, + uniqueValue?: unknown, + createData?: Record, + updateData?: Record ): Promise // Delete operations - delete(entity: string, id: string | number): Promise + delete(entity: string, id: string | number): Promise // Batch operations createMany(entity: string, data: Record[]): Promise diff --git a/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/functions/initialize.ts b/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/functions/initialize.ts index c9d1ed04e..d1adbf335 100644 --- a/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/functions/initialize.ts +++ b/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/functions/initialize.ts @@ -1,6 +1,7 @@ import { DBALClient as _DBALClient, type DBALConfig as _DBALConfig } from '@/dbal' import { InMemoryKVStore } from '@/dbal/core/kv' import { MemoryStorage } from '@/dbal/blob/providers/memory-storage' +import { setInitialized } from './is-initialized' interface DBALIntegrationState { initialized?: boolean @@ -10,24 +11,26 @@ interface DBALIntegrationState { client?: _DBALClient } +const state: DBALIntegrationState = {} + /** * Initialize the DBAL client with configuration */ -export async function initialize(this: DBALIntegrationState, config?: Partial<_DBALConfig>): Promise { - if (this.initialized) { +export async function initialize(config?: Partial<_DBALConfig>): Promise { + if (state.initialized) { console.warn('DBAL already initialized') return } try { // Initialize tenant manager (stub for now) - this.tenantManager = { tenants: new Map() } + state.tenantManager = { tenants: new Map() } // Initialize KV store - this.kvStore = new InMemoryKVStore() + state.kvStore = new InMemoryKVStore() // Initialize blob storage - this.blobStorage = new MemoryStorage() + state.blobStorage = new MemoryStorage() // Initialize DBAL client const dbalConfig: _DBALConfig = { @@ -36,9 +39,10 @@ export async function initialize(this: DBALIntegrationState, config?: Partial<_D ...config, } as _DBALConfig - this.client = new _DBALClient(dbalConfig) + state.client = new _DBALClient(dbalConfig) - this.initialized = true + state.initialized = true + setInitialized(true) } catch (error) { console.error('Failed to initialize DBAL:', error) throw error diff --git a/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/functions/is-initialized.ts b/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/functions/is-initialized.ts index 7625f90c4..970704d69 100644 --- a/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/functions/is-initialized.ts +++ b/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/functions/is-initialized.ts @@ -1,5 +1,10 @@ -import type { DBALClient as _DBALClient, DBALConfig as _DBALConfig } from '@/dbal' +let initialized = false export function isInitialized(): boolean { - return this.initialized + // TODO: Implement proper initialization state tracking + return initialized +} + +export function setInitialized(value: boolean): void { + initialized = value } diff --git a/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/index.ts b/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/index.ts index f7ca7f006..7175fd291 100644 --- a/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/index.ts +++ b/frontends/nextjs/src/lib/dbal/core/client/dbal-integration/index.ts @@ -43,6 +43,18 @@ import { getBlobStorage as getBlobStorageImpl } from './functions/get-blob-stora import { getKVStore as getKVStoreImpl } from './functions/get-k-v-store' import { getTenantContext as getTenantContextImpl } from './functions/get-tenant-context' import { getTenantManager as getTenantManagerImpl } from './functions/get-tenant-manager' +import { handleError as handleErrorImpl } from './functions/handle-error' +import { isInitialized as isInitializedImpl } from './functions/is-initialized' +import { kvSet as kvSetImpl } from './functions/kv-set' +import { kvGet as kvGetImpl } from './functions/kv-get' +import { kvDelete as kvDeleteImpl } from './functions/kv-delete' +import { kvListAdd as kvListAddImpl } from './functions/kv-list-add' +import { kvListGet as kvListGetImpl } from './functions/kv-list-get' +import { blobUpload as blobUploadImpl } from './functions/blob-upload' +import { blobDownload as blobDownloadImpl } from './functions/blob-download' +import { blobDelete as blobDeleteImpl } from './functions/blob-delete' +import { blobList as blobListImpl } from './functions/blob-list' +import { blobGetMetadata as blobGetMetadataImpl } from './functions/blob-get-metadata' // Create a namespace object for backward compatibility export const dbal = { @@ -55,6 +67,18 @@ export const dbal = { getKVStore: getKVStoreImpl, getTenantContext: getTenantContextImpl, getTenantManager: getTenantManagerImpl, + handleError: handleErrorImpl, + isInitialized: isInitializedImpl, + kvSet: kvSetImpl, + kvGet: kvGetImpl, + kvDelete: kvDeleteImpl, + kvListAdd: kvListAddImpl, + kvListGet: kvListGetImpl, + blobUpload: blobUploadImpl, + blobDownload: blobDownloadImpl, + blobDelete: blobDeleteImpl, + blobList: blobListImpl, + blobGetMetadata: blobGetMetadataImpl, } // Type alias for backward compatibility diff --git a/frontends/nextjs/src/lib/lua/ui/generate-component-tree.ts b/frontends/nextjs/src/lib/lua/ui/generate-component-tree.ts index 35a5b9ca6..3c19f7c20 100644 --- a/frontends/nextjs/src/lib/lua/ui/generate-component-tree.ts +++ b/frontends/nextjs/src/lib/lua/ui/generate-component-tree.ts @@ -2,13 +2,15 @@ * Generate component tree from Lua (stub) */ +import type { ReactNode } from 'react' + export interface ComponentTree { type: string props?: Record children?: ComponentTree[] } -export function generateComponentTree(_luaScript: string): ComponentTree { +export function generateComponentTree(_luaScript: unknown): ReactNode { // TODO: Implement Lua component tree generation - return { type: 'div' } + return null } diff --git a/frontends/nextjs/src/lib/lua/ui/types/lua-ui-package.ts b/frontends/nextjs/src/lib/lua/ui/types/lua-ui-package.ts index f163ef08c..10cfacdbc 100644 --- a/frontends/nextjs/src/lib/lua/ui/types/lua-ui-package.ts +++ b/frontends/nextjs/src/lib/lua/ui/types/lua-ui-package.ts @@ -2,6 +2,12 @@ * Lua UI types (stub) */ +export interface LuaUIComponent { + type: string + props?: Record + children?: LuaUIComponent[] +} + export interface LuaUIPackage { id: string name: string diff --git a/frontends/nextjs/src/lib/ui-pages/load-page-from-db.ts b/frontends/nextjs/src/lib/ui-pages/load-page-from-db.ts index a7e4cfa61..c31b97488 100644 --- a/frontends/nextjs/src/lib/ui-pages/load-page-from-db.ts +++ b/frontends/nextjs/src/lib/ui-pages/load-page-from-db.ts @@ -4,7 +4,14 @@ import type { PageConfig } from '../types/level-types' -export async function loadPageFromDb(_b_path: string, _tenantId?: string): Promise { +export type LuaActionHandler = (action: string, data: Record) => void | Promise + +export interface UIPageData { + layout: unknown + actions?: Record +} + +export async function loadPageFromDb(_path: string, _tenantId?: string): Promise { // TODO: Implement page loading from database return null } diff --git a/storybook/package.json b/storybook/package.json index 8f3a52d54..d5b77cb28 100644 --- a/storybook/package.json +++ b/storybook/package.json @@ -20,7 +20,7 @@ "@storybook/addon-essentials": "^8.6.15", "@storybook/addon-interactions": "^8.6.15", "@storybook/react": "^10.1.11", - "@storybook/react-vite": "^8.6.15", + "@storybook/react-vite": "^10.1.11", "@storybook/test": "^8.6.15", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5",