chore: starter template
This commit is contained in:
24
packages/components/index.ts
Normal file
24
packages/components/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export { UserCard } from './src/UserCard'
|
||||
export type { User, UserCardProps } from './src/UserCard'
|
||||
export { stubQuery } from './src/fixtures/stubQuery'
|
||||
export { stubMutation } from './src/fixtures/stubMutation'
|
||||
export { EmptyState } from './src/EmptyState'
|
||||
export { NotFoundState } from './src/NotFoundState'
|
||||
export { PageLoader } from './src/PageLoader'
|
||||
export { ServerErrorState } from './src/ServerErrorState'
|
||||
|
||||
// Layout primitives — compose these instead of hand-rolling page chrome.
|
||||
export { PageHeader } from './src/PageHeader'
|
||||
export type { PageHeaderProps } from './src/PageHeader'
|
||||
export { Panel } from './src/Panel'
|
||||
export type { PanelProps } from './src/Panel'
|
||||
export { StatCard } from './src/StatCard'
|
||||
export type { StatCardProps } from './src/StatCard'
|
||||
export { StatGrid } from './src/StatGrid'
|
||||
export type { StatGridProps } from './src/StatGrid'
|
||||
|
||||
// Data presentation — feed these the output of a list/stats RPC you implement.
|
||||
export { DataTable } from './src/DataTable'
|
||||
export type { DataTableProps, DataTableColumn } from './src/DataTable'
|
||||
export { BarChart } from './src/BarChart'
|
||||
export type { BarChartProps, BarChartDatum } from './src/BarChart'
|
||||
31
packages/components/package.json
Normal file
31
packages/components/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@project/components",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"tsc": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mantine/core": "^9.2.1",
|
||||
"@mantine/hooks": "^9.2.1",
|
||||
"@tanstack/react-query": "^5.66.0",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"lucide-react": "^0.456.0",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"typescript": "^5.9"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mantine/core": "^9.2.1",
|
||||
"@mantine/hooks": "^9.2.1",
|
||||
"@tanstack/react-query": "^5.66.0",
|
||||
"lucide-react": "^0.456.0",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5"
|
||||
}
|
||||
}
|
||||
57
packages/components/src/BarChart.tsx
Normal file
57
packages/components/src/BarChart.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import React from 'react'
|
||||
import { Box, Group, Stack, Text } from '@mantine/core'
|
||||
|
||||
export interface BarChartDatum {
|
||||
label: React.ReactNode
|
||||
value: number
|
||||
/** Mantine color key for the bar (e.g. 'teal', 'red'). Defaults to the theme primary. */
|
||||
color?: string
|
||||
}
|
||||
|
||||
export interface BarChartProps {
|
||||
data: BarChartDatum[]
|
||||
/** Format the numeric value shown at the end of each bar. */
|
||||
valueFormatter?: (value: number) => string
|
||||
}
|
||||
|
||||
// A dependency-free horizontal bar chart (CSS only, theme colours). Feed it
|
||||
// aggregated counts from a stats RPC — no charting library to install or break.
|
||||
export function BarChart({ data, valueFormatter }: BarChartProps) {
|
||||
const max = Math.max(1, ...data.map((d) => d.value))
|
||||
const fmt = valueFormatter ?? ((n: number) => String(n))
|
||||
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
{data.map((datum, index) => (
|
||||
<Stack key={index} gap={4}>
|
||||
<Group justify="space-between" gap="xs">
|
||||
<Text size="sm">{datum.label}</Text>
|
||||
<Text size="sm" fw={600}>
|
||||
{fmt(datum.value)}
|
||||
</Text>
|
||||
</Group>
|
||||
<Box
|
||||
style={{
|
||||
height: 8,
|
||||
borderRadius: 'var(--mantine-radius-xl)',
|
||||
background: 'var(--mantine-color-default-hover)',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
height: '100%',
|
||||
width: `${(datum.value / max) * 100}%`,
|
||||
borderRadius: 'var(--mantine-radius-xl)',
|
||||
background: datum.color
|
||||
? `var(--mantine-color-${datum.color}-6)`
|
||||
: 'var(--mantine-primary-color-6)',
|
||||
transition: 'width 200ms ease',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
85
packages/components/src/DataTable.tsx
Normal file
85
packages/components/src/DataTable.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import React from 'react'
|
||||
import { Center, Loader, Table, Text } from '@mantine/core'
|
||||
|
||||
export interface DataTableColumn<Row> {
|
||||
/** Stable column id; also the default cell value (row[key]) when no render is given. */
|
||||
key: string
|
||||
header: React.ReactNode
|
||||
/** Custom cell renderer. Defaults to String(row[key]). */
|
||||
render?: (row: Row) => React.ReactNode
|
||||
align?: 'left' | 'center' | 'right'
|
||||
width?: number | string
|
||||
}
|
||||
|
||||
export interface DataTableProps<Row> {
|
||||
columns: DataTableColumn<Row>[]
|
||||
rows: Row[]
|
||||
/** Stable key per row (e.g. r => r.taskId). */
|
||||
rowKey: (row: Row) => string
|
||||
loading?: boolean
|
||||
/** Shown when there are no rows (pass an <EmptyState/> for a rich version). */
|
||||
empty?: React.ReactNode
|
||||
onRowClick?: (row: Row) => void
|
||||
}
|
||||
|
||||
// A generic, typed table. Wire it to any list RPC: implement listX, pass its
|
||||
// rows here, and describe the columns. Pure presentation — no data fetching.
|
||||
export function DataTable<Row>({
|
||||
columns,
|
||||
rows,
|
||||
rowKey,
|
||||
loading,
|
||||
empty,
|
||||
onRowClick,
|
||||
}: DataTableProps<Row>) {
|
||||
if (loading) {
|
||||
return (
|
||||
<Center py="xl">
|
||||
<Loader size="sm" />
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
return (
|
||||
<Center py="xl">
|
||||
{empty ?? (
|
||||
<Text c="dimmed" size="sm">
|
||||
Nothing here yet.
|
||||
</Text>
|
||||
)}
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Table highlightOnHover={!!onRowClick} verticalSpacing="sm" horizontalSpacing="md">
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
{columns.map((col) => (
|
||||
<Table.Th key={col.key} style={{ textAlign: col.align ?? 'left', width: col.width }}>
|
||||
{col.header}
|
||||
</Table.Th>
|
||||
))}
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{rows.map((row) => (
|
||||
<Table.Tr
|
||||
key={rowKey(row)}
|
||||
onClick={onRowClick ? () => onRowClick(row) : undefined}
|
||||
style={onRowClick ? { cursor: 'pointer' } : undefined}
|
||||
>
|
||||
{columns.map((col) => (
|
||||
<Table.Td key={col.key} style={{ textAlign: col.align ?? 'left' }}>
|
||||
{col.render
|
||||
? col.render(row)
|
||||
: String((row as Record<string, unknown>)[col.key] ?? '')}
|
||||
</Table.Td>
|
||||
))}
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
58
packages/components/src/EmptyState.stories.tsx
Normal file
58
packages/components/src/EmptyState.stories.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { AlertCircle, Boxes, FolderOpen } from 'lucide-react'
|
||||
import type { Story, StoryMeta } from './csf.types'
|
||||
import { EmptyState } from './EmptyState'
|
||||
|
||||
const meta: StoryMeta = {
|
||||
title: 'EmptyState',
|
||||
component: EmptyState,
|
||||
tags: ['feedback'],
|
||||
argTypes: {
|
||||
icon: { description: 'Lucide icon component displayed above the title', control: false },
|
||||
title: { description: 'Primary heading text', control: 'text' },
|
||||
description: { description: 'Supporting text below the title', control: 'text' },
|
||||
action: { description: 'Label for the primary action button', control: 'text' },
|
||||
onAction: { description: 'Callback fired when the action button is clicked', control: false },
|
||||
compact: {
|
||||
description: 'Reduces padding and icon size for inline contexts',
|
||||
control: 'boolean',
|
||||
},
|
||||
docsHref: { description: 'Optional link shown as a Docs button', control: 'text' },
|
||||
},
|
||||
}
|
||||
export default meta
|
||||
|
||||
function DefaultStory() {
|
||||
return (
|
||||
<EmptyState
|
||||
icon={Boxes}
|
||||
title="No items yet"
|
||||
description="Create your first item to get started."
|
||||
/>
|
||||
)
|
||||
}
|
||||
export const Default: Story = { render: DefaultStory }
|
||||
|
||||
function WithActionStory() {
|
||||
return (
|
||||
<EmptyState
|
||||
icon={FolderOpen}
|
||||
title="No projects"
|
||||
description="You don't have any projects yet."
|
||||
action="New project"
|
||||
onAction={() => {}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export const WithAction: Story = { render: WithActionStory }
|
||||
|
||||
function CompactStory() {
|
||||
return (
|
||||
<EmptyState
|
||||
icon={AlertCircle}
|
||||
title="Nothing here"
|
||||
description="This section is currently empty."
|
||||
compact
|
||||
/>
|
||||
)
|
||||
}
|
||||
export const Compact: Story = { render: CompactStory }
|
||||
77
packages/components/src/EmptyState.tsx
Normal file
77
packages/components/src/EmptyState.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react'
|
||||
import { Button, Center, Group, Stack, Text } from '@mantine/core'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
|
||||
interface EmptyStateProps {
|
||||
icon: React.ComponentType<{ size?: number; strokeWidth?: number }>
|
||||
title: string
|
||||
description: React.ReactNode
|
||||
action?: string
|
||||
onAction?: () => void
|
||||
actionIcon?: React.ReactNode
|
||||
actionLoading?: boolean
|
||||
secondaryText?: React.ReactNode
|
||||
docsHref?: string
|
||||
compact?: boolean
|
||||
}
|
||||
|
||||
export function EmptyState({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
action,
|
||||
onAction,
|
||||
actionIcon,
|
||||
actionLoading,
|
||||
secondaryText,
|
||||
docsHref,
|
||||
compact = false,
|
||||
}: EmptyStateProps) {
|
||||
const Icon = icon
|
||||
|
||||
return (
|
||||
<Center flex={1}>
|
||||
<Stack
|
||||
align="center"
|
||||
justify="center"
|
||||
gap={compact ? 'sm' : 'md'}
|
||||
py={compact ? 'lg' : 'xl'}
|
||||
style={{ minHeight: compact ? undefined : '60vh', width: '100%' }}
|
||||
>
|
||||
<Icon size={compact ? 36 : 48} strokeWidth={1} />
|
||||
<Text size={compact ? 'lg' : 'xl'} fw={600}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text c="dimmed" ta="center" maw={500}>
|
||||
{description}
|
||||
</Text>
|
||||
{docsHref || (action && onAction) ? (
|
||||
<Group justify="center">
|
||||
{docsHref ? (
|
||||
<Button
|
||||
component="a"
|
||||
href={docsHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="default"
|
||||
leftSection={<ExternalLink size={16} />}
|
||||
>
|
||||
Docs
|
||||
</Button>
|
||||
) : null}
|
||||
{action && onAction ? (
|
||||
<Button onClick={onAction} loading={actionLoading} leftSection={actionIcon}>
|
||||
{action}
|
||||
</Button>
|
||||
) : null}
|
||||
</Group>
|
||||
) : null}
|
||||
{secondaryText ? (
|
||||
<Text c="dimmed" size="sm" ta="center" maw={500}>
|
||||
{secondaryText}
|
||||
</Text>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
25
packages/components/src/NotFoundState.stories.tsx
Normal file
25
packages/components/src/NotFoundState.stories.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { Story, StoryMeta } from './csf.types'
|
||||
import { NotFoundState } from './NotFoundState'
|
||||
|
||||
const meta: StoryMeta = {
|
||||
title: 'NotFoundState',
|
||||
component: NotFoundState,
|
||||
tags: ['feedback'],
|
||||
argTypes: {
|
||||
title: { description: 'Override the "Page not found" heading', control: 'text' },
|
||||
description: { description: 'Override the default description text', control: 'text' },
|
||||
},
|
||||
}
|
||||
export default meta
|
||||
|
||||
export const Default: Story = {}
|
||||
|
||||
function CustomStory() {
|
||||
return (
|
||||
<NotFoundState
|
||||
title="Board not found"
|
||||
description="The board you were looking for has been removed or never existed."
|
||||
/>
|
||||
)
|
||||
}
|
||||
export const Custom: Story = { render: CustomStory }
|
||||
40
packages/components/src/NotFoundState.tsx
Normal file
40
packages/components/src/NotFoundState.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Center, Stack, Text } from '@mantine/core'
|
||||
|
||||
interface NotFoundStateProps {
|
||||
title?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export function NotFoundState({
|
||||
title = 'Page not found',
|
||||
description = "The resource you're looking for doesn't exist or has been removed.",
|
||||
}: NotFoundStateProps) {
|
||||
return (
|
||||
<Center flex={1}>
|
||||
<Stack
|
||||
align="center"
|
||||
justify="center"
|
||||
gap="xs"
|
||||
py="xl"
|
||||
style={{ minHeight: '60vh', width: '100%' }}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 72,
|
||||
lineHeight: 1,
|
||||
letterSpacing: '-0.06em',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
404
|
||||
</Text>
|
||||
<Text size="xl" fw={600}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text ta="center" maw={520} c="dimmed">
|
||||
{description}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
32
packages/components/src/PageHeader.stories.tsx
Normal file
32
packages/components/src/PageHeader.stories.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Button } from '@mantine/core'
|
||||
import { Plus } from 'lucide-react'
|
||||
import type { Story, StoryMeta } from './csf.types'
|
||||
import { PageHeader } from './PageHeader'
|
||||
|
||||
const meta: StoryMeta = {
|
||||
title: 'PageHeader',
|
||||
component: PageHeader,
|
||||
tags: ['layout'],
|
||||
argTypes: {
|
||||
title: { description: 'Page title', control: 'text' },
|
||||
description: { description: 'Optional sub-text', control: 'text' },
|
||||
actions: { description: 'Right-aligned actions slot', control: false },
|
||||
},
|
||||
}
|
||||
export default meta
|
||||
|
||||
function DefaultStory() {
|
||||
return <PageHeader title="Tasks" description="Everything on your plate, in one place." />
|
||||
}
|
||||
export const Default: Story = { render: DefaultStory }
|
||||
|
||||
function WithActionStory() {
|
||||
return (
|
||||
<PageHeader
|
||||
title="Tasks"
|
||||
description="Everything on your plate, in one place."
|
||||
actions={<Button leftSection={<Plus size={16} />}>New task</Button>}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export const WithAction: Story = { render: WithActionStory }
|
||||
32
packages/components/src/PageHeader.tsx
Normal file
32
packages/components/src/PageHeader.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react'
|
||||
import { Box, Group, Stack, Text, Title } from '@mantine/core'
|
||||
|
||||
export interface PageHeaderProps {
|
||||
/** Page title. Pass an i18n string from the app (m.foo()). */
|
||||
title: React.ReactNode
|
||||
/** Optional one-line description under the title. */
|
||||
description?: React.ReactNode
|
||||
/** Right-aligned actions (buttons, menus). */
|
||||
actions?: React.ReactNode
|
||||
}
|
||||
|
||||
// The single shared page header every screen uses: a title, optional
|
||||
// description, and a right-aligned action slot. Keeps headers consistent across
|
||||
// pages — compose it, don't hand-roll a per-page header.
|
||||
export function PageHeader({ title, description, actions }: PageHeaderProps) {
|
||||
return (
|
||||
<Group justify="space-between" align="flex-end" wrap="nowrap" mb="xl">
|
||||
<Stack gap={4} style={{ minWidth: 0 }}>
|
||||
<Title order={1} fz={26} fw={680} style={{ letterSpacing: '-0.03em', lineHeight: 1.1 }}>
|
||||
{title}
|
||||
</Title>
|
||||
{description ? (
|
||||
<Text c="dimmed" size="sm" style={{ lineHeight: 1.5 }}>
|
||||
{description}
|
||||
</Text>
|
||||
) : null}
|
||||
</Stack>
|
||||
{actions ? <Box style={{ flexShrink: 0 }}>{actions}</Box> : null}
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
11
packages/components/src/PageLoader.stories.tsx
Normal file
11
packages/components/src/PageLoader.stories.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { Story, StoryMeta } from './csf.types'
|
||||
import { PageLoader } from './PageLoader'
|
||||
|
||||
const meta: StoryMeta = {
|
||||
title: 'PageLoader',
|
||||
component: PageLoader,
|
||||
tags: ['loading'],
|
||||
}
|
||||
export default meta
|
||||
|
||||
export const Default: Story = {}
|
||||
16
packages/components/src/PageLoader.tsx
Normal file
16
packages/components/src/PageLoader.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Center, Loader } from '@mantine/core'
|
||||
|
||||
export function PageLoader() {
|
||||
return (
|
||||
<Center
|
||||
style={{
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
minWidth: 0,
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
<Loader size="md" />
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
48
packages/components/src/Panel.tsx
Normal file
48
packages/components/src/Panel.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react'
|
||||
import { Card, Group, Stack, Text, Title } from '@mantine/core'
|
||||
|
||||
export interface PanelProps {
|
||||
/** Optional panel heading. */
|
||||
title?: React.ReactNode
|
||||
/** Optional sub-text under the heading. */
|
||||
description?: React.ReactNode
|
||||
/** Right-aligned header actions. */
|
||||
actions?: React.ReactNode
|
||||
/** Panel body. */
|
||||
children: React.ReactNode
|
||||
/** Remove inner padding (e.g. for a flush table). */
|
||||
noPadding?: boolean
|
||||
}
|
||||
|
||||
// A bordered content section with an optional titled header. The workhorse
|
||||
// container — group related content into Panels instead of loose Cards.
|
||||
export function Panel({ title, description, actions, children, noPadding }: PanelProps) {
|
||||
return (
|
||||
<Card withBorder radius="lg" padding={noPadding ? 0 : 'lg'}>
|
||||
{title || actions ? (
|
||||
<Group
|
||||
justify="space-between"
|
||||
align="flex-end"
|
||||
wrap="nowrap"
|
||||
p={noPadding ? 'lg' : 0}
|
||||
pb={noPadding ? 'sm' : 'md'}
|
||||
>
|
||||
<Stack gap={2} style={{ minWidth: 0 }}>
|
||||
{title ? (
|
||||
<Title order={2} fz={16} fw={620} style={{ letterSpacing: '-0.02em' }}>
|
||||
{title}
|
||||
</Title>
|
||||
) : null}
|
||||
{description ? (
|
||||
<Text c="dimmed" size="xs">
|
||||
{description}
|
||||
</Text>
|
||||
) : null}
|
||||
</Stack>
|
||||
{actions ? <div style={{ flexShrink: 0 }}>{actions}</div> : null}
|
||||
</Group>
|
||||
) : null}
|
||||
{children}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
47
packages/components/src/ServerErrorState.stories.tsx
Normal file
47
packages/components/src/ServerErrorState.stories.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { useState } from 'react'
|
||||
import type { Story, StoryMeta } from './csf.types'
|
||||
import { ServerErrorState } from './ServerErrorState'
|
||||
|
||||
const meta: StoryMeta = {
|
||||
title: 'ServerErrorState',
|
||||
component: ServerErrorState,
|
||||
tags: ['feedback'],
|
||||
argTypes: {
|
||||
title: { description: 'Override the "Something went wrong" heading', control: 'text' },
|
||||
description: { description: 'Override the default error description', control: 'text' },
|
||||
onRetry: { description: 'Callback fired when the retry button is clicked', control: false },
|
||||
retrying: {
|
||||
description: 'Shows a loading spinner on the retry button',
|
||||
control: 'boolean',
|
||||
},
|
||||
retryLabel: { description: 'Label for the retry button', control: 'text' },
|
||||
glyph: {
|
||||
description: 'Custom element displayed above the title instead of "500"',
|
||||
control: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
export default meta
|
||||
|
||||
export const Default: Story = {}
|
||||
|
||||
function WithRetryStory() {
|
||||
const [retrying, setRetrying] = useState(false)
|
||||
const handleRetry = () => {
|
||||
setRetrying(true)
|
||||
setTimeout(() => setRetrying(false), 1500)
|
||||
}
|
||||
return <ServerErrorState onRetry={handleRetry} retrying={retrying} />
|
||||
}
|
||||
export const WithRetry: Story = { render: WithRetryStory }
|
||||
|
||||
function CustomGlyphStory() {
|
||||
return (
|
||||
<ServerErrorState
|
||||
glyph={<span style={{ fontSize: 64, lineHeight: 1 }}>⚠️</span>}
|
||||
title="Unexpected error"
|
||||
description="Please try again or contact support if the problem persists."
|
||||
/>
|
||||
)
|
||||
}
|
||||
export const CustomGlyph: Story = { render: CustomGlyphStory }
|
||||
70
packages/components/src/ServerErrorState.tsx
Normal file
70
packages/components/src/ServerErrorState.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { Button, Center, Stack, Text } from '@mantine/core'
|
||||
import { RefreshCw } from 'lucide-react'
|
||||
|
||||
interface ServerErrorStateProps {
|
||||
title?: string
|
||||
description?: string
|
||||
onRetry?: () => void
|
||||
retrying?: boolean
|
||||
retryLabel?: string
|
||||
glyph?: ReactNode
|
||||
}
|
||||
|
||||
export function ServerErrorState({
|
||||
title = 'Something went wrong',
|
||||
description = 'The server returned an error while loading this page.',
|
||||
onRetry,
|
||||
retrying = false,
|
||||
retryLabel = 'Retry',
|
||||
glyph,
|
||||
}: ServerErrorStateProps) {
|
||||
const normalizedDescription = typeof description === 'string' ? description.trim() : ''
|
||||
const showDescription =
|
||||
normalizedDescription.length > 0 &&
|
||||
normalizedDescription !== 'HTTP 500' &&
|
||||
normalizedDescription !== 'HTTP 500.'
|
||||
|
||||
return (
|
||||
<Center flex={1}>
|
||||
<Stack
|
||||
align="center"
|
||||
justify="center"
|
||||
gap="xs"
|
||||
py="xl"
|
||||
style={{ minHeight: '60vh', width: '100%' }}
|
||||
>
|
||||
{glyph ?? (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 72,
|
||||
lineHeight: 1,
|
||||
letterSpacing: '-0.06em',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
500
|
||||
</Text>
|
||||
)}
|
||||
<Text size="xl" fw={600}>
|
||||
{title}
|
||||
</Text>
|
||||
{showDescription ? (
|
||||
<Text ta="center" maw={520} c="dimmed">
|
||||
{normalizedDescription}
|
||||
</Text>
|
||||
) : null}
|
||||
{onRetry ? (
|
||||
<Button
|
||||
onClick={onRetry}
|
||||
loading={retrying}
|
||||
leftSection={<RefreshCw size={16} />}
|
||||
variant="default"
|
||||
>
|
||||
{retryLabel}
|
||||
</Button>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
37
packages/components/src/StatCard.tsx
Normal file
37
packages/components/src/StatCard.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import { Card, Group, Stack, Text, ThemeIcon } from '@mantine/core'
|
||||
|
||||
export interface StatCardProps {
|
||||
/** Metric label, e.g. "Open tasks". */
|
||||
label: React.ReactNode
|
||||
/** The big number / value. */
|
||||
value: React.ReactNode
|
||||
/** Optional Lucide icon component. */
|
||||
icon?: React.ComponentType<{ size?: number; strokeWidth?: number }>
|
||||
/** Theme color for the icon tile (Mantine color key). Defaults to primary. */
|
||||
color?: string
|
||||
}
|
||||
|
||||
// A single metric tile: label, large value, optional icon. Compose several in a
|
||||
// StatGrid for a dashboard summary row.
|
||||
export function StatCard({ label, value, icon: Icon, color }: StatCardProps) {
|
||||
return (
|
||||
<Card withBorder radius="lg" padding="lg">
|
||||
<Group justify="space-between" align="flex-start" wrap="nowrap">
|
||||
<Stack gap={4}>
|
||||
<Text size="xs" c="dimmed" fw={550} tt="uppercase" style={{ letterSpacing: '0.04em' }}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text fz={30} fw={700} lh={1} style={{ letterSpacing: '-0.03em' }}>
|
||||
{value}
|
||||
</Text>
|
||||
</Stack>
|
||||
{Icon ? (
|
||||
<ThemeIcon variant="light" color={color} radius="md" size={38}>
|
||||
<Icon size={20} strokeWidth={1.8} />
|
||||
</ThemeIcon>
|
||||
) : null}
|
||||
</Group>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
21
packages/components/src/StatGrid.tsx
Normal file
21
packages/components/src/StatGrid.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { SimpleGrid } from '@mantine/core'
|
||||
import { StatCard, type StatCardProps } from './StatCard'
|
||||
|
||||
export interface StatGridProps {
|
||||
stats: StatCardProps[]
|
||||
/** Columns on wide screens (responsive down to 1). Defaults to stats.length capped at 4. */
|
||||
columns?: number
|
||||
}
|
||||
|
||||
// A responsive row of StatCards. Feed it the metrics from a stats RPC
|
||||
// (e.g. getTaskStats) mapped to {label, value, icon}.
|
||||
export function StatGrid({ stats, columns }: StatGridProps) {
|
||||
const cols = columns ?? Math.min(stats.length || 1, 4)
|
||||
return (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: cols }} spacing="md">
|
||||
{stats.map((stat, index) => (
|
||||
<StatCard key={index} {...stat} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)
|
||||
}
|
||||
83
packages/components/src/UserCard.tsx
Normal file
83
packages/components/src/UserCard.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Avatar, Badge, Card, Group, Loader, Stack, Text } from '@mantine/core'
|
||||
import type { UseQueryResult } from '@tanstack/react-query'
|
||||
|
||||
export type User = {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
role: string
|
||||
status: 'active' | 'away' | 'offline'
|
||||
}
|
||||
|
||||
const STATUS_COLOR: Record<User['status'], string> = {
|
||||
active: 'green',
|
||||
away: 'yellow',
|
||||
offline: 'gray',
|
||||
}
|
||||
|
||||
export type UserCardProps = {
|
||||
query: UseQueryResult<User | null>
|
||||
}
|
||||
|
||||
export const UserCard: React.FC<UserCardProps> = ({ query }) => {
|
||||
if (query.isLoading) {
|
||||
return (
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group>
|
||||
<Loader size="sm" />
|
||||
<Text size="sm" c="dimmed">
|
||||
Loading user…
|
||||
</Text>
|
||||
</Group>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
if (query.isError) {
|
||||
return (
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Stack gap={4}>
|
||||
<Text fw={600} c="red">
|
||||
Couldn’t load user
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{query.error instanceof Error ? query.error.message : 'Unknown error'}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const user = query.data
|
||||
if (!user) {
|
||||
return (
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Text size="sm" c="dimmed">
|
||||
No user to show.
|
||||
</Text>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group wrap="nowrap">
|
||||
<Avatar color="initials" name={user.name} radius="xl" />
|
||||
<Stack gap={0}>
|
||||
<Text fw={600}>{user.name}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{user.email}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
<Badge color={STATUS_COLOR[user.status]} variant="light">
|
||||
{user.status}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Text size="sm" mt="md">
|
||||
{user.role}
|
||||
</Text>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
21
packages/components/src/csf.types.ts
Normal file
21
packages/components/src/csf.types.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { ComponentType } from 'react'
|
||||
|
||||
export interface ArgType {
|
||||
description?: string
|
||||
control?: string | false
|
||||
defaultValue?: unknown
|
||||
}
|
||||
|
||||
export interface StoryMeta {
|
||||
title: string
|
||||
component: ComponentType<any>
|
||||
description?: string
|
||||
tags?: string[]
|
||||
argTypes?: Record<string, ArgType>
|
||||
}
|
||||
|
||||
export interface Story {
|
||||
args?: Record<string, unknown>
|
||||
render?: ComponentType<any>
|
||||
name?: string
|
||||
}
|
||||
69
packages/components/src/fixtures/stubMutation.ts
Normal file
69
packages/components/src/fixtures/stubMutation.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { UseMutationResult } from '@tanstack/react-query'
|
||||
|
||||
function build<TData, TVariables>(
|
||||
partial: Record<string, unknown>,
|
||||
): UseMutationResult<TData, Error, TVariables> {
|
||||
return {
|
||||
failureCount: 0,
|
||||
failureReason: null,
|
||||
isPaused: false,
|
||||
submittedAt: 0,
|
||||
context: undefined,
|
||||
variables: undefined,
|
||||
mutate: () => undefined,
|
||||
mutateAsync: () => Promise.resolve(undefined as never),
|
||||
reset: () => undefined,
|
||||
...partial,
|
||||
} as unknown as UseMutationResult<TData, Error, TVariables>
|
||||
}
|
||||
|
||||
export const stubMutation = {
|
||||
idle<TData = unknown, TVariables = unknown>(): UseMutationResult<TData, Error, TVariables> {
|
||||
return build<TData, TVariables>({
|
||||
status: 'idle',
|
||||
isIdle: true,
|
||||
isPending: false,
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
data: undefined,
|
||||
error: null,
|
||||
})
|
||||
},
|
||||
pending<TData = unknown, TVariables = unknown>(): UseMutationResult<TData, Error, TVariables> {
|
||||
return build<TData, TVariables>({
|
||||
status: 'pending',
|
||||
isIdle: false,
|
||||
isPending: true,
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
data: undefined,
|
||||
error: null,
|
||||
})
|
||||
},
|
||||
error<TData = unknown, TVariables = unknown>(
|
||||
error: Error,
|
||||
): UseMutationResult<TData, Error, TVariables> {
|
||||
return build<TData, TVariables>({
|
||||
status: 'error',
|
||||
isIdle: false,
|
||||
isPending: false,
|
||||
isError: true,
|
||||
isSuccess: false,
|
||||
data: undefined,
|
||||
error,
|
||||
})
|
||||
},
|
||||
success<TData = unknown, TVariables = unknown>(
|
||||
data: TData,
|
||||
): UseMutationResult<TData, Error, TVariables> {
|
||||
return build<TData, TVariables>({
|
||||
status: 'success',
|
||||
isIdle: false,
|
||||
isPending: false,
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data,
|
||||
error: null,
|
||||
})
|
||||
},
|
||||
}
|
||||
66
packages/components/src/fixtures/stubQuery.ts
Normal file
66
packages/components/src/fixtures/stubQuery.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { UseQueryResult } from '@tanstack/react-query'
|
||||
|
||||
function build<T>(partial: Record<string, unknown>): UseQueryResult<T> {
|
||||
return {
|
||||
failureCount: 0,
|
||||
failureReason: null,
|
||||
errorUpdateCount: 0,
|
||||
isFetched: true,
|
||||
isFetchedAfterMount: true,
|
||||
isFetching: partial.fetchStatus === 'fetching',
|
||||
isPaused: false,
|
||||
isPlaceholderData: false,
|
||||
isRefetching: false,
|
||||
isStale: false,
|
||||
isInitialLoading: partial.isLoading === true,
|
||||
isLoadingError: false,
|
||||
isRefetchError: false,
|
||||
dataUpdatedAt: 0,
|
||||
errorUpdatedAt: 0,
|
||||
refetch: () => Promise.resolve(undefined as never),
|
||||
promise: Promise.resolve(undefined as never),
|
||||
...partial,
|
||||
} as unknown as UseQueryResult<T>
|
||||
}
|
||||
|
||||
export const stubQuery = {
|
||||
loading<T>(): UseQueryResult<T> {
|
||||
return build<T>({
|
||||
status: 'pending',
|
||||
fetchStatus: 'fetching',
|
||||
isPending: true,
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
data: undefined,
|
||||
error: null,
|
||||
})
|
||||
},
|
||||
error<T>(error: Error): UseQueryResult<T> {
|
||||
return build<T>({
|
||||
status: 'error',
|
||||
fetchStatus: 'idle',
|
||||
isPending: false,
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
isSuccess: false,
|
||||
data: undefined,
|
||||
error,
|
||||
})
|
||||
},
|
||||
success<T>(data: T): UseQueryResult<T> {
|
||||
return build<T>({
|
||||
status: 'success',
|
||||
fetchStatus: 'idle',
|
||||
isPending: false,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
data,
|
||||
error: null,
|
||||
})
|
||||
},
|
||||
empty<T>(): UseQueryResult<T> {
|
||||
return stubQuery.success<T>(null as T)
|
||||
},
|
||||
}
|
||||
14
packages/components/tsconfig.json
Normal file
14
packages/components/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["index.ts", "src"]
|
||||
}
|
||||
19
packages/functions-sdk/package.json
Normal file
19
packages/functions-sdk/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@project/functions-sdk",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./pikku/api.gen": "./src/pikku/api.gen.ts",
|
||||
"./pikku/pikku-fetch.gen": "./src/pikku/pikku-fetch.gen.ts",
|
||||
"./pikku/pikku-rpc.gen": "./src/pikku/pikku-rpc.gen.ts",
|
||||
"./pikku/rpc-map.gen": "./src/pikku/rpc-map.gen.d.ts",
|
||||
"./pikku/http-map.gen": "./src/pikku/http-map.gen.d.ts",
|
||||
"./pikku/*": "./src/pikku/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pikku/fetch": "^0.12.6",
|
||||
"@pikku/react": "^0.12.5",
|
||||
"@tanstack/react-query": "^5.66.0"
|
||||
}
|
||||
}
|
||||
4
packages/functions/.pikku/agent/pikku-agent-map.gen.d.ts
vendored
Normal file
4
packages/functions/.pikku/agent/pikku-agent-map.gen.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.76
|
||||
*/
|
||||
export type AgentMap = {}
|
||||
26
packages/functions/.pikku/auth/auth.types.ts
Normal file
26
packages/functions/.pikku/auth/auth.types.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.76
|
||||
*/
|
||||
// AUTO-GENERATED by pikku CLI — do not edit
|
||||
|
||||
import { pikkuBetterAuth as _pikkuBetterAuth } from '@pikku/better-auth'
|
||||
import type { BetterAuthInstance, PikkuBetterAuthFactory } from '@pikku/better-auth'
|
||||
import type { SingletonServices } from '../function/pikku-function-types.gen.js'
|
||||
import { TypedSecretService } from '../secrets/pikku-secrets.gen.js'
|
||||
import { TypedVariablesService } from '../variables/pikku-variables.gen.js'
|
||||
|
||||
type AuthSingletonServices = Omit<SingletonServices, 'secrets' | 'variables'> & {
|
||||
secrets: TypedSecretService
|
||||
variables: TypedVariablesService
|
||||
}
|
||||
|
||||
export const pikkuBetterAuth = <I extends BetterAuthInstance>(
|
||||
factory: (services: AuthSingletonServices) => I | Promise<I>
|
||||
): PikkuBetterAuthFactory<I> =>
|
||||
_pikkuBetterAuth((services) =>
|
||||
factory({
|
||||
...services,
|
||||
secrets: new TypedSecretService(services.secrets),
|
||||
variables: new TypedVariablesService(services.variables),
|
||||
} as AuthSingletonServices)
|
||||
)
|
||||
89
packages/functions/.pikku/channel/pikku-channels-map.gen.d.ts
vendored
Normal file
89
packages/functions/.pikku/channel/pikku-channels-map.gen.d.ts
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.76
|
||||
*/
|
||||
/**
|
||||
* This provides the structure needed for TypeScript to be aware of channels
|
||||
*/
|
||||
|
||||
import type { StubCall } from '.bun/@pikku+core@0.12.57/node_modules/@pikku/core/dist/services/stub-tracker'
|
||||
|
||||
import type { StubCall } from '/Users/yasser/git/pikku/fabric/templates/starter-template/node_modules/.bun/@pikku+core@0.12.57/node_modules/@pikku/core/dist/services/stub-tracker.d.ts'
|
||||
|
||||
export type AgentApproveCallerInput = { agentName: string; runId: string; approvals: { toolCallId: string; approved: boolean; }[]; }
|
||||
export type AgentCallerInput = { agentName: string; message: string; threadId: string; resourceId: string; }
|
||||
export type AgentResumeCallerInput = { agentName: string; runId: string; toolCallId: string; approved: boolean; }
|
||||
export type AgentStreamCallerInput = { agentName: string; message: string; threadId: string; resourceId: string; context?: string | undefined; }
|
||||
export type AuthHandlerOutput = Promise<void> | Promise<any>
|
||||
export type DeleteAgentThreadInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type DeleteAgentThreadOutput = { deleted: boolean; }
|
||||
export type GetAgentThreadMessagesInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type GetAgentThreadMessagesOutput = any[]
|
||||
export type GetAgentThreadRunsInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type GetAgentThreadRunsOutput = any[]
|
||||
export type GetAgentThreadsInput = { agentName?: string | undefined; resourceId?: string | undefined; limit?: number | undefined; offset?: number | undefined; }
|
||||
export type GetAgentThreadsOutput = any[]
|
||||
export type GetSessionInput = {}
|
||||
export type GetSessionOutput = {
|
||||
userId: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
}
|
||||
export type GraphStarterInput = { workflowName: string; nodeId: string; data?: unknown; }
|
||||
export type GraphStarterOutput = { runId: string; }
|
||||
export type PikkuConsoleGetSecretInput = { secretId: string; }
|
||||
export type PikkuConsoleGetSecretOutput = { exists: boolean; value: unknown; }
|
||||
export type PikkuConsoleGetVariableInput = { variableId: string; }
|
||||
export type PikkuConsoleGetVariableOutput = { exists: boolean; value: unknown; }
|
||||
export type PikkuConsoleHasSecretInput = { secretId: string; }
|
||||
export type PikkuConsoleHasSecretOutput = { exists: boolean; }
|
||||
export type PikkuConsoleSetSecretInput = { secretId: string; value: unknown; }
|
||||
export type PikkuConsoleSetSecretOutput = { success: boolean; }
|
||||
export type PikkuConsoleSetVariableInput = { variableId: string; value: unknown; }
|
||||
export type PikkuConsoleSetVariableOutput = { success: boolean; }
|
||||
export type PikkuScenarioGetStubCallsInput = { service?: string | undefined; }
|
||||
export type PikkuScenarioGetStubCallsOutput = StubCall[]
|
||||
export type PikkuScenarioResetLiveCoverageOutput = { enabled: boolean; }
|
||||
export type PikkuScenarioResetStubsOutput = { enabled: boolean; }
|
||||
export type RealtimeEventStreamInput = { topic: string; }
|
||||
export type RealtimeSubscribeInput = { topic: string; }
|
||||
export type RealtimeUnsubscribeInput = { topic: string; }
|
||||
export type RpcCallerInput = { rpcName: string; data?: unknown; }
|
||||
export type SecretSchema_betterAuthSecret = string
|
||||
export type SessionHealthScenarioOutput = { email: string; userId: string; }
|
||||
export type WorkflowRunnerInput = { workflowName: string; data?: unknown; }
|
||||
export type WorkflowStarterInput = { workflowName: string; data?: unknown; }
|
||||
export type WorkflowStarterOutput = { runId: string; }
|
||||
export type WorkflowStatusCheckerInput = { workflowName: string; runId: string; }
|
||||
export type WorkflowStatusStreamFullInput = { workflowName: string; runId: string; }
|
||||
export type WorkflowStatusStreamInput = { workflowName: string; runId: string; }
|
||||
|
||||
interface ChannelHandler<I, O> {
|
||||
input: I;
|
||||
output: O;
|
||||
}
|
||||
|
||||
export type ChannelsMap = {
|
||||
readonly 'events': {
|
||||
readonly routes: {
|
||||
readonly action: {
|
||||
readonly subscribe: ChannelHandler<RealtimeSubscribeInput, never>,
|
||||
readonly unsubscribe: ChannelHandler<RealtimeUnsubscribeInput, never>,
|
||||
},
|
||||
},
|
||||
readonly defaultMessage: never,
|
||||
},
|
||||
};
|
||||
|
||||
export type ChannelDefaultHandlerOf<Channel extends keyof ChannelsMap> =
|
||||
ChannelsMap[Channel]['defaultMessage'] extends { input: infer I; output: infer O }
|
||||
? ChannelHandler<I, O>
|
||||
: never;
|
||||
|
||||
export type ChannelWiringHandlerOf<
|
||||
Channel extends keyof ChannelsMap,
|
||||
Route extends keyof ChannelsMap[Channel]['routes'],
|
||||
Method extends keyof ChannelsMap[Channel]['routes'][Route],
|
||||
> =
|
||||
ChannelsMap[Channel]['routes'][Route][Method] extends { input: infer I; output: infer O }
|
||||
? ChannelHandler<I, O>
|
||||
: never;
|
||||
141
packages/functions/.pikku/db/classification-map.gen.d.ts
vendored
Normal file
141
packages/functions/.pikku/db/classification-map.gen.d.ts
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
// Generated by @pikku/cli — do not edit by hand.
|
||||
// Run `pikku db migrate` to refresh.
|
||||
// Use this type in db/classifications.ts:
|
||||
// import type { DbClassificationMap } from './.pikku/db/classification-map.gen.d.ts'
|
||||
// export const classifications = { ... } satisfies DbClassificationMap
|
||||
|
||||
export type ColumnEntry = {
|
||||
/** Privacy level. Defaults to 'private' when omitted. */
|
||||
security?: 'public' | 'private' | 'pii' | 'secret' | 'encrypted'
|
||||
/** Anonymize strategy used by `pikku db anonymize`. */
|
||||
classification?: 'fake:email' | 'fake:name' | 'hash' | 'keep'
|
||||
/** Column kind override for codegen coercion + typing. */
|
||||
kind?: 'date' | 'bool' | 'json' | 'uuid'
|
||||
/** TypeScript type override, e.g. `string[]` or `MyJson`. Wins over `kind`. */
|
||||
tsType?: string
|
||||
/** Zod string-format validator (keeps the TS type as `string`). */
|
||||
format?: 'email' | 'url' | 'emoji' | 'e164' | 'jwt' | 'cuid' | 'cuid2' | 'ulid' | 'nanoid' | 'base64' | 'base64url' | 'ipv4' | 'ipv6' | 'cidrv4' | 'cidrv6' | 'isoDate' | 'isoTime' | 'isoDatetime' | 'isoDuration'
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type DbClassificationMap = {
|
||||
"account": {
|
||||
"id": ColumnEntry
|
||||
"account_id": ColumnEntry
|
||||
"provider_id": ColumnEntry
|
||||
"user_id": ColumnEntry
|
||||
"access_token": ColumnEntry
|
||||
"refresh_token": ColumnEntry
|
||||
"id_token": ColumnEntry
|
||||
"access_token_expires_at": ColumnEntry
|
||||
"refresh_token_expires_at": ColumnEntry
|
||||
"scope": ColumnEntry
|
||||
"password": ColumnEntry
|
||||
"created_at": ColumnEntry
|
||||
"updated_at": ColumnEntry
|
||||
}
|
||||
"ai_message": {
|
||||
"id": ColumnEntry
|
||||
"thread_id": ColumnEntry
|
||||
"role": ColumnEntry
|
||||
"content": ColumnEntry
|
||||
"created_at": ColumnEntry
|
||||
}
|
||||
"ai_run": {
|
||||
"run_id": ColumnEntry
|
||||
"agent_name": ColumnEntry
|
||||
"thread_id": ColumnEntry
|
||||
"resource_id": ColumnEntry
|
||||
"status": ColumnEntry
|
||||
"error_message": ColumnEntry
|
||||
"suspend_reason": ColumnEntry
|
||||
"missing_rpcs": ColumnEntry
|
||||
"usage_input_tokens": ColumnEntry
|
||||
"usage_output_tokens": ColumnEntry
|
||||
"usage_model": ColumnEntry
|
||||
"created_at": ColumnEntry
|
||||
"updated_at": ColumnEntry
|
||||
}
|
||||
"ai_threads": {
|
||||
"id": ColumnEntry
|
||||
"resource_id": ColumnEntry
|
||||
"title": ColumnEntry
|
||||
"metadata": ColumnEntry
|
||||
"created_at": ColumnEntry
|
||||
"updated_at": ColumnEntry
|
||||
}
|
||||
"ai_tool_call": {
|
||||
"id": ColumnEntry
|
||||
"thread_id": ColumnEntry
|
||||
"message_id": ColumnEntry
|
||||
"run_id": ColumnEntry
|
||||
"tool_name": ColumnEntry
|
||||
"args": ColumnEntry
|
||||
"result": ColumnEntry
|
||||
"approval_status": ColumnEntry
|
||||
"approval_type": ColumnEntry
|
||||
"agent_run_id": ColumnEntry
|
||||
"display_tool_name": ColumnEntry
|
||||
"display_args": ColumnEntry
|
||||
"created_at": ColumnEntry
|
||||
}
|
||||
"ai_working_memory": {
|
||||
"id": ColumnEntry
|
||||
"scope": ColumnEntry
|
||||
"data": ColumnEntry
|
||||
"updated_at": ColumnEntry
|
||||
}
|
||||
"audit": {
|
||||
"audit_id": ColumnEntry
|
||||
"occurred_at": ColumnEntry
|
||||
"type": ColumnEntry
|
||||
"source": ColumnEntry
|
||||
"outcome": ColumnEntry
|
||||
"function_id": ColumnEntry
|
||||
"wire_type": ColumnEntry
|
||||
"trace_id": ColumnEntry
|
||||
"transaction_id": ColumnEntry
|
||||
"query_id": ColumnEntry
|
||||
"actor_user_id": ColumnEntry
|
||||
"actor_org_id": ColumnEntry
|
||||
"tables": ColumnEntry
|
||||
"changed_cols": ColumnEntry
|
||||
"event": ColumnEntry
|
||||
"old": ColumnEntry
|
||||
"data": ColumnEntry
|
||||
}
|
||||
"session": {
|
||||
"id": ColumnEntry
|
||||
"expires_at": ColumnEntry
|
||||
"token": ColumnEntry
|
||||
"created_at": ColumnEntry
|
||||
"updated_at": ColumnEntry
|
||||
"ip_address": ColumnEntry
|
||||
"user_agent": ColumnEntry
|
||||
"user_id": ColumnEntry
|
||||
"impersonated_by": ColumnEntry
|
||||
}
|
||||
"user": {
|
||||
"id": ColumnEntry
|
||||
"name": ColumnEntry
|
||||
"email": ColumnEntry
|
||||
"email_verified": ColumnEntry
|
||||
"image": ColumnEntry
|
||||
"created_at": ColumnEntry
|
||||
"updated_at": ColumnEntry
|
||||
"actor": ColumnEntry
|
||||
"role": ColumnEntry
|
||||
"banned": ColumnEntry
|
||||
"ban_reason": ColumnEntry
|
||||
"ban_expires": ColumnEntry
|
||||
"fabric": ColumnEntry
|
||||
}
|
||||
"verification": {
|
||||
"id": ColumnEntry
|
||||
"identifier": ColumnEntry
|
||||
"value": ColumnEntry
|
||||
"expires_at": ColumnEntry
|
||||
"created_at": ColumnEntry
|
||||
"updated_at": ColumnEntry
|
||||
}
|
||||
}
|
||||
148
packages/functions/.pikku/http/pikku-http-wirings-map.gen.d.ts
vendored
Normal file
148
packages/functions/.pikku/http/pikku-http-wirings-map.gen.d.ts
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.76
|
||||
*/
|
||||
/**
|
||||
* This provides the structure needed for typescript to be aware of routes and their return types
|
||||
*/
|
||||
|
||||
import type { StubCall } from '.bun/@pikku+core@0.12.57/node_modules/@pikku/core/dist/services/stub-tracker'
|
||||
import type { StreamWorkflowRunInput } from '.bun/@pikku+addon-console@0.12.26+8a6fcc78e6b267a5/node_modules/@pikku/addon-console/dist/.pikku/rpc/pikku-rpc-wirings-map.internal.gen'
|
||||
import type { WorkflowRunStatus } from '.bun/@pikku+core@0.12.57/node_modules/@pikku/core/dist/wirings/workflow/workflow.types'
|
||||
|
||||
|
||||
export type AgentApproveCallerInput = { agentName: string; runId: string; approvals: { toolCallId: string; approved: boolean; }[]; }
|
||||
export type AgentCallerInput = { agentName: string; message: string; threadId: string; resourceId: string; }
|
||||
export type AgentResumeCallerInput = { agentName: string; runId: string; toolCallId: string; approved: boolean; }
|
||||
export type AgentStreamCallerInput = { agentName: string; message: string; threadId: string; resourceId: string; context?: string | undefined; }
|
||||
export type AuthHandlerOutput = Promise<void> | Promise<any>
|
||||
export type DeleteAgentThreadInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type DeleteAgentThreadOutput = { deleted: boolean; }
|
||||
export type GetAgentThreadMessagesInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type GetAgentThreadMessagesOutput = any[]
|
||||
export type GetAgentThreadRunsInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type GetAgentThreadRunsOutput = any[]
|
||||
export type GetAgentThreadsInput = { agentName?: string | undefined; resourceId?: string | undefined; limit?: number | undefined; offset?: number | undefined; }
|
||||
export type GetAgentThreadsOutput = any[]
|
||||
export type GetSessionInput = {}
|
||||
export type GetSessionOutput = {
|
||||
userId: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
}
|
||||
export type GraphStarterInput = { workflowName: string; nodeId: string; data?: unknown; }
|
||||
export type GraphStarterOutput = { runId: string; }
|
||||
export type PikkuConsoleGetSecretInput = { secretId: string; }
|
||||
export type PikkuConsoleGetSecretOutput = { exists: boolean; value: unknown; }
|
||||
export type PikkuConsoleGetVariableInput = { variableId: string; }
|
||||
export type PikkuConsoleGetVariableOutput = { exists: boolean; value: unknown; }
|
||||
export type PikkuConsoleHasSecretInput = { secretId: string; }
|
||||
export type PikkuConsoleHasSecretOutput = { exists: boolean; }
|
||||
export type PikkuConsoleSetSecretInput = { secretId: string; value: unknown; }
|
||||
export type PikkuConsoleSetSecretOutput = { success: boolean; }
|
||||
export type PikkuConsoleSetVariableInput = { variableId: string; value: unknown; }
|
||||
export type PikkuConsoleSetVariableOutput = { success: boolean; }
|
||||
export type PikkuScenarioGetStubCallsInput = { service?: string | undefined; }
|
||||
export type PikkuScenarioGetStubCallsOutput = StubCall[]
|
||||
export type PikkuScenarioResetLiveCoverageOutput = { enabled: boolean; }
|
||||
export type PikkuScenarioResetStubsOutput = { enabled: boolean; }
|
||||
export type RealtimeEventStreamInput = { topic: string; }
|
||||
export type RealtimeSubscribeInput = { topic: string; }
|
||||
export type RealtimeUnsubscribeInput = { topic: string; }
|
||||
export type RpcCallerInput = { rpcName: string; data?: unknown; }
|
||||
export type SecretSchema_betterAuthSecret = string
|
||||
export type SessionHealthScenarioOutput = { email: string; userId: string; }
|
||||
export type WorkflowRunnerInput = { workflowName: string; data?: unknown; }
|
||||
export type WorkflowStarterInput = { workflowName: string; data?: unknown; }
|
||||
export type WorkflowStarterOutput = { runId: string; }
|
||||
export type WorkflowStatusCheckerInput = { workflowName: string; runId: string; }
|
||||
export type WorkflowStatusStreamFullInput = { workflowName: string; runId: string; }
|
||||
export type WorkflowStatusStreamInput = { workflowName: string; runId: string; }
|
||||
|
||||
// The '& {}' is a workaround for not directly refering to a type since it confuses typescript
|
||||
export type AgentCallerInputParams = Pick<AgentCallerInput, 'agentName'> & {}
|
||||
export type AgentCallerInputBody = Omit<AgentCallerInput, 'agentName'> & {}
|
||||
export type AgentStreamCallerInputParams = Pick<AgentStreamCallerInput, 'agentName'> & {}
|
||||
export type AgentStreamCallerInputBody = Omit<AgentStreamCallerInput, 'agentName'> & {}
|
||||
export type AgentApproveCallerInputParams = Pick<AgentApproveCallerInput, 'agentName'> & {}
|
||||
export type AgentApproveCallerInputBody = Omit<AgentApproveCallerInput, 'agentName'> & {}
|
||||
export type AgentResumeCallerInputParams = Pick<AgentResumeCallerInput, 'agentName'> & {}
|
||||
export type AgentResumeCallerInputBody = Omit<AgentResumeCallerInput, 'agentName'> & {}
|
||||
export type StreamWorkflowRunInputParams = Pick<StreamWorkflowRunInput, 'runId'> & {}
|
||||
export type StreamWorkflowRunInputBody = Omit<StreamWorkflowRunInput, 'runId'> & {}
|
||||
export type RealtimeEventStreamInputParams = Pick<RealtimeEventStreamInput, 'topic'> & {}
|
||||
export type RealtimeEventStreamInputBody = Omit<RealtimeEventStreamInput, 'topic'> & {}
|
||||
export type RpcCallerInputParams = Pick<RpcCallerInput, 'rpcName'> & {}
|
||||
export type RpcCallerInputBody = Omit<RpcCallerInput, 'rpcName'> & {}
|
||||
export type WorkflowStarterInputParams = Pick<WorkflowStarterInput, 'workflowName'> & {}
|
||||
export type WorkflowStarterInputBody = Omit<WorkflowStarterInput, 'workflowName'> & {}
|
||||
export type WorkflowRunnerInputParams = Pick<WorkflowRunnerInput, 'workflowName'> & {}
|
||||
export type WorkflowRunnerInputBody = Omit<WorkflowRunnerInput, 'workflowName'> & {}
|
||||
export type WorkflowStatusCheckerInputParams = Pick<WorkflowStatusCheckerInput, 'workflowName' | 'runId'> & {}
|
||||
export type WorkflowStatusCheckerInputBody = Omit<WorkflowStatusCheckerInput, 'workflowName' | 'runId'> & {}
|
||||
export type WorkflowStatusStreamInputParams = Pick<WorkflowStatusStreamInput, 'workflowName' | 'runId'> & {}
|
||||
export type WorkflowStatusStreamInputBody = Omit<WorkflowStatusStreamInput, 'workflowName' | 'runId'> & {}
|
||||
export type WorkflowStatusStreamFullInputParams = Pick<WorkflowStatusStreamFullInput, 'workflowName' | 'runId'> & {}
|
||||
export type WorkflowStatusStreamFullInputBody = Omit<WorkflowStatusStreamFullInput, 'workflowName' | 'runId'> & {}
|
||||
export type GraphStarterInputParams = Pick<GraphStarterInput, 'workflowName' | 'nodeId'> & {}
|
||||
export type GraphStarterInputBody = Omit<GraphStarterInput, 'workflowName' | 'nodeId'> & {}
|
||||
|
||||
interface HTTPWiringHandler<I, O> {
|
||||
input: I;
|
||||
output: O;
|
||||
}
|
||||
|
||||
export type HTTPWiringsMap = {
|
||||
readonly '/api/auth{/*splat}': {
|
||||
readonly GET: HTTPWiringHandler<null, AuthHandlerOutput>,
|
||||
readonly POST: HTTPWiringHandler<null, AuthHandlerOutput>,
|
||||
},
|
||||
readonly '/workflow-run/:runId/stream': {
|
||||
readonly GET: HTTPWiringHandler<StreamWorkflowRunInput, null>,
|
||||
},
|
||||
readonly '/events/:topic': {
|
||||
readonly GET: HTTPWiringHandler<RealtimeEventStreamInput, null>,
|
||||
},
|
||||
readonly '/workflow/:workflowName/status/:runId': {
|
||||
readonly GET: HTTPWiringHandler<WorkflowStatusCheckerInput, WorkflowRunStatus>,
|
||||
},
|
||||
readonly '/workflow/:workflowName/status/:runId/stream': {
|
||||
readonly GET: HTTPWiringHandler<WorkflowStatusStreamInput, null>,
|
||||
},
|
||||
readonly '/workflow/:workflowName/status/:runId/stream/full': {
|
||||
readonly GET: HTTPWiringHandler<WorkflowStatusStreamFullInput, null>,
|
||||
},
|
||||
readonly '/rpc/agent/:agentName': {
|
||||
readonly POST: HTTPWiringHandler<AgentCallerInput, null>,
|
||||
},
|
||||
readonly '/rpc/agent/:agentName/stream': {
|
||||
readonly POST: HTTPWiringHandler<AgentStreamCallerInput, null>,
|
||||
},
|
||||
readonly '/rpc/agent/:agentName/approve': {
|
||||
readonly POST: HTTPWiringHandler<AgentApproveCallerInput, null>,
|
||||
},
|
||||
readonly '/rpc/agent/:agentName/resume': {
|
||||
readonly POST: HTTPWiringHandler<AgentResumeCallerInput, null>,
|
||||
},
|
||||
readonly '/rpc/:rpcName': {
|
||||
readonly POST: HTTPWiringHandler<RpcCallerInput, null>,
|
||||
},
|
||||
readonly '/workflow/:workflowName/start': {
|
||||
readonly POST: HTTPWiringHandler<WorkflowStarterInput, WorkflowStarterOutput>,
|
||||
},
|
||||
readonly '/workflow/:workflowName/run': {
|
||||
readonly POST: HTTPWiringHandler<WorkflowRunnerInput, null>,
|
||||
},
|
||||
readonly '/workflow/:workflowName/graph/:nodeId': {
|
||||
readonly POST: HTTPWiringHandler<GraphStarterInput, GraphStarterOutput>,
|
||||
},
|
||||
};
|
||||
|
||||
export type HTTPWiringHandlerOf<HTTPWiring extends keyof HTTPWiringsMap, Method extends keyof HTTPWiringsMap[HTTPWiring]> =
|
||||
HTTPWiringsMap[HTTPWiring][Method] extends { input: infer I; output: infer O }
|
||||
? HTTPWiringHandler<I, O>
|
||||
: never;
|
||||
|
||||
export type HTTPWiringsWithMethod<Method extends string> = {
|
||||
[HTTPWiring in keyof HTTPWiringsMap]: Method extends keyof HTTPWiringsMap[HTTPWiring] ? HTTPWiring : never;
|
||||
}[keyof HTTPWiringsMap];
|
||||
|
||||
168
packages/functions/.pikku/rpc/pikku-rpc-wirings-map.gen.d.ts
vendored
Normal file
168
packages/functions/.pikku/rpc/pikku-rpc-wirings-map.gen.d.ts
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.76
|
||||
*/
|
||||
/**
|
||||
* This provides the structure needed for typescript to be aware of RPCs and their return types
|
||||
*/
|
||||
|
||||
|
||||
import type { StubCall } from '.bun/@pikku+core@0.12.57/node_modules/@pikku/core/dist/services/stub-tracker'
|
||||
import type { FunctionCoverageReport } from '.bun/@pikku+core@0.12.57/node_modules/@pikku/core/dist/services/v8-coverage-service'
|
||||
|
||||
export type AgentApproveCallerInput = { agentName: string; runId: string; approvals: { toolCallId: string; approved: boolean; }[]; }
|
||||
export type AgentCallerInput = { agentName: string; message: string; threadId: string; resourceId: string; }
|
||||
export type AgentResumeCallerInput = { agentName: string; runId: string; toolCallId: string; approved: boolean; }
|
||||
export type AgentStreamCallerInput = { agentName: string; message: string; threadId: string; resourceId: string; context?: string | undefined; }
|
||||
export type AuthHandlerOutput = Promise<void> | Promise<any>
|
||||
export type DeleteAgentThreadInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type DeleteAgentThreadOutput = { deleted: boolean; }
|
||||
export type GetAgentThreadMessagesInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type GetAgentThreadMessagesOutput = any[]
|
||||
export type GetAgentThreadRunsInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type GetAgentThreadRunsOutput = any[]
|
||||
export type GetAgentThreadsInput = { agentName?: string | undefined; resourceId?: string | undefined; limit?: number | undefined; offset?: number | undefined; }
|
||||
export type GetAgentThreadsOutput = any[]
|
||||
export type GetSessionInput = {}
|
||||
export type GetSessionOutput = {
|
||||
userId: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
}
|
||||
export type GraphStarterInput = { workflowName: string; nodeId: string; data?: unknown; }
|
||||
export type GraphStarterOutput = { runId: string; }
|
||||
export type PikkuConsoleGetSecretInput = { secretId: string; }
|
||||
export type PikkuConsoleGetSecretOutput = { exists: boolean; value: unknown; }
|
||||
export type PikkuConsoleGetVariableInput = { variableId: string; }
|
||||
export type PikkuConsoleGetVariableOutput = { exists: boolean; value: unknown; }
|
||||
export type PikkuConsoleHasSecretInput = { secretId: string; }
|
||||
export type PikkuConsoleHasSecretOutput = { exists: boolean; }
|
||||
export type PikkuConsoleSetSecretInput = { secretId: string; value: unknown; }
|
||||
export type PikkuConsoleSetSecretOutput = { success: boolean; }
|
||||
export type PikkuConsoleSetVariableInput = { variableId: string; value: unknown; }
|
||||
export type PikkuConsoleSetVariableOutput = { success: boolean; }
|
||||
export type PikkuScenarioGetStubCallsInput = { service?: string | undefined; }
|
||||
export type PikkuScenarioGetStubCallsOutput = StubCall[]
|
||||
export type PikkuScenarioResetLiveCoverageOutput = { enabled: boolean; }
|
||||
export type PikkuScenarioResetStubsOutput = { enabled: boolean; }
|
||||
export type RealtimeEventStreamInput = { topic: string; }
|
||||
export type RealtimeSubscribeInput = { topic: string; }
|
||||
export type RealtimeUnsubscribeInput = { topic: string; }
|
||||
export type RpcCallerInput = { rpcName: string; data?: unknown; }
|
||||
export type SecretSchema_betterAuthSecret = string
|
||||
export type SessionHealthScenarioOutput = { email: string; userId: string; }
|
||||
export type WorkflowRunnerInput = { workflowName: string; data?: unknown; }
|
||||
export type WorkflowStarterInput = { workflowName: string; data?: unknown; }
|
||||
export type WorkflowStarterOutput = { runId: string; }
|
||||
export type WorkflowStatusCheckerInput = { workflowName: string; runId: string; }
|
||||
export type WorkflowStatusStreamFullInput = { workflowName: string; runId: string; }
|
||||
export type WorkflowStatusStreamInput = { workflowName: string; runId: string; }
|
||||
|
||||
interface RPCHandler<I, O> {
|
||||
input: I;
|
||||
output: O;
|
||||
}
|
||||
|
||||
export type RPCMap = {
|
||||
readonly 'getSession': RPCHandler<GetSessionInput, GetSessionOutput>,
|
||||
readonly 'getAgentThreads': RPCHandler<GetAgentThreadsInput, GetAgentThreadsOutput>,
|
||||
readonly 'getAgentThreadMessages': RPCHandler<GetAgentThreadMessagesInput, GetAgentThreadMessagesOutput>,
|
||||
readonly 'getAgentThreadRuns': RPCHandler<GetAgentThreadRunsInput, GetAgentThreadRunsOutput>,
|
||||
readonly 'deleteAgentThread': RPCHandler<DeleteAgentThreadInput, DeleteAgentThreadOutput>,
|
||||
readonly 'pikkuConsoleSetSecret': RPCHandler<PikkuConsoleSetSecretInput, PikkuConsoleSetSecretOutput>,
|
||||
readonly 'pikkuConsoleGetVariable': RPCHandler<PikkuConsoleGetVariableInput, PikkuConsoleGetVariableOutput>,
|
||||
readonly 'pikkuConsoleSetVariable': RPCHandler<PikkuConsoleSetVariableInput, PikkuConsoleSetVariableOutput>,
|
||||
readonly 'pikkuConsoleHasSecret': RPCHandler<PikkuConsoleHasSecretInput, PikkuConsoleHasSecretOutput>,
|
||||
readonly 'pikkuConsoleGetSecret': RPCHandler<PikkuConsoleGetSecretInput, PikkuConsoleGetSecretOutput>,
|
||||
readonly 'pikkuScenarioTakeLiveCoverage': RPCHandler<null, FunctionCoverageReport>,
|
||||
readonly 'pikkuScenarioResetLiveCoverage': RPCHandler<null, PikkuScenarioResetLiveCoverageOutput>,
|
||||
readonly 'pikkuScenarioResetStubs': RPCHandler<null, PikkuScenarioResetStubsOutput>,
|
||||
readonly 'pikkuScenarioGetStubCalls': RPCHandler<PikkuScenarioGetStubCallsInput, PikkuScenarioGetStubCallsOutput>,
|
||||
};
|
||||
|
||||
|
||||
// Addon package RPC maps
|
||||
import type { RPCMap as ConsoleRPCMap } from '@pikku/addon-console/.pikku/rpc/pikku-rpc-wirings-map.internal.gen.js'
|
||||
|
||||
|
||||
// Utility type to prefix keys with namespace (skips 'any' to prevent type poisoning)
|
||||
type PrefixKeys<T, Prefix extends string> = unknown extends T ? {} : {
|
||||
[K in keyof T as `${Prefix}:${string & K}`]: T[K]
|
||||
}
|
||||
|
||||
// Merge all RPC maps with namespace prefixes
|
||||
export type FlattenedRPCMap =
|
||||
RPCMap & PrefixKeys<ConsoleRPCMap, 'console'>
|
||||
|
||||
type IsAny<T> = 0 extends (1 & T) ? true : false
|
||||
type IsVoidishInput<T> = IsAny<T> extends true
|
||||
? false
|
||||
: [T] extends [void | null | undefined]
|
||||
? true
|
||||
: false
|
||||
|
||||
|
||||
export type RPCInvoke = <Name extends keyof FlattenedRPCMap>(
|
||||
...args: IsVoidishInput<FlattenedRPCMap[Name]['input']> extends true
|
||||
? [name: Name]
|
||||
: [name: Name, data: FlattenedRPCMap[Name]['input']]
|
||||
) => Promise<FlattenedRPCMap[Name]['output']>
|
||||
|
||||
export type RPCRemote = <Name extends keyof FlattenedRPCMap>(
|
||||
...args: IsVoidishInput<FlattenedRPCMap[Name]['input']> extends true
|
||||
? [name: Name]
|
||||
: [name: Name, data: FlattenedRPCMap[Name]['input']]
|
||||
) => Promise<FlattenedRPCMap[Name]['output']>
|
||||
|
||||
import type { FlattenedWorkflowMap } from '../workflow/pikku-workflow-map.gen.d.js'
|
||||
|
||||
import type { AgentMap } from '../agent/pikku-agent-map.gen.d.js'
|
||||
|
||||
// Addon package Agent maps
|
||||
import type { AgentMap as ConsoleAgentMap } from '@pikku/addon-console/.pikku/agent/pikku-agent-map.gen.d.js'
|
||||
|
||||
|
||||
type FlattenedAgentMap =
|
||||
AgentMap & PrefixKeys<ConsoleAgentMap, 'console'>
|
||||
|
||||
|
||||
import type { PikkuRPC } from '@pikku/core/rpc'
|
||||
|
||||
interface AIAgentInput {
|
||||
message: string
|
||||
threadId: string
|
||||
resourceId: string
|
||||
}
|
||||
|
||||
export type TypedStartWorkflow = <Name extends keyof FlattenedWorkflowMap>(
|
||||
name: Name,
|
||||
input: FlattenedWorkflowMap[Name]['input'],
|
||||
options?: { startNode?: string }
|
||||
) => Promise<{ runId: string }>
|
||||
|
||||
export type TypedRunWorkflow = <Name extends keyof FlattenedWorkflowMap>(
|
||||
name: Name,
|
||||
input: FlattenedWorkflowMap[Name]['input']
|
||||
) => Promise<FlattenedWorkflowMap[Name]['output']>
|
||||
|
||||
export type TypedWorkflowStatus = (
|
||||
workflowName: string,
|
||||
runId: string
|
||||
) => Promise<{ id: string; status: 'running' | 'suspended' | 'completed' | 'failed' | 'cancelled'; output?: unknown; error?: { message?: string } }>
|
||||
|
||||
type TypedAgentRun = [keyof FlattenedAgentMap] extends [never]
|
||||
? (name: string, input: AIAgentInput) => Promise<any>
|
||||
: <Name extends keyof FlattenedAgentMap>(
|
||||
name: Name,
|
||||
input: AIAgentInput
|
||||
) => Promise<{ runId: string; result: FlattenedAgentMap[Name]['output']; usage: { inputTokens: number; outputTokens: number } }>
|
||||
|
||||
type TypedAgentStream = [keyof FlattenedAgentMap] extends [never]
|
||||
? (name: string, input: AIAgentInput, options?: { requiresToolApproval?: 'all' | 'explicit' | false }) => Promise<void>
|
||||
: <Name extends keyof FlattenedAgentMap>(
|
||||
name: Name,
|
||||
input: AIAgentInput,
|
||||
options?: { requiresToolApproval?: 'all' | 'explicit' | false }
|
||||
) => Promise<void>
|
||||
|
||||
export type TypedPikkuRPC = PikkuRPC<RPCInvoke, RPCRemote, TypedStartWorkflow, TypedAgentRun, TypedAgentStream>
|
||||
|
||||
185
packages/functions/.pikku/rpc/pikku-rpc-wirings-map.internal.gen.d.ts
vendored
Normal file
185
packages/functions/.pikku/rpc/pikku-rpc-wirings-map.internal.gen.d.ts
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.76
|
||||
*/
|
||||
/**
|
||||
* This provides the structure needed for typescript to be aware of RPCs and their return types
|
||||
*/
|
||||
|
||||
|
||||
import type { StubCall } from '.bun/@pikku+core@0.12.57/node_modules/@pikku/core/dist/services/stub-tracker'
|
||||
import type { FunctionCoverageReport } from '.bun/@pikku+core@0.12.57/node_modules/@pikku/core/dist/services/v8-coverage-service'
|
||||
import type { WorkflowRunStatus } from '.bun/@pikku+core@0.12.57/node_modules/@pikku/core/dist/wirings/workflow/workflow.types'
|
||||
|
||||
export type AgentApproveCallerInput = { agentName: string; runId: string; approvals: { toolCallId: string; approved: boolean; }[]; }
|
||||
export type AgentCallerInput = { agentName: string; message: string; threadId: string; resourceId: string; }
|
||||
export type AgentResumeCallerInput = { agentName: string; runId: string; toolCallId: string; approved: boolean; }
|
||||
export type AgentStreamCallerInput = { agentName: string; message: string; threadId: string; resourceId: string; context?: string | undefined; }
|
||||
export type AuthHandlerOutput = Promise<void> | Promise<any>
|
||||
export type DeleteAgentThreadInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type DeleteAgentThreadOutput = { deleted: boolean; }
|
||||
export type GetAgentThreadMessagesInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type GetAgentThreadMessagesOutput = any[]
|
||||
export type GetAgentThreadRunsInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type GetAgentThreadRunsOutput = any[]
|
||||
export type GetAgentThreadsInput = { agentName?: string | undefined; resourceId?: string | undefined; limit?: number | undefined; offset?: number | undefined; }
|
||||
export type GetAgentThreadsOutput = any[]
|
||||
export type GetSessionInput = {}
|
||||
export type GetSessionOutput = {
|
||||
userId: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
}
|
||||
export type GraphStarterInput = { workflowName: string; nodeId: string; data?: unknown; }
|
||||
export type GraphStarterOutput = { runId: string; }
|
||||
export type PikkuConsoleGetSecretInput = { secretId: string; }
|
||||
export type PikkuConsoleGetSecretOutput = { exists: boolean; value: unknown; }
|
||||
export type PikkuConsoleGetVariableInput = { variableId: string; }
|
||||
export type PikkuConsoleGetVariableOutput = { exists: boolean; value: unknown; }
|
||||
export type PikkuConsoleHasSecretInput = { secretId: string; }
|
||||
export type PikkuConsoleHasSecretOutput = { exists: boolean; }
|
||||
export type PikkuConsoleSetSecretInput = { secretId: string; value: unknown; }
|
||||
export type PikkuConsoleSetSecretOutput = { success: boolean; }
|
||||
export type PikkuConsoleSetVariableInput = { variableId: string; value: unknown; }
|
||||
export type PikkuConsoleSetVariableOutput = { success: boolean; }
|
||||
export type PikkuScenarioGetStubCallsInput = { service?: string | undefined; }
|
||||
export type PikkuScenarioGetStubCallsOutput = StubCall[]
|
||||
export type PikkuScenarioResetLiveCoverageOutput = { enabled: boolean; }
|
||||
export type PikkuScenarioResetStubsOutput = { enabled: boolean; }
|
||||
export type RealtimeEventStreamInput = { topic: string; }
|
||||
export type RealtimeSubscribeInput = { topic: string; }
|
||||
export type RealtimeUnsubscribeInput = { topic: string; }
|
||||
export type RpcCallerInput = { rpcName: string; data?: unknown; }
|
||||
export type SecretSchema_betterAuthSecret = string
|
||||
export type SessionHealthScenarioOutput = { email: string; userId: string; }
|
||||
export type WorkflowRunnerInput = { workflowName: string; data?: unknown; }
|
||||
export type WorkflowStarterInput = { workflowName: string; data?: unknown; }
|
||||
export type WorkflowStarterOutput = { runId: string; }
|
||||
export type WorkflowStatusCheckerInput = { workflowName: string; runId: string; }
|
||||
export type WorkflowStatusStreamFullInput = { workflowName: string; runId: string; }
|
||||
export type WorkflowStatusStreamInput = { workflowName: string; runId: string; }
|
||||
|
||||
interface RPCHandler<I, O> {
|
||||
input: I;
|
||||
output: O;
|
||||
}
|
||||
|
||||
export type RPCMap = {
|
||||
readonly 'getSession': RPCHandler<GetSessionInput, GetSessionOutput>,
|
||||
readonly 'sessionHealthScenario': RPCHandler<null, SessionHealthScenarioOutput>,
|
||||
readonly 'agentCaller': RPCHandler<AgentCallerInput, null>,
|
||||
readonly 'agentStreamCaller': RPCHandler<AgentStreamCallerInput, null>,
|
||||
readonly 'agentApproveCaller': RPCHandler<AgentApproveCallerInput, null>,
|
||||
readonly 'agentResumeCaller': RPCHandler<AgentResumeCallerInput, null>,
|
||||
readonly 'getAgentThreads': RPCHandler<GetAgentThreadsInput, GetAgentThreadsOutput>,
|
||||
readonly 'getAgentThreadMessages': RPCHandler<GetAgentThreadMessagesInput, GetAgentThreadMessagesOutput>,
|
||||
readonly 'getAgentThreadRuns': RPCHandler<GetAgentThreadRunsInput, GetAgentThreadRunsOutput>,
|
||||
readonly 'deleteAgentThread': RPCHandler<DeleteAgentThreadInput, DeleteAgentThreadOutput>,
|
||||
readonly 'authHandler': RPCHandler<null, AuthHandlerOutput>,
|
||||
readonly 'pikkuConsoleSetSecret': RPCHandler<PikkuConsoleSetSecretInput, PikkuConsoleSetSecretOutput>,
|
||||
readonly 'pikkuConsoleGetVariable': RPCHandler<PikkuConsoleGetVariableInput, PikkuConsoleGetVariableOutput>,
|
||||
readonly 'pikkuConsoleSetVariable': RPCHandler<PikkuConsoleSetVariableInput, PikkuConsoleSetVariableOutput>,
|
||||
readonly 'pikkuConsoleHasSecret': RPCHandler<PikkuConsoleHasSecretInput, PikkuConsoleHasSecretOutput>,
|
||||
readonly 'pikkuConsoleGetSecret': RPCHandler<PikkuConsoleGetSecretInput, PikkuConsoleGetSecretOutput>,
|
||||
readonly 'realtimeSubscribe': RPCHandler<RealtimeSubscribeInput, null>,
|
||||
readonly 'realtimeUnsubscribe': RPCHandler<RealtimeUnsubscribeInput, null>,
|
||||
readonly 'realtimeEventStream': RPCHandler<RealtimeEventStreamInput, null>,
|
||||
readonly 'rpcCaller': RPCHandler<RpcCallerInput, null>,
|
||||
readonly 'pikkuScenarioTakeLiveCoverage': RPCHandler<null, FunctionCoverageReport>,
|
||||
readonly 'pikkuScenarioResetLiveCoverage': RPCHandler<null, PikkuScenarioResetLiveCoverageOutput>,
|
||||
readonly 'pikkuScenarioResetStubs': RPCHandler<null, PikkuScenarioResetStubsOutput>,
|
||||
readonly 'pikkuScenarioGetStubCalls': RPCHandler<PikkuScenarioGetStubCallsInput, PikkuScenarioGetStubCallsOutput>,
|
||||
readonly 'workflowStarter': RPCHandler<WorkflowStarterInput, WorkflowStarterOutput>,
|
||||
readonly 'workflowRunner': RPCHandler<WorkflowRunnerInput, null>,
|
||||
readonly 'workflowStatusChecker': RPCHandler<WorkflowStatusCheckerInput, WorkflowRunStatus>,
|
||||
readonly 'workflowStatusStream': RPCHandler<WorkflowStatusStreamInput, null>,
|
||||
readonly 'workflowStatusStreamFull': RPCHandler<WorkflowStatusStreamFullInput, null>,
|
||||
readonly 'graphStarter': RPCHandler<GraphStarterInput, GraphStarterOutput>,
|
||||
};
|
||||
|
||||
|
||||
// Addon package RPC maps
|
||||
import type { RPCMap as ConsoleRPCMap } from '@pikku/addon-console/.pikku/rpc/pikku-rpc-wirings-map.internal.gen.js'
|
||||
|
||||
|
||||
// Utility type to prefix keys with namespace (skips 'any' to prevent type poisoning)
|
||||
type PrefixKeys<T, Prefix extends string> = unknown extends T ? {} : {
|
||||
[K in keyof T as `${Prefix}:${string & K}`]: T[K]
|
||||
}
|
||||
|
||||
// Merge all RPC maps with namespace prefixes
|
||||
export type FlattenedRPCMap =
|
||||
RPCMap & PrefixKeys<ConsoleRPCMap, 'console'>
|
||||
|
||||
type IsAny<T> = 0 extends (1 & T) ? true : false
|
||||
type IsVoidishInput<T> = IsAny<T> extends true
|
||||
? false
|
||||
: [T] extends [void | null | undefined]
|
||||
? true
|
||||
: false
|
||||
|
||||
|
||||
export type RPCInvoke = <Name extends keyof FlattenedRPCMap>(
|
||||
...args: IsVoidishInput<FlattenedRPCMap[Name]['input']> extends true
|
||||
? [name: Name]
|
||||
: [name: Name, data: FlattenedRPCMap[Name]['input']]
|
||||
) => Promise<FlattenedRPCMap[Name]['output']>
|
||||
|
||||
export type RPCRemote = <Name extends keyof FlattenedRPCMap>(
|
||||
...args: IsVoidishInput<FlattenedRPCMap[Name]['input']> extends true
|
||||
? [name: Name]
|
||||
: [name: Name, data: FlattenedRPCMap[Name]['input']]
|
||||
) => Promise<FlattenedRPCMap[Name]['output']>
|
||||
|
||||
import type { FlattenedWorkflowMap } from '../workflow/pikku-workflow-map.gen.d.js'
|
||||
|
||||
import type { AgentMap } from '../agent/pikku-agent-map.gen.d.js'
|
||||
|
||||
// Addon package Agent maps
|
||||
import type { AgentMap as ConsoleAgentMap } from '@pikku/addon-console/.pikku/agent/pikku-agent-map.gen.d.js'
|
||||
|
||||
|
||||
type FlattenedAgentMap =
|
||||
AgentMap & PrefixKeys<ConsoleAgentMap, 'console'>
|
||||
|
||||
|
||||
import type { PikkuRPC } from '@pikku/core/rpc'
|
||||
|
||||
interface AIAgentInput {
|
||||
message: string
|
||||
threadId: string
|
||||
resourceId: string
|
||||
}
|
||||
|
||||
export type TypedStartWorkflow = <Name extends keyof FlattenedWorkflowMap>(
|
||||
name: Name,
|
||||
input: FlattenedWorkflowMap[Name]['input'],
|
||||
options?: { startNode?: string }
|
||||
) => Promise<{ runId: string }>
|
||||
|
||||
export type TypedRunWorkflow = <Name extends keyof FlattenedWorkflowMap>(
|
||||
name: Name,
|
||||
input: FlattenedWorkflowMap[Name]['input']
|
||||
) => Promise<FlattenedWorkflowMap[Name]['output']>
|
||||
|
||||
export type TypedWorkflowStatus = (
|
||||
workflowName: string,
|
||||
runId: string
|
||||
) => Promise<{ id: string; status: 'running' | 'suspended' | 'completed' | 'failed' | 'cancelled'; output?: unknown; error?: { message?: string } }>
|
||||
|
||||
type TypedAgentRun = [keyof FlattenedAgentMap] extends [never]
|
||||
? (name: string, input: AIAgentInput) => Promise<any>
|
||||
: <Name extends keyof FlattenedAgentMap>(
|
||||
name: Name,
|
||||
input: AIAgentInput
|
||||
) => Promise<{ runId: string; result: FlattenedAgentMap[Name]['output']; usage: { inputTokens: number; outputTokens: number } }>
|
||||
|
||||
type TypedAgentStream = [keyof FlattenedAgentMap] extends [never]
|
||||
? (name: string, input: AIAgentInput, options?: { requiresToolApproval?: 'all' | 'explicit' | false }) => Promise<void>
|
||||
: <Name extends keyof FlattenedAgentMap>(
|
||||
name: Name,
|
||||
input: AIAgentInput,
|
||||
options?: { requiresToolApproval?: 'all' | 'explicit' | false }
|
||||
) => Promise<void>
|
||||
|
||||
export type TypedPikkuRPC = PikkuRPC<RPCInvoke, RPCRemote, TypedStartWorkflow, TypedAgentRun, TypedAgentStream>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"agentName":{"type":"string"},"runId":{"type":"string"},"approvals":{"type":"array","items":{"type":"object","properties":{"toolCallId":{"type":"string"},"approved":{"type":"boolean"}},"required":["toolCallId","approved"],"additionalProperties":false}}},"required":["agentName","runId","approvals"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"agentName":{"type":"string"},"message":{"type":"string"},"threadId":{"type":"string"},"resourceId":{"type":"string"}},"required":["agentName","message","threadId","resourceId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"agentName":{"type":"string"},"runId":{"type":"string"},"toolCallId":{"type":"string"},"approved":{"type":"boolean"}},"required":["agentName","runId","toolCallId","approved"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"agentName":{"type":"string"},"message":{"type":"string"},"threadId":{"type":"string"},"resourceId":{"type":"string"},"context":{"type":"string"}},"required":["agentName","message","threadId","resourceId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"type":"null"},{}],"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"threadId":{"type":"string"},"resourceId":{"type":"string"}},"required":["threadId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"deleted":{"type":"boolean"}},"required":["deleted"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"generatedAt":{"type":"string"},"summary":{"type":"object","properties":{"total":{"type":"number"},"covered":{"type":"number"},"partial":{"type":"number"},"uncovered":{"type":"number"},"unknown":{"type":"number"},"overallRatio":{"type":"number"}},"required":["total","covered","partial","uncovered","unknown","overallRatio"],"additionalProperties":false},"functions":{"type":"array","items":{"$ref":"#/definitions/FunctionCoverageEntry"}}},"required":["generatedAt","summary","functions"],"additionalProperties":false,"definitions":{"FunctionCoverageEntry":{"type":"object","properties":{"name":{"type":"string"},"sourceFile":{"type":"string"},"exposed":{"type":"boolean"},"description":{"type":["string","null"]},"coveredLines":{"type":"number"},"totalLines":{"type":"number"},"missedLines":{"type":"array","items":{"type":"number"}},"ratio":{"type":"number"},"status":{"$ref":"#/definitions/CoverageStatus"}},"required":["name","sourceFile","exposed","description","coveredLines","totalLines","missedLines","ratio","status"],"additionalProperties":false},"CoverageStatus":{"type":"string","enum":["covered","partial","uncovered","unknown"]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"threadId":{"type":"string"},"resourceId":{"type":"string"}},"required":["threadId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"array","items":{},"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"threadId":{"type":"string"},"resourceId":{"type":"string"}},"required":["threadId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"array","items":{},"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"agentName":{"type":"string"},"resourceId":{"type":"string"},"limit":{"type":"number"},"offset":{"type":"number"}},"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"array","items":{},"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{},"additionalProperties":false}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"userId":{"type":"string"},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["userId","email","name"],"additionalProperties":false}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"workflowName":{"type":"string"},"nodeId":{"type":"string"},"data":{}},"required":["workflowName","nodeId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"runId":{"type":"string"}},"required":["runId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"secretId":{"type":"string"}},"required":["secretId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"exists":{"type":"boolean"},"value":{}},"required":["exists","value"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"variableId":{"type":"string"}},"required":["variableId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"exists":{"type":"boolean"},"value":{}},"required":["exists","value"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"secretId":{"type":"string"}},"required":["secretId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"exists":{"type":"boolean"}},"required":["exists"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"secretId":{"type":"string"},"value":{}},"required":["secretId","value"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"success":{"type":"boolean"}},"required":["success"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"variableId":{"type":"string"},"value":{}},"required":["variableId","value"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"success":{"type":"boolean"}},"required":["success"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"service":{"type":"string"}},"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"array","items":{"$ref":"#/definitions/StubCall"},"definitions":{"StubCall":{"type":"object","properties":{"service":{"type":"string"},"method":{"type":"string"},"args":{"type":"array","items":{}}},"required":["service","method","args"],"additionalProperties":false}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"topic":{"type":"string"}},"required":["topic"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"topic":{"type":"string"}},"required":["topic"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"topic":{"type":"string"}},"required":["topic"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"rpcName":{"type":"string"},"data":{}},"required":["rpcName"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string"}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"email":{"type":"string"},"userId":{"type":"string"}},"required":["email","userId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"runId":{"type":"string"}},"required":["runId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"id":{"type":"string"},"status":{"$ref":"#/definitions/WorkflowStatus"},"startedAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"deterministic":{"type":"boolean"},"plannedSteps":{"type":"array","items":{"$ref":"#/definitions/WorkflowPlannedStep"}},"steps":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"status":{"$ref":"#/definitions/StepStatus"},"duration":{"type":"number"},"attempts":{"type":"number","description":"Number of attempts for this step (1 = first try; > 1 means it retried)."}},"required":["name","status"],"additionalProperties":false}},"output":{},"error":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}},"required":["id","status","startedAt","steps"],"additionalProperties":false,"definitions":{"WorkflowStatus":{"type":"string","enum":["running","suspended","completed","failed","cancelled"],"description":"Workflow run status"},"WorkflowPlannedStep":{"type":"object","properties":{"stepName":{"type":"string","description":"Durable step key — matches the runtime step name stored in the DB"},"displayName":{"type":"string","description":"Optional human-readable label for the UI timeline (falls back to stepName)"}},"required":["stepName"],"additionalProperties":false},"StepStatus":{"type":"string","enum":["pending","running","scheduled","succeeded","failed","suspended"],"description":"Workflow step status"}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"workflowName":{"type":"string"},"data":{}},"required":["workflowName"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"workflowName":{"type":"string"},"data":{}},"required":["workflowName"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"runId":{"type":"string"}},"required":["runId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"workflowName":{"type":"string"},"runId":{"type":"string"}},"required":["workflowName","runId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"workflowName":{"type":"string"},"runId":{"type":"string"}},"required":["workflowName","runId"],"additionalProperties":false,"definitions":{}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"workflowName":{"type":"string"},"runId":{"type":"string"}},"required":["workflowName","runId"],"additionalProperties":false,"definitions":{}}
|
||||
97
packages/functions/.pikku/workflow/pikku-workflow-map.gen.d.ts
vendored
Normal file
97
packages/functions/.pikku/workflow/pikku-workflow-map.gen.d.ts
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.76
|
||||
*/
|
||||
/**
|
||||
* Workflow type map with input/output types for each workflow
|
||||
*/
|
||||
|
||||
import type { StubCall } from '.bun/@pikku+core@0.12.57/node_modules/@pikku/core/dist/services/stub-tracker'
|
||||
|
||||
export type AgentApproveCallerInput = { agentName: string; runId: string; approvals: { toolCallId: string; approved: boolean; }[]; }
|
||||
export type AgentCallerInput = { agentName: string; message: string; threadId: string; resourceId: string; }
|
||||
export type AgentResumeCallerInput = { agentName: string; runId: string; toolCallId: string; approved: boolean; }
|
||||
export type AgentStreamCallerInput = { agentName: string; message: string; threadId: string; resourceId: string; context?: string | undefined; }
|
||||
export type AuthHandlerOutput = Promise<void> | Promise<any>
|
||||
export type DeleteAgentThreadInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type DeleteAgentThreadOutput = { deleted: boolean; }
|
||||
export type GetAgentThreadMessagesInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type GetAgentThreadMessagesOutput = any[]
|
||||
export type GetAgentThreadRunsInput = { threadId: string; resourceId?: string | undefined; }
|
||||
export type GetAgentThreadRunsOutput = any[]
|
||||
export type GetAgentThreadsInput = { agentName?: string | undefined; resourceId?: string | undefined; limit?: number | undefined; offset?: number | undefined; }
|
||||
export type GetAgentThreadsOutput = any[]
|
||||
export type GetSessionInput = {}
|
||||
export type GetSessionOutput = {
|
||||
userId: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
}
|
||||
export type GraphStarterInput = { workflowName: string; nodeId: string; data?: unknown; }
|
||||
export type GraphStarterOutput = { runId: string; }
|
||||
export type PikkuConsoleGetSecretInput = { secretId: string; }
|
||||
export type PikkuConsoleGetSecretOutput = { exists: boolean; value: unknown; }
|
||||
export type PikkuConsoleGetVariableInput = { variableId: string; }
|
||||
export type PikkuConsoleGetVariableOutput = { exists: boolean; value: unknown; }
|
||||
export type PikkuConsoleHasSecretInput = { secretId: string; }
|
||||
export type PikkuConsoleHasSecretOutput = { exists: boolean; }
|
||||
export type PikkuConsoleSetSecretInput = { secretId: string; value: unknown; }
|
||||
export type PikkuConsoleSetSecretOutput = { success: boolean; }
|
||||
export type PikkuConsoleSetVariableInput = { variableId: string; value: unknown; }
|
||||
export type PikkuConsoleSetVariableOutput = { success: boolean; }
|
||||
export type PikkuScenarioGetStubCallsInput = { service?: string | undefined; }
|
||||
export type PikkuScenarioGetStubCallsOutput = StubCall[]
|
||||
export type PikkuScenarioResetLiveCoverageOutput = { enabled: boolean; }
|
||||
export type PikkuScenarioResetStubsOutput = { enabled: boolean; }
|
||||
export type RealtimeEventStreamInput = { topic: string; }
|
||||
export type RealtimeSubscribeInput = { topic: string; }
|
||||
export type RealtimeUnsubscribeInput = { topic: string; }
|
||||
export type RpcCallerInput = { rpcName: string; data?: unknown; }
|
||||
export type SecretSchema_betterAuthSecret = string
|
||||
export type SessionHealthScenarioOutput = { email: string; userId: string; }
|
||||
export type WorkflowRunnerInput = { workflowName: string; data?: unknown; }
|
||||
export type WorkflowStarterInput = { workflowName: string; data?: unknown; }
|
||||
export type WorkflowStarterOutput = { runId: string; }
|
||||
export type WorkflowStatusCheckerInput = { workflowName: string; runId: string; }
|
||||
export type WorkflowStatusStreamFullInput = { workflowName: string; runId: string; }
|
||||
export type WorkflowStatusStreamInput = { workflowName: string; runId: string; }
|
||||
|
||||
// Addon package Workflow maps
|
||||
import type { WorkflowMap as ConsoleWorkflowMap } from '@pikku/addon-console/.pikku/workflow/pikku-workflow-map.gen.d.js'
|
||||
|
||||
|
||||
interface WorkflowHandler<I, O> {
|
||||
input: I;
|
||||
output: O;
|
||||
}
|
||||
|
||||
interface GraphNodeHandler<I> {
|
||||
input: I;
|
||||
}
|
||||
|
||||
export type WorkflowMap = {
|
||||
readonly 'sessionHealthScenario': WorkflowHandler<void, SessionHealthScenarioOutput>,
|
||||
};
|
||||
|
||||
export type GraphsMap = {
|
||||
readonly 'sessionHealthScenario': {
|
||||
readonly 'visitor signs in and reads their session': GraphNodeHandler<GetSessionInput>,
|
||||
},
|
||||
};
|
||||
|
||||
type PrefixWorkflowKeys<T, Prefix extends string> = unknown extends T ? {} : {
|
||||
[K in keyof T as `${Prefix}:${string & K}`]: T[K]
|
||||
}
|
||||
|
||||
export type FlattenedWorkflowMap =
|
||||
WorkflowMap & PrefixWorkflowKeys<ConsoleWorkflowMap, 'console'>
|
||||
|
||||
|
||||
export type WorkflowClient<Name extends keyof FlattenedWorkflowMap> = {
|
||||
start: (input: FlattenedWorkflowMap[Name]['input']) => Promise<{ runId: string }>;
|
||||
getRun: <output extends keyof FlattenedWorkflowMap[Name]>(runId: string) => Promise<FlattenedWorkflowMap[Name][output]>;
|
||||
cancelRun: (runId: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
export type TypedWorkflowClients = {
|
||||
[Name in keyof FlattenedWorkflowMap]: WorkflowClient<Name>;
|
||||
}
|
||||
34
packages/functions/package.json
Normal file
34
packages/functions/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@project/functions",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"imports": {
|
||||
"#pikku": "./.pikku/pikku-types.gen.ts",
|
||||
"#pikku/*.gen.js": "./.pikku/*.gen.ts",
|
||||
"#pikku/*": "./.pikku/*"
|
||||
},
|
||||
"scripts": {
|
||||
"db:types": "pikku db types"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai-compatible": "^2.0.48",
|
||||
"@pikku/addon-console": "^0.12.22",
|
||||
"@pikku/ai-vercel": "^0.12.7",
|
||||
"@pikku/better-auth": "^0.12.16",
|
||||
"@pikku/core": "^0.12.57",
|
||||
"@pikku/kysely": "^0.13.0",
|
||||
"@pikku/schema-cfworker": "^0.12.4",
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"ai": "^6.0.0",
|
||||
"better-auth": "^1.6.18",
|
||||
"kysely": "^0.29.0",
|
||||
"tsx": "^4.21.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pikku/deploy-standalone": "^0.12.7",
|
||||
"@types/node": "^22",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
39
packages/functions/src/application-types.d.ts
vendored
Normal file
39
packages/functions/src/application-types.d.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { CoreServices, CoreSingletonServices, CoreConfig, CoreUserSession } from '@pikku/core'
|
||||
import type { AuditLog, EmailService } from '@pikku/core/services'
|
||||
import type { Kysely } from 'kysely'
|
||||
import type { DB } from '#pikku/db/schema.gen.js'
|
||||
import type { TypedSecretService } from '../.pikku/secrets/pikku-secrets.gen.js'
|
||||
import type { TypedVariablesService } from '../.pikku/variables/pikku-variables.gen.js'
|
||||
import type { auth } from './auth.js'
|
||||
|
||||
export interface UserSession extends CoreUserSession {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface Config extends CoreConfig {
|
||||
port: number
|
||||
hostname: string
|
||||
}
|
||||
|
||||
export interface SingletonServices extends CoreSingletonServices<Config> {
|
||||
variables: TypedVariablesService
|
||||
secrets: TypedSecretService
|
||||
kysely: Kysely<DB>
|
||||
// Resolved Better Auth instance, injected once by the generated pikkuServices
|
||||
// wrapper from the `auth` factory. Typed as the factory's return so functions
|
||||
// get better-auth's full server `api`/`handler` surface via DI.
|
||||
auth: Awaited<ReturnType<typeof auth>>
|
||||
// Always constructed in services.ts, so declare it REQUIRED here — it is
|
||||
// optional in CoreSingletonServices, which otherwise makes every emailService
|
||||
// use read as possibly-undefined and forces needless `!`/guards in functions.
|
||||
emailService: EmailService
|
||||
// Per-invocation audit log, ALWAYS returned from createWireServices (see
|
||||
// services.ts) so general activity logging is available in every function —
|
||||
// `await auditLog.write({ type, source: 'explicit', metadata })`. Declared
|
||||
// REQUIRED (like emailService above) so a plain `auditLog.write(...)` doesn't
|
||||
// read as possibly-undefined and force needless `?.`/guards. A function with
|
||||
// `audit: true` ADDITIONALLY gets a kysely wrapped to capture every table write.
|
||||
auditLog: AuditLog
|
||||
}
|
||||
|
||||
export interface Services extends CoreServices<SingletonServices> {}
|
||||
72
packages/functions/src/auth.ts
Normal file
72
packages/functions/src/auth.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { betterAuth } from 'better-auth'
|
||||
import { admin } from 'better-auth/plugins'
|
||||
import { actor, fabric } from '@pikku/better-auth'
|
||||
import { pikkuBetterAuth } from '#pikku'
|
||||
|
||||
/**
|
||||
* Better Auth configuration — email + password sign-in.
|
||||
*
|
||||
* `pikkuBetterAuth` has no side effects: the pikku CLI statically inspects this single
|
||||
* exported `auth` const and generates the catch-all `/api/auth/**` HTTP wiring,
|
||||
* the session-bridge middleware, and a `wireSecret` for `BETTER_AUTH_SECRET` (and
|
||||
* one per social provider, if you add any) — so the auth routes and secret
|
||||
* requirements flow through normal inspection into the deploy manifest.
|
||||
*
|
||||
* The factory runs once when singleton services are built, pulling the secret
|
||||
* (and the database) off the injected `services`; the resolved instance is then
|
||||
* available to every function as `services.auth`. Better Auth is given the app's
|
||||
* own kysely: the CamelCasePlugin maps Better Auth's camelCase field names onto
|
||||
* the snake_case columns created in db/sqlite/0001-init.sql, keeping the whole DB
|
||||
* on one naming convention. To offer Google / GitHub / ... add a `socialProviders`
|
||||
* entry (and a button on the login page) — the CLI will wire its secret too.
|
||||
*/
|
||||
// The factory receives the FULL singleton services (emailService, logger, …) —
|
||||
// destructure whatever you need, e.g. `{ kysely, secrets, emailService }` to wire
|
||||
// sendResetPassword/verification emails. It runs lazily after all services exist,
|
||||
// so never re-construct a service here or reach for a dynamic import.
|
||||
export const auth = pikkuBetterAuth(async ({ kysely, secrets, variables }) => {
|
||||
const BETTER_AUTH_SECRET = await secrets.getSecret('BETTER_AUTH_SECRET')
|
||||
// Genuinely optional: unset simply disables /api/auth/sign-in/actor (scenarios
|
||||
// off for this deployment) — the actor plugin refuses all sign-ins
|
||||
// without it.
|
||||
const SCENARIO_ACTOR_SECRET = await secrets
|
||||
.getSecret('SCENARIO_ACTOR_SECRET')
|
||||
.catch(() => undefined)
|
||||
// Fabric operator admin: the RSA public key the control plane's token is
|
||||
// verified against. The Fabric deployer pushes FABRIC_AUTH_PUBLIC_KEY onto
|
||||
// every stage; locally it's simply absent, which disables /sign-in/fabric.
|
||||
// Asymmetric — the app verifies, it can never forge an operator login.
|
||||
const FABRIC_AUTH_PUBLIC_KEY = await variables.get('FABRIC_AUTH_PUBLIC_KEY')
|
||||
|
||||
return betterAuth({
|
||||
secret: BETTER_AUTH_SECRET,
|
||||
database: { db: kysely, type: 'sqlite' },
|
||||
emailAndPassword: { enabled: true },
|
||||
// Stateless session: CLI splits out betterAuthStatelessSession so non-auth
|
||||
// units verify the signed cookie instead of bundling better-auth. pikku #737.
|
||||
session: { cookieCache: { enabled: true } },
|
||||
advanced: { database: { generateId: 'uuid' } },
|
||||
// Scenario actors: synthetic users (user.actor = true, see
|
||||
// db/sqlite/0003-user-actor.sql) signed in by pikkuScenario via
|
||||
// POST /api/auth/sign-in/actor { email, secret }. Never signs in real users.
|
||||
//
|
||||
// admin(): exposes /api/auth/admin/* (listUsers, setRole, impersonateUser,
|
||||
// …) so an app admin can list and "view as" their end-users — this is what
|
||||
// the Fabric console's Users tab drives. Adds role/banned/impersonatedBy
|
||||
// columns (see db/sqlite/0004-admin.sql). No user is an admin by default:
|
||||
// grant it by setting a user's `role` to 'admin' (or pass
|
||||
// `adminUserIds: [...]` here) — the admin API refuses non-admins.
|
||||
//
|
||||
// fabric(): exposes /api/auth/sign-in/fabric — the Fabric control plane
|
||||
// mints a short-lived RS256 token and signs in as a synthetic `fabric: true`
|
||||
// admin operator (db/sqlite/0005-fabric.sql), so the console Users tab can
|
||||
// list/impersonate real users without the operator being one of them. It
|
||||
// pairs with admin() and verifies against FABRIC_AUTH_PUBLIC_KEY; missing
|
||||
// key disables the endpoint.
|
||||
plugins: [
|
||||
actor({ secret: SCENARIO_ACTOR_SECRET }),
|
||||
admin(),
|
||||
fabric({ publicKey: FABRIC_AUTH_PUBLIC_KEY }),
|
||||
],
|
||||
})
|
||||
})
|
||||
6
packages/functions/src/config.ts
Normal file
6
packages/functions/src/config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { pikkuConfig } from '../.pikku/pikku-types.gen.js'
|
||||
|
||||
export const createConfig = pikkuConfig(async () => ({
|
||||
port: parseInt(process.env.API_PORT || '4003', 10),
|
||||
hostname: process.env.HOST || '0.0.0.0',
|
||||
}))
|
||||
32
packages/functions/src/functions/get-session.function.ts
Normal file
32
packages/functions/src/functions/get-session.function.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { z } from 'zod'
|
||||
import { pikkuFunc } from '#pikku'
|
||||
|
||||
export const GetSessionInput = z.object({})
|
||||
|
||||
export const GetSessionOutput = z.object({
|
||||
userId: z.string(),
|
||||
email: z.string().email(),
|
||||
name: z.string().nullable(),
|
||||
})
|
||||
|
||||
export const getSession = pikkuFunc({
|
||||
expose: true,
|
||||
readonly: true,
|
||||
auth: true,
|
||||
description: 'Returns the current signed-in user.',
|
||||
input: GetSessionInput,
|
||||
output: GetSessionOutput,
|
||||
func: async ({ kysely }, _input, { session }) => {
|
||||
const user = await kysely
|
||||
.selectFrom('user')
|
||||
.select(['id', 'email', 'name'])
|
||||
.where('id', '=', session!.userId)
|
||||
.executeTakeFirstOrThrow()
|
||||
|
||||
return {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
name: user.name ?? null,
|
||||
}
|
||||
},
|
||||
})
|
||||
33
packages/functions/src/functions/session-health.scenario.ts
Normal file
33
packages/functions/src/functions/session-health.scenario.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { pikkuScenario } from '#pikku/workflow/pikku-workflow-types.gen.js'
|
||||
|
||||
/**
|
||||
* A scenario is a story of RPC calls told through a synthetic persona (an
|
||||
* "actor" — see scenarios.actors in pikku.config.json). Every step runs over
|
||||
* the REAL transport with the actor's session cookie, so a passing scenario
|
||||
* proves the deployed API works exactly as a signed-in user would experience it.
|
||||
*
|
||||
* Run it with `pikku scenario run local` (needs SCENARIO_ACTOR_SECRET in the
|
||||
* environment — the actor plugin in src/auth.ts refuses sign-ins without it).
|
||||
* This one signs the visitor in and reads their own session: actor sign-in,
|
||||
* session cookie, authed RPC, and session mapping in one pass — a ready-made
|
||||
* health check for staging or production.
|
||||
*/
|
||||
export const sessionHealthScenario = pikkuScenario<void, { email: string; userId: string }>({
|
||||
title: 'Session health (scenario)',
|
||||
tags: ['scenario'],
|
||||
func: async ({ logger }, _input, { workflow, actors }) => {
|
||||
if (!actors?.visitor) {
|
||||
throw new Error(
|
||||
'sessionHealthScenario needs run actors (visitor) — run via `pikku scenario run <environment>`',
|
||||
)
|
||||
}
|
||||
logger.debug('session-health scenario starting')
|
||||
const session = await workflow.do(
|
||||
'visitor signs in and reads their session',
|
||||
'getSession',
|
||||
{},
|
||||
{ actor: actors.visitor },
|
||||
)
|
||||
return { email: session.email, userId: session.userId }
|
||||
},
|
||||
})
|
||||
52
packages/functions/src/lib/email-service.ts
Normal file
52
packages/functions/src/lib/email-service.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
LocalEmailService,
|
||||
type EmailService,
|
||||
type SendEmailInput,
|
||||
type SendEmailResult,
|
||||
} from '@pikku/core/services'
|
||||
import { renderEmailTemplate, type EmailTemplateName } from '../../.pikku/email/pikku-emails.gen.js'
|
||||
|
||||
type GeneratedTemplateEmailServiceOptions = {
|
||||
delegate?: EmailService
|
||||
defaultLocale?: string
|
||||
}
|
||||
|
||||
export class GeneratedTemplateEmailService implements EmailService {
|
||||
private readonly delegate: EmailService
|
||||
private readonly defaultLocale: string
|
||||
|
||||
constructor(options: GeneratedTemplateEmailServiceOptions = {}) {
|
||||
this.delegate = options.delegate ?? new LocalEmailService()
|
||||
this.defaultLocale = options.defaultLocale ?? 'en'
|
||||
}
|
||||
|
||||
async send(input: SendEmailInput): Promise<SendEmailResult> {
|
||||
if (!('template' in input) || !input.template) {
|
||||
return this.delegate.send(input)
|
||||
}
|
||||
|
||||
const rendered = renderEmailTemplate({
|
||||
name: input.template.name as EmailTemplateName,
|
||||
locale: (input.template.locale ?? this.defaultLocale) as Parameters<
|
||||
typeof renderEmailTemplate
|
||||
>[0]['locale'],
|
||||
data: input.template.data ?? {},
|
||||
})
|
||||
|
||||
return this.delegate.send({
|
||||
to: input.to,
|
||||
from: input.from,
|
||||
cc: input.cc,
|
||||
bcc: input.bcc,
|
||||
replyTo: input.replyTo,
|
||||
headers: {
|
||||
...(input.headers ?? {}),
|
||||
'x-pikku-email-template': String(input.template.name),
|
||||
'x-pikku-email-hash': rendered.hash,
|
||||
},
|
||||
subject: rendered.subject,
|
||||
html: rendered.html,
|
||||
...(rendered.text ? { text: rendered.text } : {}),
|
||||
})
|
||||
}
|
||||
}
|
||||
18
packages/functions/src/middleware/cors.middleware.ts
Normal file
18
packages/functions/src/middleware/cors.middleware.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { addHTTPMiddleware } from '@pikku/core/http'
|
||||
import { cors } from '@pikku/core/middleware'
|
||||
|
||||
const corsOrigins = process.env.CORS_ORIGINS
|
||||
? process.env.CORS_ORIGINS.split(',')
|
||||
.map((origin) => origin.trim())
|
||||
.filter(Boolean)
|
||||
: [process.env.FRONTEND_URL, 'http://localhost:7104', 'http://127.0.0.1:7104'].filter(
|
||||
(origin): origin is string => Boolean(origin),
|
||||
)
|
||||
|
||||
addHTTPMiddleware('*', [
|
||||
cors({
|
||||
origin: corsOrigins,
|
||||
credentials: true,
|
||||
headers: ['Content-Type', 'Authorization', 'X-Auth-Return-Redirect'],
|
||||
}),
|
||||
])
|
||||
98
packages/functions/src/services.ts
Normal file
98
packages/functions/src/services.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
JsonConsoleLogger,
|
||||
LocalEmailService,
|
||||
LocalSecretService,
|
||||
LocalVariablesService,
|
||||
NoopAuditService,
|
||||
createInvocationAudit,
|
||||
} from '@pikku/core/services'
|
||||
import { createAuditedKysely } from '@pikku/kysely'
|
||||
import { pikkuServices, pikkuWireServices } from '../.pikku/pikku-types.gen.js'
|
||||
import { TypedSecretService } from '../.pikku/secrets/pikku-secrets.gen.js'
|
||||
import { TypedVariablesService } from '../.pikku/variables/pikku-variables.gen.js'
|
||||
import { CFWorkerSchemaService } from '@pikku/schema-cfworker'
|
||||
import type { Kysely } from 'kysely'
|
||||
import type { VercelAIAgentRunner } from '@pikku/ai-vercel'
|
||||
import { GeneratedTemplateEmailService } from './lib/email-service.js'
|
||||
import type { DB } from '#pikku/db/schema.gen.js'
|
||||
|
||||
export const createSingletonServices = pikkuServices(async (config, existingServices) => {
|
||||
const variables =
|
||||
existingServices?.variables ?? new TypedVariablesService(new LocalVariablesService())
|
||||
const secrets =
|
||||
existingServices?.secrets ?? new TypedSecretService(new LocalSecretService(variables))
|
||||
const logger = existingServices?.logger ?? new JsonConsoleLogger()
|
||||
const schema = existingServices?.schema ?? new CFWorkerSchemaService(logger)
|
||||
const emailService =
|
||||
existingServices?.emailService ??
|
||||
new GeneratedTemplateEmailService({
|
||||
delegate: new LocalEmailService(),
|
||||
})
|
||||
// The durable audit sink. In a deployed stage fabric injects the platform's
|
||||
// audit service; locally it falls back to a no-op so nothing is persisted.
|
||||
const audit = existingServices?.audit ?? new NoopAuditService()
|
||||
// kysely is injected by pikku dev (node:sqlite) or the CF Worker workflow (libsql).
|
||||
// The template never constructs its own dialect — dialects are fabric/runtime
|
||||
// concerns — so it must always be provided by the runtime.
|
||||
if (!existingServices?.kysely) {
|
||||
throw new Error('kysely service was not injected by the runtime (pikku dev / fabric)')
|
||||
}
|
||||
const kysely: Kysely<DB> = existingServices.kysely
|
||||
|
||||
const litellmProxyUrl = process.env.LITELLM_PROXY_URL ?? null
|
||||
const litellmApiKey = process.env.LITELLM_API_KEY ?? null
|
||||
let aiAgentRunner: VercelAIAgentRunner | undefined
|
||||
if (litellmProxyUrl && litellmApiKey) {
|
||||
// The AI SDKs (~3MB) are stubbed out of non-agent units at bundle time —
|
||||
// only units with the `ai-model` capability keep them. So import them
|
||||
// dynamically and guard on the module resolving to a real export; in a
|
||||
// stubbed unit the import yields `{}` and the runner is simply not built.
|
||||
const aiVercel = await import('@pikku/ai-vercel')
|
||||
const aiSdk = await import('@ai-sdk/openai-compatible')
|
||||
if (aiVercel.VercelAIAgentRunner && aiSdk.createOpenAICompatible) {
|
||||
const litellmProvider = aiSdk.createOpenAICompatible({
|
||||
name: 'litellm',
|
||||
baseURL: litellmProxyUrl,
|
||||
apiKey: litellmApiKey,
|
||||
})
|
||||
aiAgentRunner = new aiVercel.VercelAIAgentRunner({
|
||||
openai: (modelId: string) => litellmProvider.chatModel(modelId),
|
||||
anthropic: (modelId: string) => litellmProvider.chatModel(modelId),
|
||||
google: (modelId: string) => litellmProvider.chatModel(modelId),
|
||||
deepseek: (modelId: string) => litellmProvider.chatModel(modelId),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...(existingServices ?? {}),
|
||||
config,
|
||||
variables,
|
||||
secrets,
|
||||
schema,
|
||||
logger,
|
||||
emailService,
|
||||
audit,
|
||||
kysely,
|
||||
...(aiAgentRunner ? { aiAgentRunner } : {}),
|
||||
}
|
||||
})
|
||||
|
||||
export const createWireServices = pikkuWireServices(async (singletonServices, wire) => {
|
||||
if (!singletonServices.audit) {
|
||||
return {}
|
||||
}
|
||||
const auditLog = createInvocationAudit(singletonServices.audit, wire)
|
||||
// auditLog is ALWAYS returned so `auditLog.write(...)` works in every function
|
||||
// for general activity logging. `auditLog.config` is set ONLY when this
|
||||
// invocation opts into row-level auditing (`audit: true`); when it is, ALSO wrap
|
||||
// kysely so every query is captured and the runner flushes the buffer on close.
|
||||
// Without audit: true, leave the plain kysely untouched — no per-query overhead.
|
||||
if (!auditLog.config) {
|
||||
return { auditLog }
|
||||
}
|
||||
return {
|
||||
auditLog,
|
||||
kysely: createAuditedKysely(singletonServices.kysely, { audit: auditLog }),
|
||||
}
|
||||
})
|
||||
3
packages/mantine-theme/active.json
Normal file
3
packages/mantine-theme/active.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"id": "default"
|
||||
}
|
||||
215
packages/mantine-theme/index.ts
Normal file
215
packages/mantine-theme/index.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import { createTheme, type MantineColorsTuple, type MantineThemeOverride } from '@mantine/core'
|
||||
import { generateColors } from '@mantine/colors-generator'
|
||||
import active from './active.json'
|
||||
import { themeSpecs } from './themes'
|
||||
|
||||
const FONT_FALLBACK = "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
||||
const MONO_FALLBACK =
|
||||
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace"
|
||||
|
||||
// Mono families need a monospace fallback chain, not the sans-serif one — else a
|
||||
// terminal/matrix theme degrades to a proportional font before the webfont loads.
|
||||
const isMonoFamily = (family: string): boolean => /\bmono\b|consol|courier/i.test(family)
|
||||
|
||||
// A theme is ONE Mantine theme, authored as two clearly-labelled sections:
|
||||
// brand — HIGH-LEVEL: colour palette + fonts (what makes apps look different)
|
||||
// structure — LOW-LEVEL Mantine: radius, shadows, spacing, component defaults
|
||||
// buildMantineTheme takes the structure as the base and EXTENDS it with the
|
||||
// brand, then that single merged object is registered with MantineProvider.
|
||||
export type Brand = {
|
||||
colors?: Record<string, string>
|
||||
fonts?: { heading?: string; body?: string; mono?: string }
|
||||
}
|
||||
|
||||
export type Structure = {
|
||||
defaultRadius?: string
|
||||
autoContrast?: boolean
|
||||
primaryShade?: number | { light?: number; dark?: number }
|
||||
shadows?: Record<string, string>
|
||||
spacing?: Record<string, string>
|
||||
radius?: Record<string, string>
|
||||
components?: Record<string, unknown>
|
||||
// The style's natural scheme — seeds ColorSchemeScript + MantineProvider so a
|
||||
// light style renders light and a dark style dark by default. NOT a createTheme
|
||||
// key, so it is stripped before the theme is built.
|
||||
defaultColorScheme?: 'light' | 'dark' | 'auto'
|
||||
// An explicit 10-step Mantine `dark` tuple used verbatim (idx0 lightest text →
|
||||
// idx7 body bg → idx9 deepest). When present it overrides the auto-tint so a
|
||||
// dark-first style gets its exact background. NOT a createTheme key.
|
||||
darkColors?: string[]
|
||||
}
|
||||
|
||||
export type Theme = {
|
||||
name: string
|
||||
description?: string
|
||||
brand?: Brand
|
||||
structure?: Structure
|
||||
}
|
||||
|
||||
// Back-compat alias: older imports referred to a colours-only "Palette".
|
||||
export type Palette = Theme
|
||||
|
||||
const fontStack = (family?: string): string | undefined =>
|
||||
family ? `'${family}', ${isMonoFamily(family) ? MONO_FALLBACK : FONT_FALLBACK}` : undefined
|
||||
|
||||
// Brand-tinted dark surfaces. Mantine's dark scheme derives the body, surfaces,
|
||||
// borders and inputs from the `dark` colour tuple; the default ramp is neutral
|
||||
// grey, so every app looked identical regardless of the brand colour. We rebuild
|
||||
// the dark ramp at the brand's HUE so the whole background takes on the theme —
|
||||
// keeping text shades (0..3) near-neutral for contrast and saturating the deep
|
||||
// background shades (6..9) for a clearly distinct, on-brand dark look.
|
||||
const hexToHsl = (hex: string): [number, number, number] | null => {
|
||||
const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim())
|
||||
if (!m) return null
|
||||
const int = parseInt(m[1], 16)
|
||||
const r = ((int >> 16) & 255) / 255
|
||||
const g = ((int >> 8) & 255) / 255
|
||||
const b = (int & 255) / 255
|
||||
const max = Math.max(r, g, b)
|
||||
const min = Math.min(r, g, b)
|
||||
const l = (max + min) / 2
|
||||
let h = 0
|
||||
let s = 0
|
||||
if (max !== min) {
|
||||
const d = max - min
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
||||
if (max === r) h = (g - b) / d + (g < b ? 6 : 0)
|
||||
else if (max === g) h = (b - r) / d + 2
|
||||
else h = (r - g) / d + 4
|
||||
h *= 60
|
||||
}
|
||||
return [h, s * 100, l * 100]
|
||||
}
|
||||
|
||||
const hslToHex = (h: number, s: number, l: number): string => {
|
||||
s /= 100
|
||||
l /= 100
|
||||
const k = (n: number) => (n + h / 30) % 12
|
||||
const a = s * Math.min(l, 1 - l)
|
||||
const f = (n: number) => {
|
||||
const v = l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)))
|
||||
return Math.round(255 * v)
|
||||
.toString(16)
|
||||
.padStart(2, '0')
|
||||
}
|
||||
return `#${f(0)}${f(8)}${f(4)}`
|
||||
}
|
||||
|
||||
// Lightness/saturation anchors per dark-tuple index (0 = lightest text → 9 =
|
||||
// deepest background). dark[7] is the body background in Mantine's dark scheme.
|
||||
// Text shades (0..3) stay near-neutral for readable contrast; the surface and
|
||||
// background shades (5..9) carry real brand saturation so the dark UI reads as a
|
||||
// rich, on-brand dark — NOT the flat near-grey it used to be (the "all gray" bug).
|
||||
const DARK_L = [96, 88, 77, 62, 48, 36, 25, 17, 12, 9]
|
||||
// Restrained saturation: just enough hue to read as on-brand, never the lurid
|
||||
// over-saturated background that made dark apps look "absurd". Dark-first styles
|
||||
// that want a precise background ship an explicit `darkColors` tuple instead.
|
||||
const DARK_S = [8, 10, 12, 14, 16, 18, 20, 22, 24, 26]
|
||||
|
||||
// Below this saturation a brand reads as greyscale (e.g. a mono/neutral theme);
|
||||
// tinting it at its near-arbitrary hue would turn the whole UI an off-colour
|
||||
// (a grey seed sits at hue 0 → a red-tinted dark). Keep those neutral.
|
||||
const MIN_TINT_SATURATION = 12
|
||||
|
||||
const tintedDarkTuple = (hex: string): MantineColorsTuple | null => {
|
||||
const hsl = hexToHsl(hex)
|
||||
if (!hsl) return null
|
||||
const [h, s] = hsl
|
||||
if (s < MIN_TINT_SATURATION) return null
|
||||
return DARK_L.map((l, i) => hslToHex(h, DARK_S[i]!, l)) as unknown as MantineColorsTuple
|
||||
}
|
||||
|
||||
// structure (base) → extend with brand (colours + fonts) → one Mantine theme.
|
||||
export const buildMantineTheme = (spec: Theme): MantineThemeOverride => {
|
||||
// Pull the two non-Mantine fields out of structure before it is spread into
|
||||
// createTheme — they drive scheme/dark-tuple, not the theme object.
|
||||
const {
|
||||
defaultColorScheme: _scheme,
|
||||
darkColors,
|
||||
...structureRest
|
||||
} = (spec.structure ?? {}) as Structure
|
||||
const structure = structureRest as MantineThemeOverride
|
||||
const brand = spec.brand ?? {}
|
||||
const colors: Record<string, MantineColorsTuple> = Object.fromEntries(
|
||||
Object.entries(brand.colors ?? {}).map(([role, hex]) => [role, generateColors(hex)]),
|
||||
)
|
||||
// An explicit dark tuple (from a dark-first style) wins verbatim; otherwise tint
|
||||
// the dark surfaces/background at the brand hue so apps still look distinct.
|
||||
const dark =
|
||||
darkColors && darkColors.length === 10
|
||||
? (darkColors as unknown as MantineColorsTuple)
|
||||
: brand.colors?.primary
|
||||
? tintedDarkTuple(brand.colors.primary)
|
||||
: null
|
||||
if (dark) colors.dark = dark
|
||||
const body = fontStack(brand.fonts?.body)
|
||||
const heading = fontStack(brand.fonts?.heading ?? brand.fonts?.body)
|
||||
const mono = fontStack(brand.fonts?.mono)
|
||||
// Mantine's default dark primaryShade is 8 — a deep, muted tone that reads as
|
||||
// near-grey against the dark background, so buttons/accents/the logo all looked
|
||||
// washed out (the other half of the "all gray" bug). Force a vibrant shade in
|
||||
// dark mode so the brand actually pops; honour an explicit theme override.
|
||||
// Cast to Mantine's narrow MantineColorShade union — structure JSON types the
|
||||
// shade loosely as number, which the literal 0–9 union rejects.
|
||||
const specShade = (spec.structure ?? {}).primaryShade
|
||||
const primaryShade = (specShade ?? { light: 6, dark: 5 }) as MantineThemeOverride['primaryShade']
|
||||
return createTheme({
|
||||
...structure,
|
||||
primaryShade,
|
||||
...(body ? { fontFamily: body } : {}),
|
||||
...(heading ? { headings: { fontFamily: heading } } : {}),
|
||||
...(mono ? { fontFamilyMonospace: mono } : {}),
|
||||
...(Object.keys(colors).length ? { colors } : {}),
|
||||
// Drive the primary colour from the brand. Previously a theme could define
|
||||
// colours that Mantine never used as the primary — that was the "themes
|
||||
// don't react" bug.
|
||||
...('primary' in colors ? { primaryColor: 'primary' } : {}),
|
||||
})
|
||||
}
|
||||
|
||||
// Back-compat alias for callers that pass a spec.
|
||||
export const buildTheme = buildMantineTheme
|
||||
|
||||
const specs = themeSpecs as Record<string, Theme>
|
||||
|
||||
export const themes: Record<string, MantineThemeOverride> = Object.fromEntries(
|
||||
Object.entries(specs).map(([id, s]) => [id, buildMantineTheme(s)]),
|
||||
)
|
||||
|
||||
export const themeList = Object.entries(specs).map(([id, s]) => ({
|
||||
id,
|
||||
name: s.name,
|
||||
description: s.description,
|
||||
}))
|
||||
|
||||
export const activeId = active.id
|
||||
export const activeTheme: MantineThemeOverride = themes[active.id] ?? themes.default
|
||||
export const theme = activeTheme
|
||||
|
||||
// The natural color scheme each theme declares, so the app can seed
|
||||
// ColorSchemeScript + MantineProvider to match (a light style boots light, a dark
|
||||
// style dark). Defaults to 'dark' (the template's historical default).
|
||||
export type ColorScheme = 'light' | 'dark' | 'auto'
|
||||
export const themeColorSchemes: Record<string, ColorScheme> = Object.fromEntries(
|
||||
Object.entries(specs).map(([id, s]) => [id, s.structure?.defaultColorScheme ?? 'dark']),
|
||||
)
|
||||
export const activeColorScheme: ColorScheme = themeColorSchemes[active.id] ?? 'dark'
|
||||
|
||||
// Distinct font families referenced by any theme, for the <head> Google Fonts
|
||||
// <link> so a theme's typography actually renders.
|
||||
export const brandFontFamilies: string[] = [
|
||||
...new Set(
|
||||
Object.values(specs).flatMap((s) => {
|
||||
const f = s.brand?.fonts ?? {}
|
||||
return [f.body, f.heading, f.mono].filter((x): x is string => Boolean(x))
|
||||
}),
|
||||
),
|
||||
]
|
||||
|
||||
export const googleFontsHref = (families: string[] = brandFontFamilies): string | null => {
|
||||
if (!families.length) return null
|
||||
const params = families
|
||||
.map((f) => `family=${encodeURIComponent(f)}:wght@400;500;600;700`)
|
||||
.join('&')
|
||||
return `https://fonts.googleapis.com/css2?${params}&display=swap`
|
||||
}
|
||||
25
packages/mantine-theme/package.json
Normal file
25
packages/mantine-theme/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@project/mantine-themes",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts",
|
||||
"./base.json": "./base.json",
|
||||
"./active.json": "./active.json"
|
||||
},
|
||||
"scripts": {
|
||||
"tsc": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/colors-generator": "^9.2.1",
|
||||
"chroma-js": "^3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mantine/core": "^9.2.1",
|
||||
"typescript": "^5.9"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mantine/core": "^9.2.1"
|
||||
}
|
||||
}
|
||||
25
packages/mantine-theme/themes/default.json
Normal file
25
packages/mantine-theme/themes/default.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "Default",
|
||||
"description": "Confident indigo with soft elevation — a solid, non-gray starting point.",
|
||||
"brand": {
|
||||
"colors": { "primary": "#4f46e5", "secondary": "#0ea5e9", "accent": "#f59e0b" },
|
||||
"fonts": { "heading": "Inter", "body": "Inter" }
|
||||
},
|
||||
"structure": {
|
||||
"defaultRadius": "md",
|
||||
"autoContrast": true,
|
||||
"primaryShade": { "light": 6, "dark": 5 },
|
||||
"shadows": {
|
||||
"xs": "0 1px 3px rgba(0,0,0,0.08)",
|
||||
"sm": "0 2px 8px rgba(0,0,0,0.10)",
|
||||
"md": "0 6px 18px -4px rgba(0,0,0,0.15)",
|
||||
"lg": "0 12px 32px -8px rgba(0,0,0,0.20)",
|
||||
"xl": "0 20px 48px -12px rgba(0,0,0,0.24)"
|
||||
},
|
||||
"defaultGradient": { "from": "primary.5", "to": "secondary.6", "deg": 135 },
|
||||
"components": {
|
||||
"Card": { "defaultProps": { "shadow": "sm", "radius": "md", "withBorder": true } },
|
||||
"Paper": { "defaultProps": { "shadow": "sm", "radius": "md" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
8
packages/mantine-theme/themes/index.ts
Normal file
8
packages/mantine-theme/themes/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// Generated by the fabric-theme tool — do not edit by hand.
|
||||
// One JSON per theme. Each bundles a `brand` (colours + fonts) and a
|
||||
// `structure` (radius/shadows/spacing) section. Add themes via the fabric-theme tool.
|
||||
import t_default from './default.json'
|
||||
|
||||
export const themeSpecs: Record<string, unknown> = {
|
||||
default: t_default,
|
||||
}
|
||||
15
packages/mantine-theme/tsconfig.json
Normal file
15
packages/mantine-theme/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": true,
|
||||
"types": []
|
||||
},
|
||||
"include": ["index.ts", "base.json", "active.json", "palettes/**/*.ts", "palettes/**/*.json"]
|
||||
}
|
||||
Reference in New Issue
Block a user