mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
Merge branch 'main' into dependabot/npm_and_yarn/vitejs/plugin-react-5.1.2
This commit is contained in:
@@ -25,11 +25,12 @@ export const findByField = (context: ACLContext) => async (entity: string, field
|
||||
|
||||
export const upsert = (context: ACLContext) => async (
|
||||
entity: string,
|
||||
filter: Record<string, unknown>,
|
||||
uniqueField: string,
|
||||
uniqueValue: unknown,
|
||||
createData: Record<string, unknown>,
|
||||
updateData: Record<string, unknown>,
|
||||
) => {
|
||||
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 (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -12,11 +12,12 @@ type PrismaModelDelegate = {
|
||||
delete: (...args: unknown[]) => Promise<unknown>
|
||||
deleteMany: (...args: unknown[]) => Promise<{ count: number }>
|
||||
upsert: (...args: unknown[]) => Promise<unknown>
|
||||
count: (...args: unknown[]) => Promise<number>
|
||||
}
|
||||
|
||||
export function getModel(context: PrismaContext, entity: string): PrismaModelDelegate {
|
||||
const modelName = entity.charAt(0).toLowerCase() + entity.slice(1)
|
||||
const model = (context.prisma as Record<string, PrismaModelDelegate>)[modelName]
|
||||
const model = (context.prisma as unknown as Record<string, PrismaModelDelegate>)[modelName]
|
||||
|
||||
if (!model) {
|
||||
throw DBALError.notFound(`Entity ${entity} not found`)
|
||||
|
||||
@@ -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<Uint8Array>
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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<string, string>
|
||||
}
|
||||
|
||||
return {
|
||||
key,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<TenantContext> => {
|
||||
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 => {
|
||||
|
||||
@@ -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<string, unknown>,
|
||||
uniqueField: string,
|
||||
uniqueValue: unknown,
|
||||
createData: Record<string, unknown>,
|
||||
updateData: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
// Convert the new signature to the old one for compatibility
|
||||
const filter = { [uniqueField]: uniqueValue }
|
||||
return this.operations.upsert(entity, filter, createData, updateData)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<T> {
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ListOptions {
|
||||
filter?: Record<string, any>;
|
||||
sort?: Record<string, 'asc' | 'desc'>;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
skip?: number;
|
||||
take?: number;
|
||||
where?: Record<string, any>;
|
||||
orderBy?: Record<string, 'asc' | 'desc'>;
|
||||
}
|
||||
|
||||
export interface ListResult<T> {
|
||||
items?: T[];
|
||||
data?: T[];
|
||||
total: number;
|
||||
skip?: number;
|
||||
take?: number;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
export interface InMemoryStore {
|
||||
sessions: Map<string, any>;
|
||||
sessionTokens: Map<string, string>;
|
||||
users: Map<string, any>;
|
||||
generateId(entityType: string): string;
|
||||
}
|
||||
|
||||
@@ -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<T> {
|
||||
@@ -32,3 +38,22 @@ export interface Result<T> {
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ListOptions {
|
||||
filter?: Record<string, any>;
|
||||
sort?: Record<string, 'asc' | 'desc'>;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
skip?: number;
|
||||
take?: number;
|
||||
where?: Record<string, any>;
|
||||
orderBy?: Record<string, 'asc' | 'desc'>;
|
||||
}
|
||||
|
||||
export interface ListResult<T> {
|
||||
items?: T[];
|
||||
data?: T[];
|
||||
total: number;
|
||||
skip?: number;
|
||||
take?: number;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<T> {
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ListOptions {
|
||||
filter?: Record<string, any>;
|
||||
sort?: Record<string, 'asc' | 'desc'>;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
skip?: number;
|
||||
take?: number;
|
||||
where?: Record<string, any>;
|
||||
orderBy?: Record<string, 'asc' | 'desc'>;
|
||||
}
|
||||
|
||||
export interface ListResult<T> {
|
||||
items?: T[];
|
||||
data?: T[];
|
||||
total: number;
|
||||
skip?: number;
|
||||
take?: number;
|
||||
}
|
||||
|
||||
@@ -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_]+$/
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<T> {
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ListOptions {
|
||||
filter?: Record<string, any>;
|
||||
sort?: Record<string, 'asc' | 'desc'>;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
skip?: number;
|
||||
take?: number;
|
||||
where?: Record<string, any>;
|
||||
orderBy?: Record<string, 'asc' | 'desc'>;
|
||||
}
|
||||
|
||||
export interface ListResult<T> {
|
||||
items?: T[];
|
||||
data?: T[];
|
||||
total: number;
|
||||
skip?: number;
|
||||
take?: number;
|
||||
}
|
||||
|
||||
@@ -33,3 +33,10 @@ export interface TenantContext {
|
||||
canCreateRecord(): boolean
|
||||
canAddToList(additionalItems: number): boolean
|
||||
}
|
||||
|
||||
export interface TenantManager {
|
||||
getTenantContext(tenantId: string): Promise<TenantContext>
|
||||
updateQuota(tenantId: string, quota: Partial<TenantQuota>): Promise<void>
|
||||
validateTenantAccess(tenantId: string, userId: string): Promise<boolean>
|
||||
updateBlobUsage(tenantId: string, bytesChange: number, countChange: number): Promise<void>
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
export interface BadgeProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'color'> {
|
||||
export interface BadgeProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'color' | 'content'> {
|
||||
children?: React.ReactNode
|
||||
content?: React.ReactNode
|
||||
dot?: boolean
|
||||
|
||||
@@ -46,7 +46,7 @@ export const ButtonBase = forwardRef<HTMLElement, ButtonBaseProps>(function Butt
|
||||
)
|
||||
})
|
||||
|
||||
export interface InputBaseProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'> {
|
||||
export interface InputBaseProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue' | 'onFocus' | 'onBlur'> {
|
||||
disabled?: boolean
|
||||
error?: boolean
|
||||
fullWidth?: boolean
|
||||
|
||||
@@ -33,7 +33,7 @@ export const TextField = forwardRef<HTMLInputElement | HTMLSelectElement, TextFi
|
||||
id={id}
|
||||
error={error}
|
||||
className="select--full-width"
|
||||
{...(props as React.SelectHTMLAttributes<HTMLSelectElement>)}
|
||||
{...(props as unknown as React.SelectHTMLAttributes<HTMLSelectElement>)}
|
||||
>
|
||||
{children}
|
||||
</Select>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { forwardRef } from 'react'
|
||||
|
||||
export interface BottomNavigationProps extends React.HTMLAttributes<HTMLElement> {
|
||||
export interface BottomNavigationProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange'> {
|
||||
children?: React.ReactNode
|
||||
value?: any
|
||||
onChange?: (event: React.SyntheticEvent, value: any) => void
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface PaginationRenderItemParams {
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
export interface PaginationProps extends Omit<React.HTMLAttributes<HTMLElement>, 'color'> {
|
||||
export interface PaginationProps extends Omit<React.HTMLAttributes<HTMLElement>, 'color' | 'onChange'> {
|
||||
count?: number
|
||||
page?: number
|
||||
onChange?: (page: number) => void
|
||||
|
||||
@@ -80,7 +80,7 @@ export function SpeedDial({
|
||||
)
|
||||
}
|
||||
|
||||
export interface SpeedDialActionProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export interface SpeedDialActionProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onClick'> {
|
||||
icon?: React.ReactNode
|
||||
tooltipTitle?: string
|
||||
tooltipOpen?: boolean
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { forwardRef } from 'react'
|
||||
|
||||
export interface TabsProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export interface TabsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
||||
children?: React.ReactNode
|
||||
value?: any
|
||||
onChange?: (event: React.SyntheticEvent, value: any) => void
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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<string, JsonValue[]>
|
||||
data?: PackageSeedData
|
||||
}
|
||||
|
||||
interface RouteParams {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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<string, unknown>) }
|
||||
: result.data
|
||||
|
||||
// Map operation to appropriate status code
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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(),
|
||||
}))
|
||||
|
||||
@@ -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(),
|
||||
}))
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
25
frontends/nextjs/src/hooks/data/useLevelRouting.ts
Normal file
25
frontends/nextjs/src/hooks/data/useLevelRouting.ts
Normal file
@@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
22
frontends/nextjs/src/hooks/data/useResolvedUser.ts
Normal file
22
frontends/nextjs/src/hooks/data/useResolvedUser.ts
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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}`)
|
||||
|
||||
@@ -17,6 +17,7 @@ export async function compile(source: string, _options?: CompileOptions): Promis
|
||||
return { code: source }
|
||||
}
|
||||
|
||||
export async function loadAndInjectStyles(_packageId: string): Promise<void> {
|
||||
export async function loadAndInjectStyles(_packageId: string): Promise<string> {
|
||||
// TODO: Implement style loading and injection
|
||||
return ''
|
||||
}
|
||||
|
||||
32
frontends/nextjs/src/lib/components/component-catalog.ts
Normal file
32
frontends/nextjs/src/lib/components/component-catalog.ts
Normal file
@@ -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<string, ComponentCatalogEntry>()
|
||||
|
||||
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()
|
||||
33
frontends/nextjs/src/lib/components/component-registry.ts
Normal file
33
frontends/nextjs/src/lib/components/component-registry.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Component registry for managing component metadata
|
||||
*/
|
||||
|
||||
export interface ComponentMetadata {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
props?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Component registry
|
||||
* TODO: Implement full component registry functionality
|
||||
*/
|
||||
export class ComponentRegistry {
|
||||
private registry = new Map<string, ComponentMetadata>()
|
||||
|
||||
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()
|
||||
@@ -7,5 +7,5 @@ import type { User } from '../../types/level-types'
|
||||
|
||||
export async function dbalAddUser(user: User): Promise<void> {
|
||||
const adapter = getAdapter()
|
||||
await adapter.create('User', user)
|
||||
await adapter.create('User', user as unknown as Record<string, unknown>)
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -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<Comment[]> {
|
||||
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,
|
||||
|
||||
@@ -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<void> {
|
||||
const adapter = getAdapter()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<void> {
|
||||
const adapter = getAdapter()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface ComponentConfig {
|
||||
|
||||
export interface ComponentNode {
|
||||
id: string
|
||||
name: string
|
||||
name?: string
|
||||
type: string
|
||||
parentId?: string
|
||||
childIds?: string[]
|
||||
|
||||
@@ -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<string, unknown>
|
||||
styles: Record<string, unknown>
|
||||
events: Record<string, string>
|
||||
events?: Record<string, string>
|
||||
conditionalRendering?: {
|
||||
condition: string
|
||||
luaScriptId?: string
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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<Session | null> {
|
||||
const adapter = getAdapter()
|
||||
const record = await adapter.read('Session', sessionId)
|
||||
if (!record) return null
|
||||
return mapSessionRecord(record)
|
||||
return mapSessionRecord(record as SessionRecord)
|
||||
}
|
||||
|
||||
@@ -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<Session | null> {
|
||||
@@ -8,7 +8,7 @@ export async function getSessionByToken(token: string): Promise<Session | null>
|
||||
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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -21,6 +21,20 @@ class PrismaAdapter implements DBALAdapter {
|
||||
return await model.findUnique({ where: { id } })
|
||||
}
|
||||
|
||||
async read(entity: string, id: string | number): Promise<unknown> {
|
||||
// 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<string, unknown> }): Promise<unknown> {
|
||||
// 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<ListResult> {
|
||||
// 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<string, unknown>,
|
||||
updateData: Record<string, unknown>
|
||||
uniqueFieldOrOptions: string | { where: Record<string, unknown>; update: Record<string, unknown>; create: Record<string, unknown> },
|
||||
uniqueValue?: unknown,
|
||||
createData?: Record<string, unknown>,
|
||||
updateData?: Record<string, unknown>
|
||||
): Promise<unknown> {
|
||||
// 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<void> {
|
||||
async delete(entity: string, id: string | number): Promise<boolean> {
|
||||
// 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<string, unknown>[]): Promise<unknown[]> {
|
||||
|
||||
@@ -16,26 +16,31 @@ export interface ListResult<T = unknown> {
|
||||
hasMore: boolean
|
||||
}
|
||||
|
||||
export type UpsertOptions = { where: Record<string, unknown>; update: Record<string, unknown>; create: Record<string, unknown> }
|
||||
|
||||
export interface DBALAdapter {
|
||||
// Create operations
|
||||
create(entity: string, data: Record<string, unknown>): Promise<unknown>
|
||||
|
||||
// Read operations
|
||||
get(entity: string, id: string | number): Promise<unknown>
|
||||
read(entity: string, id: string | number): Promise<unknown>
|
||||
findFirst(entity: string, options: { where: Record<string, unknown> }): Promise<unknown>
|
||||
list(entity: string, options?: ListOptions): Promise<ListResult>
|
||||
|
||||
// Update operations
|
||||
update(entity: string, id: string | number, data: Record<string, unknown>): Promise<unknown>
|
||||
// Upsert has two signatures: 5-param form or options object form
|
||||
upsert(
|
||||
entity: string,
|
||||
uniqueField: string,
|
||||
uniqueValue: unknown,
|
||||
createData: Record<string, unknown>,
|
||||
updateData: Record<string, unknown>
|
||||
uniqueFieldOrOptions: string | UpsertOptions,
|
||||
uniqueValue?: unknown,
|
||||
createData?: Record<string, unknown>,
|
||||
updateData?: Record<string, unknown>
|
||||
): Promise<unknown>
|
||||
|
||||
// Delete operations
|
||||
delete(entity: string, id: string | number): Promise<void>
|
||||
delete(entity: string, id: string | number): Promise<boolean>
|
||||
|
||||
// Batch operations
|
||||
createMany(entity: string, data: Record<string, unknown>[]): Promise<unknown[]>
|
||||
|
||||
@@ -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<void> {
|
||||
if (this.initialized) {
|
||||
export async function initialize(config?: Partial<_DBALConfig>): Promise<void> {
|
||||
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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
* Generate component tree from Lua (stub)
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
export interface ComponentTree {
|
||||
type: string
|
||||
props?: Record<string, unknown>
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
* Lua UI types (stub)
|
||||
*/
|
||||
|
||||
export interface LuaUIComponent {
|
||||
type: string
|
||||
props?: Record<string, unknown>
|
||||
children?: LuaUIComponent[]
|
||||
}
|
||||
|
||||
export interface LuaUIPackage {
|
||||
id: string
|
||||
name: string
|
||||
|
||||
@@ -4,7 +4,14 @@
|
||||
|
||||
import type { PageConfig } from '../types/level-types'
|
||||
|
||||
export async function loadPageFromDb(_b_path: string, _tenantId?: string): Promise<PageConfig | null> {
|
||||
export type LuaActionHandler = (action: string, data: Record<string, unknown>) => void | Promise<void>
|
||||
|
||||
export interface UIPageData {
|
||||
layout: unknown
|
||||
actions?: Record<string, LuaActionHandler>
|
||||
}
|
||||
|
||||
export async function loadPageFromDb(_path: string, _tenantId?: string): Promise<PageConfig | null> {
|
||||
// TODO: Implement page loading from database
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user