chore: kanban template
This commit is contained in:
49
.gitignore
vendored
Normal file
49
.gitignore
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
node_modules
|
||||
dist
|
||||
.next
|
||||
.vite
|
||||
.tanstack
|
||||
.deploy/
|
||||
.wrangler/
|
||||
.pikku-runtime/
|
||||
|
||||
# Local dev SQLite database (regenerated via `pikku db migrate`)
|
||||
packages/functions/.pikku/dev.db
|
||||
packages/functions/.pikku/dev.db-*
|
||||
|
||||
# Locally instantiated apps. Real projects commit these; the template
|
||||
# repo never ships an instantiated user app.
|
||||
!apps/fabric-design/
|
||||
|
||||
# Generated by @pikku/cli — regenerated on `pikku all`.
|
||||
# Ignore the contents (not the directory) so negation below can whitelist
|
||||
# the `api.gen.ts` stub that keeps the skeleton compilable pre-`pikku all`.
|
||||
packages/functions/src/pikku/*
|
||||
packages/functions/.pikku/*
|
||||
!packages/functions/.pikku/client
|
||||
packages/functions/.pikku/client/*
|
||||
!packages/functions/.pikku/client/api.gen.ts
|
||||
packages/functions/.pikku-runtime/
|
||||
# Compiled .js siblings of the checked-in scaffold .gen.ts files.
|
||||
packages/functions/src/scaffold/*.gen.js
|
||||
|
||||
# Yarn — commit the root lockfile; ignore PnP/cache and generated per-unit lockfiles
|
||||
.yarn/
|
||||
.pnp.*
|
||||
yarn.lock
|
||||
!/yarn.lock
|
||||
|
||||
# pikku deploy plan output — regenerated by the deploy container
|
||||
packages/functions/.deploy/
|
||||
|
||||
# OpenCode config, skills, tools + templates — all written by the Fabric dev
|
||||
# machine at startup, never committed to the project repo.
|
||||
.opencode/
|
||||
|
||||
# Vendored pikku tarballs — a temporary packaging hack. The stable-named
|
||||
# tarballs (e.g. vendor/pikku-core.tgz) are committed: package.json resolves
|
||||
# `file:./vendor/*.tgz` against them and project creation seeds new repos from
|
||||
# them, so they must stay tracked. The dev machine's sync_vendor_packages also
|
||||
# drops versioned copies (e.g. vendor/pikku-core-0.12.20.tgz) at boot — ignore
|
||||
# only those so they don't dirty the tree and block fast-forward pulls.
|
||||
vendor/*-[0-9]*.tgz
|
||||
4
.yarnrc.yml
Normal file
4
.yarnrc.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
# Force node_modules linker — some toolchain deps (uWebSockets.js native
|
||||
# addon, pikku CLI bin, esbuild native) resolve cleanly only with a
|
||||
# traditional tree.
|
||||
nodeLinker: node-modules
|
||||
40
README.md
Normal file
40
README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Kanban Board Template
|
||||
|
||||
Production-style Fabric demo template that exercises all core surfaces in one project:
|
||||
|
||||
- `http` - board APIs + workflow start routes
|
||||
- `channel` - `/ws/kanban` connect/message handlers
|
||||
- `queue` - `kanban-health-check` worker
|
||||
- `schedule` - periodic health-check trigger
|
||||
- `workflow` - demo orchestration workflow
|
||||
- `mcp` - exposed command endpoint (`mcpKanbanCommand`)
|
||||
- `agent` - `kanbanOpsAgent` with tool wiring
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. `yarn install`
|
||||
2. `yarn workspace @project/functions pikku db migrate`
|
||||
3. `yarn prebuild`
|
||||
4. `yarn dev`
|
||||
5. Open `/kanban`
|
||||
|
||||
## Demo RPCs
|
||||
|
||||
- `listKanbanCards`
|
||||
- `getKanbanMetric`
|
||||
- `getKanbanWorkflowStatus`
|
||||
- `createKanbanCard`
|
||||
- `moveKanbanCard`
|
||||
- `enqueueKanbanHealthCheck`
|
||||
- `startKanbanDemoWorkflow`
|
||||
- `mcpKanbanCommand`
|
||||
|
||||
## Template Conventions
|
||||
|
||||
- Same workspace and deploy layout as Fabric `starter-template`
|
||||
- Root `pikku.config.json` and `packages/functions/pikku.config.json` preserved
|
||||
- SQL migrations live in `db/migrations/`
|
||||
- Optional `db/seed.sql` runs only via `pikku db seed` / `pikku db reset`
|
||||
- DB types regenerate at `packages/functions/.pikku/db/schema.d.ts` on every `pikku db migrate`
|
||||
- UI routes live in `apps/kanban/src/routes/` (TanStack Start, SSR on Cloudflare)
|
||||
- Shared widgets/components live in `packages/components/`
|
||||
10
apps/kanban/.gitignore
vendored
Normal file
10
apps/kanban/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
node_modules
|
||||
dist
|
||||
.vite
|
||||
|
||||
# Generated by @pikku/cli — regenerated on `pikku all`.
|
||||
src/gen/
|
||||
|
||||
# Generated by @tanstack/router-plugin — the seed at src/routeTree.gen.ts
|
||||
# is committed so tsc passes before the plugin has run; the plugin
|
||||
# overwrites it as soon as dev starts.
|
||||
33
apps/kanban/.stylelintrc.json
Normal file
33
apps/kanban/.stylelintrc.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"extends": ["stylelint-config-standard"],
|
||||
"plugins": ["stylelint-use-logical"],
|
||||
"rules": {
|
||||
"csstools/use-logical": [
|
||||
"always",
|
||||
{
|
||||
"except": [
|
||||
"top",
|
||||
"bottom",
|
||||
"width",
|
||||
"height",
|
||||
"min-width",
|
||||
"max-width",
|
||||
"min-height",
|
||||
"max-height"
|
||||
]
|
||||
}
|
||||
],
|
||||
"declaration-no-important": true,
|
||||
"at-rule-no-unknown": [
|
||||
true,
|
||||
{ "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen", "layer"] }
|
||||
]
|
||||
},
|
||||
"ignoreFiles": [
|
||||
"dist/**",
|
||||
"node_modules/**",
|
||||
"src/routeTree.gen.ts",
|
||||
"**/*.gen.ts",
|
||||
"**/*.gen.d.ts"
|
||||
]
|
||||
}
|
||||
30
apps/kanban/package.json
Normal file
30
apps/kanban/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@project/kanban",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev --port 7104",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --port 7104",
|
||||
"tsc": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@project/functions-sdk": "workspace:*",
|
||||
"@tanstack/react-router": "^1.132.0",
|
||||
"@tanstack/react-start": "^1.132.0",
|
||||
"i18next": "^25.2.1",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"react-i18next": "^15.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.0",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"typescript": "^5.9",
|
||||
"vite": "^7.0.0"
|
||||
}
|
||||
}
|
||||
90
apps/kanban/src/i18n/config.ts
Normal file
90
apps/kanban/src/i18n/config.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import i18n from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
import en from './en.json'
|
||||
|
||||
// Typed tokens: every `t('key')` is checked against en.json's shape (i18next
|
||||
// flattens it into dot-path keys), so a typo or removed key is a compile error.
|
||||
// Other locale files are loaded dynamically and are not key-checked against en;
|
||||
// a missing key falls back to the default locale at runtime.
|
||||
declare module 'i18next' {
|
||||
interface CustomTypeOptions {
|
||||
defaultNS: 'translation'
|
||||
resources: {
|
||||
translation: typeof en
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a language: drop `i18n/<lang>.json` in this folder — Vite's
|
||||
// import.meta.glob auto-registers it below into `resources`, and
|
||||
// `supportedLocales` is derived from the files present, so no edits are needed
|
||||
// here. Content is reachable via the `/<lang>` URL prefix (e.g. `/de`, `/es`);
|
||||
// the default locale (`en`) needs no prefix.
|
||||
const localeModules = import.meta.glob('./*.json', { eager: true }) as Record<
|
||||
string,
|
||||
{ default: Record<string, unknown> }
|
||||
>
|
||||
|
||||
const resources: Record<string, { translation: Record<string, unknown> }> = {}
|
||||
for (const path in localeModules) {
|
||||
const code = path.slice(path.lastIndexOf('/') + 1).replace(/\.json$/, '')
|
||||
resources[code] = { translation: localeModules[path].default }
|
||||
}
|
||||
|
||||
export const supportedLocales = Object.keys(resources)
|
||||
export type Locale = string
|
||||
export const defaultLocale = 'en'
|
||||
|
||||
const RTL_LOCALES = new Set(['ar', 'he', 'fa', 'ur'])
|
||||
// Direction for a locale — RTL for Arabic/Hebrew/Farsi/Urdu, else LTR. Set this
|
||||
// on <html dir> at the root so the browser mirrors the layout; see the
|
||||
// pikku-rtl skill. Returns 'ltr' while the app is English-only.
|
||||
export function localeDir(locale: string = defaultLocale): 'rtl' | 'ltr' {
|
||||
return RTL_LOCALES.has(locale.split('-')[0]) ? 'rtl' : 'ltr'
|
||||
}
|
||||
|
||||
export function detectLocale(pathname: string): Locale {
|
||||
const segment = pathname.split('/')[1]
|
||||
if (supportedLocales.includes(segment as Locale)) {
|
||||
return segment as Locale
|
||||
}
|
||||
if (typeof navigator !== 'undefined') {
|
||||
const browserLang = navigator.language?.split('-')[0]
|
||||
if (supportedLocales.includes(browserLang as Locale)) {
|
||||
return browserLang as Locale
|
||||
}
|
||||
}
|
||||
return defaultLocale
|
||||
}
|
||||
|
||||
// i18n debug mode: when enabled, every *translated* string is masked to block
|
||||
// glyphs (█), so any readable text left on screen is text that never went
|
||||
// through a token — a hardcoded/inlined string. Surfaces missing i18n at a
|
||||
// glance. Toggle with `?i18n-debug` in the URL or `localStorage['i18n-debug']
|
||||
// = '1'` (or `I18N_DEBUG=1` for server-rendered builds). Off by default.
|
||||
export function isI18nDebug(): boolean {
|
||||
if (typeof process !== 'undefined' && process.env?.I18N_DEBUG === '1') return true
|
||||
if (typeof window === 'undefined') return false
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
if (params.has('i18n-debug')) return params.get('i18n-debug') !== '0'
|
||||
return window.localStorage?.getItem('i18n-debug') === '1'
|
||||
}
|
||||
|
||||
const i18nDebugPostProcessor = {
|
||||
type: 'postProcessor' as const,
|
||||
name: 'i18nDebug',
|
||||
process: (value: string) => (isI18nDebug() ? value.replace(/\S/g, '█') : value),
|
||||
}
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.use(i18nDebugPostProcessor)
|
||||
.init({
|
||||
resources,
|
||||
lng: typeof window !== 'undefined' ? detectLocale(window.location.pathname) : defaultLocale,
|
||||
fallbackLng: defaultLocale,
|
||||
interpolation: { escapeValue: false },
|
||||
postProcess: ['i18nDebug'],
|
||||
})
|
||||
|
||||
export default i18n
|
||||
123
apps/kanban/src/i18n/en.json
Normal file
123
apps/kanban/src/i18n/en.json
Normal file
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"shell": {
|
||||
"eyebrow": "Pikku Fabric template",
|
||||
"title": "Kanban operations cockpit",
|
||||
"subtitle": "A lightweight board for work intake, automation visibility, and deployable app surfaces. Built to grow into a real product, not stay a demo.",
|
||||
"stats": {
|
||||
"boardHealth": "Board health",
|
||||
"cardsTracked": "Cards tracked",
|
||||
"runtime": "Runtime"
|
||||
},
|
||||
"runtimeConnected": "connected",
|
||||
"runtimeDegraded": "degraded",
|
||||
"nav": {
|
||||
"overview": "Overview",
|
||||
"board": "Board",
|
||||
"automations": "Automations"
|
||||
}
|
||||
},
|
||||
"health": {
|
||||
"idle": "idle",
|
||||
"backlogged": "backlogged",
|
||||
"flowing": "flowing"
|
||||
},
|
||||
"status": {
|
||||
"todo": "Backlog",
|
||||
"doing": "In progress",
|
||||
"done": "Done"
|
||||
},
|
||||
"relativeTime": {
|
||||
"seconds": "{{count}}s ago",
|
||||
"minutes": "{{count}}m ago",
|
||||
"hours": "{{count}}h ago",
|
||||
"days": "{{count}}d ago"
|
||||
},
|
||||
"overview": {
|
||||
"heading": "Overview",
|
||||
"sub": "Snapshot of current delivery flow and template wiring.",
|
||||
"loadFailed": "Failed to load cards: {{error}}",
|
||||
"loading": "Loading current board state…",
|
||||
"kpi": {
|
||||
"boardHealth": "Board health",
|
||||
"boardHealthDetail": "Live signal from current card distribution",
|
||||
"backlog": "Backlog",
|
||||
"backlogDetail": "Items waiting for pickup",
|
||||
"inProgress": "In progress",
|
||||
"inProgressDetail": "Cards currently moving",
|
||||
"done": "Done",
|
||||
"doneDetail": "Completed work ready to review",
|
||||
"latest": "Latest card",
|
||||
"latestNone": "None yet",
|
||||
"latestDetail": "{{status}} · {{time}}",
|
||||
"latestEmptyDetail": "Create the first card to get started"
|
||||
},
|
||||
"executionModel": {
|
||||
"heading": "Execution model",
|
||||
"sub": "What this template already includes out of the box.",
|
||||
"bullets": [
|
||||
"Board CRUD over Pikku RPC and HTTP wires.",
|
||||
"Workflow hooks for card onboarding and notifications.",
|
||||
"Queue processing for async card events.",
|
||||
"MCP and CLI entrypoints for ops-driven control."
|
||||
]
|
||||
},
|
||||
"nextScreens": {
|
||||
"heading": "Suggested next screens",
|
||||
"sub": "Natural expansion points once the app goes beyond the starter board.",
|
||||
"bullets": [
|
||||
"Card detail with history, notes, and workflow runs.",
|
||||
"Release view for branch deploys and environment promotions.",
|
||||
"Team inbox for triage, alerts, and assignment queues."
|
||||
]
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"heading": "Board",
|
||||
"sub": "Track work across the three canonical lanes.",
|
||||
"loadFailed": "Failed to load cards: {{error}}",
|
||||
"loading": "Loading board…",
|
||||
"columnEmpty": "No cards yet.",
|
||||
"createHeading": "Create card",
|
||||
"createSub": "Use the generated mutation, not ad hoc fetches.",
|
||||
"titleLabel": "Title",
|
||||
"titlePlaceholder": "Audit workspace webhooks",
|
||||
"columnLabel": "Column",
|
||||
"creating": "Creating…",
|
||||
"createCta": "Create card",
|
||||
"createFailed": "Failed to create card: {{error}}"
|
||||
},
|
||||
"automations": {
|
||||
"heading": "Automations",
|
||||
"sub": "Operational surfaces already included in the template.",
|
||||
"rails": {
|
||||
"onboarding": {
|
||||
"name": "Card onboarding workflow",
|
||||
"detail": "Handles enrich → create → notify when new work enters the system."
|
||||
},
|
||||
"queue": {
|
||||
"name": "Process card event queue",
|
||||
"detail": "Queues side effects away from the request path for safer scaling."
|
||||
},
|
||||
"surface": {
|
||||
"name": "Kanban MCP + CLI surface",
|
||||
"detail": "Lets operators inspect or create board state from tools outside the UI."
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"active": "active",
|
||||
"idle": "idle",
|
||||
"warming": "warming",
|
||||
"standby": "standby",
|
||||
"ready": "ready"
|
||||
},
|
||||
"why": {
|
||||
"heading": "Why routing now",
|
||||
"sub": "TanStack Router gives this app room to grow past a demo board.",
|
||||
"bullets": [
|
||||
"Separate product surfaces without a one-file SPA.",
|
||||
"Clear paths for card details, releases, and diagnostics.",
|
||||
"Navigation semantics that match future expansion."
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
422
apps/kanban/src/index.css
Normal file
422
apps/kanban/src/index.css
Normal file
@@ -0,0 +1,422 @@
|
||||
:root {
|
||||
--bg: #081018;
|
||||
--bg-accent: #101d2a;
|
||||
--surface: rgba(17, 27, 39, 0.9);
|
||||
--surface-strong: rgba(22, 35, 50, 0.96);
|
||||
--surface-soft: rgba(255, 255, 255, 0.04);
|
||||
--border: rgba(160, 196, 255, 0.16);
|
||||
--border-strong: rgba(160, 196, 255, 0.28);
|
||||
--text: #edf4ff;
|
||||
--muted: #8ba0bb;
|
||||
--primary: #82aaff;
|
||||
--secondary: #5eead4;
|
||||
--warning: #fbbf24;
|
||||
--danger: #fb7185;
|
||||
--shadow: 0 24px 60px rgba(0, 0, 0, 0.34);
|
||||
font-family: 'IBM Plex Sans', 'Segoe UI', sans-serif;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(94, 234, 212, 0.08), transparent 28%),
|
||||
radial-gradient(circle at top right, rgba(130, 170, 255, 0.12), transparent 30%),
|
||||
linear-gradient(180deg, var(--bg-accent) 0%, var(--bg) 52%, #060b11 100%);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
max-width: 1240px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 28px 56px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.3fr) minmax(260px, 0.7fr);
|
||||
gap: 24px;
|
||||
align-items: end;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin-bottom: 10px;
|
||||
color: var(--secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.18em;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(2.2rem, 5vw, 4.25rem);
|
||||
line-height: 0.94;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
max-width: 56ch;
|
||||
margin: 14px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.hero-status {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.hero-stat,
|
||||
.panel,
|
||||
.task-card,
|
||||
.automation-card,
|
||||
.kpi-card,
|
||||
.column {
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.hero-stat {
|
||||
padding: 16px 18px;
|
||||
border-radius: 18px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.hero-stat span,
|
||||
.kpi-card span,
|
||||
.task-card-meta,
|
||||
.column-empty,
|
||||
.automation-card p,
|
||||
.section-heading p,
|
||||
.bullet-list,
|
||||
.field span,
|
||||
.inline-error,
|
||||
.empty-state {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.hero-stat strong {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 22px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.hero-stat strong[data-tone='flowing'] {
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.hero-stat strong[data-tone='backlogged'] {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.hero-stat strong[data-tone='idle'] {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 28px;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 16px;
|
||||
background: rgba(9, 15, 24, 0.72);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
padding: 10px 14px;
|
||||
border-radius: 12px;
|
||||
color: var(--muted);
|
||||
transition:
|
||||
background-color 150ms ease,
|
||||
color 150ms ease;
|
||||
}
|
||||
|
||||
.nav-link:hover,
|
||||
.nav-link.is-active {
|
||||
color: var(--text);
|
||||
background: rgba(130, 170, 255, 0.12);
|
||||
}
|
||||
|
||||
.page-content {
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
.page-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.2fr) minmax(280px, 0.8fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
padding: 22px;
|
||||
border-radius: 22px;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.panel.wide {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.section-heading h2 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.section-heading p {
|
||||
margin: 6px 0 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.kpi-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
background: var(--surface-strong);
|
||||
}
|
||||
|
||||
.kpi-card strong {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
font-size: 22px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.kpi-card small {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
color: var(--muted);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.bullet-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.board-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.column {
|
||||
padding: 14px;
|
||||
border-radius: 18px;
|
||||
background: var(--surface-strong);
|
||||
}
|
||||
|
||||
.column-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.column-header h3 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.column-header span {
|
||||
min-width: 28px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
background: rgba(130, 170, 255, 0.12);
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.column-cards {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
padding: 14px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
}
|
||||
|
||||
.task-card-title {
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.task-card-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.column-empty,
|
||||
.empty-state {
|
||||
padding: 18px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.error-state,
|
||||
.inline-error {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.card-form {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.field span {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.field input,
|
||||
.field select {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.field input:focus,
|
||||
.field select:focus {
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
padding: 13px 16px;
|
||||
border: 0;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(135deg, var(--primary), #6ee7f9);
|
||||
color: #071018;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.primary-button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.automation-list {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.automation-card {
|
||||
padding: 16px 18px;
|
||||
border-radius: 16px;
|
||||
background: var(--surface-strong);
|
||||
}
|
||||
|
||||
.automation-topline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.automation-topline h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.automation-topline span {
|
||||
padding: 5px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(130, 170, 255, 0.12);
|
||||
color: var(--primary);
|
||||
text-transform: capitalize;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.automation-topline span[data-state='active'] {
|
||||
background: rgba(94, 234, 212, 0.12);
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.automation-topline span[data-state='warming'] {
|
||||
background: rgba(251, 191, 36, 0.14);
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.hero,
|
||||
.page-grid,
|
||||
.kpi-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.panel.wide {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.board-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.app-shell {
|
||||
padding: 24px 16px 40px;
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
46
apps/kanban/src/lib/api.ts
Normal file
46
apps/kanban/src/lib/api.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { PikkuRPC } from '@project/functions-sdk/pikku/pikku-rpc.gen'
|
||||
import { apiUrl } from './env'
|
||||
|
||||
// The Pikku-RPC shim. `rpc.invoke` is typed by the generated FlattenedRPCMap, so
|
||||
// `makeApi().invoke('listCards', input)` checks the name and payload at compile
|
||||
// time. Pikku emits unversioned latest-aliases (`listCards` → `listCards@v2`),
|
||||
// so you call clean names. Used from TanStack Start loaders (SSR + client) and
|
||||
// from event handlers for mutations.
|
||||
export function makeApi(): PikkuRPC {
|
||||
const rpc = new PikkuRPC()
|
||||
rpc.setServerUrl(apiUrl())
|
||||
return rpc
|
||||
}
|
||||
|
||||
export async function formatApiError(error: unknown): Promise<string> {
|
||||
if (error instanceof Response) {
|
||||
const status = [error.status, error.statusText].filter(Boolean).join(' ')
|
||||
|
||||
try {
|
||||
const payload = (await error.clone().json()) as {
|
||||
message?: unknown
|
||||
errorId?: unknown
|
||||
}
|
||||
if (typeof payload?.message === 'string') {
|
||||
const detail =
|
||||
typeof payload.errorId === 'string'
|
||||
? `${payload.message} (${payload.errorId})`
|
||||
: payload.message
|
||||
return status ? `${status}: ${detail}` : detail
|
||||
}
|
||||
} catch {
|
||||
// Fall through to plain text/body-less formatting.
|
||||
}
|
||||
|
||||
try {
|
||||
const text = (await error.clone().text()).trim()
|
||||
if (text) return status ? `${status}: ${text}` : text
|
||||
} catch {
|
||||
// Ignore body-read failures and fall through.
|
||||
}
|
||||
|
||||
return status || 'Request failed'
|
||||
}
|
||||
|
||||
return error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
17
apps/kanban/src/lib/env.ts
Normal file
17
apps/kanban/src/lib/env.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// Endpoints come from env, never hardcoded. Throws on a missing var rather than
|
||||
// falling back to a localhost default, so a misconfigured deploy fails loudly.
|
||||
//
|
||||
// SSR (workerd inside the sandbox container) can't reach the external host port
|
||||
// that VITE_API_URL points to. VITE_SERVER_API_URL holds the internal Caddy
|
||||
// address (http://127.0.0.1:8080/api) and is set by generate-frontend-runtime.mjs
|
||||
// for local sandbox mode. In deployed/hosted contexts VITE_SERVER_API_URL is
|
||||
// unset and we fall back to VITE_API_URL, which is correct there.
|
||||
export function apiUrl(): string {
|
||||
const url = import.meta.env.SSR
|
||||
? (import.meta.env.VITE_SERVER_API_URL ?? import.meta.env.VITE_API_URL)
|
||||
: import.meta.env.VITE_API_URL
|
||||
if (!url) {
|
||||
throw new Error('VITE_API_URL is not set — point it at the Pikku API base URL')
|
||||
}
|
||||
return url
|
||||
}
|
||||
49
apps/kanban/src/lib/kanban.ts
Normal file
49
apps/kanban/src/lib/kanban.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import i18n from '../i18n/config'
|
||||
|
||||
export type CardStatus = 'todo' | 'doing' | 'done'
|
||||
|
||||
export interface Card {
|
||||
cardId: string
|
||||
title: string
|
||||
status: CardStatus | string
|
||||
position: number
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export const STATUSES: CardStatus[] = ['todo', 'doing', 'done']
|
||||
|
||||
export function sortCards(cards: Card[]) {
|
||||
return [...cards].sort((a, b) => {
|
||||
if (a.status !== b.status) return a.status.localeCompare(b.status)
|
||||
return a.position - b.position
|
||||
})
|
||||
}
|
||||
|
||||
export function classifyHealth(cards: Card[]) {
|
||||
const todo = cards.filter((card) => card.status === 'todo').length
|
||||
const doing = cards.filter((card) => card.status === 'doing').length
|
||||
if (doing === 0) return 'idle'
|
||||
if (todo > doing * 2) return 'backlogged'
|
||||
return 'flowing'
|
||||
}
|
||||
|
||||
export function prettyStatus(status: string) {
|
||||
switch (status) {
|
||||
case 'todo':
|
||||
return i18n.t('status.todo')
|
||||
case 'doing':
|
||||
return i18n.t('status.doing')
|
||||
case 'done':
|
||||
return i18n.t('status.done')
|
||||
default:
|
||||
return status
|
||||
}
|
||||
}
|
||||
|
||||
export function relativeTime(timestamp: string) {
|
||||
const delta = Math.max(0, Math.floor((Date.now() - new Date(timestamp).getTime()) / 1000))
|
||||
if (delta < 60) return i18n.t('relativeTime.seconds', { count: delta })
|
||||
if (delta < 3600) return i18n.t('relativeTime.minutes', { count: Math.floor(delta / 60) })
|
||||
if (delta < 86400) return i18n.t('relativeTime.hours', { count: Math.floor(delta / 3600) })
|
||||
return i18n.t('relativeTime.days', { count: Math.floor(delta / 86400) })
|
||||
}
|
||||
104
apps/kanban/src/routeTree.gen.ts
Normal file
104
apps/kanban/src/routeTree.gen.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as BoardRouteImport } from './routes/board'
|
||||
import { Route as AutomationsRouteImport } from './routes/automations'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
|
||||
const BoardRoute = BoardRouteImport.update({
|
||||
id: '/board',
|
||||
path: '/board',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const AutomationsRoute = AutomationsRouteImport.update({
|
||||
id: '/automations',
|
||||
path: '/automations',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/automations': typeof AutomationsRoute
|
||||
'/board': typeof BoardRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/automations': typeof AutomationsRoute
|
||||
'/board': typeof BoardRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/automations': typeof AutomationsRoute
|
||||
'/board': typeof BoardRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/automations' | '/board'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/automations' | '/board'
|
||||
id: '__root__' | '/' | '/automations' | '/board'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
AutomationsRoute: typeof AutomationsRoute
|
||||
BoardRoute: typeof BoardRoute
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/board': {
|
||||
id: '/board'
|
||||
path: '/board'
|
||||
fullPath: '/board'
|
||||
preLoaderRoute: typeof BoardRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/automations': {
|
||||
id: '/automations'
|
||||
path: '/automations'
|
||||
fullPath: '/automations'
|
||||
preLoaderRoute: typeof AutomationsRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
AutomationsRoute: AutomationsRoute,
|
||||
BoardRoute: BoardRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
import type { getRouter } from './router.tsx'
|
||||
import type { createStart } from '@tanstack/react-start'
|
||||
declare module '@tanstack/react-start' {
|
||||
interface Register {
|
||||
ssr: true
|
||||
router: Awaited<ReturnType<typeof getRouter>>
|
||||
}
|
||||
}
|
||||
20
apps/kanban/src/router.tsx
Normal file
20
apps/kanban/src/router.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createRouter } from '@tanstack/react-router'
|
||||
import { routeTree } from './routeTree.gen'
|
||||
|
||||
// TanStack Start discovers this `getRouter` factory to build a router per
|
||||
// request (server) and once on the client (hydration). routeTree.gen.ts is
|
||||
// generated by the tanstackStart() Vite plugin from src/routes/** — don't
|
||||
// hand-edit it.
|
||||
export function getRouter() {
|
||||
return createRouter({
|
||||
routeTree,
|
||||
scrollRestoration: true,
|
||||
defaultPreload: 'intent',
|
||||
})
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: ReturnType<typeof getRouter>
|
||||
}
|
||||
}
|
||||
111
apps/kanban/src/routes/__root.tsx
Normal file
111
apps/kanban/src/routes/__root.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import { createRootRoute, HeadContent, Link, Outlet, Scripts } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { defaultLocale, localeDir } from '../i18n/config'
|
||||
import { classifyHealth, sortCards, type Card } from '../lib/kanban'
|
||||
import { formatApiError, makeApi } from '../lib/api'
|
||||
import '../i18n/config'
|
||||
import '../index.css'
|
||||
|
||||
export interface BoardData {
|
||||
cards: Card[]
|
||||
error: string | null
|
||||
}
|
||||
|
||||
// Root route renders the full HTML document for SSR (owns <html lang dir> so the
|
||||
// tree mirrors for RTL locales) and the shared board chrome. Its loader fetches
|
||||
// the cards once on the server; child routes read this same data via
|
||||
// RootRoute.useLoaderData(), and mutations call router.invalidate() to re-run it.
|
||||
export const Route = createRootRoute({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ charSet: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ title: 'Kanban Board' },
|
||||
],
|
||||
}),
|
||||
loader: async (): Promise<BoardData> => {
|
||||
try {
|
||||
const { cards } = await makeApi().invoke('listCards', {})
|
||||
return { cards: sortCards(cards as Card[]), error: null }
|
||||
} catch (thrown: unknown) {
|
||||
let message: string
|
||||
try {
|
||||
message = await formatApiError(thrown)
|
||||
} catch {
|
||||
message = 'Request failed'
|
||||
}
|
||||
return { cards: [], error: message }
|
||||
}
|
||||
},
|
||||
component: RootDocument,
|
||||
})
|
||||
|
||||
function RootDocument() {
|
||||
const locale = defaultLocale
|
||||
const { t } = useTranslation()
|
||||
const { cards, error } = Route.useLoaderData()
|
||||
const health = classifyHealth(cards)
|
||||
|
||||
return (
|
||||
<html lang={locale} dir={localeDir(locale)}>
|
||||
<head>
|
||||
<HeadContent />
|
||||
</head>
|
||||
<body>
|
||||
<div className="app-shell">
|
||||
<header className="hero">
|
||||
<div className="hero-copy">
|
||||
<div className="eyebrow">{t('shell.eyebrow')}</div>
|
||||
<h1>{t('shell.title')}</h1>
|
||||
<p>{t('shell.subtitle')}</p>
|
||||
</div>
|
||||
<div className="hero-status">
|
||||
<div className="hero-stat">
|
||||
<span>{t('shell.stats.boardHealth')}</span>
|
||||
<strong data-tone={health}>{t(`health.${health}`)}</strong>
|
||||
</div>
|
||||
<div className="hero-stat">
|
||||
<span>{t('shell.stats.cardsTracked')}</span>
|
||||
<strong>{cards.length}</strong>
|
||||
</div>
|
||||
<div className="hero-stat">
|
||||
<span>{t('shell.stats.runtime')}</span>
|
||||
<strong>{error ? t('shell.runtimeDegraded') : t('shell.runtimeConnected')}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav className="top-nav">
|
||||
<Link
|
||||
to="/"
|
||||
className="nav-link"
|
||||
activeOptions={{ exact: true }}
|
||||
activeProps={{ className: 'nav-link is-active' }}
|
||||
>
|
||||
{t('shell.nav.overview')}
|
||||
</Link>
|
||||
<Link
|
||||
to="/board"
|
||||
className="nav-link"
|
||||
activeProps={{ className: 'nav-link is-active' }}
|
||||
>
|
||||
{t('shell.nav.board')}
|
||||
</Link>
|
||||
<Link
|
||||
to="/automations"
|
||||
className="nav-link"
|
||||
activeProps={{ className: 'nav-link is-active' }}
|
||||
>
|
||||
{t('shell.nav.automations')}
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<main className="page-content">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
63
apps/kanban/src/routes/automations.tsx
Normal file
63
apps/kanban/src/routes/automations.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Route as RootRoute } from './__root'
|
||||
|
||||
export const Route = createFileRoute('/automations')({
|
||||
component: AutomationsPage,
|
||||
})
|
||||
|
||||
function AutomationsPage() {
|
||||
const { t } = useTranslation()
|
||||
const { cards } = RootRoute.useLoaderData()
|
||||
const total = cards.length
|
||||
const active = cards.filter((card) => card.status === 'doing').length
|
||||
|
||||
const rails = [
|
||||
{
|
||||
key: 'onboarding',
|
||||
state: total > 0 ? 'active' : 'idle',
|
||||
},
|
||||
{
|
||||
key: 'queue',
|
||||
state: active > 0 ? 'warming' : 'standby',
|
||||
},
|
||||
{
|
||||
key: 'surface',
|
||||
state: 'ready',
|
||||
},
|
||||
] as const
|
||||
|
||||
return (
|
||||
<section className="page-grid">
|
||||
<div className="panel wide">
|
||||
<div className="section-heading">
|
||||
<h2>{t('automations.heading')}</h2>
|
||||
<p>{t('automations.sub')}</p>
|
||||
</div>
|
||||
<div className="automation-list">
|
||||
{rails.map((rail) => (
|
||||
<article key={rail.key} className="automation-card">
|
||||
<div className="automation-topline">
|
||||
<h3>{t(`automations.rails.${rail.key}.name`)}</h3>
|
||||
<span data-state={rail.state}>{t(`automations.states.${rail.state}`)}</span>
|
||||
</div>
|
||||
<p>{t(`automations.rails.${rail.key}.detail`)}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="panel">
|
||||
<div className="section-heading">
|
||||
<h2>{t('automations.why.heading')}</h2>
|
||||
<p>{t('automations.why.sub')}</p>
|
||||
</div>
|
||||
<ul className="bullet-list">
|
||||
{(t('automations.why.bullets', { returnObjects: true }) as string[]).map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
121
apps/kanban/src/routes/board.tsx
Normal file
121
apps/kanban/src/routes/board.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import { useState } from 'react'
|
||||
import { createFileRoute, useRouter } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Route as RootRoute } from './__root'
|
||||
import { formatApiError, makeApi } from '../lib/api'
|
||||
import { type CardStatus, STATUSES, prettyStatus, relativeTime } from '../lib/kanban'
|
||||
|
||||
export const Route = createFileRoute('/board')({
|
||||
component: BoardPage,
|
||||
})
|
||||
|
||||
function BoardPage() {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const { cards, error } = RootRoute.useLoaderData()
|
||||
const [title, setTitle] = useState('')
|
||||
const [status, setStatus] = useState<CardStatus>('todo')
|
||||
const [pending, setPending] = useState(false)
|
||||
const [createError, setCreateError] = useState<string | null>(null)
|
||||
|
||||
const columns = STATUSES.map((columnStatus) => ({
|
||||
key: columnStatus,
|
||||
title: prettyStatus(columnStatus),
|
||||
cards: cards.filter((card) => card.status === columnStatus),
|
||||
}))
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!title.trim()) return
|
||||
setPending(true)
|
||||
setCreateError(null)
|
||||
try {
|
||||
await makeApi().invoke('createCard', { title: title.trim(), status })
|
||||
setTitle('')
|
||||
await router.invalidate()
|
||||
} catch (err) {
|
||||
setCreateError(await formatApiError(err))
|
||||
} finally {
|
||||
setPending(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="page-grid">
|
||||
<div className="panel wide">
|
||||
<div className="section-heading">
|
||||
<h2>{t('board.heading')}</h2>
|
||||
<p>{t('board.sub')}</p>
|
||||
</div>
|
||||
{error ? (
|
||||
<div className="empty-state error-state">{t('board.loadFailed', { error })}</div>
|
||||
) : (
|
||||
<div className="board-grid">
|
||||
{columns.map((column) => (
|
||||
<div key={column.key} className="column">
|
||||
<div className="column-header">
|
||||
<h3>{column.title}</h3>
|
||||
<span>{column.cards.length}</span>
|
||||
</div>
|
||||
<div className="column-cards">
|
||||
{column.cards.length === 0 ? (
|
||||
<div className="column-empty">{t('board.columnEmpty')}</div>
|
||||
) : (
|
||||
column.cards.map((card) => (
|
||||
<article key={card.cardId} className="task-card">
|
||||
<div className="task-card-title">{card.title}</div>
|
||||
<div className="task-card-meta">
|
||||
<span>#{card.position}</span>
|
||||
<span>{relativeTime(card.createdAt)}</span>
|
||||
</div>
|
||||
</article>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="panel">
|
||||
<div className="section-heading">
|
||||
<h2>{t('board.createHeading')}</h2>
|
||||
<p>{t('board.createSub')}</p>
|
||||
</div>
|
||||
<form className="card-form" onSubmit={handleSubmit}>
|
||||
<label className="field">
|
||||
<span>{t('board.titleLabel')}</span>
|
||||
<input
|
||||
value={title}
|
||||
onChange={(event) => setTitle(event.target.value)}
|
||||
placeholder={t('board.titlePlaceholder')}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="field">
|
||||
<span>{t('board.columnLabel')}</span>
|
||||
<select
|
||||
value={status}
|
||||
onChange={(event) => setStatus(event.target.value as CardStatus)}
|
||||
>
|
||||
{STATUSES.map((value) => (
|
||||
<option key={value} value={value}>
|
||||
{prettyStatus(value)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<button type="submit" className="primary-button" disabled={pending || !title.trim()}>
|
||||
{pending ? t('board.creating') : t('board.createCta')}
|
||||
</button>
|
||||
|
||||
{createError && (
|
||||
<div className="inline-error">{t('board.createFailed', { error: createError })}</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
105
apps/kanban/src/routes/index.tsx
Normal file
105
apps/kanban/src/routes/index.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Route as RootRoute } from './__root'
|
||||
import { classifyHealth, prettyStatus, relativeTime } from '../lib/kanban'
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: OverviewPage,
|
||||
})
|
||||
|
||||
function KpiCard(props: { label: string; value: string; detail: string }) {
|
||||
return (
|
||||
<article className="kpi-card">
|
||||
<span>{props.label}</span>
|
||||
<strong>{props.value}</strong>
|
||||
<small>{props.detail}</small>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
function OverviewPage() {
|
||||
const { t } = useTranslation()
|
||||
const { cards, error } = RootRoute.useLoaderData()
|
||||
const todo = cards.filter((card) => card.status === 'todo').length
|
||||
const doing = cards.filter((card) => card.status === 'doing').length
|
||||
const done = cards.filter((card) => card.status === 'done').length
|
||||
const newest = cards.at(-1) ?? null
|
||||
const health = classifyHealth(cards)
|
||||
|
||||
return (
|
||||
<section className="page-grid">
|
||||
<div className="panel wide">
|
||||
<div className="section-heading">
|
||||
<h2>{t('overview.heading')}</h2>
|
||||
<p>{t('overview.sub')}</p>
|
||||
</div>
|
||||
{error ? (
|
||||
<div className="empty-state error-state">{t('overview.loadFailed', { error })}</div>
|
||||
) : (
|
||||
<div className="kpi-grid">
|
||||
<KpiCard
|
||||
label={t('overview.kpi.boardHealth')}
|
||||
value={t(`health.${health}`)}
|
||||
detail={t('overview.kpi.boardHealthDetail')}
|
||||
/>
|
||||
<KpiCard
|
||||
label={t('overview.kpi.backlog')}
|
||||
value={String(todo)}
|
||||
detail={t('overview.kpi.backlogDetail')}
|
||||
/>
|
||||
<KpiCard
|
||||
label={t('overview.kpi.inProgress')}
|
||||
value={String(doing)}
|
||||
detail={t('overview.kpi.inProgressDetail')}
|
||||
/>
|
||||
<KpiCard
|
||||
label={t('overview.kpi.done')}
|
||||
value={String(done)}
|
||||
detail={t('overview.kpi.doneDetail')}
|
||||
/>
|
||||
<KpiCard
|
||||
label={t('overview.kpi.latest')}
|
||||
value={newest ? newest.title : t('overview.kpi.latestNone')}
|
||||
detail={
|
||||
newest
|
||||
? t('overview.kpi.latestDetail', {
|
||||
status: prettyStatus(newest.status),
|
||||
time: relativeTime(newest.createdAt),
|
||||
})
|
||||
: t('overview.kpi.latestEmptyDetail')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="panel">
|
||||
<div className="section-heading">
|
||||
<h2>{t('overview.executionModel.heading')}</h2>
|
||||
<p>{t('overview.executionModel.sub')}</p>
|
||||
</div>
|
||||
<ul className="bullet-list">
|
||||
{(t('overview.executionModel.bullets', { returnObjects: true }) as string[]).map(
|
||||
(bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="panel">
|
||||
<div className="section-heading">
|
||||
<h2>{t('overview.nextScreens.heading')}</h2>
|
||||
<p>{t('overview.nextScreens.sub')}</p>
|
||||
</div>
|
||||
<ul className="bullet-list">
|
||||
{(t('overview.nextScreens.bullets', { returnObjects: true }) as string[]).map(
|
||||
(bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
22
apps/kanban/tsconfig.json
Normal file
22
apps/kanban/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"allowImportingTsExtensions": false,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"types": ["vite/client", "node"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "vite.config.ts", "wrangler.jsonc"]
|
||||
}
|
||||
22
apps/kanban/vite.config.ts
Normal file
22
apps/kanban/vite.config.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
||||
|
||||
// Plain TanStack Start config (Node target) — used for local sandbox dev. This
|
||||
// template is deploy-provider agnostic: it ships NO @cloudflare/vite-plugin. At
|
||||
// deploy, fabric CI injects the CF adapter (vite.config.cf.ts merges cloudflare()
|
||||
// on top of this config) to emit the CF Workers SSR bundle. Do not add cloudflare
|
||||
// here — that would double-apply it and couple the template to a provider.
|
||||
export default defineConfig({
|
||||
plugins: [tanstackStart(), react()],
|
||||
server: {
|
||||
// Bind to 0.0.0.0 so the IPv4 loopback is reachable from Caddy's
|
||||
// reverse_proxy. Without this, Vite binds to ::1 (IPv6 localhost) in the
|
||||
// container because /etc/hosts lists ::1 before 127.0.0.1 in Node's
|
||||
// resolver order, causing Caddy's 127.0.0.1:port probe to be refused.
|
||||
host: '0.0.0.0',
|
||||
// Allow requests from any hostname (including nip.io reverse-proxy URLs
|
||||
// used in sandbox preview iframes).
|
||||
allowedHosts: true,
|
||||
},
|
||||
})
|
||||
9
apps/kanban/wrangler.jsonc
Normal file
9
apps/kanban/wrangler.jsonc
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
// Consumed by @cloudflare/vite-plugin. `main` is TanStack Start's built-in
|
||||
// server entry (no hand-written worker file); the plugin wires client assets
|
||||
// and the ssr environment automatically.
|
||||
"name": "kanban",
|
||||
"main": "@tanstack/react-start/server-entry",
|
||||
"compatibility_date": "2024-12-01",
|
||||
"compatibility_flags": ["nodejs_compat"],
|
||||
}
|
||||
6
db/annotations.ts
Normal file
6
db/annotations.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { DbClassificationMap } from '../.pikku/db/classification-map.gen.d.ts'
|
||||
|
||||
export const classifications = {
|
||||
"kanban_card": {
|
||||
},
|
||||
} satisfies DbClassificationMap
|
||||
8
db/sqlite-seed.sql
Normal file
8
db/sqlite-seed.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Demo seed data. Runs after migrations in local dev only (never in production).
|
||||
-- All statements must be idempotent (INSERT OR IGNORE / ON CONFLICT DO NOTHING).
|
||||
|
||||
INSERT INTO kanban_card (card_id, title, description, status, priority, position)
|
||||
VALUES
|
||||
('card-seed-1', 'Wire up local sandbox', 'Boot the project and verify the preview stack.', 'doing', 'high', 1),
|
||||
('card-seed-2', 'Polish board interactions', 'Tighten drag/drop and empty-state behavior.', 'todo', 'medium', 1)
|
||||
ON CONFLICT DO NOTHING;
|
||||
13
db/sqlite/0001-init.sql
Normal file
13
db/sqlite/0001-init.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE IF NOT EXISTS kanban_card (
|
||||
card_id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'todo',
|
||||
priority TEXT NOT NULL DEFAULT 'medium',
|
||||
position INTEGER NOT NULL DEFAULT 0,
|
||||
due_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS kanban_card_status_idx ON kanban_card(status);
|
||||
16
db/sqlite/0002-better-auth.sql
Normal file
16
db/sqlite/0002-better-auth.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- Generated by `pikku db generate` from pikkuBetterAuth (Better Auth).
|
||||
-- Re-run the command after changing the auth config.
|
||||
|
||||
create table "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "email_verified" integer not null, "image" text, "created_at" date not null, "updated_at" date not null);
|
||||
|
||||
create table "session" ("id" text not null primary key, "expires_at" date not null, "token" text not null unique, "created_at" date not null, "updated_at" date not null, "ip_address" text, "user_agent" text, "user_id" text not null references "user" ("id") on delete cascade);
|
||||
|
||||
create table "account" ("id" text not null primary key, "account_id" text not null, "provider_id" text not null, "user_id" text not null references "user" ("id") on delete cascade, "access_token" text, "refresh_token" text, "id_token" text, "access_token_expires_at" date, "refresh_token_expires_at" date, "scope" text, "password" text, "created_at" date not null, "updated_at" date not null);
|
||||
|
||||
create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expires_at" date not null, "created_at" date not null, "updated_at" date not null);
|
||||
|
||||
create index "session_user_id_idx" on "session" ("user_id");
|
||||
|
||||
create index "account_user_id_idx" on "account" ("user_id");
|
||||
|
||||
create index "verification_identifier_idx" on "verification" ("identifier");
|
||||
86
design/scratchpads/empty-states.tsx
Normal file
86
design/scratchpads/empty-states.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
// A scratchpad of the "non-happy" states for a list or page — loading, empty,
|
||||
// error, and skeleton. Each option is a real @mantine/core composition so the one
|
||||
// you pick drops straight into your app as a component variation. Open the Design
|
||||
// tab → Scratchpad to compare them on the canvas.
|
||||
import { Alert, Button, Center, Group, Loader, Paper, Skeleton, Stack, Text, ThemeIcon } from '@mantine/core'
|
||||
|
||||
export const title = 'Empty & loading states'
|
||||
|
||||
export const objects = [
|
||||
{
|
||||
name: 'Loading',
|
||||
mantine: ['Loader', 'Stack'],
|
||||
render: () => (
|
||||
<Stack align="center" gap="xs" maw={200} py="sm">
|
||||
<Loader size="md" color="grape" />
|
||||
<Text size="sm" c="dimmed">
|
||||
Loading your items…
|
||||
</Text>
|
||||
</Stack>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Empty',
|
||||
mantine: ['ThemeIcon', 'Stack', 'Button'],
|
||||
render: () => (
|
||||
<Stack align="center" gap="xs" maw={224} py="sm">
|
||||
<ThemeIcon size={46} radius="xl" variant="light" color="grape">
|
||||
<Text size="xl">✦</Text>
|
||||
</ThemeIcon>
|
||||
<Text size="sm" fw={600}>
|
||||
No items yet
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" ta="center">
|
||||
Create your first item to get started.
|
||||
</Text>
|
||||
<Button size="xs" variant="light" mt={4}>
|
||||
New item
|
||||
</Button>
|
||||
</Stack>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Error',
|
||||
mantine: ['Alert', 'Button'],
|
||||
render: () => (
|
||||
<Alert color="red" radius="md" title="Couldn't load" maw={236}>
|
||||
<Stack gap="xs">
|
||||
<Text size="xs">Something went wrong fetching your items.</Text>
|
||||
<Button size="xs" variant="white" color="red" w="fit-content">
|
||||
Try again
|
||||
</Button>
|
||||
</Stack>
|
||||
</Alert>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Skeleton list',
|
||||
mantine: ['Skeleton', 'Group'],
|
||||
render: () => (
|
||||
<Stack gap="sm" maw={236}>
|
||||
{[0, 1, 2].map((i) => (
|
||||
<Group key={i} wrap="nowrap" gap="sm">
|
||||
<Skeleton height={32} circle />
|
||||
<Stack gap={6} style={{ flex: 1 }}>
|
||||
<Skeleton height={8} radius="xl" />
|
||||
<Skeleton height={8} width="60%" radius="xl" />
|
||||
</Stack>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Inline empty',
|
||||
mantine: ['Paper', 'Center'],
|
||||
render: () => (
|
||||
<Paper radius="md" p="lg" maw={236} style={{ borderStyle: 'dashed' }} withBorder>
|
||||
<Center>
|
||||
<Text size="sm" c="dimmed">
|
||||
Nothing here yet — drop something in.
|
||||
</Text>
|
||||
</Center>
|
||||
</Paper>
|
||||
),
|
||||
},
|
||||
]
|
||||
154
design/scratchpads/entity-card.tsx
Normal file
154
design/scratchpads/entity-card.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
// A richer scratchpad: several ways to display the SAME entity (a team member)
|
||||
// in a list or grid. Every option is a real @mantine/core composition — picking
|
||||
// one gives you an adoptable component variation, not a pile of CSS. Open the
|
||||
// Design tab → Scratchpad to compare them on the canvas.
|
||||
import {
|
||||
Anchor,
|
||||
Avatar,
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
Divider,
|
||||
Group,
|
||||
Paper,
|
||||
Progress,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@mantine/core'
|
||||
|
||||
export const title = 'Member card'
|
||||
|
||||
export const objects = [
|
||||
{
|
||||
name: 'List row',
|
||||
mantine: ['Group', 'Avatar', 'Badge'],
|
||||
render: () => (
|
||||
<Group wrap="nowrap" maw={264} gap="sm">
|
||||
<Avatar radius="xl" color="grape">
|
||||
AL
|
||||
</Avatar>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" fw={600} truncate>
|
||||
Ada Lovelace
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
ada@example.com
|
||||
</Text>
|
||||
</div>
|
||||
<Badge color="green" variant="light" radius="sm">
|
||||
Active
|
||||
</Badge>
|
||||
</Group>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Detail card',
|
||||
mantine: ['Card', 'Avatar', 'Badge', 'Divider'],
|
||||
render: () => (
|
||||
<Card withBorder radius="md" padding="md" maw={248}>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Avatar radius="xl" color="grape">
|
||||
AL
|
||||
</Avatar>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<Text size="sm" fw={600} truncate>
|
||||
Ada Lovelace
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Engineering
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
<Badge color="green" variant="dot">
|
||||
Online
|
||||
</Badge>
|
||||
</Group>
|
||||
<Divider my="sm" />
|
||||
<Group justify="space-between">
|
||||
<Stack gap={0}>
|
||||
<Text size="xs" c="dimmed">
|
||||
Commits
|
||||
</Text>
|
||||
<Text size="sm" fw={600}>
|
||||
1,204
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack gap={0}>
|
||||
<Text size="xs" c="dimmed">
|
||||
Reviews
|
||||
</Text>
|
||||
<Text size="sm" fw={600}>
|
||||
318
|
||||
</Text>
|
||||
</Stack>
|
||||
<Anchor size="xs" fw={500}>
|
||||
View
|
||||
</Anchor>
|
||||
</Group>
|
||||
</Card>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Grid tile',
|
||||
mantine: ['Card', 'Avatar', 'Button'],
|
||||
render: () => (
|
||||
<Card withBorder radius="md" padding="lg" maw={184}>
|
||||
<Stack align="center" gap="xs">
|
||||
<Avatar size="lg" radius="xl" color="grape">
|
||||
AL
|
||||
</Avatar>
|
||||
<Text size="sm" fw={600}>
|
||||
Ada Lovelace
|
||||
</Text>
|
||||
<Badge color="grape" variant="light" radius="sm">
|
||||
Maintainer
|
||||
</Badge>
|
||||
<Button size="xs" variant="light" fullWidth mt={4}>
|
||||
View profile
|
||||
</Button>
|
||||
</Stack>
|
||||
</Card>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Compact chip',
|
||||
mantine: ['Badge', 'Avatar'],
|
||||
render: () => (
|
||||
<Badge
|
||||
size="lg"
|
||||
radius="xl"
|
||||
variant="light"
|
||||
color="grape"
|
||||
pl={3}
|
||||
leftSection={
|
||||
<Avatar size={18} radius="xl" color="grape">
|
||||
AL
|
||||
</Avatar>
|
||||
}
|
||||
>
|
||||
Ada Lovelace
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Stat card',
|
||||
mantine: ['Paper', 'Progress', 'Badge'],
|
||||
render: () => (
|
||||
<Paper withBorder radius="md" p="md" maw={224}>
|
||||
<Group justify="space-between" mb={6}>
|
||||
<Text size="xs" c="dimmed" fw={600} tt="uppercase">
|
||||
Contribution
|
||||
</Text>
|
||||
<Badge color="teal" variant="light" size="sm">
|
||||
+12%
|
||||
</Badge>
|
||||
</Group>
|
||||
<Text size="xl" fw={700} lh={1}>
|
||||
84%
|
||||
</Text>
|
||||
<Progress value={84} color="grape" radius="xl" mt="sm" />
|
||||
</Paper>
|
||||
),
|
||||
},
|
||||
]
|
||||
95
design/scratchpads/status-display.tsx
Normal file
95
design/scratchpads/status-display.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
// A scratchpad: several real, themed ways to show one thing — here, a deploy's
|
||||
// status. Each option is built from @mantine/core, so it carries your theme. The
|
||||
// design agent writes files like this; open the Design tab → Scratchpad to compare
|
||||
// them on the canvas and pick the one you like.
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Group,
|
||||
Indicator,
|
||||
Paper,
|
||||
Progress,
|
||||
RingProgress,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@mantine/core'
|
||||
|
||||
export const title = 'Status display'
|
||||
|
||||
export const objects = [
|
||||
{
|
||||
name: 'Status pill',
|
||||
mantine: ['Badge'],
|
||||
render: () => (
|
||||
<Badge color="green" variant="light" size="lg" radius="sm">
|
||||
Live
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Progress card',
|
||||
mantine: ['Paper', 'Progress'],
|
||||
render: () => (
|
||||
<Paper withBorder p="md" radius="md" maw={220}>
|
||||
<Stack gap={8}>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" fw={600}>
|
||||
Deploying
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
72%
|
||||
</Text>
|
||||
</Group>
|
||||
<Progress value={72} color="blue" radius="xl" />
|
||||
</Stack>
|
||||
</Paper>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Solid badge',
|
||||
mantine: ['Badge'],
|
||||
render: () => (
|
||||
<Badge color="green" variant="filled" size="lg" radius="sm">
|
||||
Deployed
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Progress ring',
|
||||
mantine: ['RingProgress'],
|
||||
render: () => (
|
||||
<RingProgress
|
||||
size={92}
|
||||
thickness={9}
|
||||
roundCaps
|
||||
sections={[{ value: 72, color: 'blue' }]}
|
||||
label={
|
||||
<Text ta="center" size="sm" fw={700}>
|
||||
72%
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Inline dot',
|
||||
mantine: ['Indicator'],
|
||||
render: () => (
|
||||
<Group gap={10}>
|
||||
<Indicator color="green" size={9} processing />
|
||||
<Text size="sm" fw={500}>
|
||||
Running
|
||||
</Text>
|
||||
</Group>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Tinted banner',
|
||||
mantine: ['Alert'],
|
||||
render: () => (
|
||||
<Alert color="green" radius="md" title="Deployment succeeded" maw={240}>
|
||||
Your changes are live in production.
|
||||
</Alert>
|
||||
),
|
||||
},
|
||||
]
|
||||
122
e2e/README.md
Normal file
122
e2e/README.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# E2E Test Harness
|
||||
|
||||
Cucumber + Playwright end-to-end tests for the project. The intent is that
|
||||
**every locked workflow has a `.feature` file before it gets a line of
|
||||
implementation code** (see "Phase 7.5 — Acceptance scenarios" in the
|
||||
discovery playbook).
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
e2e/
|
||||
package.json — cucumber, @playwright/test, tsx
|
||||
cucumber.mjs — runner config
|
||||
tests/
|
||||
features/*.feature — Gherkin scenarios (one feature per locked workflow)
|
||||
steps/*.steps.ts — step definitions; common.steps.ts is generic
|
||||
support/
|
||||
world.ts — AppWorld (browser, page, login, gotoApp, expectText)
|
||||
hooks.ts — spawns servers (optional), DB reset hook, browser lifecycle
|
||||
types.ts — config (URLs, timeouts) read from env
|
||||
reports/ — generated HTML reports
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
The harness assumes the app is reachable. Either:
|
||||
|
||||
**A. Start servers yourself** (recommended during dev):
|
||||
|
||||
```sh
|
||||
yarn dev # in repo root — starts frontend + backend
|
||||
yarn workspace @project/e2e test
|
||||
```
|
||||
|
||||
**B. Let the harness manage the servers**:
|
||||
|
||||
```sh
|
||||
E2E_MANAGE_SERVERS=1 \
|
||||
E2E_BACKEND_CMD="yarn dev:backend" \
|
||||
E2E_FRONTEND_CMD="yarn dev" \
|
||||
yarn workspace @project/e2e test
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
All env vars optional; defaults match the kanban-board-template's ports.
|
||||
|
||||
| Var | Default | Purpose |
|
||||
|----------------------|----------------------------|-----------------------------------------------|
|
||||
| `APP_URL` | `http://localhost:7104` | Vite frontend URL |
|
||||
| `API_URL` | `http://localhost:4003` | Pikku backend URL |
|
||||
| `E2E_TIMEOUT` | `30000` | Per-step Playwright timeout (ms) |
|
||||
| `E2E_MANAGE_SERVERS` | unset | Set to `1` to have hooks spawn dev servers |
|
||||
| `E2E_BACKEND_CMD` | `yarn dev:backend` | Used when `E2E_MANAGE_SERVERS=1` |
|
||||
| `E2E_FRONTEND_CMD` | `yarn dev` | Used when `E2E_MANAGE_SERVERS=1` |
|
||||
| `E2E_RESET_URL` | unset | POST URL that resets DB to seed between tests |
|
||||
| `HEADED` | unset | Set to `1` to run with a visible browser |
|
||||
|
||||
## DB reset between scenarios
|
||||
|
||||
Scenarios pollute each other. To get a deterministic DB state per scenario,
|
||||
implement a `__test_reset` Pikku function gated behind a test-only flag, and
|
||||
point `E2E_RESET_URL` at it. The harness will POST to it in the `Before`
|
||||
hook. If the URL is unset or unreachable the harness logs a warning and
|
||||
continues.
|
||||
|
||||
Example shape:
|
||||
|
||||
```ts
|
||||
export const testReset = pikkuSessionlessFunc({
|
||||
expose: true,
|
||||
description: 'Reset DB to seed state. Test-only — gated by NODE_ENV.',
|
||||
func: async ({ kysely, config }) => {
|
||||
if (config.env !== 'test') throw new Error('forbidden')
|
||||
await kysely.deleteFrom('booking').execute()
|
||||
// ... drop + reseed all mutable tables
|
||||
return { ok: true as const }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Writing a feature
|
||||
|
||||
```gherkin
|
||||
Feature: <one locked workflow>
|
||||
|
||||
Scenario: <one happy path>
|
||||
Given I am logged in as "demo@yoga-retreat.example" with password "demo1234"
|
||||
When I visit "/bookings"
|
||||
Then I see "Summer Yoga Retreat"
|
||||
```
|
||||
|
||||
Generic steps live in `tests/steps/common.steps.ts`:
|
||||
|
||||
- `Given I visit "<path>"`
|
||||
- `Given I am logged in as "<email>" with password "<password>"`
|
||||
- `When I log in as "..." with password "..."`
|
||||
- `When I log out`
|
||||
- `When I fill "<selector>" with "<value>"`
|
||||
- `When I click "<button label>"`
|
||||
- `Then I see "<text>"`
|
||||
- `Then I do not see "<text>"`
|
||||
- `Then the URL contains "<fragment>"`
|
||||
|
||||
Project-specific steps go in their own `*.steps.ts` files (e.g.
|
||||
`bookings.steps.ts`). Keep `common.steps.ts` framework-agnostic so the next
|
||||
project that copies this harness inherits a clean baseline.
|
||||
|
||||
## Tags
|
||||
|
||||
Run a subset by tag:
|
||||
|
||||
```sh
|
||||
yarn workspace @project/e2e test:tag '@auth'
|
||||
yarn workspace @project/e2e test:tag 'not @slow'
|
||||
```
|
||||
|
||||
Mark scenarios as `@skip` to exclude from the default run.
|
||||
|
||||
## Reports
|
||||
|
||||
After a run, open `tests/reports/cucumber-report.html`.
|
||||
20
e2e/package.json
Normal file
20
e2e/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@project/e2e",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "End-to-end test harness — Cucumber + Playwright.",
|
||||
"scripts": {
|
||||
"test": "cucumber-js --config tests/cucumber.mjs --tags 'not @skip'",
|
||||
"test:headed": "HEADED=1 cucumber-js --config tests/cucumber.mjs --tags 'not @skip'",
|
||||
"test:tag": "cucumber-js --config tests/cucumber.mjs --tags",
|
||||
"tsc": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cucumber/cucumber": "^11.0.0",
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@types/node": "^22",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "~5.8.0"
|
||||
}
|
||||
}
|
||||
7
e2e/tests/cucumber.mjs
Normal file
7
e2e/tests/cucumber.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
requireModule: ['tsx'],
|
||||
require: ['tests/support/**/*.ts', 'tests/steps/**/*.ts'],
|
||||
paths: ['tests/features/**/*.feature'],
|
||||
format: ['progress', 'html:tests/reports/cucumber-report.html'],
|
||||
forceExit: true,
|
||||
}
|
||||
0
e2e/tests/features/.gitkeep
Normal file
0
e2e/tests/features/.gitkeep
Normal file
23
e2e/tests/features/content.feature
Normal file
23
e2e/tests/features/content.feature
Normal file
@@ -0,0 +1,23 @@
|
||||
@content
|
||||
Feature: Card file attachments
|
||||
Files attached to cards are uploaded via a presigned PUT URL and accessed via
|
||||
a time-limited signed download URL. Access without a valid signature must be
|
||||
rejected by the storage backend.
|
||||
|
||||
Scenario: Upload and download a card attachment via signed URLs
|
||||
Given a card exists
|
||||
When I request an upload URL for filename "hello.txt" and content type "text/plain"
|
||||
And I PUT "Hello, Pikku!" to the upload URL
|
||||
Then the upload response status is 200 or 201
|
||||
When I request a signed download URL for filename "hello.txt"
|
||||
Then the signed URL response contains a signedUrl
|
||||
And downloading the signed URL returns "Hello, Pikku!"
|
||||
|
||||
Scenario: Unsigned access to an uploaded file is rejected
|
||||
Given a card exists
|
||||
When I request an upload URL for filename "secret.txt" and content type "text/plain"
|
||||
And I PUT "Top secret" to the upload URL
|
||||
Then the upload response status is 200 or 201
|
||||
When I request a signed download URL for filename "secret.txt"
|
||||
And I strip the signature from the signed URL
|
||||
Then fetching the unsigned URL returns a 4xx status
|
||||
7
e2e/tests/features/smoke.feature
Normal file
7
e2e/tests/features/smoke.feature
Normal file
@@ -0,0 +1,7 @@
|
||||
@smoke
|
||||
Feature: App smoke
|
||||
The harness can reach the running app.
|
||||
|
||||
Scenario: home page loads
|
||||
When I visit "/"
|
||||
Then the URL contains "/"
|
||||
58
e2e/tests/steps/common.steps.ts
Normal file
58
e2e/tests/steps/common.steps.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Given, When, Then } from '@cucumber/cucumber'
|
||||
import { expect } from '@playwright/test'
|
||||
import type { AppWorld } from '../support/world.js'
|
||||
|
||||
/**
|
||||
* Generic step library — shared across every project that copies this harness.
|
||||
*
|
||||
* Project-specific business steps (e.g. "I create a booking for <date>")
|
||||
* belong in their own *.steps.ts files. Keep this file framework-agnostic.
|
||||
*/
|
||||
|
||||
Given('I visit {string}', async function (this: AppWorld, path: string) {
|
||||
await this.gotoApp(path)
|
||||
})
|
||||
|
||||
Given(
|
||||
'I am logged in as {string} with password {string}',
|
||||
async function (this: AppWorld, email: string, password: string) {
|
||||
await this.login(email, password)
|
||||
},
|
||||
)
|
||||
|
||||
When('I log in as {string} with password {string}', async function (
|
||||
this: AppWorld,
|
||||
email: string,
|
||||
password: string,
|
||||
) {
|
||||
await this.login(email, password)
|
||||
})
|
||||
|
||||
When('I log out', async function (this: AppWorld) {
|
||||
await this.logout()
|
||||
})
|
||||
|
||||
When(
|
||||
'I fill {string} with {string}',
|
||||
async function (this: AppWorld, selector: string, value: string) {
|
||||
await this.page.locator(selector).fill(value)
|
||||
},
|
||||
)
|
||||
|
||||
When('I click {string}', async function (this: AppWorld, label: string) {
|
||||
await this.page.getByRole('button', { name: label }).first().click()
|
||||
})
|
||||
|
||||
Then('I see {string}', async function (this: AppWorld, text: string) {
|
||||
await this.expectText(text)
|
||||
})
|
||||
|
||||
Then('I do not see {string}', async function (this: AppWorld, text: string) {
|
||||
await expect(
|
||||
this.page.getByText(text, { exact: false }).first(),
|
||||
).toBeHidden()
|
||||
})
|
||||
|
||||
Then('the URL contains {string}', async function (this: AppWorld, fragment: string) {
|
||||
await this.page.waitForURL((url) => url.toString().includes(fragment))
|
||||
})
|
||||
106
e2e/tests/steps/content.steps.ts
Normal file
106
e2e/tests/steps/content.steps.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Given, When, Then } from '@cucumber/cucumber'
|
||||
import { expect } from '@playwright/test'
|
||||
import type { AppWorld } from '../support/world.js'
|
||||
import { config } from '../support/types.js'
|
||||
|
||||
interface ContentWorld extends AppWorld {
|
||||
cardId: string
|
||||
uploadUrl: string
|
||||
signedUrl: string
|
||||
uploadStatus: number
|
||||
}
|
||||
|
||||
Given('a card exists', async function (this: ContentWorld) {
|
||||
const res = await fetch(`${config.apiUrl}/cards`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ title: 'Attachment test card' }),
|
||||
})
|
||||
expect(res.ok, `POST /cards failed: ${res.status}`).toBe(true)
|
||||
const body = (await res.json()) as { cardId: string }
|
||||
this.cardId = body.cardId
|
||||
})
|
||||
|
||||
When(
|
||||
'I request an upload URL for filename {string} and content type {string}',
|
||||
async function (this: ContentWorld, fileName: string, contentType: string) {
|
||||
const res = await fetch(
|
||||
`${config.apiUrl}/cards/${this.cardId}/attachments/upload-url`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ cardId: this.cardId, fileName, contentType }),
|
||||
},
|
||||
)
|
||||
expect(res.ok, `upload-url request failed: ${res.status} ${await res.text()}`).toBe(true)
|
||||
const body = (await res.json()) as { uploadUrl: string }
|
||||
this.uploadUrl = body.uploadUrl
|
||||
},
|
||||
)
|
||||
|
||||
When(
|
||||
'I PUT {string} to the upload URL',
|
||||
async function (this: ContentWorld, content: string) {
|
||||
const res = await fetch(this.uploadUrl, {
|
||||
method: 'PUT',
|
||||
body: content,
|
||||
headers: { 'content-type': 'text/plain' },
|
||||
})
|
||||
this.uploadStatus = res.status
|
||||
},
|
||||
)
|
||||
|
||||
Then(
|
||||
'the upload response status is 200 or 201',
|
||||
function (this: ContentWorld) {
|
||||
expect(
|
||||
this.uploadStatus === 200 || this.uploadStatus === 201,
|
||||
`expected 200 or 201 from upload PUT, got ${this.uploadStatus}`,
|
||||
).toBe(true)
|
||||
},
|
||||
)
|
||||
|
||||
When(
|
||||
'I request a signed download URL for filename {string}',
|
||||
async function (this: ContentWorld, fileName: string) {
|
||||
const res = await fetch(
|
||||
`${config.apiUrl}/cards/${this.cardId}/attachments/${encodeURIComponent(fileName)}/signed-url`,
|
||||
)
|
||||
expect(res.ok, `signed-url request failed: ${res.status} ${await res.text()}`).toBe(true)
|
||||
const body = (await res.json()) as { signedUrl: string }
|
||||
this.signedUrl = body.signedUrl
|
||||
},
|
||||
)
|
||||
|
||||
Then(
|
||||
'the signed URL response contains a signedUrl',
|
||||
function (this: ContentWorld) {
|
||||
expect(typeof this.signedUrl).toBe('string')
|
||||
expect(this.signedUrl.length).toBeGreaterThan(0)
|
||||
},
|
||||
)
|
||||
|
||||
Then(
|
||||
'downloading the signed URL returns {string}',
|
||||
async function (this: ContentWorld, expectedContent: string) {
|
||||
const res = await fetch(this.signedUrl)
|
||||
expect(res.ok, `signed download failed: ${res.status}`).toBe(true)
|
||||
const text = await res.text()
|
||||
expect(text).toBe(expectedContent)
|
||||
},
|
||||
)
|
||||
|
||||
When('I strip the signature from the signed URL', function (this: ContentWorld) {
|
||||
const url = new URL(this.signedUrl)
|
||||
url.searchParams.delete('sig')
|
||||
this.signedUrl = url.toString()
|
||||
})
|
||||
|
||||
Then(
|
||||
'fetching the unsigned URL returns a 4xx status',
|
||||
async function (this: ContentWorld) {
|
||||
const res = await fetch(this.signedUrl)
|
||||
expect(res.status, `expected 4xx for unsigned access, got ${res.status}`).toBeGreaterThanOrEqual(400)
|
||||
expect(res.status).toBeLessThan(500)
|
||||
},
|
||||
)
|
||||
126
e2e/tests/support/hooks.ts
Normal file
126
e2e/tests/support/hooks.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
Before,
|
||||
After,
|
||||
BeforeAll,
|
||||
AfterAll,
|
||||
setDefaultTimeout,
|
||||
} from '@cucumber/cucumber'
|
||||
import { spawn, type ChildProcess } from 'child_process'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import type { AppWorld } from './world.js'
|
||||
import { config } from './types.js'
|
||||
|
||||
setDefaultTimeout(config.responseTimeout)
|
||||
|
||||
let backendProcess: ChildProcess | undefined
|
||||
let frontendProcess: ChildProcess | undefined
|
||||
|
||||
async function waitForUrl(url: string, label: string, timeoutMs = 60_000) {
|
||||
const deadline = Date.now() + timeoutMs
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const res = await fetch(url)
|
||||
if (res.status < 500) return
|
||||
} catch {
|
||||
// not ready yet
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 500))
|
||||
}
|
||||
throw new Error(`[e2e] ${label} did not become ready at ${url} within ${timeoutMs}ms`)
|
||||
}
|
||||
|
||||
BeforeAll(async function () {
|
||||
if (!config.manageServers) {
|
||||
// Caller is expected to start backend + frontend (e.g. via pm2, docker,
|
||||
// or a separate `yarn dev` terminal) before running tests.
|
||||
return
|
||||
}
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const repoRoot = resolve(__dirname, '../../..')
|
||||
|
||||
// Project-specific: override these commands by setting E2E_BACKEND_CMD /
|
||||
// E2E_FRONTEND_CMD env vars. The defaults assume `yarn dev` at the repo
|
||||
// root spawns the frontend, and the project provides a `dev:backend`
|
||||
// script for the API.
|
||||
const backendCmd = process.env.E2E_BACKEND_CMD ?? 'yarn dev:backend'
|
||||
const frontendCmd = process.env.E2E_FRONTEND_CMD ?? 'yarn dev'
|
||||
|
||||
backendProcess = spawn(backendCmd, {
|
||||
cwd: repoRoot,
|
||||
env: { ...process.env, NODE_ENV: 'test' },
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
detached: true,
|
||||
})
|
||||
backendProcess.stderr?.on('data', (d: Buffer) =>
|
||||
process.stderr.write(`[backend] ${d}`),
|
||||
)
|
||||
backendProcess.stdout?.on('data', (d: Buffer) =>
|
||||
process.stdout.write(`[backend] ${d}`),
|
||||
)
|
||||
|
||||
frontendProcess = spawn(frontendCmd, {
|
||||
cwd: repoRoot,
|
||||
env: { ...process.env, NODE_ENV: 'test' },
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
detached: true,
|
||||
})
|
||||
frontendProcess.stderr?.on('data', (d: Buffer) =>
|
||||
process.stderr.write(`[frontend] ${d}`),
|
||||
)
|
||||
frontendProcess.stdout?.on('data', (d: Buffer) =>
|
||||
process.stdout.write(`[frontend] ${d}`),
|
||||
)
|
||||
|
||||
await Promise.all([
|
||||
waitForUrl(config.apiUrl, 'backend'),
|
||||
waitForUrl(config.appUrl, 'frontend'),
|
||||
])
|
||||
})
|
||||
|
||||
AfterAll(async function () {
|
||||
for (const p of [backendProcess, frontendProcess]) {
|
||||
if (!p?.pid) continue
|
||||
try {
|
||||
// Detached + pgid kill takes the whole tree (vite/wrangler spawn children).
|
||||
process.kill(-p.pid, 'SIGTERM')
|
||||
} catch {
|
||||
try {
|
||||
p.kill('SIGTERM')
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Before(async function (this: AppWorld) {
|
||||
await this.openBrowser()
|
||||
if (config.resetUrl) {
|
||||
const body = config.resetRpcName
|
||||
? JSON.stringify({ rpcName: config.resetRpcName, data: {} })
|
||||
: undefined
|
||||
await fetch(config.resetUrl, {
|
||||
method: 'POST',
|
||||
headers: body ? { 'content-type': 'application/json' } : undefined,
|
||||
body,
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) {
|
||||
process.stderr.write(
|
||||
`[e2e] WARN: reset hook ${config.resetUrl} returned ${res.status}\n`,
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
process.stderr.write(`[e2e] WARN: reset hook ${config.resetUrl} unreachable\n`)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
After(async function (this: AppWorld) {
|
||||
await this.closeBrowser()
|
||||
})
|
||||
28
e2e/tests/support/types.ts
Normal file
28
e2e/tests/support/types.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// Project-level e2e configuration. Override via env vars per environment.
|
||||
//
|
||||
// Defaults match the kanban-board-template's default ports:
|
||||
// apps/kanban → tanstack-start (ssr) on 7104
|
||||
// backend → wrangler/cfw worker on 4003 (or whatever the project wires)
|
||||
|
||||
export const config = {
|
||||
/** URL of the running frontend (vite dev server or built preview). */
|
||||
appUrl: process.env.APP_URL ?? 'http://localhost:7104',
|
||||
/** URL of the running backend (Pikku API / CFW worker). */
|
||||
apiUrl: process.env.API_URL ?? 'http://localhost:4003',
|
||||
/** Per-step Playwright timeout (ms). */
|
||||
responseTimeout: Number(process.env.E2E_TIMEOUT ?? 30_000),
|
||||
/** Whether the harness should spawn the dev servers itself. */
|
||||
manageServers: process.env.E2E_MANAGE_SERVERS === '1',
|
||||
/**
|
||||
* Reset hook URL — POST here to reset the DB to seed state between scenarios.
|
||||
* Project must implement this as a sessionless RPC gated behind a test-only
|
||||
* flag (e.g. NODE_ENV !== 'production'). Leave undefined to skip reset.
|
||||
*/
|
||||
resetUrl: process.env.E2E_RESET_URL,
|
||||
/**
|
||||
* RPC name to invoke at resetUrl. When set, the reset hook posts
|
||||
* `{rpcName, data: {}}` as the body — matching pikku's public /rpc/:name
|
||||
* envelope. Leave unset for non-pikku reset endpoints.
|
||||
*/
|
||||
resetRpcName: process.env.E2E_RESET_RPC_NAME,
|
||||
} as const
|
||||
84
e2e/tests/support/world.ts
Normal file
84
e2e/tests/support/world.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { World, setWorldConstructor } from '@cucumber/cucumber'
|
||||
import {
|
||||
chromium,
|
||||
type Browser,
|
||||
type BrowserContext,
|
||||
type Page,
|
||||
} from '@playwright/test'
|
||||
import { config } from './types.js'
|
||||
|
||||
/**
|
||||
* AppWorld — generic Playwright world for portal-style apps.
|
||||
*
|
||||
* Exposes the primitives every feature needs:
|
||||
* - openBrowser / closeBrowser — lifecycle
|
||||
* - gotoApp(path) — navigate within the frontend
|
||||
* - login(email, password) — fill + submit /login, wait for redirect
|
||||
* - logout() — visit /logout
|
||||
* - expectText(text) — assert visible text
|
||||
*
|
||||
* Project-specific helpers (e.g. createBooking, assignRoom) belong in
|
||||
* step files, not here. Keep this generic.
|
||||
*/
|
||||
export class AppWorld extends World {
|
||||
browser!: Browser
|
||||
context!: BrowserContext
|
||||
page!: Page
|
||||
|
||||
async openBrowser() {
|
||||
const headed = process.env.HEADED === '1' || process.env.HEADED === 'true'
|
||||
this.browser = await chromium.launch({
|
||||
headless: !headed,
|
||||
slowMo: headed ? 150 : 0,
|
||||
})
|
||||
this.context = await this.browser.newContext(
|
||||
process.env.E2E_LOCALE ? { locale: process.env.E2E_LOCALE } : undefined,
|
||||
)
|
||||
this.page = await this.context.newPage()
|
||||
this.page.setDefaultTimeout(config.responseTimeout)
|
||||
}
|
||||
|
||||
async closeBrowser() {
|
||||
await this.context?.close()
|
||||
await this.browser?.close()
|
||||
}
|
||||
|
||||
async gotoApp(path: string) {
|
||||
const url = path.startsWith('http')
|
||||
? path
|
||||
: `${config.appUrl}${path.startsWith('/') ? path : `/${path}`}`
|
||||
await this.page.goto(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the login form on /login and waits for navigation away from it.
|
||||
* Assumes the login form has inputs named/typed `email` and a password
|
||||
* input. Override per-project if the form differs.
|
||||
*/
|
||||
async login(email: string, password: string) {
|
||||
await this.gotoApp('/login')
|
||||
await this.page.locator('input[type="email"]').fill(email)
|
||||
await this.page.locator('input[type="password"]').fill(password)
|
||||
await this.page.getByRole('button', { name: /(sign in|login|anmelden)/i }).click()
|
||||
await this.page.waitForURL((url) => !url.pathname.startsWith('/login'), {
|
||||
timeout: config.responseTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
async logout() {
|
||||
await this.gotoApp('/logout')
|
||||
}
|
||||
|
||||
async expectText(text: string) {
|
||||
await this.page.getByText(text, { exact: false }).first().waitFor({
|
||||
state: 'visible',
|
||||
timeout: config.responseTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
async getPageText(): Promise<string> {
|
||||
return this.page.innerText('body')
|
||||
}
|
||||
}
|
||||
|
||||
setWorldConstructor(AppWorld)
|
||||
14
e2e/tsconfig.json
Normal file
14
e2e/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"types": ["node"],
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["tests/**/*.ts"]
|
||||
}
|
||||
42
package.json
Normal file
42
package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@project/root",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"packages/functions/tests",
|
||||
"apps/*",
|
||||
"design/*"
|
||||
],
|
||||
"scripts": {
|
||||
"prebuild": "yarn workspace @project/functions pikku all",
|
||||
"dev": "yarn workspace @project/kanban dev",
|
||||
"tsc": "yarn workspaces foreach --all run tsc",
|
||||
"lint": "oxlint --config ../.oxlintrc.json apps packages",
|
||||
"format": "oxfmt --config ../.oxfmtrc.json apps packages",
|
||||
"format:check": "oxfmt --check --config ../.oxfmtrc.json apps packages",
|
||||
"e2e": "cd e2e && yarn test",
|
||||
"e2e:headed": "cd e2e && yarn test:headed"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pikku/addon-console": "^0.12.16",
|
||||
"@pikku/ai-vercel": "^0.12.6",
|
||||
"@pikku/better-auth": "^0.12.9",
|
||||
"@pikku/core": "^0.12.35",
|
||||
"@pikku/fetch": "^0.12.3",
|
||||
"@pikku/kysely": "^0.12.16",
|
||||
"@pikku/kysely-sqlite": "^0.12.6",
|
||||
"@pikku/node-http-server": "^0.12.2",
|
||||
"@pikku/react": "^0.12.3",
|
||||
"@pikku/schedule": "^0.12.2",
|
||||
"@pikku/schema-cfworker": "^0.12.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pikku/cli": "^0.12.45",
|
||||
"@pikku/cucumber": "^0.12.4",
|
||||
"@pikku/inspector": "^0.12.23",
|
||||
"@pikku/kysely-node-sqlite": "^0.12.2",
|
||||
"oxfmt": "^0.53.0",
|
||||
"oxlint": "^1.68.0"
|
||||
}
|
||||
}
|
||||
8
packages/components/index.ts
Normal file
8
packages/components/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
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'
|
||||
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": "^8.3.8",
|
||||
"@mantine/hooks": "^8.3.8",
|
||||
"@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": "^8.3.8",
|
||||
"@mantine/hooks": "^8.3.8",
|
||||
"@tanstack/react-query": "^5.66.0",
|
||||
"lucide-react": "^0.456.0",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5"
|
||||
}
|
||||
}
|
||||
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>
|
||||
)
|
||||
}
|
||||
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>
|
||||
)
|
||||
}
|
||||
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>
|
||||
)
|
||||
}
|
||||
54
packages/components/src/UserCard.app.stories.tsx
Normal file
54
packages/components/src/UserCard.app.stories.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { AppStory, AppStoryMeta } from './csf.types'
|
||||
import { UserCard, type User } from './UserCard'
|
||||
import { stubQuery } from './fixtures/stubQuery'
|
||||
|
||||
// App-level widget: composed from library primitives (Avatar + Badge + Card) and
|
||||
// driven by a query, so it's previewed across its named data-state scenarios in
|
||||
// the Design tab's App lens — not as simple variants.
|
||||
const meta: AppStoryMeta = {
|
||||
title: 'UserCard',
|
||||
component: UserCard,
|
||||
group: 'Account',
|
||||
description: 'Shows the current user, composed from the library Avatar + Badge.',
|
||||
tags: ['app'],
|
||||
inputs: [
|
||||
{
|
||||
name: 'query',
|
||||
kind: 'query',
|
||||
type: 'UseQueryResult<User | null>',
|
||||
description: 'Fetches the user to display.',
|
||||
},
|
||||
],
|
||||
argTypes: {
|
||||
query: { description: 'User query result (loading / error / empty / success).', control: false },
|
||||
},
|
||||
}
|
||||
export default meta
|
||||
|
||||
const sampleUser: User = {
|
||||
id: 'u_1',
|
||||
name: 'Ada Lovelace',
|
||||
email: 'ada@example.com',
|
||||
role: 'Principal Engineer',
|
||||
status: 'active',
|
||||
}
|
||||
|
||||
function LoadingScenario() {
|
||||
return <UserCard query={stubQuery.loading<User | null>()} />
|
||||
}
|
||||
export const Loading: AppStory = { tag: 'query: pending', render: LoadingScenario }
|
||||
|
||||
function ErrorScenario() {
|
||||
return <UserCard query={stubQuery.error<User | null>(new Error('Request failed'))} />
|
||||
}
|
||||
export const Errored: AppStory = { name: 'Error', tag: 'query: error', render: ErrorScenario }
|
||||
|
||||
function EmptyScenario() {
|
||||
return <UserCard query={stubQuery.empty<User | null>()} />
|
||||
}
|
||||
export const Empty: AppStory = { tag: 'query: no data', render: EmptyScenario }
|
||||
|
||||
function ActiveScenario() {
|
||||
return <UserCard query={stubQuery.success<User | null>(sampleUser)} />
|
||||
}
|
||||
export const Active: AppStory = { name: 'Active user', tag: 'query: success', render: ActiveScenario }
|
||||
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>
|
||||
)
|
||||
}
|
||||
45
packages/components/src/csf.types.ts
Normal file
45
packages/components/src/csf.types.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
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
|
||||
/** Left-menu group in the Design tab; falls back to the first tag. */
|
||||
group?: string
|
||||
tags?: string[]
|
||||
argTypes?: Record<string, ArgType>
|
||||
}
|
||||
|
||||
export interface Story {
|
||||
args?: Record<string, unknown>
|
||||
render?: ComponentType<any>
|
||||
name?: string
|
||||
}
|
||||
|
||||
/** A query or mutation an app widget consumes. Listed in the Design tab's
|
||||
* right-column inspector under the App lens. */
|
||||
export interface AppInput {
|
||||
name: string
|
||||
kind: 'query' | 'mutation'
|
||||
type?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
/** Meta for an app-level widget (`*.app.stories.tsx`). Such a widget is composed
|
||||
* from the library and driven by queries/mutations, so it's previewed across
|
||||
* named data-state scenarios rather than simple variants. */
|
||||
export interface AppStoryMeta extends StoryMeta {
|
||||
inputs?: AppInput[]
|
||||
}
|
||||
|
||||
/** One named data-state scenario of an app widget (e.g. Loading / Error / Ready).
|
||||
* `tag` annotates the data state, e.g. "query: pending". */
|
||||
export interface AppStory extends Story {
|
||||
tag?: 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"]
|
||||
}
|
||||
18
packages/functions-sdk/package.json
Normal file
18
packages/functions-sdk/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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.3",
|
||||
"@pikku/react": "^0.12.3"
|
||||
}
|
||||
}
|
||||
56
packages/functions-sdk/src/pikku/api.gen.ts
Normal file
56
packages/functions-sdk/src/pikku/api.gen.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.45
|
||||
*/
|
||||
import { useQuery, useInfiniteQuery, useMutation, type UseQueryOptions, type UseInfiniteQueryOptions, type UseMutationOptions, type InfiniteData } from '@tanstack/react-query'
|
||||
import { usePikkuRPC } from '@pikku/react'
|
||||
import type { FlattenedRPCMap } from '../../../../../../../../../../../tmp/lc/.pikku/rpc/pikku-rpc-wirings-map.gen.d.js'
|
||||
|
||||
type RPCInvoke = <Name extends keyof FlattenedRPCMap>(name: Name, data: FlattenedRPCMap[Name]['input']) => Promise<FlattenedRPCMap[Name]['output']>
|
||||
|
||||
export const usePikkuQuery = <Name extends keyof FlattenedRPCMap>(
|
||||
name: Name,
|
||||
data: FlattenedRPCMap[Name]['input'],
|
||||
options?: Omit<UseQueryOptions<FlattenedRPCMap[Name]['output'], Error>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
const rpc = usePikkuRPC<{ invoke: RPCInvoke }>()
|
||||
return useQuery<FlattenedRPCMap[Name]['output'], Error>({
|
||||
queryKey: [name, data],
|
||||
queryFn: () => rpc.invoke(name, data),
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const usePikkuMutation = <Name extends keyof FlattenedRPCMap>(
|
||||
name: Name,
|
||||
options?: Omit<UseMutationOptions<FlattenedRPCMap[Name]['output'], Error, FlattenedRPCMap[Name]['input']>, 'mutationFn'>
|
||||
) => {
|
||||
const rpc = usePikkuRPC<{ invoke: RPCInvoke }>()
|
||||
return useMutation<FlattenedRPCMap[Name]['output'], Error, FlattenedRPCMap[Name]['input']>({
|
||||
mutationFn: (data) => rpc.invoke(name, data),
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
type PaginatedKeys = {
|
||||
[K in keyof FlattenedRPCMap]: FlattenedRPCMap[K]['output'] extends { nextCursor?: string | null } ? K : never
|
||||
}[keyof FlattenedRPCMap]
|
||||
|
||||
type InfiniteOpts<Name extends PaginatedKeys> = Omit<
|
||||
UseInfiniteQueryOptions<FlattenedRPCMap[Name]['output'], Error, InfiniteData<FlattenedRPCMap[Name]['output'], string | undefined>, readonly unknown[], string | undefined>,
|
||||
'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'
|
||||
>
|
||||
|
||||
export const usePikkuInfiniteQuery = <Name extends PaginatedKeys>(
|
||||
name: Name,
|
||||
data: Omit<FlattenedRPCMap[Name]['input'], 'nextCursor'>,
|
||||
options?: InfiniteOpts<Name>
|
||||
) => {
|
||||
const rpc = usePikkuRPC<{ invoke: RPCInvoke }>()
|
||||
return useInfiniteQuery({
|
||||
queryKey: [name, data] as const,
|
||||
queryFn: ({ pageParam }: { pageParam: string | undefined }) => rpc.invoke(name, { ...data, nextCursor: pageParam } as unknown as FlattenedRPCMap[Name]['input']),
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: (lastPage: FlattenedRPCMap[Name]['output']) => (lastPage as { nextCursor?: string }).nextCursor ?? undefined,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
67
packages/functions-sdk/src/pikku/pikku-fetch.gen.ts
Normal file
67
packages/functions-sdk/src/pikku/pikku-fetch.gen.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.45
|
||||
*/
|
||||
|
||||
import { CorePikkuFetch, HTTPMethod } from '@pikku/fetch'
|
||||
import type { HTTPWiringsMap, HTTPWiringHandlerOf, HTTPWiringsWithMethod } from '../../../../../../../../../../../tmp/lc/.pikku/http/pikku-http-wirings-map.gen.d.js'
|
||||
|
||||
export class PikkuFetch extends CorePikkuFetch {
|
||||
public async post<Route extends HTTPWiringsWithMethod<'POST'>>(
|
||||
route: Route,
|
||||
...args: null extends HTTPWiringHandlerOf<Route, 'POST'>['input']
|
||||
? [data?: Exclude<HTTPWiringHandlerOf<Route, 'POST'>['input'], null>, options?: Omit<RequestInit, 'body'>]
|
||||
: [data: HTTPWiringHandlerOf<Route, 'POST'>['input'], options?: Omit<RequestInit, 'body'>]
|
||||
): Promise<HTTPWiringHandlerOf<Route, 'POST'>['output']> {
|
||||
const [data, options] = args;
|
||||
return super.api(route, 'POST', data, options);
|
||||
}
|
||||
|
||||
public async get<Route extends HTTPWiringsWithMethod<'GET'>>(
|
||||
route: Route,
|
||||
...args: null extends HTTPWiringHandlerOf<Route, 'GET'>['input']
|
||||
? [data?: Exclude<HTTPWiringHandlerOf<Route, 'GET'>['input'], null>, options?: Omit<RequestInit, 'body'>]
|
||||
: [data: HTTPWiringHandlerOf<Route, 'GET'>['input'], options?: Omit<RequestInit, 'body'>]
|
||||
): Promise<HTTPWiringHandlerOf<Route, 'GET'>['output']> {
|
||||
const [data, options] = args;
|
||||
return super.api(route, 'GET', data, options);
|
||||
}
|
||||
|
||||
public async patch<Route extends HTTPWiringsWithMethod<'PATCH'>>(
|
||||
route: Route,
|
||||
...args: null extends HTTPWiringHandlerOf<Route, 'PATCH'>['input']
|
||||
? [data?: Exclude<HTTPWiringHandlerOf<Route, 'PATCH'>['input'], null>, options?: Omit<RequestInit, 'body'>]
|
||||
: [data: HTTPWiringHandlerOf<Route, 'PATCH'>['input'], options?: Omit<RequestInit, 'body'>]
|
||||
): Promise<HTTPWiringHandlerOf<Route, 'PATCH'>['output']> {
|
||||
const [data, options] = args;
|
||||
return super.api(route, 'PATCH', data, options);
|
||||
}
|
||||
|
||||
public async head<Route extends HTTPWiringsWithMethod<'HEAD'>>(
|
||||
route: Route,
|
||||
...args: null extends HTTPWiringHandlerOf<Route, 'HEAD'>['input']
|
||||
? [data?: Exclude<HTTPWiringHandlerOf<Route, 'HEAD'>['input'], null>, options?: Omit<RequestInit, 'body'>]
|
||||
: [data: HTTPWiringHandlerOf<Route, 'HEAD'>['input'], options?: Omit<RequestInit, 'body'>]
|
||||
): Promise<HTTPWiringHandlerOf<Route, 'HEAD'>['output']> {
|
||||
const [data, options] = args;
|
||||
return super.api(route, 'HEAD', data, options);
|
||||
}
|
||||
|
||||
public async delete<Route extends HTTPWiringsWithMethod<'DELETE'>>(
|
||||
route: Route,
|
||||
...args: null extends HTTPWiringHandlerOf<Route, 'DELETE'>['input']
|
||||
? [data?: Exclude<HTTPWiringHandlerOf<Route, 'DELETE'>['input'], null>, options?: Omit<RequestInit, 'body'>]
|
||||
: [data: HTTPWiringHandlerOf<Route, 'DELETE'>['input'], options?: Omit<RequestInit, 'body'>]
|
||||
): Promise<HTTPWiringHandlerOf<Route, 'DELETE'>['output']> {
|
||||
const [data, options] = args;
|
||||
return super.api(route, 'DELETE', data, options);
|
||||
}
|
||||
|
||||
public async fetch<
|
||||
Route extends keyof HTTPWiringsMap,
|
||||
Method extends keyof HTTPWiringsMap[Route]
|
||||
>(route: Route, method: Method, data: HTTPWiringHandlerOf<Route, Method>['input'], options?: Omit<RequestInit, 'body'>): Promise<Response> {
|
||||
return await super.fetch(route, method as HTTPMethod, data, options);
|
||||
}
|
||||
}
|
||||
|
||||
export const pikkuFetch = new PikkuFetch();
|
||||
148
packages/functions-sdk/src/pikku/pikku-rpc.gen.ts
Normal file
148
packages/functions-sdk/src/pikku/pikku-rpc.gen.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.45
|
||||
*/
|
||||
|
||||
import { PikkuFetch } from "./pikku-fetch.gen.js"
|
||||
import type { RPCInvoke, TypedAgentRun, TypedStartWorkflow, TypedRunWorkflow, TypedWorkflowStatus } from '../../../../../../../../../../../tmp/lc/.pikku/rpc/pikku-rpc-wirings-map.gen.d.js'
|
||||
import type { WorkflowRunStatus } from '@pikku/core/workflow'
|
||||
|
||||
/**
|
||||
* PikkuRPC provides a type-safe client for making Remote Procedure Calls (RPC)
|
||||
* to your Pikku server. It wraps the underlying HTTP client and provides a
|
||||
* simple interface for invoking server-side functions.
|
||||
*/
|
||||
export class PikkuRPC {
|
||||
/** The underlying HTTP client used for making RPC calls */
|
||||
pikkuFetch = new PikkuFetch()
|
||||
|
||||
/**
|
||||
* Sets a custom PikkuFetch instance to use for RPC calls.
|
||||
* This allows you to configure custom settings or use a shared client instance.
|
||||
*
|
||||
* @param pikkuFetch - The PikkuFetch instance to use
|
||||
*/
|
||||
setPikkuFetch(pikkuFetch: PikkuFetch): void {
|
||||
this.pikkuFetch = pikkuFetch
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base server URL for all RPC calls.
|
||||
*
|
||||
* @param serverUrl - The base URL of your Pikku server (e.g., 'https://api.example.com')
|
||||
*/
|
||||
setServerUrl(serverUrl: string): void {
|
||||
this.pikkuFetch.setServerUrl(serverUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the JWT token for authorization on all RPC calls.
|
||||
*
|
||||
* @param jwt - The JWT token to use for authorization, or null to remove authorization
|
||||
*/
|
||||
setAuthorizationJWT(jwt: string | null): void {
|
||||
this.pikkuFetch.setAuthorizationJWT(jwt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API key for authorization on all RPC calls.
|
||||
*
|
||||
* @param apiKey - The API key to use for authorization, or null to remove the API key
|
||||
*/
|
||||
setAPIKey(apiKey: string | null): void {
|
||||
this.pikkuFetch.setAPIKey(apiKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a remote procedure call on the server.
|
||||
* This is a generic method that routes to the appropriate server function
|
||||
* based on the function name and passes the provided data.
|
||||
*
|
||||
* @param rpcName - The name of the server function to invoke
|
||||
* @param data - The data to pass to the server function
|
||||
* @returns A promise that resolves with the function's return value
|
||||
*/
|
||||
invoke = ((rpcName: string, data?: unknown) => {
|
||||
return this.pikkuFetch.post(`/rpc/${String(rpcName)}` as never, { rpcName: String(rpcName), data }) as any
|
||||
}) as RPCInvoke
|
||||
|
||||
/**
|
||||
* Starts a workflow by name with the given input.
|
||||
* Posts to \`/workflow/:workflowName/start\`.
|
||||
*
|
||||
* @param workflowName - The registered workflow name
|
||||
* @param input - The workflow input data
|
||||
* @returns A promise that resolves with the new run ID
|
||||
*/
|
||||
startWorkflow: TypedStartWorkflow = async (workflowName, input) => {
|
||||
return await this.pikkuFetch.post(`/workflow/${String(workflowName)}/start` as never, { data: input }) as any
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a workflow to completion and returns the output.
|
||||
* Posts to \`/workflow/:workflowName/run\`.
|
||||
*
|
||||
* @param workflowName - The registered workflow name
|
||||
* @param input - The workflow input data
|
||||
* @returns A promise that resolves with the workflow output
|
||||
*/
|
||||
runWorkflow: TypedRunWorkflow = async (workflowName, input) => {
|
||||
return await this.pikkuFetch.post(`/workflow/${String(workflowName)}/run` as never, { data: input }) as any
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current status of a workflow run.
|
||||
* GET \`/workflow/:workflowName/status/:runId\`.
|
||||
*
|
||||
* @param workflowName - The registered workflow name
|
||||
* @param runId - The workflow run ID
|
||||
* @returns A promise with the minimal run status
|
||||
*/
|
||||
workflowStatus: TypedWorkflowStatus = async (workflowName, runId) => {
|
||||
return await this.pikkuFetch.get(`/workflow/${String(workflowName)}/status/${runId}` as never) as unknown as WorkflowRunStatus
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent namespace — methods for running, streaming, and approving AI agents.
|
||||
* All methods post to \`/rpc/agent/:agentName/*\`.
|
||||
*/
|
||||
agent = {
|
||||
/**
|
||||
* Runs an agent to completion and returns the result.
|
||||
*
|
||||
* @param agentName - The registered agent name
|
||||
* @param input - The agent input (message, threadId, resourceId)
|
||||
* @returns A promise with runId, result, and token usage
|
||||
*/
|
||||
run: (async (agentName, input) => {
|
||||
return await this.pikkuFetch.post(`/rpc/agent/${String(agentName)}` as never, input) as any
|
||||
}) as TypedAgentRun,
|
||||
/**
|
||||
* Streams agent responses via SSE. Used for real-time chat interfaces.
|
||||
*
|
||||
* @param agentName - The registered agent name
|
||||
* @param input - The agent input (message, threadId, resourceId)
|
||||
*/
|
||||
stream: async (agentName: string, input: Record<string, unknown>) => {
|
||||
return await this.pikkuFetch.post(`/rpc/agent/${String(agentName)}/stream` as never, input) as any
|
||||
},
|
||||
/**
|
||||
* Approves or denies pending tool calls for an agent run.
|
||||
*
|
||||
* @param agentName - The registered agent name
|
||||
* @param input - The approval payload (runId, approvals array)
|
||||
*/
|
||||
approve: async (agentName: string, input: Record<string, unknown>) => {
|
||||
return await this.pikkuFetch.post(`/rpc/agent/${String(agentName)}/approve` as never, input) as any
|
||||
},
|
||||
}
|
||||
|
||||
subscribeToSSE<T = unknown>(
|
||||
path: string,
|
||||
handler: (event: T) => void,
|
||||
onError?: (err: unknown) => void
|
||||
): { close: () => void } {
|
||||
return this.pikkuFetch.subscribeToSSE(path, handler, onError)
|
||||
}
|
||||
}
|
||||
|
||||
export const pikkuRPC = new PikkuRPC();
|
||||
297
packages/functions-sdk/src/pikku/rpc-map.gen.d.ts
vendored
Normal file
297
packages/functions-sdk/src/pikku/rpc-map.gen.d.ts
vendored
Normal file
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.21
|
||||
*/
|
||||
/**
|
||||
* This provides the structure needed for typescript to be aware of RPCs and their return 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
|
||||
}
|
||||
export type CreateCardInput = {
|
||||
title: string
|
||||
status: 'todo' | 'doing' | 'done'
|
||||
priority?: ('low' | 'medium' | 'high') | undefined
|
||||
}
|
||||
export type CreateCardOutput = {
|
||||
cardId: string
|
||||
title: string
|
||||
status: string
|
||||
position: number
|
||||
createdAt: string
|
||||
}
|
||||
export type CreateCardToolInput = {
|
||||
title: string
|
||||
status: 'todo' | 'doing' | 'done'
|
||||
}
|
||||
export type CreateCardToolOutput = { type: 'text'; text: string }[]
|
||||
export type DeleteAgentThreadInput = { threadId: string; resourceId?: string }
|
||||
export type DeleteAgentThreadOutput = { deleted: boolean }
|
||||
export type EnrichCardInput = {
|
||||
title: string
|
||||
status: 'todo' | 'doing' | 'done'
|
||||
}
|
||||
export type EnrichCardOutput = {
|
||||
title: string
|
||||
status: string
|
||||
priority: 'low' | 'medium' | 'high'
|
||||
labels: string[]
|
||||
}
|
||||
export type GetAgentThreadMessagesInput = { threadId: string; resourceId?: string }
|
||||
export type GetAgentThreadMessagesOutput = any[]
|
||||
export type GetAgentThreadRunsInput = { threadId: string; resourceId?: string }
|
||||
export type GetAgentThreadRunsOutput = any[]
|
||||
export type GetAgentThreadsInput = {
|
||||
agentName?: string
|
||||
resourceId?: string
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
export type GetAgentThreadsOutput = any[]
|
||||
export type GetCardFileUploadUrlInput = {
|
||||
cardId: string
|
||||
fileName: string
|
||||
contentType: string
|
||||
}
|
||||
export type GetCardFileUploadUrlOutput = {
|
||||
uploadUrl: string
|
||||
fileKey: string
|
||||
expiresAt: string
|
||||
uploadMethod?: ('PUT' | 'POST') | undefined
|
||||
uploadHeaders?:
|
||||
| {
|
||||
[key: string]: string
|
||||
}
|
||||
| undefined
|
||||
}
|
||||
export type GraphStarterInput = { workflowName: string; nodeId: string; data?: unknown }
|
||||
export type GraphStarterOutput = { runId: string }
|
||||
export type ListCardsInput = {
|
||||
status?: ('todo' | 'doing' | 'done') | undefined
|
||||
}
|
||||
export type ListCardsOutput = {
|
||||
cards: {
|
||||
cardId: string
|
||||
title: string
|
||||
status: string
|
||||
position: number
|
||||
createdAt: string
|
||||
}[]
|
||||
}
|
||||
export type ListCardsToolOutput = { type: 'text'; text: string }[]
|
||||
export type NotifyCardCreatedInput = {
|
||||
cardId: string
|
||||
title: string
|
||||
priority: 'low' | 'medium' | 'high'
|
||||
labels: string[]
|
||||
}
|
||||
export type OnCardEventTriggerInput = {
|
||||
title: string
|
||||
status: 'todo' | 'doing' | 'done'
|
||||
source?: string | undefined
|
||||
}
|
||||
export type OnCardEventTriggerOutput = {
|
||||
cardId: string
|
||||
title: string
|
||||
status: string
|
||||
}
|
||||
export type OnCardsClearSessionInput = Record<string, never>
|
||||
export type OnCardsClearSessionOutput = { cleared: true }
|
||||
export type OnCardsConnectInput = { type: 'hello'; count: number }
|
||||
export type OnCardsEchoInput = { text: string }
|
||||
export type OnCardsEchoOutput = { echo: string }
|
||||
export type OnCardsGetSessionInput = Record<string, never>
|
||||
export type OnCardsGetSessionOutput = { session: unknown }
|
||||
export type OnCardsSetSessionInput = { userId: string; label: string }
|
||||
export type OnCardsSetSessionOutput = { set: true; label: 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 ProcessCardEventInput = {
|
||||
cardId: string
|
||||
action: string
|
||||
status: string
|
||||
createdAt: string
|
||||
}
|
||||
export type RemoteRPCHandlerInput = { rpcName: string; data?: unknown }
|
||||
export type RpcCallerInput = { rpcName: string; data?: unknown }
|
||||
export type SecretSchema_notificationWebhookSecret = string
|
||||
export type SignCardFileInput = {
|
||||
cardId: string
|
||||
fileName: string
|
||||
}
|
||||
export type SignCardFileOutput = {
|
||||
signedUrl: string
|
||||
expiresAt: string
|
||||
}
|
||||
export type VariableSchema_maxCardsPerColumn = number
|
||||
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 createCard: RPCHandler<CreateCardInput, CreateCardOutput>
|
||||
readonly getCardFileUploadUrl: RPCHandler<GetCardFileUploadUrlInput, GetCardFileUploadUrlOutput>
|
||||
readonly listCards: RPCHandler<ListCardsInput, ListCardsOutput>
|
||||
readonly signCardFile: RPCHandler<SignCardFileInput, SignCardFileOutput>
|
||||
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
|
||||
>
|
||||
}
|
||||
|
||||
// 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 '../../../functions/.pikku/workflow/pikku-workflow-map.gen.d.js'
|
||||
|
||||
import type { AgentMap } from '../../../functions/.pikku/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
|
||||
>
|
||||
41
packages/functions/bin/db-migrate.ts
Normal file
41
packages/functions/bin/db-migrate.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import postgres from 'postgres'
|
||||
import { readdir, readFile } from 'node:fs/promises'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const sqlDir = join(__dirname, '../../../sql')
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL
|
||||
if (!databaseUrl) {
|
||||
console.error('[db-migrate] DATABASE_URL is not set')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const sql = postgres(databaseUrl)
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
filename TEXT PRIMARY KEY,
|
||||
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`
|
||||
|
||||
const applied = new Set((await sql`SELECT filename FROM schema_migrations`).map((r) => r.filename))
|
||||
|
||||
const files = (await readdir(sqlDir)).filter((f) => f.endsWith('.sql')).sort()
|
||||
|
||||
for (const file of files) {
|
||||
if (applied.has(file)) {
|
||||
console.log(`[db-migrate] skip ${file} (already applied)`)
|
||||
continue
|
||||
}
|
||||
const content = await readFile(join(sqlDir, file), 'utf8')
|
||||
console.log(`[db-migrate] applying ${file}...`)
|
||||
await sql.unsafe(content)
|
||||
await sql`INSERT INTO schema_migrations (filename) VALUES (${file})`
|
||||
console.log(`[db-migrate] applied ${file}`)
|
||||
}
|
||||
|
||||
await sql.end()
|
||||
console.log('[db-migrate] done')
|
||||
5680
packages/functions/coverage/coverage-final.json
Normal file
5680
packages/functions/coverage/coverage-final.json
Normal file
File diff suppressed because it is too large
Load Diff
16669
packages/functions/coverage/tmp/coverage-57615-1780003237390-1.json
Normal file
16669
packages/functions/coverage/tmp/coverage-57615-1780003237390-1.json
Normal file
File diff suppressed because it is too large
Load Diff
133639
packages/functions/coverage/tmp/coverage-57615-1780003237413-0.json
Normal file
133639
packages/functions/coverage/tmp/coverage-57615-1780003237413-0.json
Normal file
File diff suppressed because one or more lines are too long
4106
packages/functions/coverage/tmp/coverage-57616-1780003236411-0.json
Normal file
4106
packages/functions/coverage/tmp/coverage-57616-1780003236411-0.json
Normal file
File diff suppressed because it is too large
Load Diff
33
packages/functions/package.json
Normal file
33
packages/functions/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@project/functions",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"imports": {
|
||||
"#pikku": "./.pikku/pikku-types.gen.ts",
|
||||
"#pikku/*": "./.pikku/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai-compatible": "^2.0.48",
|
||||
"@cloudflare/workers-types": "^4.20241218.0",
|
||||
"@pikku/ai-vercel": "^0.12.6",
|
||||
"@pikku/better-auth": "^0.12.9",
|
||||
"@pikku/kysely": "^0.12.16",
|
||||
"@pikku/schema-cfworker": "^0.12.2",
|
||||
"ai": "^6.0.0",
|
||||
"better-auth": "^1.6.18",
|
||||
"kysely": "^0.28.12",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pikku/ai-vercel": "^0.12.6",
|
||||
"@pikku/better-auth": "^0.12.9",
|
||||
"@pikku/cloudflare": "^0.12.10",
|
||||
"@pikku/core": "^0.12.35",
|
||||
"@pikku/kysely": "^0.12.16",
|
||||
"@pikku/kysely-node-sqlite": "^0.12.2",
|
||||
"@pikku/kysely-sqlite": "^0.12.6",
|
||||
"@pikku/schedule": "^0.12.2",
|
||||
"@pikku/schema-cfworker": "^0.12.2"
|
||||
}
|
||||
}
|
||||
4
packages/functions/packages/functions/.pikku/agent/pikku-agent-map.gen.d.ts
vendored
Normal file
4
packages/functions/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.23
|
||||
*/
|
||||
export type AgentMap = {}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
import {
|
||||
CoreAIAgent,
|
||||
PikkuAIMiddlewareHooks,
|
||||
} from '@pikku/core/ai-agent'
|
||||
import {
|
||||
agent as coreAgent,
|
||||
agentStream as coreAgentStream,
|
||||
agentResume as coreAgentResume,
|
||||
agentApprove as coreAgentApprove,
|
||||
} from '@pikku/core/ai-agent'
|
||||
import type { PikkuPermission, PikkuMiddleware, Services, PikkuFunctionConfig } from '../function/pikku-function-types.gen.js'
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
||||
import type { AIAgentMemoryConfig, AIAgentInput } from '@pikku/core/ai-agent'
|
||||
import type { AgentMap } from './pikku-agent-map.gen.d.js'
|
||||
|
||||
type AIAgentConfig<
|
||||
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
||||
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
||||
> = Omit<CoreAIAgent<PikkuPermission, PikkuMiddleware>, 'tools' | 'agents' | 'memory' | 'input' | 'output'> & {
|
||||
input?: InputSchema
|
||||
output?: OutputSchema
|
||||
memory?: Omit<AIAgentMemoryConfig, 'workingMemory'> & { workingMemory?: StandardSchemaV1 }
|
||||
tools?: object[]
|
||||
agents?: AIAgentConfig<StandardSchemaV1 | undefined, StandardSchemaV1 | undefined>[]
|
||||
}
|
||||
|
||||
export const pikkuAIAgent = <
|
||||
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
||||
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
||||
>(
|
||||
agent: AIAgentConfig<InputSchema, OutputSchema>
|
||||
) => {
|
||||
return agent
|
||||
}
|
||||
|
||||
export const pikkuAIMiddleware = <
|
||||
State extends Record<string, unknown> = Record<string, unknown>,
|
||||
RequiredServices extends Services = Services,
|
||||
>(
|
||||
hooks: PikkuAIMiddlewareHooks<State, RequiredServices>
|
||||
): PikkuAIMiddlewareHooks<State, RequiredServices> => hooks
|
||||
|
||||
export const agent = <Name extends keyof AgentMap>(
|
||||
agentName: Name
|
||||
) => {
|
||||
return coreAgent<AgentMap>(agentName as string & keyof AgentMap) as PikkuFunctionConfig<
|
||||
AIAgentInput,
|
||||
{ runId: string; result: AgentMap[Name]['output']; usage: { inputTokens: number; outputTokens: number } },
|
||||
'session' | 'rpc'
|
||||
>
|
||||
}
|
||||
|
||||
export const agentStream = <Name extends keyof AgentMap>(
|
||||
agentName?: Name
|
||||
) => {
|
||||
return coreAgentStream<AgentMap>(agentName as string & keyof AgentMap) as PikkuFunctionConfig<
|
||||
{ agentName?: string; message: string; threadId: string; resourceId: string },
|
||||
void,
|
||||
'session' | 'rpc'
|
||||
>
|
||||
}
|
||||
|
||||
export const agentResume = () => {
|
||||
return coreAgentResume() as PikkuFunctionConfig<
|
||||
{ runId: string; toolCallId: string; approved: boolean },
|
||||
void,
|
||||
'session' | 'rpc'
|
||||
>
|
||||
}
|
||||
|
||||
export const agentApprove = <Name extends keyof AgentMap>(
|
||||
agentName: Name
|
||||
) => {
|
||||
return coreAgentApprove<AgentMap>(agentName as string & keyof AgentMap) as PikkuFunctionConfig<
|
||||
{ runId: string; approvals: { toolCallId: string; approved: boolean }[] },
|
||||
unknown,
|
||||
'session' | 'rpc'
|
||||
>
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
* Channel-specific type definitions for tree-shaking optimization
|
||||
*/
|
||||
|
||||
import { CoreChannel, CorePikkuChannelMiddleware, CorePikkuChannelMiddlewareFactory, wireChannel as wireChannelCore, defineChannelRoutes as defineChannelRoutesCore, addChannelMiddleware as addChannelMiddlewareCore } from '@pikku/core/channel'
|
||||
import { AssertHTTPWiringParams } from '@pikku/core/http'
|
||||
import type { PikkuFunctionConfig, PikkuFunctionSessionless, PikkuPermission, PikkuMiddleware, Services } from '../function/pikku-function-types.gen.js'
|
||||
import type { CorePermissionGroup } from '@pikku/core'
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
||||
|
||||
/**
|
||||
* Helper type to infer the output type from a Standard Schema
|
||||
*/
|
||||
type InferSchemaOutput<T> = T extends StandardSchemaV1<any, infer Output> ? Output : never
|
||||
|
||||
/**
|
||||
* Type definition for WebSocket channels with typed data exchange.
|
||||
* Supports connection, disconnection, and message handling.
|
||||
* Accepts both session-based (PikkuFunction) and sessionless (PikkuFunctionSessionless) functions.
|
||||
*
|
||||
* @template ChannelData - Type of data exchanged through the channel
|
||||
* @template Channel - String literal type for the channel name
|
||||
*/
|
||||
type ChannelWiring<ChannelData, Channel extends string> = CoreChannel<
|
||||
ChannelData,
|
||||
Channel,
|
||||
PikkuFunctionConfig<void, any, 'channel' | 'session' | 'rpc'>,
|
||||
PikkuFunctionConfig<void, void, 'channel' | 'session' | 'rpc'>,
|
||||
PikkuFunctionConfig<any, any, 'channel' | 'session' | 'rpc'>,
|
||||
PikkuPermission,
|
||||
PikkuMiddleware
|
||||
>
|
||||
|
||||
/**
|
||||
* Creates a function that handles WebSocket channel connections.
|
||||
* Called when a client connects to a channel.
|
||||
*
|
||||
* @template Out - Output type for connection response
|
||||
* @param func - Function definition, either direct function or configuration object
|
||||
* @returns The normalized configuration object
|
||||
*/
|
||||
export const pikkuChannelConnectionFunc = <Out = unknown>(
|
||||
func:
|
||||
| PikkuFunctionSessionless<void, Out, 'channel' | 'session' | 'rpc'>
|
||||
| PikkuFunctionConfig<void, Out, 'channel' | 'session' | 'rpc'>
|
||||
) => {
|
||||
return typeof func === 'function' ? { func } : func
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that handles WebSocket channel disconnections.
|
||||
* Called when a client disconnects from a channel.
|
||||
*
|
||||
* @param func - Function definition, either direct function or configuration object
|
||||
* @returns The normalized configuration object
|
||||
*/
|
||||
export const pikkuChannelDisconnectionFunc = (
|
||||
func:
|
||||
| PikkuFunctionSessionless<void, void, 'channel'>
|
||||
| PikkuFunctionConfig<void, void, 'channel' | 'session' | 'rpc'>
|
||||
) => {
|
||||
return typeof func === 'function' ? { func } : func
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration object for channel functions with Zod schema validation.
|
||||
*/
|
||||
type PikkuChannelFuncConfigWithSchema<
|
||||
InputSchema extends StandardSchemaV1,
|
||||
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
||||
> = {
|
||||
name?: string
|
||||
tags?: string[]
|
||||
expose?: boolean
|
||||
remote?: boolean
|
||||
func: PikkuFunctionSessionless<
|
||||
InferSchemaOutput<InputSchema>,
|
||||
OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
|
||||
'channel' | 'session' | 'rpc'
|
||||
>
|
||||
auth?: boolean
|
||||
permissions?: CorePermissionGroup<PikkuPermission<InferSchemaOutput<InputSchema>>>
|
||||
middleware?: PikkuMiddleware[]
|
||||
input: InputSchema
|
||||
output?: OutputSchema
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that handles WebSocket channel messages.
|
||||
* Called when a message is received on a channel.
|
||||
*
|
||||
* Supports two patterns:
|
||||
* 1. Generic types: `pikkuChannelFunc<Input, Output>({ func: ... })`
|
||||
* 2. Zod schemas: `pikkuChannelFunc({ input: z.object(...), func: ... })`
|
||||
*
|
||||
* @template In - Input type for channel messages (inferred from schema if provided)
|
||||
* @template Out - Output type for channel responses (inferred from schema if provided)
|
||||
* @param func - Function definition, either direct function or configuration object
|
||||
* @returns The normalized configuration object
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Pattern 1: Using generic types
|
||||
* const handleMessage = pikkuChannelFunc<{text: string}, {received: boolean}>({
|
||||
* func: async (_services, { text }) => ({ received: true })
|
||||
* })
|
||||
*
|
||||
* // Pattern 2: Using Zod schemas
|
||||
* const messageInput = z.object({ text: z.string() })
|
||||
* const messageOutput = z.object({ received: z.boolean() })
|
||||
*
|
||||
* const handleMessage = pikkuChannelFunc({
|
||||
* input: messageInput,
|
||||
* output: messageOutput,
|
||||
* func: async (_services, { text }) => ({ received: true })
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function pikkuChannelFunc<
|
||||
InputSchema extends StandardSchemaV1,
|
||||
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
||||
>(
|
||||
config: PikkuChannelFuncConfigWithSchema<InputSchema, OutputSchema>
|
||||
): PikkuFunctionConfig<InferSchemaOutput<InputSchema>, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'channel' | 'session' | 'rpc'>
|
||||
export function pikkuChannelFunc<In, Out = unknown>(
|
||||
func:
|
||||
| PikkuFunctionSessionless<In, Out, 'channel' | 'session' | 'rpc'>
|
||||
| PikkuFunctionConfig<In, Out, 'channel' | 'session' | 'rpc'>
|
||||
): PikkuFunctionConfig<In, Out, 'channel' | 'session' | 'rpc'>
|
||||
export function pikkuChannelFunc(func: any) {
|
||||
return typeof func === 'function' ? { func } : func
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a WebSocket channel with the Pikku framework.
|
||||
*
|
||||
* @template ChannelData - Type of data associated with the channel
|
||||
* @template Channel - String literal type for the channel name
|
||||
* @param channel - Channel definition with connection, disconnection, and message handlers
|
||||
*/
|
||||
export const wireChannel = <ChannelData, Channel extends string>(
|
||||
channel: ChannelWiring<ChannelData, Channel> & AssertHTTPWiringParams<ChannelData, Channel>
|
||||
) => {
|
||||
wireChannelCore(channel as any)
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe helper for defining channel message routes that can be composed.
|
||||
* Returns the routes record as-is for use with wireChannel's onMessageWiring.
|
||||
*
|
||||
* @template T - Record of channel route handlers
|
||||
* @param routes - The channel routes record
|
||||
* @returns The same routes record (identity function for type safety)
|
||||
*/
|
||||
export function defineChannelRoutes<T extends Record<string, any>>(routes: T): T {
|
||||
return defineChannelRoutesCore(routes)
|
||||
}
|
||||
|
||||
export type PikkuChannelMiddleware<RequiredServices extends Services = Services, Event = unknown> = CorePikkuChannelMiddleware<RequiredServices, Event>
|
||||
|
||||
export const pikkuChannelMiddleware = <RequiredServices extends Services = Services, Event = unknown>(
|
||||
middleware: PikkuChannelMiddleware<RequiredServices, Event>
|
||||
): PikkuChannelMiddleware<RequiredServices, Event> => {
|
||||
return middleware
|
||||
}
|
||||
|
||||
export const pikkuChannelMiddlewareFactory = <In = any>(
|
||||
factory: CorePikkuChannelMiddlewareFactory<In>
|
||||
): CorePikkuChannelMiddlewareFactory<In> => {
|
||||
return factory
|
||||
}
|
||||
|
||||
export const addChannelMiddleware = (tag: string, middleware: PikkuChannelMiddleware[]) =>
|
||||
addChannelMiddlewareCore(tag, middleware, null)
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
|
||||
* CLI-specific type definitions for tree-shaking optimization
|
||||
*/
|
||||
|
||||
import { CoreCLI, wireCLI as wireCLICore, CorePikkuCLIRender, CoreCLICommandConfig, defineCLICommands as defineCLICommandsCore } from '@pikku/core/cli'
|
||||
import type { PikkuFunctionConfig, PikkuMiddleware } from '../function/pikku-function-types.gen.js'
|
||||
import type { UserSession } from '../../../../src/application-types.d.js'
|
||||
import type { SingletonServices } from '../../../../src/application-types.d.js'
|
||||
|
||||
|
||||
type Session = UserSession
|
||||
|
||||
/**
|
||||
* Type-safe CLI renderer definition that can access your application's services.
|
||||
* Use this to define custom renderers for CLI command output.
|
||||
*
|
||||
* @template Data - The output data type from the CLI command
|
||||
* @template RequiredServices - The services required for this renderer
|
||||
*/
|
||||
type PikkuCLIRender<Data, RequiredServices extends SingletonServices = SingletonServices> = CorePikkuCLIRender<Data, RequiredServices, Session>
|
||||
|
||||
/**
|
||||
* Creates a type-safe CLI renderer with access to your application's singleton services.
|
||||
* The renderer receives the full singleton services and output data to format and display results.
|
||||
*
|
||||
* @template Data - The output data type from the CLI command
|
||||
* @template RequiredServices - The minimum services required for type checking (defaults to SingletonServices)
|
||||
* @param render - Function that receives singleton services and data to render output
|
||||
* @returns A CLI renderer configuration
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const myRenderer = pikkuCLIRender<MyData>(({ logger }, data) => {
|
||||
* logger.info(data.message)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const pikkuCLIRender = <Data, RequiredServices extends SingletonServices = SingletonServices>(
|
||||
render: (services: SingletonServices, data: Data) => void | Promise<void>
|
||||
): PikkuCLIRender<Data, RequiredServices> => {
|
||||
return render as any
|
||||
}
|
||||
|
||||
/**
|
||||
* CLI command configuration with project-specific types.
|
||||
* Uses CoreCLICommandConfig from @pikku/core with local middleware and render types.
|
||||
*/
|
||||
type CLICommandConfig<Func extends PikkuFunctionConfig<In, Out, 'cli' | 'rpc' | 'session'>, In = any, Out = any, Params extends string = string> = CoreCLICommandConfig<Func, PikkuMiddleware, PikkuCLIRender<any>, Params>
|
||||
|
||||
/**
|
||||
* Type definition for CLI applications with commands and global options.
|
||||
*
|
||||
* @template Commands - Type describing the command structure
|
||||
* @template GlobalOptions - Type for global CLI options
|
||||
*/
|
||||
type CLIWiring<Commands extends Record<string, CoreCLICommandConfig<any, PikkuMiddleware, PikkuCLIRender<any>, any>>, GlobalOptions> = CoreCLI<Commands, GlobalOptions, PikkuMiddleware, PikkuCLIRender<any>>
|
||||
|
||||
/**
|
||||
* Registers a CLI application with the Pikku framework.
|
||||
* Creates command-line interfaces with type-safe commands and options.
|
||||
*
|
||||
* @template Commands - Type describing the command structure
|
||||
* @template GlobalOptions - Type for global CLI options
|
||||
* @param cli - CLI definition with program name, commands, and global options
|
||||
*/
|
||||
export const wireCLI = <Commands extends Record<string, CoreCLICommandConfig<any, PikkuMiddleware, PikkuCLIRender<any>, any>>, GlobalOptions>(
|
||||
cli: CLIWiring<Commands, GlobalOptions>
|
||||
) => {
|
||||
wireCLICore(cli as any)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CLI command definition with automatic option inference from the function's input type.
|
||||
* This allows TypeScript to automatically derive CLI options from the function signature.
|
||||
*
|
||||
* @template FuncConfig - The Pikku function config type
|
||||
* @template Params - The parameters string literal type
|
||||
* @param config - CLI command configuration
|
||||
* @returns CLI command configuration with inferred types
|
||||
*/
|
||||
export const pikkuCLICommand = <
|
||||
FuncConfig extends PikkuFunctionConfig<any, any, 'cli' | 'rpc' | 'session'>,
|
||||
Params extends string
|
||||
>(
|
||||
config: CLICommandConfig<FuncConfig, any, any, Params>
|
||||
): CoreCLICommandConfig<FuncConfig, PikkuMiddleware, PikkuCLIRender<any>, string> => {
|
||||
return config as any
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe helper for defining CLI commands that can be composed and spread into wireCLI.
|
||||
*
|
||||
* @template T - Record of CLI command configurations
|
||||
* @param commands - The CLI commands record
|
||||
* @returns The same commands record (identity function for type safety)
|
||||
*/
|
||||
export const defineCLICommands = <T extends Record<string, CoreCLICommandConfig<any, PikkuMiddleware, PikkuCLIRender<any>, any>>>(
|
||||
commands: T
|
||||
): T => {
|
||||
return defineCLICommandsCore(commands as any) as T
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
import type { FlattenedRPCMap } from '../rpc/pikku-rpc-wirings-map.internal.gen.js'
|
||||
|
||||
export type NodeCategory = never
|
||||
|
||||
export type NodeRPCName = keyof FlattenedRPCMap
|
||||
@@ -0,0 +1,709 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
* Core function, middleware, and permission types for all wirings
|
||||
*/
|
||||
|
||||
import type { CorePikkuMiddleware, CorePermissionGroup, ListInput, ListOutput, PikkuWire, PickRequired } from '@pikku/core'
|
||||
import type { CorePikkuFunctionConfig, CorePikkuAuth, CorePikkuAuthConfig, CorePikkuPermission } from '@pikku/core/function'
|
||||
import { pikkuAuth as pikkuAuthCore } from '@pikku/core/function'
|
||||
import {
|
||||
addTagMiddleware as addTagMiddlewareCore,
|
||||
addGlobalMiddleware as addGlobalMiddlewareCore,
|
||||
addTagPermission as addTagPermissionCore,
|
||||
addGlobalPermission as addGlobalPermissionCore,
|
||||
} from '@pikku/core/middleware'
|
||||
import { pikkuState as __pikkuState, CreateWireServices } from '@pikku/core/internal'
|
||||
import type { NodeType } from '@pikku/core/node'
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
||||
import { CorePikkuFunction, CorePikkuFunctionSessionless } from '@pikku/core/function'
|
||||
|
||||
import type { UserSession } from '../../../../src/application-types.d.js'
|
||||
import type { SingletonServices } from '../../../../src/application-types.d.js'
|
||||
import type { Services } from '../../../../src/application-types.d.js'
|
||||
import type { Config } from '../../../../src/application-types.d.js'
|
||||
import type { TypedPikkuRPC, FlattenedRPCMap } from '../rpc/pikku-rpc-wirings-map.internal.gen.d.js'
|
||||
import type { RequiredSingletonServices, RequiredWireServices } from '../pikku-services.gen.js'
|
||||
import type { TypedWorkflow } from '../workflow/pikku-workflow-types.gen.js'
|
||||
|
||||
export type { SingletonServices as SingletonServices }
|
||||
export type { Services as Services }
|
||||
export type Session = UserSession
|
||||
|
||||
|
||||
/**
|
||||
* Inline node configuration for function definitions.
|
||||
*/
|
||||
export type NodeConfig = {
|
||||
displayName: string
|
||||
category: string
|
||||
type: NodeType
|
||||
errorOutput?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe API permission definition that integrates with your application's session type.
|
||||
* Use this to define authorization logic for your API endpoints.
|
||||
*
|
||||
* @template In - The input type that the permission check will receive
|
||||
* @template RequiredServices - The services required for this permission check
|
||||
*/
|
||||
export type PikkuPermission<In = unknown, RequiredServices extends Services = Services> = CorePikkuPermission<In, RequiredServices, PikkuWire<In, never, false, Session>>
|
||||
|
||||
/**
|
||||
* Type-safe middleware definition that can access your application's services and session.
|
||||
* Use this to define reusable middleware that can be applied to multiple wirings.
|
||||
*
|
||||
* @template RequiredServices - The services required for this middleware
|
||||
*/
|
||||
export type PikkuMiddleware<RequiredServices extends SingletonServices = SingletonServices> = CorePikkuMiddleware<RequiredServices>
|
||||
|
||||
/**
|
||||
* Configuration object for creating a permission with metadata
|
||||
*/
|
||||
export type PikkuPermissionConfig<In = unknown, RequiredServices extends Services = Services> = {
|
||||
/** The permission function */
|
||||
func: PikkuPermission<In, RequiredServices>
|
||||
/** Optional human-readable name for the permission */
|
||||
name?: string
|
||||
/** Optional description of what the permission checks */
|
||||
description?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function for creating permissions with tree-shaking support.
|
||||
* Supports both direct function and configuration object syntax.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Direct function syntax
|
||||
* const permission = pikkuPermission(async ({ logger }, data, { session }) => {
|
||||
* const session = await session?.get()
|
||||
* return session?.role === 'admin'
|
||||
* })
|
||||
*
|
||||
* // Configuration object syntax with metadata
|
||||
* const adminPermission = pikkuPermission({
|
||||
* name: 'Admin Permission',
|
||||
* description: 'Checks if user has admin role',
|
||||
* func: async ({ logger }, data, { session }) => {
|
||||
* const session = await session?.get()
|
||||
* return session?.role === 'admin'
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const pikkuPermission = <In>(
|
||||
permission: PikkuPermission<In> | PikkuPermissionConfig<In>
|
||||
): PikkuPermission<In> => {
|
||||
return typeof permission === 'function' ? permission : permission.func
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe auth-only permission that only needs services and session.
|
||||
* Use this for upfront authorization gates (MCP tools, AI agents, workflows)
|
||||
* where request data isn't available yet.
|
||||
*
|
||||
* @template RequiredServices - The services required for this auth check
|
||||
*/
|
||||
export type PikkuAuth<RequiredServices extends SingletonServices = SingletonServices> = CorePikkuAuth<RequiredServices, Session>
|
||||
|
||||
/**
|
||||
* Configuration object for creating an auth permission with metadata
|
||||
*/
|
||||
export type PikkuAuthConfig<RequiredServices extends SingletonServices = SingletonServices> = CorePikkuAuthConfig<RequiredServices, Session>
|
||||
|
||||
/**
|
||||
* Factory function for creating auth-only permissions with tree-shaking support.
|
||||
* Auth permissions only receive services and session (no request data),
|
||||
* making them evaluable before request data is available.
|
||||
*
|
||||
* @example
|
||||
* \`\`\`typescript
|
||||
* const isAuthenticated = pikkuAuth(async ({ logger }, session) => {
|
||||
* return !!session
|
||||
* })
|
||||
*
|
||||
* const isAdmin = pikkuAuth({
|
||||
* name: 'Admin Auth',
|
||||
* description: 'Checks if user is an admin',
|
||||
* func: async ({ logger }, session) => {
|
||||
* return session?.role === 'admin'
|
||||
* }
|
||||
* })
|
||||
* \`\`\`
|
||||
*/
|
||||
export const pikkuAuth = <RequiredServices extends SingletonServices = SingletonServices>(
|
||||
auth: PikkuAuth<RequiredServices> | PikkuAuthConfig<RequiredServices>
|
||||
): PikkuPermission<any, any> => {
|
||||
return pikkuAuthCore(auth as any) as any
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration object for creating middleware with metadata
|
||||
*/
|
||||
export type PikkuMiddlewareConfig<RequiredServices extends SingletonServices = SingletonServices> = {
|
||||
/** The middleware function */
|
||||
func: PikkuMiddleware<RequiredServices>
|
||||
/** Optional human-readable name for the middleware */
|
||||
name?: string
|
||||
/** Optional description of what the middleware does */
|
||||
description?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function for creating middleware with tree-shaking support.
|
||||
* Supports both direct function and configuration object syntax.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Direct function syntax
|
||||
* const middleware = pikkuMiddleware(({ logger }, wires, next) => {
|
||||
* logger.info('Middleware executed')
|
||||
* await next()
|
||||
* })
|
||||
*
|
||||
* // Configuration object syntax with metadata
|
||||
* const logMiddleware = pikkuMiddleware({
|
||||
* name: 'Request Logger',
|
||||
* description: 'Logs all incoming requests',
|
||||
* func: async ({ logger }, wires, next) => {
|
||||
* logger.info('Request started')
|
||||
* await next()
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const pikkuMiddleware = <RequiredServices extends SingletonServices = SingletonServices>(
|
||||
middleware: PikkuMiddleware<RequiredServices> | PikkuMiddlewareConfig<RequiredServices>
|
||||
): PikkuMiddleware<RequiredServices> => {
|
||||
return typeof middleware === 'function' ? middleware : middleware.func
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function for creating middleware factories
|
||||
* Use this when your middleware needs configuration/input parameters
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* export const logMiddleware = pikkuMiddlewareFactory<LogOptions>(({
|
||||
* message,
|
||||
* level = 'info'
|
||||
* }) => {
|
||||
* return pikkuMiddleware(async ({ logger }, next) => {
|
||||
* logger[level](message)
|
||||
* await next()
|
||||
* })
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const pikkuMiddlewareFactory = <In = any>(
|
||||
factory: (input: In) => PikkuMiddleware
|
||||
): ((input: In) => PikkuMiddleware) => {
|
||||
return factory
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function for creating permission factories
|
||||
* Use this when your permission needs configuration/input parameters
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* export const requireRole = pikkuPermissionFactory<{ role: string }>(({
|
||||
* role
|
||||
* }) => {
|
||||
* return pikkuPermission(async ({ logger }, data, { session }) => {
|
||||
* if (!session || session.role !== role) {
|
||||
* logger.warn(`Permission denied: required role '${role}'`)
|
||||
* return false
|
||||
* }
|
||||
* return true
|
||||
* })
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const pikkuPermissionFactory = <In = any>(
|
||||
factory: (input: In) => PikkuPermission<any>
|
||||
): ((input: In) => PikkuPermission<any>) => {
|
||||
return factory
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that generates a human-readable description of a pending approval action.
|
||||
* Used by AI agents to show meaningful approval prompts instead of raw tool arguments.
|
||||
*
|
||||
* @template In - The input type (same as the function it describes)
|
||||
* @template RequiredServices - The services required for this description function
|
||||
*/
|
||||
export type PikkuApprovalDescription<In = unknown, RequiredServices extends Services = Services> = (
|
||||
services: RequiredServices,
|
||||
data: In
|
||||
) => Promise<string>
|
||||
|
||||
/**
|
||||
* Factory function for creating approval description functions with tree-shaking support.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* export const deleteTodoApproval = pikkuApprovalDescription(
|
||||
* async ({ todoStore }, { id }) => {
|
||||
* const todo = await todoStore.get(id)
|
||||
* return \`Delete todo: "${todo.title}"\`
|
||||
* }
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export const pikkuApprovalDescription = <In = unknown, RequiredServices extends Services = Services>(
|
||||
fn: PikkuApprovalDescription<In, RequiredServices>
|
||||
): PikkuApprovalDescription<In, RequiredServices> => {
|
||||
return fn
|
||||
}
|
||||
|
||||
/**
|
||||
* A sessionless API function that doesn't require user authentication.
|
||||
* Use this for public endpoints, health checks, or operations that don't need user context.
|
||||
*
|
||||
* @template In - The input type
|
||||
* @template Out - The output type that the function returns
|
||||
* @template RequiredServices - Services required by this function
|
||||
*/
|
||||
export type PikkuFunctionSessionless<
|
||||
In = unknown,
|
||||
Out = never,
|
||||
RequiredWires extends keyof PikkuWire = never,
|
||||
RequiredServices extends Services = Services
|
||||
> = CorePikkuFunctionSessionless<
|
||||
In,
|
||||
Out,
|
||||
RequiredServices,
|
||||
Session,
|
||||
PickRequired<PikkuWire<In, Out, false, Session, TypedPikkuRPC, null, any, TypedWorkflow>, RequiredWires>
|
||||
>
|
||||
|
||||
/**
|
||||
* A session-aware API function that requires user authentication.
|
||||
* Use this for protected endpoints that need access to user session data.
|
||||
*
|
||||
* @template In - The input type
|
||||
* @template Out - The output type that the function returns
|
||||
* @template RequiredServices - Services required by this function
|
||||
*/
|
||||
export type PikkuFunction<
|
||||
In = unknown,
|
||||
Out = never,
|
||||
RequiredWires extends keyof PikkuWire = 'session',
|
||||
RequiredServices extends Services = Services
|
||||
> = CorePikkuFunction<
|
||||
In,
|
||||
Out,
|
||||
RequiredServices,
|
||||
Session,
|
||||
PickRequired<PikkuWire<In, Out, true, Session, TypedPikkuRPC, null, any, TypedWorkflow>, RequiredWires>
|
||||
>
|
||||
|
||||
/**
|
||||
* Helper type to infer the output type from a Standard Schema
|
||||
*/
|
||||
export type InferSchemaOutput<T> = T extends StandardSchemaV1<any, infer Output> ? Output : never
|
||||
|
||||
/**
|
||||
* Configuration object for Pikku functions with optional middleware, permissions, tags, and documentation.
|
||||
* This type wraps CorePikkuFunctionConfig with the user's custom types.
|
||||
*
|
||||
* @template In - The input type
|
||||
* @template Out - The output type
|
||||
* @template PikkuFunc - The function type (can be narrowed to PikkuFunction or PikkuFunctionSessionless)
|
||||
*/
|
||||
export type PikkuFunctionConfig<
|
||||
In = unknown,
|
||||
Out = unknown,
|
||||
RequiredWires extends keyof PikkuWire = never,
|
||||
PikkuFunc extends PikkuFunction<In, Out, RequiredWires> | PikkuFunctionSessionless<In, Out, RequiredWires> = PikkuFunction<In, Out, RequiredWires> | PikkuFunctionSessionless<In, Out, RequiredWires>,
|
||||
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
||||
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
||||
> = CorePikkuFunctionConfig<PikkuFunc, PikkuPermission<In>, PikkuMiddleware, InputSchema, OutputSchema>
|
||||
|
||||
/**
|
||||
* Configuration object for Pikku functions with Zod schema validation.
|
||||
* Use this when you want to define input/output schemas using Zod.
|
||||
* Types are automatically inferred from the schemas.
|
||||
*/
|
||||
type SchemaInferred<S, Fallback = unknown> = S extends StandardSchemaV1
|
||||
? InferSchemaOutput<S>
|
||||
: Fallback
|
||||
|
||||
/**
|
||||
* Schema-overload variant for pikkuFunc. Derived from CorePikkuFunctionConfig
|
||||
* so adding a field on the core type automatically propagates here.
|
||||
*/
|
||||
export type PikkuFunctionConfigWithSchema<
|
||||
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
||||
OutputSchema extends StandardSchemaV1 | undefined = undefined,
|
||||
RequiredWires extends keyof PikkuWire = never
|
||||
> = Omit<
|
||||
CorePikkuFunctionConfig<
|
||||
| PikkuFunction<SchemaInferred<InputSchema>, SchemaInferred<OutputSchema>, RequiredWires>
|
||||
| PikkuFunctionSessionless<SchemaInferred<InputSchema>, SchemaInferred<OutputSchema>, RequiredWires>
|
||||
>,
|
||||
'func' | 'input' | 'output' | 'permissions' | 'approvalDescription'
|
||||
> & {
|
||||
func:
|
||||
| PikkuFunction<SchemaInferred<InputSchema>, SchemaInferred<OutputSchema>, RequiredWires>
|
||||
| PikkuFunctionSessionless<SchemaInferred<InputSchema>, SchemaInferred<OutputSchema>, RequiredWires>
|
||||
input?: InputSchema
|
||||
output?: OutputSchema
|
||||
permissions?: InputSchema extends StandardSchemaV1
|
||||
? CorePermissionGroup<PikkuPermission<InferSchemaOutput<InputSchema>>>
|
||||
: undefined
|
||||
approvalDescription?: InputSchema extends StandardSchemaV1
|
||||
? PikkuApprovalDescription<InferSchemaOutput<InputSchema>>
|
||||
: never
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Pikku function that can be either session-aware or sessionless.
|
||||
* This is the main function wrapper for creating API endpoints.
|
||||
*
|
||||
* Supports two patterns:
|
||||
* 1. Generic types: `pikkuFunc<Input, Output>({ func: ... })`
|
||||
* 2. Zod schemas: `pikkuFunc({ input: z.object(...), output: z.object(...), func: ... })`
|
||||
*
|
||||
* @template In - Input type for the function (inferred from schema if provided)
|
||||
* @template Out - Output type for the function (inferred from schema if provided)
|
||||
* @param func - Function definition, either direct function or configuration object
|
||||
* @returns The normalized configuration object
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Pattern 1: Using generic types
|
||||
* const createUser = pikkuFunc<{name: string, email: string}, {id: number}>({
|
||||
* func: async ({db}, input) => {
|
||||
* const user = await db.users.create(input)
|
||||
* return { id: user.id }
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* // Pattern 2: Using Zod schemas (types inferred automatically)
|
||||
* const createUserInput = z.object({ name: z.string(), email: z.string() })
|
||||
* const createUserOutput = z.object({ id: z.number() })
|
||||
*
|
||||
* const createUser = pikkuFunc({
|
||||
* input: createUserInput,
|
||||
* output: createUserOutput,
|
||||
* func: async ({db}, input) => {
|
||||
* // input is typed as { name: string, email: string }
|
||||
* const user = await db.users.create(input)
|
||||
* return { id: user.id } // must match output schema
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function pikkuFunc<
|
||||
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
||||
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
||||
>(
|
||||
config: PikkuFunctionConfigWithSchema<InputSchema, OutputSchema, 'session' | 'rpc'>
|
||||
): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'session' | 'rpc'>
|
||||
export function pikkuFunc<In, Out = unknown>(
|
||||
func:
|
||||
| PikkuFunction<In, Out, 'session' | 'rpc'>
|
||||
| PikkuFunctionConfig<In, Out, 'session' | 'rpc'>
|
||||
): PikkuFunctionConfig<In, Out, 'session' | 'rpc'>
|
||||
export function pikkuFunc(func: any) {
|
||||
return typeof func === 'function' ? { func } : func
|
||||
}
|
||||
|
||||
export type PikkuListFunction<
|
||||
F extends Record<string, unknown> = {},
|
||||
Row = unknown,
|
||||
S extends string = never
|
||||
> =
|
||||
| PikkuFunction<ListInput<F, S>, ListOutput<Row>, 'session' | 'rpc'>
|
||||
| PikkuFunctionSessionless<
|
||||
ListInput<F, S>,
|
||||
ListOutput<Row>,
|
||||
'session' | 'rpc'
|
||||
>
|
||||
|
||||
export const pikkuListFunc = <
|
||||
F extends Record<string, unknown> = {},
|
||||
Row = unknown,
|
||||
S extends string = never
|
||||
>(
|
||||
config: PikkuFunctionConfig<
|
||||
ListInput<F, S>,
|
||||
ListOutput<Row>,
|
||||
'session' | 'rpc'
|
||||
>
|
||||
): PikkuFunctionConfig<ListInput<F, S>, ListOutput<Row>, 'session' | 'rpc'> => {
|
||||
return pikkuFunc(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration object for sessionless Pikku functions with Zod schema validation.
|
||||
*/
|
||||
/**
|
||||
* Schema-overload variant for pikkuSessionlessFunc. Derived from
|
||||
* CorePikkuFunctionConfig to stay in sync with the generic-typed config.
|
||||
*/
|
||||
export type PikkuFunctionSessionlessConfigWithSchema<
|
||||
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
||||
OutputSchema extends StandardSchemaV1 | undefined = undefined,
|
||||
RequiredWires extends keyof PikkuWire = never
|
||||
> = Omit<
|
||||
CorePikkuFunctionConfig<
|
||||
PikkuFunctionSessionless<SchemaInferred<InputSchema>, SchemaInferred<OutputSchema>, RequiredWires>
|
||||
>,
|
||||
'func' | 'input' | 'output' | 'permissions' | 'approvalDescription'
|
||||
> & {
|
||||
func: PikkuFunctionSessionless<
|
||||
SchemaInferred<InputSchema>,
|
||||
SchemaInferred<OutputSchema>,
|
||||
RequiredWires
|
||||
>
|
||||
input?: InputSchema
|
||||
output?: OutputSchema
|
||||
permissions?: InputSchema extends StandardSchemaV1
|
||||
? CorePermissionGroup<PikkuPermission<InferSchemaOutput<InputSchema>>>
|
||||
: undefined
|
||||
approvalDescription?: InputSchema extends StandardSchemaV1
|
||||
? PikkuApprovalDescription<InferSchemaOutput<InputSchema>>
|
||||
: never
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a sessionless Pikku function that doesn't require user authentication.
|
||||
* Use this for public endpoints, webhooks, or background tasks.
|
||||
*
|
||||
* Supports two patterns:
|
||||
* 1. Generic types: `pikkuSessionlessFunc<Input, Output>({ func: ... })`
|
||||
* 2. Zod schemas: `pikkuSessionlessFunc({ input: z.object(...), func: ... })`
|
||||
*
|
||||
* @template In - Input type for the function (inferred from schema if provided)
|
||||
* @template Out - Output type for the function (inferred from schema if provided)
|
||||
* @param func - Function definition, either direct function or configuration object
|
||||
* @returns The normalized configuration object
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Pattern 1: Using generic types
|
||||
* const healthCheck = pikkuSessionlessFunc<void, {status: string}>({
|
||||
* func: async ({logger}) => {
|
||||
* return { status: 'healthy' }
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* // Pattern 2: Using Zod schemas
|
||||
* const greetInput = z.object({ name: z.string() })
|
||||
* const greetOutput = z.object({ message: z.string() })
|
||||
*
|
||||
* const greet = pikkuSessionlessFunc({
|
||||
* input: greetInput,
|
||||
* output: greetOutput,
|
||||
* func: async (_services, { name }) => {
|
||||
* return { message: `Hello, ${name}!` }
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function pikkuSessionlessFunc<
|
||||
InputSchema extends StandardSchemaV1 | undefined = undefined,
|
||||
OutputSchema extends StandardSchemaV1 | undefined = undefined
|
||||
>(
|
||||
config: PikkuFunctionSessionlessConfigWithSchema<InputSchema, OutputSchema, 'session' | 'rpc'>
|
||||
): PikkuFunctionConfig<InputSchema extends StandardSchemaV1 ? InferSchemaOutput<InputSchema> : unknown, OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown, 'session' | 'rpc'>
|
||||
export function pikkuSessionlessFunc<In, Out = unknown>(
|
||||
func:
|
||||
| PikkuFunctionSessionless<In, Out, 'session' | 'rpc'>
|
||||
| PikkuFunctionConfig<In, Out, 'session' | 'rpc'>
|
||||
): PikkuFunctionConfig<In, Out, 'session' | 'rpc'>
|
||||
export function pikkuSessionlessFunc(func: any) {
|
||||
return typeof func === 'function' ? { func } : func
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that takes no input and returns no output.
|
||||
* Useful for health checks, triggers, or cleanup operations.
|
||||
*
|
||||
* @param func - Function definition, either direct function or configuration object
|
||||
* @returns The normalized configuration object
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const cleanupTempFiles = pikkuVoidFunc(async ({fileSystem, logger}) => {
|
||||
* logger.info('Starting cleanup of temporary files')
|
||||
* await fileSystem.deleteDirectory('/tmp/uploads')
|
||||
* logger.info('Cleanup completed')
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const pikkuVoidFunc = (
|
||||
func:
|
||||
| PikkuFunctionSessionless<void, void, 'session' | 'rpc'>
|
||||
| PikkuFunctionConfig<void, void, 'session' | 'rpc'>
|
||||
) => {
|
||||
return typeof func === 'function' ? { func } : func
|
||||
}
|
||||
|
||||
/**
|
||||
* References a registered function by name for use in any wiring.
|
||||
* Works for both local and addon functions — resolves via RPC at runtime.
|
||||
*
|
||||
* @template Name - The function name (must be a key in FlattenedRPCMap)
|
||||
* @param rpcName - The name of the function to reference
|
||||
* @returns A Pikku function config that proxies calls via RPC
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Use in agent tools
|
||||
* tools: [ref('todos:listTodos'), ref('myLocalFunc')]
|
||||
*
|
||||
* // Use in HTTP wiring
|
||||
* wireHTTP({ route: '/greet', method: 'post', func: ref('greet') })
|
||||
* ```
|
||||
*/
|
||||
export const ref = <Name extends keyof FlattenedRPCMap>(
|
||||
rpcName: Name
|
||||
): PikkuFunctionConfig<
|
||||
FlattenedRPCMap[Name]['input'],
|
||||
FlattenedRPCMap[Name]['output'],
|
||||
'session' | 'rpc'
|
||||
> => {
|
||||
return {
|
||||
func: async (_services: any, data: FlattenedRPCMap[Name]['input'], { rpc }: any) => {
|
||||
return rpc.invoke(rpcName, data)
|
||||
}
|
||||
} as PikkuFunctionConfig<
|
||||
FlattenedRPCMap[Name]['input'],
|
||||
FlattenedRPCMap[Name]['output'],
|
||||
'session' | 'rpc'
|
||||
>
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Pikku config factory.
|
||||
* Use this to define your application's configuration factory.
|
||||
*
|
||||
* @param func - Config factory function that returns your application's config
|
||||
* @returns The config factory function
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* export const createConfig = pikkuConfig(async () => {
|
||||
* return {
|
||||
* apiUrl: process.env.API_URL || 'http://localhost:3000',
|
||||
* dbUrl: process.env.DATABASE_URL
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const pikkuConfig = (
|
||||
func: (variables?: any, ...args: any[]) => Promise<Config>
|
||||
) => func
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Pikku singleton services factory.
|
||||
* Use this to define services that are created once and shared across all requests.
|
||||
*
|
||||
* @param func - Singleton services factory function
|
||||
* @returns The singleton services factory function
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* export const createSingletonServices = pikkuServices(async (config, existingServices) => {
|
||||
* return {
|
||||
* config,
|
||||
* logger: new CustomLogger(),
|
||||
* db: await createDatabaseConnection(config.dbUrl)
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const pikkuServices = (
|
||||
func: (config: Config, existingServices: Partial<SingletonServices>) => Promise<RequiredSingletonServices>
|
||||
) => {
|
||||
return async (config: Config, existingServices: Partial<SingletonServices> = {}) => {
|
||||
const services = await func(config, existingServices)
|
||||
__pikkuState(null, 'package', 'singletonServices', services as any)
|
||||
return services
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Pikku wire services factory.
|
||||
* Use this to define services that are created per-request/session.
|
||||
*
|
||||
* @param func - Wire services factory function
|
||||
* @returns The wire services factory function
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* export const createWireServices = pikkuWireServices(async (services, wire) => {
|
||||
* const session = await wire.session?.get()
|
||||
* return {
|
||||
* userCache: new UserCache(session?.userId)
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const pikkuWireServices = (
|
||||
func: (
|
||||
services: SingletonServices,
|
||||
wire: any
|
||||
) => Promise<RequiredWireServices>
|
||||
): CreateWireServices => {
|
||||
const factories = __pikkuState(null, 'package', 'factories')
|
||||
__pikkuState(null, 'package', 'factories', { ...factories, createWireServices: func as any })
|
||||
return func as unknown as CreateWireServices
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag-scoped middleware. Applies to any wiring that carries the matching tag.
|
||||
*
|
||||
* @example
|
||||
* addTagMiddleware('admin', [adminMiddleware])
|
||||
*/
|
||||
export const addTagMiddleware = (tag: string, middleware: PikkuMiddleware[]) => {
|
||||
addTagMiddlewareCore(tag, middleware as any, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire-agnostic global middleware. Runs at the top of every wiring's
|
||||
* middleware chain — before wire-, tag-, and function-level entries.
|
||||
*
|
||||
* Resolution order: global -> wire -> tag -> function.
|
||||
*
|
||||
* @example
|
||||
* addGlobalMiddleware([telemetryMiddleware])
|
||||
*/
|
||||
export const addGlobalMiddleware = (middleware: PikkuMiddleware[]) => {
|
||||
addGlobalMiddlewareCore(middleware as any, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag-scoped permissions. Applies to any wiring that carries the matching tag.
|
||||
*
|
||||
* @example
|
||||
* addTagPermission('admin', [adminPermission])
|
||||
*/
|
||||
export const addTagPermission = <In = unknown>(tag: string, permissions: CorePermissionGroup<PikkuPermission<In>> | PikkuPermission<In>[]) => {
|
||||
addTagPermissionCore(tag, permissions as any, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire-agnostic global permissions. Runs at the top of every wiring's
|
||||
* permission resolution — before wire-, tag-, and function-level entries.
|
||||
*
|
||||
* Resolution order: global -> wire -> tag -> function.
|
||||
*
|
||||
* @example
|
||||
* addGlobalPermission([signedInUser])
|
||||
*/
|
||||
export const addGlobalPermission = <In = unknown>(permissions: CorePermissionGroup<PikkuPermission<In>> | PikkuPermission<In>[]) => {
|
||||
addGlobalPermissionCore(permissions as any, null)
|
||||
}
|
||||
|
||||
export { wireAddon } from '@pikku/core/rpc'
|
||||
export type { WireAddonConfig } from '@pikku/core/rpc'
|
||||
@@ -0,0 +1,728 @@
|
||||
{
|
||||
"pikkuConsoleSetSecret": {
|
||||
"pikkuFuncId": "pikkuConsoleSetSecret",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "pikkuConsoleSetSecret",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"secrets"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "PikkuConsoleSetSecretInput",
|
||||
"outputSchemaName": "PikkuConsoleSetSecretOutput",
|
||||
"inputs": [
|
||||
"PikkuConsoleSetSecretInput"
|
||||
],
|
||||
"outputs": [
|
||||
"PikkuConsoleSetSecretOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "c33e04e6aff5ca2e",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"description": "Set the value of a secret",
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/console.gen.ts",
|
||||
"exportedName": "pikkuConsoleSetSecret",
|
||||
"contractHash": "fcb625d86fe88b8f",
|
||||
"inputHash": "a0c8a043",
|
||||
"outputHash": "57702b41"
|
||||
},
|
||||
"pikkuConsoleGetVariable": {
|
||||
"pikkuFuncId": "pikkuConsoleGetVariable",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "pikkuConsoleGetVariable",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"variables"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "PikkuConsoleGetVariableInput",
|
||||
"outputSchemaName": "PikkuConsoleGetVariableOutput",
|
||||
"inputs": [
|
||||
"PikkuConsoleGetVariableInput"
|
||||
],
|
||||
"outputs": [
|
||||
"PikkuConsoleGetVariableOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "45c792dfb6f8b1a5",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"description": "Get the current value of a variable",
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/console.gen.ts",
|
||||
"exportedName": "pikkuConsoleGetVariable",
|
||||
"contractHash": "3b66dda4d82fa48a",
|
||||
"inputHash": "ce4b2e06",
|
||||
"outputHash": "f54f1059"
|
||||
},
|
||||
"pikkuConsoleSetVariable": {
|
||||
"pikkuFuncId": "pikkuConsoleSetVariable",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "pikkuConsoleSetVariable",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"variables"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "PikkuConsoleSetVariableInput",
|
||||
"outputSchemaName": "PikkuConsoleSetVariableOutput",
|
||||
"inputs": [
|
||||
"PikkuConsoleSetVariableInput"
|
||||
],
|
||||
"outputs": [
|
||||
"PikkuConsoleSetVariableOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "f6806d8dc9d50055",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"description": "Set the value of a variable",
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/console.gen.ts",
|
||||
"exportedName": "pikkuConsoleSetVariable",
|
||||
"contractHash": "9dda40633b173a08",
|
||||
"inputHash": "d91a4af5",
|
||||
"outputHash": "91fe907b"
|
||||
},
|
||||
"pikkuConsoleHasSecret": {
|
||||
"pikkuFuncId": "pikkuConsoleHasSecret",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "pikkuConsoleHasSecret",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"secrets"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "PikkuConsoleHasSecretInput",
|
||||
"outputSchemaName": "PikkuConsoleHasSecretOutput",
|
||||
"inputs": [
|
||||
"PikkuConsoleHasSecretInput"
|
||||
],
|
||||
"outputs": [
|
||||
"PikkuConsoleHasSecretOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "b8c818fb2291b032",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"description": "Check if a secret exists without reading its value",
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/console.gen.ts",
|
||||
"exportedName": "pikkuConsoleHasSecret",
|
||||
"contractHash": "eeff751c00461679",
|
||||
"inputHash": "a67adc7c",
|
||||
"outputHash": "1da66eac"
|
||||
},
|
||||
"pikkuConsoleGetSecret": {
|
||||
"pikkuFuncId": "pikkuConsoleGetSecret",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "pikkuConsoleGetSecret",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"secrets"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "PikkuConsoleGetSecretInput",
|
||||
"outputSchemaName": "PikkuConsoleGetSecretOutput",
|
||||
"inputs": [
|
||||
"PikkuConsoleGetSecretInput"
|
||||
],
|
||||
"outputs": [
|
||||
"PikkuConsoleGetSecretOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "5c1e39a4b0cc46a1",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"description": "Get the current value of a secret",
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/console.gen.ts",
|
||||
"exportedName": "pikkuConsoleGetSecret",
|
||||
"contractHash": "d651ad93642eaeed",
|
||||
"inputHash": "329251da",
|
||||
"outputHash": "fb675a26"
|
||||
},
|
||||
"http:get:/workflow-run/:runId/stream": {
|
||||
"pikkuFuncId": "http:get:/workflow-run/:runId/stream",
|
||||
"functionType": "inline",
|
||||
"name": "/workflow-run/:runId/stream",
|
||||
"services": {
|
||||
"optimized": false,
|
||||
"services": []
|
||||
},
|
||||
"inputSchemaName": "StreamWorkflowRunInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"StreamWorkflowRunInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"contractHash": "ab85b338c10d89e8",
|
||||
"inputHash": "76838d3e",
|
||||
"outputHash": "fc2fd4a0"
|
||||
},
|
||||
"remoteRPCHandler": {
|
||||
"pikkuFuncId": "remoteRPCHandler",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "remoteRPCHandler",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": []
|
||||
},
|
||||
"wires": {
|
||||
"optimized": true,
|
||||
"wires": [
|
||||
"rpc"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "RemoteRPCHandlerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"RemoteRPCHandlerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"remote": true,
|
||||
"implementationHash": "105018a823bdb551",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/rpc-remote.gen.ts",
|
||||
"exportedName": "remoteRPCHandler"
|
||||
},
|
||||
"workflowStarter": {
|
||||
"pikkuFuncId": "workflowStarter",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "workflowStarter",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"workflowService"
|
||||
]
|
||||
},
|
||||
"wires": {
|
||||
"optimized": true,
|
||||
"wires": [
|
||||
"rpc"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "WorkflowStarterInput",
|
||||
"outputSchemaName": "WorkflowStarterOutput",
|
||||
"inputs": [
|
||||
"WorkflowStarterInput"
|
||||
],
|
||||
"outputs": [
|
||||
"WorkflowStarterOutput"
|
||||
],
|
||||
"implementationHash": "153116058265d9d4",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"exportedName": "workflowStarter",
|
||||
"contractHash": "770c10618e2d471b",
|
||||
"inputHash": "73e8dfbd",
|
||||
"outputHash": "8f2061b7"
|
||||
},
|
||||
"workflowRunner": {
|
||||
"pikkuFuncId": "workflowRunner",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "workflowRunner",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"workflowService"
|
||||
]
|
||||
},
|
||||
"wires": {
|
||||
"optimized": true,
|
||||
"wires": [
|
||||
"rpc"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "WorkflowRunnerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"WorkflowRunnerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "4807ebd71047f932",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"exportedName": "workflowRunner",
|
||||
"contractHash": "ca617784c6d9ec8d",
|
||||
"inputHash": "3c634245",
|
||||
"outputHash": "316bdb87"
|
||||
},
|
||||
"workflowStatusChecker": {
|
||||
"pikkuFuncId": "workflowStatusChecker",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "workflowStatusChecker",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"workflowService"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "WorkflowStatusCheckerInput",
|
||||
"outputSchemaName": "WorkflowRunStatus",
|
||||
"inputs": [
|
||||
"WorkflowStatusCheckerInput"
|
||||
],
|
||||
"outputs": [
|
||||
"WorkflowRunStatus"
|
||||
],
|
||||
"implementationHash": "e4159df9ad118a6c",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"exportedName": "workflowStatusChecker",
|
||||
"contractHash": "1bb7182390464bc0",
|
||||
"inputHash": "458ba7ec",
|
||||
"outputHash": "bc4d864f"
|
||||
},
|
||||
"workflowStatusStream": {
|
||||
"pikkuFuncId": "workflowStatusStream",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "workflowStatusStream",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"workflowRunService"
|
||||
]
|
||||
},
|
||||
"wires": {
|
||||
"optimized": true,
|
||||
"wires": [
|
||||
"channel"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "WorkflowStatusStreamInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"WorkflowStatusStreamInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "233d0f04a86a4237",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"exportedName": "workflowStatusStream",
|
||||
"contractHash": "3b99296ba5064030",
|
||||
"inputHash": "c6ee79ff",
|
||||
"outputHash": "6180f124"
|
||||
},
|
||||
"workflowStatusStreamFull": {
|
||||
"pikkuFuncId": "workflowStatusStreamFull",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "workflowStatusStreamFull",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"workflowRunService"
|
||||
]
|
||||
},
|
||||
"wires": {
|
||||
"optimized": true,
|
||||
"wires": [
|
||||
"channel"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "WorkflowStatusStreamFullInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"WorkflowStatusStreamFullInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "aba5bf474596ee50",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"exportedName": "workflowStatusStreamFull",
|
||||
"contractHash": "1e7b9f00193d10ae",
|
||||
"inputHash": "2da90ff6",
|
||||
"outputHash": "dcd02549"
|
||||
},
|
||||
"graphStarter": {
|
||||
"pikkuFuncId": "graphStarter",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "graphStarter",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"workflowService"
|
||||
]
|
||||
},
|
||||
"wires": {
|
||||
"optimized": true,
|
||||
"wires": [
|
||||
"rpc"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "GraphStarterInput",
|
||||
"outputSchemaName": "GraphStarterOutput",
|
||||
"inputs": [
|
||||
"GraphStarterInput"
|
||||
],
|
||||
"outputs": [
|
||||
"GraphStarterOutput"
|
||||
],
|
||||
"implementationHash": "d12993125e177525",
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"exportedName": "graphStarter",
|
||||
"contractHash": "422510c83dee8491",
|
||||
"inputHash": "70868669",
|
||||
"outputHash": "6f988595"
|
||||
},
|
||||
"rpcCaller": {
|
||||
"pikkuFuncId": "rpcCaller",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "rpcCaller",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": []
|
||||
},
|
||||
"wires": {
|
||||
"optimized": true,
|
||||
"wires": [
|
||||
"rpc"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "RpcCallerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"RpcCallerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "f92b60dda357f483",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/rpc-public.gen.ts",
|
||||
"exportedName": "rpcCaller",
|
||||
"contractHash": "f6901fd233740bf4",
|
||||
"inputHash": "a69cacc4",
|
||||
"outputHash": "629e0b5a"
|
||||
},
|
||||
"agentCaller": {
|
||||
"pikkuFuncId": "agentCaller",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "agentCaller",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": []
|
||||
},
|
||||
"wires": {
|
||||
"optimized": true,
|
||||
"wires": [
|
||||
"rpc"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "AgentCallerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"AgentCallerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "4af198bf5bbd9bd8",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"exportedName": "agentCaller",
|
||||
"contractHash": "76619abd99f23189",
|
||||
"inputHash": "4d1eddbc",
|
||||
"outputHash": "9a3af204"
|
||||
},
|
||||
"agentStreamCaller": {
|
||||
"pikkuFuncId": "agentStreamCaller",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "agentStreamCaller",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": []
|
||||
},
|
||||
"wires": {
|
||||
"optimized": true,
|
||||
"wires": [
|
||||
"rpc"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "AgentStreamCallerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"AgentStreamCallerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "92b3cd7c92fb3de4",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"exportedName": "agentStreamCaller",
|
||||
"contractHash": "0e5c814e26d3294f",
|
||||
"inputHash": "773d4d52",
|
||||
"outputHash": "641a263e"
|
||||
},
|
||||
"agentApproveCaller": {
|
||||
"pikkuFuncId": "agentApproveCaller",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "agentApproveCaller",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": []
|
||||
},
|
||||
"wires": {
|
||||
"optimized": true,
|
||||
"wires": [
|
||||
"rpc"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "AgentApproveCallerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"AgentApproveCallerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "ae5d4bfb0293119a",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"exportedName": "agentApproveCaller",
|
||||
"contractHash": "83a9293c4b51d65c",
|
||||
"inputHash": "f1628b7c",
|
||||
"outputHash": "ef71f08a"
|
||||
},
|
||||
"agentResumeCaller": {
|
||||
"pikkuFuncId": "agentResumeCaller",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "agentResumeCaller",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": []
|
||||
},
|
||||
"wires": {
|
||||
"optimized": true,
|
||||
"wires": [
|
||||
"rpc"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "AgentResumeCallerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"AgentResumeCallerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "d17ccf1259ba4a60",
|
||||
"tags": [
|
||||
"pikku"
|
||||
],
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"exportedName": "agentResumeCaller",
|
||||
"contractHash": "0648ffb2cfa9d861",
|
||||
"inputHash": "f25b50dd",
|
||||
"outputHash": "ee89e454"
|
||||
},
|
||||
"getAgentThreads": {
|
||||
"pikkuFuncId": "getAgentThreads",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "getAgentThreads",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"agentRunService"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "GetAgentThreadsInput",
|
||||
"outputSchemaName": "GetAgentThreadsOutput",
|
||||
"inputs": [
|
||||
"GetAgentThreadsInput"
|
||||
],
|
||||
"outputs": [
|
||||
"GetAgentThreadsOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "3823f8f3911c48e2",
|
||||
"title": "Get Agent Threads",
|
||||
"tags": [
|
||||
"pikku",
|
||||
"pikku:agent"
|
||||
],
|
||||
"description": "Returns a list of AI agent threads from storage. Accepts optional filters: agentName, resourceId, limit, and offset for pagination.",
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"exportedName": "getAgentThreads",
|
||||
"contractHash": "d629208347702d27",
|
||||
"inputHash": "41f5350c",
|
||||
"outputHash": "6598bdff"
|
||||
},
|
||||
"getAgentThreadMessages": {
|
||||
"pikkuFuncId": "getAgentThreadMessages",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "getAgentThreadMessages",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"agentRunService"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "GetAgentThreadMessagesInput",
|
||||
"outputSchemaName": "GetAgentThreadMessagesOutput",
|
||||
"inputs": [
|
||||
"GetAgentThreadMessagesInput"
|
||||
],
|
||||
"outputs": [
|
||||
"GetAgentThreadMessagesOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "dffe581287205c6e",
|
||||
"title": "Get Agent Thread Messages",
|
||||
"tags": [
|
||||
"pikku",
|
||||
"pikku:agent"
|
||||
],
|
||||
"description": "Returns all messages for a given AI agent thread, ordered by creation time.",
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"exportedName": "getAgentThreadMessages",
|
||||
"contractHash": "44e43e96024680f3",
|
||||
"inputHash": "a158e652",
|
||||
"outputHash": "04960780"
|
||||
},
|
||||
"getAgentThreadRuns": {
|
||||
"pikkuFuncId": "getAgentThreadRuns",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "getAgentThreadRuns",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"agentRunService"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "GetAgentThreadRunsInput",
|
||||
"outputSchemaName": "GetAgentThreadRunsOutput",
|
||||
"inputs": [
|
||||
"GetAgentThreadRunsInput"
|
||||
],
|
||||
"outputs": [
|
||||
"GetAgentThreadRunsOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "2e83ec1d0e08772d",
|
||||
"title": "Get Agent Thread Runs",
|
||||
"tags": [
|
||||
"pikku",
|
||||
"pikku:agent"
|
||||
],
|
||||
"description": "Returns the run history for a given AI agent thread, ordered by creation time.",
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"exportedName": "getAgentThreadRuns",
|
||||
"contractHash": "c51892c37c245537",
|
||||
"inputHash": "fb544997",
|
||||
"outputHash": "6352aca1"
|
||||
},
|
||||
"deleteAgentThread": {
|
||||
"pikkuFuncId": "deleteAgentThread",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "deleteAgentThread",
|
||||
"services": {
|
||||
"optimized": true,
|
||||
"services": [
|
||||
"agentRunService"
|
||||
]
|
||||
},
|
||||
"inputSchemaName": "DeleteAgentThreadInput",
|
||||
"outputSchemaName": "DeleteAgentThreadOutput",
|
||||
"inputs": [
|
||||
"DeleteAgentThreadInput"
|
||||
],
|
||||
"outputs": [
|
||||
"DeleteAgentThreadOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "7c0d0fb40a42fc20",
|
||||
"title": "Delete Agent Thread",
|
||||
"tags": [
|
||||
"pikku",
|
||||
"pikku:agent"
|
||||
],
|
||||
"description": "Deletes an AI agent thread and all of its persisted state.",
|
||||
"isDirectFunction": false,
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"exportedName": "deleteAgentThread",
|
||||
"contractHash": "b970011db1e0230e",
|
||||
"inputHash": "3ed36ee5",
|
||||
"outputHash": "a2094cc1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
{
|
||||
"pikkuConsoleSetSecret": {
|
||||
"pikkuFuncId": "pikkuConsoleSetSecret",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "pikkuConsoleSetSecret",
|
||||
"inputSchemaName": "PikkuConsoleSetSecretInput",
|
||||
"outputSchemaName": "PikkuConsoleSetSecretOutput",
|
||||
"inputs": [
|
||||
"PikkuConsoleSetSecretInput"
|
||||
],
|
||||
"outputs": [
|
||||
"PikkuConsoleSetSecretOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "c33e04e6aff5ca2e",
|
||||
"contractHash": "fcb625d86fe88b8f",
|
||||
"inputHash": "a0c8a043",
|
||||
"outputHash": "57702b41"
|
||||
},
|
||||
"pikkuConsoleGetVariable": {
|
||||
"pikkuFuncId": "pikkuConsoleGetVariable",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "pikkuConsoleGetVariable",
|
||||
"inputSchemaName": "PikkuConsoleGetVariableInput",
|
||||
"outputSchemaName": "PikkuConsoleGetVariableOutput",
|
||||
"inputs": [
|
||||
"PikkuConsoleGetVariableInput"
|
||||
],
|
||||
"outputs": [
|
||||
"PikkuConsoleGetVariableOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "45c792dfb6f8b1a5",
|
||||
"contractHash": "3b66dda4d82fa48a",
|
||||
"inputHash": "ce4b2e06",
|
||||
"outputHash": "f54f1059"
|
||||
},
|
||||
"pikkuConsoleSetVariable": {
|
||||
"pikkuFuncId": "pikkuConsoleSetVariable",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "pikkuConsoleSetVariable",
|
||||
"inputSchemaName": "PikkuConsoleSetVariableInput",
|
||||
"outputSchemaName": "PikkuConsoleSetVariableOutput",
|
||||
"inputs": [
|
||||
"PikkuConsoleSetVariableInput"
|
||||
],
|
||||
"outputs": [
|
||||
"PikkuConsoleSetVariableOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "f6806d8dc9d50055",
|
||||
"contractHash": "9dda40633b173a08",
|
||||
"inputHash": "d91a4af5",
|
||||
"outputHash": "91fe907b"
|
||||
},
|
||||
"pikkuConsoleHasSecret": {
|
||||
"pikkuFuncId": "pikkuConsoleHasSecret",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "pikkuConsoleHasSecret",
|
||||
"inputSchemaName": "PikkuConsoleHasSecretInput",
|
||||
"outputSchemaName": "PikkuConsoleHasSecretOutput",
|
||||
"inputs": [
|
||||
"PikkuConsoleHasSecretInput"
|
||||
],
|
||||
"outputs": [
|
||||
"PikkuConsoleHasSecretOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "b8c818fb2291b032",
|
||||
"contractHash": "eeff751c00461679",
|
||||
"inputHash": "a67adc7c",
|
||||
"outputHash": "1da66eac"
|
||||
},
|
||||
"pikkuConsoleGetSecret": {
|
||||
"pikkuFuncId": "pikkuConsoleGetSecret",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "pikkuConsoleGetSecret",
|
||||
"inputSchemaName": "PikkuConsoleGetSecretInput",
|
||||
"outputSchemaName": "PikkuConsoleGetSecretOutput",
|
||||
"inputs": [
|
||||
"PikkuConsoleGetSecretInput"
|
||||
],
|
||||
"outputs": [
|
||||
"PikkuConsoleGetSecretOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "5c1e39a4b0cc46a1",
|
||||
"contractHash": "d651ad93642eaeed",
|
||||
"inputHash": "329251da",
|
||||
"outputHash": "fb675a26"
|
||||
},
|
||||
"http:get:/workflow-run/:runId/stream": {
|
||||
"pikkuFuncId": "http:get:/workflow-run/:runId/stream",
|
||||
"functionType": "inline",
|
||||
"name": "/workflow-run/:runId/stream",
|
||||
"inputSchemaName": "StreamWorkflowRunInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"StreamWorkflowRunInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"contractHash": "ab85b338c10d89e8",
|
||||
"inputHash": "76838d3e",
|
||||
"outputHash": "fc2fd4a0"
|
||||
},
|
||||
"remoteRPCHandler": {
|
||||
"pikkuFuncId": "remoteRPCHandler",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "remoteRPCHandler",
|
||||
"inputSchemaName": "RemoteRPCHandlerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"RemoteRPCHandlerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"remote": true,
|
||||
"implementationHash": "105018a823bdb551"
|
||||
},
|
||||
"workflowStarter": {
|
||||
"pikkuFuncId": "workflowStarter",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "workflowStarter",
|
||||
"inputSchemaName": "WorkflowStarterInput",
|
||||
"outputSchemaName": "WorkflowStarterOutput",
|
||||
"inputs": [
|
||||
"WorkflowStarterInput"
|
||||
],
|
||||
"outputs": [
|
||||
"WorkflowStarterOutput"
|
||||
],
|
||||
"implementationHash": "153116058265d9d4",
|
||||
"contractHash": "770c10618e2d471b",
|
||||
"inputHash": "73e8dfbd",
|
||||
"outputHash": "8f2061b7"
|
||||
},
|
||||
"workflowRunner": {
|
||||
"pikkuFuncId": "workflowRunner",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "workflowRunner",
|
||||
"inputSchemaName": "WorkflowRunnerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"WorkflowRunnerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "4807ebd71047f932",
|
||||
"contractHash": "ca617784c6d9ec8d",
|
||||
"inputHash": "3c634245",
|
||||
"outputHash": "316bdb87"
|
||||
},
|
||||
"workflowStatusChecker": {
|
||||
"pikkuFuncId": "workflowStatusChecker",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "workflowStatusChecker",
|
||||
"inputSchemaName": "WorkflowStatusCheckerInput",
|
||||
"outputSchemaName": "WorkflowRunStatus",
|
||||
"inputs": [
|
||||
"WorkflowStatusCheckerInput"
|
||||
],
|
||||
"outputs": [
|
||||
"WorkflowRunStatus"
|
||||
],
|
||||
"implementationHash": "e4159df9ad118a6c",
|
||||
"contractHash": "1bb7182390464bc0",
|
||||
"inputHash": "458ba7ec",
|
||||
"outputHash": "bc4d864f"
|
||||
},
|
||||
"workflowStatusStream": {
|
||||
"pikkuFuncId": "workflowStatusStream",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "workflowStatusStream",
|
||||
"inputSchemaName": "WorkflowStatusStreamInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"WorkflowStatusStreamInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "233d0f04a86a4237",
|
||||
"contractHash": "3b99296ba5064030",
|
||||
"inputHash": "c6ee79ff",
|
||||
"outputHash": "6180f124"
|
||||
},
|
||||
"workflowStatusStreamFull": {
|
||||
"pikkuFuncId": "workflowStatusStreamFull",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "workflowStatusStreamFull",
|
||||
"inputSchemaName": "WorkflowStatusStreamFullInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"WorkflowStatusStreamFullInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "aba5bf474596ee50",
|
||||
"contractHash": "1e7b9f00193d10ae",
|
||||
"inputHash": "2da90ff6",
|
||||
"outputHash": "dcd02549"
|
||||
},
|
||||
"graphStarter": {
|
||||
"pikkuFuncId": "graphStarter",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "graphStarter",
|
||||
"inputSchemaName": "GraphStarterInput",
|
||||
"outputSchemaName": "GraphStarterOutput",
|
||||
"inputs": [
|
||||
"GraphStarterInput"
|
||||
],
|
||||
"outputs": [
|
||||
"GraphStarterOutput"
|
||||
],
|
||||
"implementationHash": "d12993125e177525",
|
||||
"contractHash": "422510c83dee8491",
|
||||
"inputHash": "70868669",
|
||||
"outputHash": "6f988595"
|
||||
},
|
||||
"rpcCaller": {
|
||||
"pikkuFuncId": "rpcCaller",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "rpcCaller",
|
||||
"inputSchemaName": "RpcCallerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"RpcCallerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "f92b60dda357f483",
|
||||
"contractHash": "f6901fd233740bf4",
|
||||
"inputHash": "a69cacc4",
|
||||
"outputHash": "629e0b5a"
|
||||
},
|
||||
"agentCaller": {
|
||||
"pikkuFuncId": "agentCaller",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "agentCaller",
|
||||
"inputSchemaName": "AgentCallerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"AgentCallerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "4af198bf5bbd9bd8",
|
||||
"contractHash": "76619abd99f23189",
|
||||
"inputHash": "4d1eddbc",
|
||||
"outputHash": "9a3af204"
|
||||
},
|
||||
"agentStreamCaller": {
|
||||
"pikkuFuncId": "agentStreamCaller",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "agentStreamCaller",
|
||||
"inputSchemaName": "AgentStreamCallerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"AgentStreamCallerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "92b3cd7c92fb3de4",
|
||||
"contractHash": "0e5c814e26d3294f",
|
||||
"inputHash": "773d4d52",
|
||||
"outputHash": "641a263e"
|
||||
},
|
||||
"agentApproveCaller": {
|
||||
"pikkuFuncId": "agentApproveCaller",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "agentApproveCaller",
|
||||
"inputSchemaName": "AgentApproveCallerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"AgentApproveCallerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "ae5d4bfb0293119a",
|
||||
"contractHash": "83a9293c4b51d65c",
|
||||
"inputHash": "f1628b7c",
|
||||
"outputHash": "ef71f08a"
|
||||
},
|
||||
"agentResumeCaller": {
|
||||
"pikkuFuncId": "agentResumeCaller",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "agentResumeCaller",
|
||||
"inputSchemaName": "AgentResumeCallerInput",
|
||||
"outputSchemaName": null,
|
||||
"inputs": [
|
||||
"AgentResumeCallerInput"
|
||||
],
|
||||
"outputs": [],
|
||||
"implementationHash": "d17ccf1259ba4a60",
|
||||
"contractHash": "0648ffb2cfa9d861",
|
||||
"inputHash": "f25b50dd",
|
||||
"outputHash": "ee89e454"
|
||||
},
|
||||
"getAgentThreads": {
|
||||
"pikkuFuncId": "getAgentThreads",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "getAgentThreads",
|
||||
"inputSchemaName": "GetAgentThreadsInput",
|
||||
"outputSchemaName": "GetAgentThreadsOutput",
|
||||
"inputs": [
|
||||
"GetAgentThreadsInput"
|
||||
],
|
||||
"outputs": [
|
||||
"GetAgentThreadsOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "3823f8f3911c48e2",
|
||||
"contractHash": "d629208347702d27",
|
||||
"inputHash": "41f5350c",
|
||||
"outputHash": "6598bdff"
|
||||
},
|
||||
"getAgentThreadMessages": {
|
||||
"pikkuFuncId": "getAgentThreadMessages",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "getAgentThreadMessages",
|
||||
"inputSchemaName": "GetAgentThreadMessagesInput",
|
||||
"outputSchemaName": "GetAgentThreadMessagesOutput",
|
||||
"inputs": [
|
||||
"GetAgentThreadMessagesInput"
|
||||
],
|
||||
"outputs": [
|
||||
"GetAgentThreadMessagesOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "dffe581287205c6e",
|
||||
"contractHash": "44e43e96024680f3",
|
||||
"inputHash": "a158e652",
|
||||
"outputHash": "04960780"
|
||||
},
|
||||
"getAgentThreadRuns": {
|
||||
"pikkuFuncId": "getAgentThreadRuns",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "getAgentThreadRuns",
|
||||
"inputSchemaName": "GetAgentThreadRunsInput",
|
||||
"outputSchemaName": "GetAgentThreadRunsOutput",
|
||||
"inputs": [
|
||||
"GetAgentThreadRunsInput"
|
||||
],
|
||||
"outputs": [
|
||||
"GetAgentThreadRunsOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "2e83ec1d0e08772d",
|
||||
"contractHash": "c51892c37c245537",
|
||||
"inputHash": "fb544997",
|
||||
"outputHash": "6352aca1"
|
||||
},
|
||||
"deleteAgentThread": {
|
||||
"pikkuFuncId": "deleteAgentThread",
|
||||
"functionType": "user",
|
||||
"funcWrapper": "pikkuSessionlessFunc",
|
||||
"sessionless": true,
|
||||
"name": "deleteAgentThread",
|
||||
"inputSchemaName": "DeleteAgentThreadInput",
|
||||
"outputSchemaName": "DeleteAgentThreadOutput",
|
||||
"inputs": [
|
||||
"DeleteAgentThreadInput"
|
||||
],
|
||||
"outputs": [
|
||||
"DeleteAgentThreadOutput"
|
||||
],
|
||||
"expose": true,
|
||||
"implementationHash": "7c0d0fb40a42fc20",
|
||||
"contractHash": "b970011db1e0230e",
|
||||
"inputHash": "3ed36ee5",
|
||||
"outputHash": "a2094cc1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
import { pikkuState } from '@pikku/core/internal'
|
||||
import type { FunctionsMeta } from '@pikku/core'
|
||||
import metaData from './pikku-functions-meta.gen.json' with { type: 'json' }
|
||||
pikkuState(null, 'function', 'meta', metaData as FunctionsMeta)
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/* Import and register functions used by RPCs */
|
||||
import { addFunction } from '@pikku/core/function'
|
||||
import { deleteAgentThread } from '../../../../src/scaffold/agent.gen.js'
|
||||
import { getAgentThreadMessages } from '../../../../src/scaffold/agent.gen.js'
|
||||
import { getAgentThreadRuns } from '../../../../src/scaffold/agent.gen.js'
|
||||
import { getAgentThreads } from '../../../../src/scaffold/agent.gen.js'
|
||||
import { pikkuConsoleGetSecret } from '../../../../src/scaffold/console.gen.js'
|
||||
import { pikkuConsoleGetVariable } from '../../../../src/scaffold/console.gen.js'
|
||||
import { pikkuConsoleHasSecret } from '../../../../src/scaffold/console.gen.js'
|
||||
import { pikkuConsoleSetSecret } from '../../../../src/scaffold/console.gen.js'
|
||||
import { pikkuConsoleSetVariable } from '../../../../src/scaffold/console.gen.js'
|
||||
import { remoteRPCHandler } from '../../../../src/scaffold/rpc-remote.gen.js'
|
||||
|
||||
addFunction('deleteAgentThread', deleteAgentThread)
|
||||
addFunction('getAgentThreadMessages', getAgentThreadMessages)
|
||||
addFunction('getAgentThreadRuns', getAgentThreadRuns)
|
||||
addFunction('getAgentThreads', getAgentThreads)
|
||||
addFunction('pikkuConsoleGetSecret', pikkuConsoleGetSecret)
|
||||
addFunction('pikkuConsoleGetVariable', pikkuConsoleGetVariable)
|
||||
addFunction('pikkuConsoleHasSecret', pikkuConsoleHasSecret)
|
||||
addFunction('pikkuConsoleSetSecret', pikkuConsoleSetSecret)
|
||||
addFunction('pikkuConsoleSetVariable', pikkuConsoleSetVariable)
|
||||
addFunction('remoteRPCHandler', remoteRPCHandler)
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
* HTTP-specific type definitions for tree-shaking optimization
|
||||
*/
|
||||
|
||||
import { AssertHTTPWiringParams, wireHTTP as wireHTTPCore, addHTTPMiddleware as addHTTPMiddlewareCore, addHTTPPermission as addHTTPPermissionCore, wireHTTPRoutes as wireHTTPRoutesCore, defineHTTPRoutes as defineHTTPRoutesCore } from '@pikku/core/http'
|
||||
import type { PikkuFunction, PikkuFunctionSessionless, PikkuPermission, PikkuMiddleware, PikkuFunctionConfig } from '../function/pikku-function-types.gen.js'
|
||||
import type { CoreHTTPFunctionWiring, HTTPMethod, HTTPRouteBaseConfig } from '@pikku/core/http'
|
||||
|
||||
/**
|
||||
* Type definition for HTTP API wirings with type-safe path parameters.
|
||||
* Supports both authenticated and unauthenticated functions.
|
||||
*
|
||||
* @template In - Input type for the HTTP wiring
|
||||
* @template Out - Output type for the HTTP wiring
|
||||
* @template Route - String literal type for the HTTP path (e.g., "/users/:id")
|
||||
*/
|
||||
type HTTPWiring<In, Out, Route extends string> = CoreHTTPFunctionWiring<In, Out, Route, PikkuFunction<In, Out, 'rpc' | 'session'>, PikkuFunctionSessionless<In, Out, 'rpc' | 'session'>, PikkuPermission<In>, PikkuMiddleware>
|
||||
|
||||
/**
|
||||
* Registers an HTTP wiring with the Pikku framework.
|
||||
*
|
||||
* @template In - Input type for the HTTP wiring
|
||||
* @template Out - Output type for the HTTP wiring
|
||||
* @template Route - String literal type for the HTTP path (e.g., "/users/:id")
|
||||
* @param httpWiring - HTTP wiring definition with handler, method, and optional middleware
|
||||
*/
|
||||
export const wireHTTP = <In, Out, Route extends string>(
|
||||
httpWiring: HTTPWiring<In, Out, Route> & AssertHTTPWiringParams<In, Route>
|
||||
) => {
|
||||
wireHTTPCore(httpWiring as any)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers HTTP middleware either globally or for a specific route pattern.
|
||||
*
|
||||
* When a string route pattern is provided along with middleware, the middleware
|
||||
* is applied only to that route. Otherwise, if an array is provided, it is treated
|
||||
* as global middleware (applied to all routes).
|
||||
*
|
||||
* @param routeOrMiddleware - Either a global middleware array or a route pattern string
|
||||
* @param middleware - The middleware array to apply when a route pattern is specified
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Add global HTTP middleware
|
||||
* addHTTPMiddleware([authMiddleware, loggingMiddleware])
|
||||
*
|
||||
* // Add route-specific middleware
|
||||
* addHTTPMiddleware('/api/admin/*', [adminAuthMiddleware])
|
||||
* ```
|
||||
*/
|
||||
export const addHTTPMiddleware = (
|
||||
routeOrMiddleware: PikkuMiddleware[] | string,
|
||||
middleware?: PikkuMiddleware[]
|
||||
) => {
|
||||
addHTTPMiddlewareCore(routeOrMiddleware as any, middleware as any)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers HTTP permissions either globally or for a specific route pattern.
|
||||
*
|
||||
* When a string route pattern is provided along with permissions, the permissions
|
||||
* are applied only to that route. Permissions can be passed as an array or as a
|
||||
* permission group object.
|
||||
*
|
||||
* @param pattern - Route pattern string (e.g., '*' for all routes, '/api/*' for specific routes)
|
||||
* @param permissions - The permissions to apply for the specified route pattern
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Add global HTTP permissions
|
||||
* addHTTPPermission('*', { global: globalPermission })
|
||||
*
|
||||
* // Add route-specific permissions
|
||||
* addHTTPPermission('/api/admin/*', { admin: adminPermission })
|
||||
* ```
|
||||
*/
|
||||
export const addHTTPPermission = <In = unknown>(
|
||||
pattern: string,
|
||||
permissions: Record<string, PikkuPermission<In>> | PikkuPermission<In>[]
|
||||
) => {
|
||||
addHTTPPermissionCore(pattern, permissions as any)
|
||||
}
|
||||
|
||||
/**
|
||||
* Route configuration for wireHTTPRoutes with proper typing
|
||||
*/
|
||||
type HTTPRouteConfig = HTTPRouteBaseConfig & {
|
||||
method: HTTPMethod
|
||||
route: string
|
||||
func: PikkuFunctionConfig<any, any, any, any, any, any>
|
||||
auth?: boolean
|
||||
permissions?: Record<string, PikkuPermission | PikkuPermission[]>
|
||||
middleware?: PikkuMiddleware[]
|
||||
sse?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Typed route map for wireHTTPRoutes
|
||||
*/
|
||||
type TypedHTTPRouteMap = {
|
||||
[key: string]: HTTPRouteConfig | TypedHTTPRouteMap | TypedHTTPRouteContract
|
||||
}
|
||||
|
||||
/**
|
||||
* Typed route contract for defineHTTPRoutes
|
||||
*/
|
||||
type TypedHTTPRouteContract<T extends TypedHTTPRouteMap = TypedHTTPRouteMap> = TypedHTTPRoutesGroupConfig & {
|
||||
routes: T
|
||||
}
|
||||
|
||||
/**
|
||||
* Group config with typed middleware/permissions
|
||||
*/
|
||||
type TypedHTTPRoutesGroupConfig = {
|
||||
basePath?: string
|
||||
tags?: string[]
|
||||
auth?: boolean
|
||||
middleware?: PikkuMiddleware[]
|
||||
permissions?: Record<string, PikkuPermission | PikkuPermission[]>
|
||||
}
|
||||
|
||||
/**
|
||||
* Full config for wireHTTPRoutes
|
||||
*/
|
||||
type TypedWireHTTPRoutesConfig = TypedHTTPRoutesGroupConfig & {
|
||||
routes: TypedHTTPRouteMap | HTTPRouteConfig[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe helper for defining route contracts that can be composed.
|
||||
*/
|
||||
export function defineHTTPRoutes<T extends TypedHTTPRouteMap>(routes: T): TypedHTTPRouteContract<T>
|
||||
export function defineHTTPRoutes<T extends TypedHTTPRouteMap>(config: TypedHTTPRoutesGroupConfig & { routes: T }): TypedHTTPRouteContract<T>
|
||||
export function defineHTTPRoutes<T extends TypedHTTPRouteMap>(configOrRoutes: T | (TypedHTTPRoutesGroupConfig & { routes: T })): TypedHTTPRouteContract<T> {
|
||||
return defineHTTPRoutesCore(configOrRoutes as any) as unknown as TypedHTTPRouteContract<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Wires multiple HTTP routes from a nested map or array configuration.
|
||||
*/
|
||||
export const wireHTTPRoutes = (config: TypedWireHTTPRoutesConfig): void => {
|
||||
wireHTTPRoutesCore(config as any)
|
||||
}
|
||||
129
packages/functions/packages/functions/.pikku/http/pikku-http-wirings-map.gen.d.ts
vendored
Normal file
129
packages/functions/packages/functions/.pikku/http/pikku-http-wirings-map.gen.d.ts
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
* This provides the structure needed for typescript to be aware of routes and their return types
|
||||
*/
|
||||
|
||||
import type { StreamWorkflowRunInput } from '@pikku/addon-console/dist/.pikku/rpc/pikku-rpc-wirings-map.internal.gen'
|
||||
import type { WorkflowRunStatus } from '@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; }
|
||||
export type DeleteAgentThreadInput = { threadId: string; resourceId?: string; }
|
||||
export type DeleteAgentThreadOutput = { deleted: boolean; }
|
||||
export type GetAgentThreadMessagesInput = { threadId: string; resourceId?: string; }
|
||||
export type GetAgentThreadMessagesOutput = any[]
|
||||
export type GetAgentThreadRunsInput = { threadId: string; resourceId?: string; }
|
||||
export type GetAgentThreadRunsOutput = any[]
|
||||
export type GetAgentThreadsInput = { agentName?: string; resourceId?: string; limit?: number; offset?: number; }
|
||||
export type GetAgentThreadsOutput = any[]
|
||||
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 RemoteRPCHandlerInput = { rpcName: string; data?: unknown; }
|
||||
export type RpcCallerInput = { rpcName: string; data?: unknown; }
|
||||
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 StreamWorkflowRunInputParams = Pick<StreamWorkflowRunInput, 'runId'> & {}
|
||||
export type StreamWorkflowRunInputBody = Omit<StreamWorkflowRunInput, 'runId'> & {}
|
||||
export type RemoteRPCHandlerInputParams = Pick<RemoteRPCHandlerInput, 'rpcName'> & {}
|
||||
export type RemoteRPCHandlerInputBody = Omit<RemoteRPCHandlerInput, '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'> & {}
|
||||
export type RpcCallerInputParams = Pick<RpcCallerInput, 'rpcName'> & {}
|
||||
export type RpcCallerInputBody = Omit<RpcCallerInput, 'rpcName'> & {}
|
||||
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'> & {}
|
||||
|
||||
interface HTTPWiringHandler<I, O> {
|
||||
input: I;
|
||||
output: O;
|
||||
}
|
||||
|
||||
export type HTTPWiringsMap = {
|
||||
readonly '/workflow-run/:runId/stream': {
|
||||
readonly GET: HTTPWiringHandler<StreamWorkflowRunInput, 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 '/remote/rpc/:rpcName': {
|
||||
readonly POST: HTTPWiringHandler<RemoteRPCHandlerInput, 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>,
|
||||
},
|
||||
readonly '/rpc/:rpcName': {
|
||||
readonly POST: HTTPWiringHandler<RpcCallerInput, 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>,
|
||||
},
|
||||
};
|
||||
|
||||
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];
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
{
|
||||
"get": {
|
||||
"/workflow-run/:runId/stream": {
|
||||
"pikkuFuncId": "http:get:/workflow-run/:runId/stream",
|
||||
"route": "/workflow-run/:runId/stream",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/console.gen.ts",
|
||||
"method": "get",
|
||||
"params": [
|
||||
"runId"
|
||||
],
|
||||
"sse": true
|
||||
},
|
||||
"/workflow/:workflowName/status/:runId": {
|
||||
"pikkuFuncId": "workflowStatusChecker",
|
||||
"route": "/workflow/:workflowName/status/:runId",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"method": "get",
|
||||
"params": [
|
||||
"workflowName",
|
||||
"runId"
|
||||
]
|
||||
},
|
||||
"/workflow/:workflowName/status/:runId/stream": {
|
||||
"pikkuFuncId": "workflowStatusStream",
|
||||
"route": "/workflow/:workflowName/status/:runId/stream",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"method": "get",
|
||||
"params": [
|
||||
"workflowName",
|
||||
"runId"
|
||||
],
|
||||
"sse": true
|
||||
},
|
||||
"/workflow/:workflowName/status/:runId/stream/full": {
|
||||
"pikkuFuncId": "workflowStatusStreamFull",
|
||||
"route": "/workflow/:workflowName/status/:runId/stream/full",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"method": "get",
|
||||
"params": [
|
||||
"workflowName",
|
||||
"runId"
|
||||
],
|
||||
"sse": true
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"/remote/rpc/:rpcName": {
|
||||
"pikkuFuncId": "remoteRPCHandler",
|
||||
"route": "/remote/rpc/:rpcName",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/rpc-remote.gen.ts",
|
||||
"method": "post",
|
||||
"params": [
|
||||
"rpcName"
|
||||
],
|
||||
"middleware": [
|
||||
{
|
||||
"type": "wire",
|
||||
"name": "pikkuRemoteAuthMiddleware",
|
||||
"inline": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"/workflow/:workflowName/start": {
|
||||
"pikkuFuncId": "workflowStarter",
|
||||
"route": "/workflow/:workflowName/start",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"method": "post",
|
||||
"params": [
|
||||
"workflowName"
|
||||
]
|
||||
},
|
||||
"/workflow/:workflowName/run": {
|
||||
"pikkuFuncId": "workflowRunner",
|
||||
"route": "/workflow/:workflowName/run",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"method": "post",
|
||||
"params": [
|
||||
"workflowName"
|
||||
]
|
||||
},
|
||||
"/workflow/:workflowName/graph/:nodeId": {
|
||||
"pikkuFuncId": "graphStarter",
|
||||
"route": "/workflow/:workflowName/graph/:nodeId",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/workflow-routes.gen.ts",
|
||||
"method": "post",
|
||||
"params": [
|
||||
"workflowName",
|
||||
"nodeId"
|
||||
]
|
||||
},
|
||||
"/rpc/:rpcName": {
|
||||
"pikkuFuncId": "rpcCaller",
|
||||
"route": "/rpc/:rpcName",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/rpc-public.gen.ts",
|
||||
"method": "post",
|
||||
"params": [
|
||||
"rpcName"
|
||||
]
|
||||
},
|
||||
"/rpc/agent/:agentName": {
|
||||
"pikkuFuncId": "agentCaller",
|
||||
"route": "/rpc/agent/:agentName",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"method": "post",
|
||||
"params": [
|
||||
"agentName"
|
||||
],
|
||||
"tags": [
|
||||
"pikku:public"
|
||||
]
|
||||
},
|
||||
"/rpc/agent/:agentName/stream": {
|
||||
"pikkuFuncId": "agentStreamCaller",
|
||||
"route": "/rpc/agent/:agentName/stream",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"method": "post",
|
||||
"params": [
|
||||
"agentName"
|
||||
],
|
||||
"tags": [
|
||||
"pikku:public"
|
||||
],
|
||||
"sse": true
|
||||
},
|
||||
"/rpc/agent/:agentName/approve": {
|
||||
"pikkuFuncId": "agentApproveCaller",
|
||||
"route": "/rpc/agent/:agentName/approve",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"method": "post",
|
||||
"params": [
|
||||
"agentName"
|
||||
],
|
||||
"tags": [
|
||||
"pikku:public"
|
||||
]
|
||||
},
|
||||
"/rpc/agent/:agentName/resume": {
|
||||
"pikkuFuncId": "agentResumeCaller",
|
||||
"route": "/rpc/agent/:agentName/resume",
|
||||
"sourceFile": "/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/src/scaffold/agent.gen.ts",
|
||||
"method": "post",
|
||||
"params": [
|
||||
"agentName"
|
||||
],
|
||||
"tags": [
|
||||
"pikku:public"
|
||||
],
|
||||
"sse": true
|
||||
}
|
||||
},
|
||||
"put": {},
|
||||
"delete": {},
|
||||
"head": {},
|
||||
"patch": {},
|
||||
"options": {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
import { pikkuState } from '@pikku/core/internal'
|
||||
import type { HTTPWiringsMeta } from '@pikku/core/http'
|
||||
import metaData from './pikku-http-wirings-meta.gen.json' with { type: 'json' }
|
||||
pikkuState(null, 'http', 'meta', metaData as HTTPWiringsMeta)
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/* The files with an wireHTTP function call */
|
||||
import '../../../../src/scaffold/agent.gen.js'
|
||||
import '../../../../src/scaffold/console.gen.js'
|
||||
import '../../../../src/scaffold/rpc-public.gen.js'
|
||||
import '../../../../src/scaffold/rpc-remote.gen.js'
|
||||
import '../../../../src/scaffold/workflow-routes.gen.js'
|
||||
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
* MCP-specific type definitions for tree-shaking optimization
|
||||
*/
|
||||
|
||||
import {
|
||||
CoreMCPResource,
|
||||
CoreMCPPrompt,
|
||||
wireMCPResource as wireMCPResourceCore,
|
||||
wireMCPPrompt as wireMCPPromptCore,
|
||||
MCPResourceResponse,
|
||||
MCPToolResponse,
|
||||
MCPPromptResponse,
|
||||
AssertMCPResourceURIParams
|
||||
} from '@pikku/core/mcp'
|
||||
|
||||
import type { PikkuFunctionConfig, PikkuFunctionSessionless, PikkuMiddleware, PikkuPermission, InferSchemaOutput } from '../function/pikku-function-types.gen.js'
|
||||
import type { CorePermissionGroup } from '@pikku/core'
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
||||
|
||||
/**
|
||||
* Type definition for MCP resources that provide data to AI models.
|
||||
*
|
||||
* @template In - Input type for the resource request
|
||||
* @template URI - URI template string type for compile-time parameter validation
|
||||
*/
|
||||
type MCPResourceWiring<In, URI extends string> = CoreMCPResource<PikkuFunctionConfig<In, MCPResourceResponse, 'rpc' | 'session' | 'mcp'>> & { uri: URI }
|
||||
|
||||
/**
|
||||
* Type definition for MCP prompts that provide templates to AI models.
|
||||
*
|
||||
* @template In - Input type for the prompt parameters
|
||||
*/
|
||||
type MCPPromptWiring<In> = CoreMCPPrompt<PikkuFunctionConfig<In, MCPPromptResponse, 'rpc' | 'session' | 'mcp'>>
|
||||
|
||||
/**
|
||||
* Registers an MCP resource with the Pikku framework.
|
||||
* Resources provide data that AI models can access.
|
||||
*
|
||||
* @template In - Input type for the resource request
|
||||
* @template URI - URI template string for compile-time parameter validation
|
||||
* @param mcpResource - MCP resource definition with data provider function
|
||||
*/
|
||||
export const wireMCPResource = <In, URI extends string>(
|
||||
mcpResource: MCPResourceWiring<In, URI> & AssertMCPResourceURIParams<In, URI>
|
||||
) => {
|
||||
wireMCPResourceCore(mcpResource as any)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an MCP prompt with the Pikku framework.
|
||||
* Prompts provide templates that AI models can use.
|
||||
*
|
||||
* @template In - Input type for the prompt parameters
|
||||
* @param mcpPrompt - MCP prompt definition with template function
|
||||
*/
|
||||
export const wireMCPPrompt = <In>(
|
||||
mcpPrompt: MCPPromptWiring<In>
|
||||
) => {
|
||||
wireMCPPromptCore(mcpPrompt as any)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for MCP prompt with Zod schema input validation.
|
||||
*/
|
||||
type MCPPromptFuncConfigWithSchema<InputSchema extends StandardSchemaV1> = {
|
||||
func: PikkuFunctionSessionless<InferSchemaOutput<InputSchema>, MCPPromptResponse, 'mcp' | 'rpc'>
|
||||
name?: string
|
||||
input: InputSchema
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function for handling MCP prompt requests.
|
||||
* These functions generate prompt templates for AI models.
|
||||
*
|
||||
* Supports two patterns:
|
||||
* 1. Generic types: `pikkuMCPPromptFunc<Input>({ func: ... })`
|
||||
* 2. Zod schemas: `pikkuMCPPromptFunc({ input: z.object(...), func: ... })`
|
||||
*
|
||||
* @template In - Input type for the prompt parameters (inferred from schema if provided)
|
||||
* @param func - Function definition, either direct function or configuration object
|
||||
* @returns The unwrapped function for internal use
|
||||
*/
|
||||
export function pikkuMCPPromptFunc<InputSchema extends StandardSchemaV1>(
|
||||
config: MCPPromptFuncConfigWithSchema<InputSchema>
|
||||
): PikkuFunctionConfig<InferSchemaOutput<InputSchema>, MCPPromptResponse, 'mcp' | 'rpc'>
|
||||
export function pikkuMCPPromptFunc<In>(
|
||||
func:
|
||||
| PikkuFunctionSessionless<In, MCPPromptResponse, 'mcp' | 'rpc'>
|
||||
| {
|
||||
func: PikkuFunctionSessionless<In, MCPPromptResponse, 'mcp' | 'rpc'>
|
||||
name?: string
|
||||
}
|
||||
): PikkuFunctionConfig<In, MCPPromptResponse, 'mcp' | 'rpc'>
|
||||
export function pikkuMCPPromptFunc(func: any): any {
|
||||
return typeof func === 'function' ? { func } : func
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for MCP tool with Zod schema input validation.
|
||||
*/
|
||||
type MCPToolFuncConfigWithSchema<InputSchema extends StandardSchemaV1> = {
|
||||
func: PikkuFunctionSessionless<InferSchemaOutput<InputSchema>, MCPToolResponse, 'mcp' | 'rpc'>
|
||||
description?: string
|
||||
tags?: string[]
|
||||
title?: string
|
||||
summary?: string
|
||||
name?: string
|
||||
middleware?: PikkuMiddleware[]
|
||||
permissions?: CorePermissionGroup<PikkuPermission<InferSchemaOutput<InputSchema>>>
|
||||
input: InputSchema
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function for handling MCP tool invocations.
|
||||
* These functions perform actions that AI models can request.
|
||||
*
|
||||
* Supports two patterns:
|
||||
* 1. Generic types: `pikkuMCPToolFunc<Input>({ func: ... })`
|
||||
* 2. Zod schemas: `pikkuMCPToolFunc({ input: z.object(...), func: ... })`
|
||||
*
|
||||
* @template In - Input type for the tool invocation (inferred from schema if provided)
|
||||
* @param func - Function definition, either direct function or configuration object
|
||||
* @returns The unwrapped function for internal use
|
||||
*/
|
||||
export function pikkuMCPToolFunc<InputSchema extends StandardSchemaV1>(
|
||||
config: MCPToolFuncConfigWithSchema<InputSchema>
|
||||
): PikkuFunctionConfig<InferSchemaOutput<InputSchema>, MCPToolResponse, 'mcp' | 'rpc'>
|
||||
export function pikkuMCPToolFunc<In>(
|
||||
func:
|
||||
| PikkuFunctionSessionless<In, MCPToolResponse, 'mcp' | 'rpc'>
|
||||
| {
|
||||
func: PikkuFunctionSessionless<In, MCPToolResponse, 'mcp' | 'rpc'>
|
||||
description?: string
|
||||
tags?: string[]
|
||||
title?: string
|
||||
summary?: string
|
||||
name?: string
|
||||
middleware?: PikkuMiddleware[]
|
||||
permissions?: CorePermissionGroup<PikkuPermission<In>>
|
||||
}
|
||||
): PikkuFunctionConfig<In, MCPToolResponse, 'mcp' | 'rpc'>
|
||||
export function pikkuMCPToolFunc(func: any): any {
|
||||
return typeof func === 'function' ? { func } : func
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for MCP resource with Zod schema input validation.
|
||||
*/
|
||||
type MCPResourceFuncConfigWithSchema<InputSchema extends StandardSchemaV1> = {
|
||||
func: PikkuFunctionSessionless<InferSchemaOutput<InputSchema>, MCPResourceResponse, 'mcp' | 'rpc'>
|
||||
name?: string
|
||||
input: InputSchema
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function for handling MCP resource requests.
|
||||
* These functions provide data that AI models can access.
|
||||
*
|
||||
* Supports two patterns:
|
||||
* 1. Generic types: `pikkuMCPResourceFunc<Input>({ func: ... })`
|
||||
* 2. Zod schemas: `pikkuMCPResourceFunc({ input: z.object(...), func: ... })`
|
||||
*
|
||||
* @template In - Input type for the resource request (inferred from schema if provided)
|
||||
* @param func - Function definition, either direct function or configuration object
|
||||
* @returns The unwrapped function for internal use
|
||||
*/
|
||||
export function pikkuMCPResourceFunc<InputSchema extends StandardSchemaV1>(
|
||||
config: MCPResourceFuncConfigWithSchema<InputSchema>
|
||||
): PikkuFunctionConfig<InferSchemaOutput<InputSchema>, MCPResourceResponse, 'mcp' | 'rpc'>
|
||||
export function pikkuMCPResourceFunc<In>(
|
||||
func:
|
||||
| PikkuFunctionSessionless<In, MCPResourceResponse, 'mcp' | 'rpc'>
|
||||
| {
|
||||
func: PikkuFunctionSessionless<In, MCPResourceResponse, 'mcp' | 'rpc'>
|
||||
name?: string
|
||||
}
|
||||
): PikkuFunctionConfig<In, MCPResourceResponse, 'mcp' | 'rpc'>
|
||||
export function pikkuMCPResourceFunc(func: any): any {
|
||||
return typeof func === 'function' ? { func } : func
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
import './rpc/pikku-rpc-wirings-meta.internal.gen.js'
|
||||
import './http/pikku-http-wirings-meta.gen.js'
|
||||
import './function/pikku-functions-meta.gen.js'
|
||||
import './queue/pikku-queue-workers-wirings-meta.gen.js'
|
||||
import './schemas/register.gen.js'
|
||||
import './http/pikku-http-wirings.gen.js'
|
||||
import './function/pikku-functions.gen.js'
|
||||
import './queue/pikku-queue-workers-wirings.gen.js'
|
||||
import '../../../src/scaffold/console.gen.js'
|
||||
import '@pikku/addon-console/.pikku/pikku-bootstrap.gen.js'
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
import { LocalMetaService } from '@pikku/core/services/local-meta'
|
||||
|
||||
export class PikkuMetaService extends LocalMetaService {
|
||||
constructor() {
|
||||
super('/Users/yasser/git/pikku/fabric/templates/kanban-board-template/packages/functions/packages/functions/.pikku')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
import type { SingletonServices } from '../../../src/application-types.d.js'
|
||||
import type { Services } from '../../../src/application-types.d.js'
|
||||
|
||||
// Singleton services map: true if required, false if available but unused
|
||||
export const requiredSingletonServices = {
|
||||
'agentRunService': true,
|
||||
'aiAgentRunner': true,
|
||||
'aiRunState': true,
|
||||
'aiStorage': true,
|
||||
'config': true,
|
||||
'content': false,
|
||||
'credentialService': true,
|
||||
'deploymentService': true,
|
||||
'eventHub': false,
|
||||
'jwt': false,
|
||||
'kysely': false,
|
||||
'logger': true,
|
||||
'metaService': true,
|
||||
'queueService': false,
|
||||
'schedulerService': true,
|
||||
'schema': true,
|
||||
'secrets': true,
|
||||
'sessionStore': false,
|
||||
'variables': true,
|
||||
'workflowRunService': true,
|
||||
'workflowService': true,
|
||||
} as const
|
||||
|
||||
// Wire services map: true if required, false if available but unused
|
||||
export const requiredWireServices = {
|
||||
} as const
|
||||
|
||||
// Type exports
|
||||
export type RequiredSingletonServices = Pick<SingletonServices, 'agentRunService' | 'aiAgentRunner' | 'aiRunState' | 'aiStorage' | 'config' | 'credentialService' | 'deploymentService' | 'logger' | 'metaService' | 'schedulerService' | 'schema' | 'secrets' | 'variables' | 'workflowRunService' | 'workflowService'> & Partial<Omit<SingletonServices, 'agentRunService' | 'aiAgentRunner' | 'aiRunState' | 'aiStorage' | 'config' | 'credentialService' | 'deploymentService' | 'logger' | 'metaService' | 'schedulerService' | 'schema' | 'secrets' | 'variables' | 'workflowRunService' | 'workflowService'>>
|
||||
|
||||
export type RequiredWireServices = Partial<Services>
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
* Main type export hub - re-exports all wiring-specific types
|
||||
*/
|
||||
|
||||
// Core function, middleware, and permission types
|
||||
export * from './function/pikku-function-types.gen.js'
|
||||
|
||||
// HTTP wiring types
|
||||
export * from './http/pikku-http-types.gen.js'
|
||||
|
||||
// Channel wiring types
|
||||
export * from './channel/pikku-channel-types.gen.js'
|
||||
|
||||
// Trigger wiring types
|
||||
export * from './trigger/pikku-trigger-types.gen.js'
|
||||
|
||||
// Scheduler wiring types
|
||||
export * from './scheduler/pikku-scheduler-types.gen.js'
|
||||
|
||||
// Queue wiring types
|
||||
export * from './queue/pikku-queue-types.gen.js'
|
||||
|
||||
// MCP wiring types
|
||||
export * from './mcp/pikku-mcp-types.gen.js'
|
||||
|
||||
// CLI wiring types
|
||||
export * from './cli/pikku-cli-types.gen.js'
|
||||
|
||||
// Node wiring types
|
||||
export * from './console/pikku-node-types.gen.js'
|
||||
|
||||
// Secret definition types
|
||||
export * from './secrets/pikku-secret-types.gen.js'
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
* Queue-specific type definitions for tree-shaking optimization
|
||||
*/
|
||||
|
||||
import { CoreQueueWorker, wireQueueWorker as wireQueueWorkerCore } from '@pikku/core/queue'
|
||||
import type { PikkuFunctionConfig } from '../function/pikku-function-types.gen.js'
|
||||
|
||||
/**
|
||||
* Type definition for queue workers that process background jobs.
|
||||
*
|
||||
* @template In - Input type for the queue job
|
||||
* @template Out - Output type for the queue job
|
||||
*/
|
||||
type QueueWiring<In, Out> = CoreQueueWorker<PikkuFunctionConfig<In, Out, 'session' | 'rpc'>>
|
||||
|
||||
/**
|
||||
* Registers a queue worker with the Pikku framework.
|
||||
* Workers process background jobs from queues.
|
||||
*
|
||||
* @param queueWorker - Queue worker definition with job handler
|
||||
*/
|
||||
export const wireQueueWorker = (queueWorker: QueueWiring<any, any>) => {
|
||||
wireQueueWorkerCore(queueWorker as any)
|
||||
}
|
||||
78
packages/functions/packages/functions/.pikku/queue/pikku-queue-workers-wirings-map.gen.d.ts
vendored
Normal file
78
packages/functions/packages/functions/.pikku/queue/pikku-queue-workers-wirings-map.gen.d.ts
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
* This provides the structure needed for typescript to be aware of Queue workers and their input/output 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; }
|
||||
export type DeleteAgentThreadInput = { threadId: string; resourceId?: string; }
|
||||
export type DeleteAgentThreadOutput = { deleted: boolean; }
|
||||
export type GetAgentThreadMessagesInput = { threadId: string; resourceId?: string; }
|
||||
export type GetAgentThreadMessagesOutput = any[]
|
||||
export type GetAgentThreadRunsInput = { threadId: string; resourceId?: string; }
|
||||
export type GetAgentThreadRunsOutput = any[]
|
||||
export type GetAgentThreadsInput = { agentName?: string; resourceId?: string; limit?: number; offset?: number; }
|
||||
export type GetAgentThreadsOutput = any[]
|
||||
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 RemoteRPCHandlerInput = { rpcName: string; data?: unknown; }
|
||||
export type RpcCallerInput = { rpcName: string; data?: unknown; }
|
||||
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; }
|
||||
|
||||
import type { QueueJob } from '@pikku/core/queue'
|
||||
|
||||
interface QueueHandler<I, O> {
|
||||
input: I;
|
||||
output: O;
|
||||
}
|
||||
|
||||
export type QueueMap = {
|
||||
readonly 'pikku-remote-internal-rpc': QueueHandler<RemoteRPCHandlerInput, null>,
|
||||
};
|
||||
|
||||
|
||||
type QueueAdd = <Name extends keyof QueueMap>(
|
||||
name: Name,
|
||||
data: QueueMap[Name]['input'],
|
||||
options?: {
|
||||
priority?: number
|
||||
delay?: number
|
||||
attempts?: number
|
||||
removeOnComplete?: number
|
||||
removeOnFail?: number
|
||||
jobId?: string
|
||||
}
|
||||
) => Promise<string>
|
||||
|
||||
type QueueGetJob = <Name extends keyof QueueMap>(
|
||||
name: Name,
|
||||
jobId: string
|
||||
) => Promise<QueueJob<QueueMap[Name]['input'], QueueMap[Name]['output']> | null>
|
||||
|
||||
export type TypedPikkuQueue = {
|
||||
add: QueueAdd;
|
||||
getJob: QueueGetJob;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"pikku-remote-internal-rpc": {
|
||||
"pikkuFuncId": "remoteRPCHandler",
|
||||
"name": "pikku-remote-internal-rpc"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
import { pikkuState } from '@pikku/core/internal'
|
||||
import { QueueWorkersMeta } from '@pikku/core/queue'
|
||||
import metaData from './pikku-queue-workers-wirings-meta.gen.json' with { type: 'json' }
|
||||
|
||||
pikkuState(null, 'queue', 'meta', metaData as QueueWorkersMeta)
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/* The files with an addQueueWorkers function call */
|
||||
import '../../../../src/scaffold/rpc-remote.gen.js'
|
||||
148
packages/functions/packages/functions/.pikku/rpc/pikku-rpc-wirings-map.gen.d.ts
vendored
Normal file
148
packages/functions/packages/functions/.pikku/rpc/pikku-rpc-wirings-map.gen.d.ts
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
* This provides the structure needed for typescript to be aware of RPCs and their return 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; }
|
||||
export type DeleteAgentThreadInput = { threadId: string; resourceId?: string; }
|
||||
export type DeleteAgentThreadOutput = { deleted: boolean; }
|
||||
export type GetAgentThreadMessagesInput = { threadId: string; resourceId?: string; }
|
||||
export type GetAgentThreadMessagesOutput = any[]
|
||||
export type GetAgentThreadRunsInput = { threadId: string; resourceId?: string; }
|
||||
export type GetAgentThreadRunsOutput = any[]
|
||||
export type GetAgentThreadsInput = { agentName?: string; resourceId?: string; limit?: number; offset?: number; }
|
||||
export type GetAgentThreadsOutput = any[]
|
||||
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 RemoteRPCHandlerInput = { rpcName: string; data?: unknown; }
|
||||
export type RpcCallerInput = { rpcName: string; data?: unknown; }
|
||||
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 '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 'getAgentThreads': RPCHandler<GetAgentThreadsInput, GetAgentThreadsOutput>,
|
||||
readonly 'getAgentThreadMessages': RPCHandler<GetAgentThreadMessagesInput, GetAgentThreadMessagesOutput>,
|
||||
readonly 'getAgentThreadRuns': RPCHandler<GetAgentThreadRunsInput, GetAgentThreadRunsOutput>,
|
||||
readonly 'deleteAgentThread': RPCHandler<DeleteAgentThreadInput, DeleteAgentThreadOutput>,
|
||||
};
|
||||
|
||||
|
||||
// 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>
|
||||
|
||||
160
packages/functions/packages/functions/.pikku/rpc/pikku-rpc-wirings-map.internal.gen.d.ts
vendored
Normal file
160
packages/functions/packages/functions/.pikku/rpc/pikku-rpc-wirings-map.internal.gen.d.ts
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
* This provides the structure needed for typescript to be aware of RPCs and their return types
|
||||
*/
|
||||
|
||||
|
||||
import type { WorkflowRunStatus } from '@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; }
|
||||
export type DeleteAgentThreadInput = { threadId: string; resourceId?: string; }
|
||||
export type DeleteAgentThreadOutput = { deleted: boolean; }
|
||||
export type GetAgentThreadMessagesInput = { threadId: string; resourceId?: string; }
|
||||
export type GetAgentThreadMessagesOutput = any[]
|
||||
export type GetAgentThreadRunsInput = { threadId: string; resourceId?: string; }
|
||||
export type GetAgentThreadRunsOutput = any[]
|
||||
export type GetAgentThreadsInput = { agentName?: string; resourceId?: string; limit?: number; offset?: number; }
|
||||
export type GetAgentThreadsOutput = any[]
|
||||
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 RemoteRPCHandlerInput = { rpcName: string; data?: unknown; }
|
||||
export type RpcCallerInput = { rpcName: string; data?: unknown; }
|
||||
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 '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 'remoteRPCHandler': RPCHandler<RemoteRPCHandlerInput, null>,
|
||||
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>,
|
||||
readonly 'rpcCaller': RPCHandler<RpcCallerInput, null>,
|
||||
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>,
|
||||
};
|
||||
|
||||
|
||||
// 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,23 @@
|
||||
{
|
||||
"pikkuConsoleSetSecret": "pikkuConsoleSetSecret",
|
||||
"pikkuConsoleGetVariable": "pikkuConsoleGetVariable",
|
||||
"pikkuConsoleSetVariable": "pikkuConsoleSetVariable",
|
||||
"pikkuConsoleHasSecret": "pikkuConsoleHasSecret",
|
||||
"pikkuConsoleGetSecret": "pikkuConsoleGetSecret",
|
||||
"remoteRPCHandler": "remoteRPCHandler",
|
||||
"workflowStarter": "workflowStarter",
|
||||
"workflowRunner": "workflowRunner",
|
||||
"workflowStatusChecker": "workflowStatusChecker",
|
||||
"workflowStatusStream": "workflowStatusStream",
|
||||
"workflowStatusStreamFull": "workflowStatusStreamFull",
|
||||
"graphStarter": "graphStarter",
|
||||
"rpcCaller": "rpcCaller",
|
||||
"agentCaller": "agentCaller",
|
||||
"agentStreamCaller": "agentStreamCaller",
|
||||
"agentApproveCaller": "agentApproveCaller",
|
||||
"agentResumeCaller": "agentResumeCaller",
|
||||
"getAgentThreads": "getAgentThreads",
|
||||
"getAgentThreadMessages": "getAgentThreadMessages",
|
||||
"getAgentThreadRuns": "getAgentThreadRuns",
|
||||
"deleteAgentThread": "deleteAgentThread"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
import { pikkuState } from '@pikku/core/internal'
|
||||
import metaData from './pikku-rpc-wirings-meta.internal.gen.json' with { type: 'json' }
|
||||
pikkuState(null, 'rpc', 'meta', metaData as Record<string, string>)
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
/**
|
||||
* Scheduler-specific type definitions for tree-shaking optimization
|
||||
*/
|
||||
|
||||
import { CoreScheduledTask, wireScheduler as wireSchedulerCore } from '@pikku/core/scheduler'
|
||||
import type { PikkuFunctionConfig, PikkuMiddleware } from '../function/pikku-function-types.gen.js'
|
||||
|
||||
/**
|
||||
* Type definition for scheduled tasks that run at specified intervals.
|
||||
* These are sessionless functions that execute based on cron expressions.
|
||||
*/
|
||||
type SchedulerWiring = CoreScheduledTask<PikkuFunctionConfig<void, void, 'session' | 'rpc'>, PikkuMiddleware>
|
||||
|
||||
/**
|
||||
* Registers a scheduled task with the Pikku framework.
|
||||
* Tasks run based on cron expressions and are sessionless.
|
||||
*
|
||||
* @param task - Scheduled task definition with cron expression and handler
|
||||
*/
|
||||
export const wireScheduler = (task: SchedulerWiring) => {
|
||||
wireSchedulerCore(task as any)
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* This file was generated by @pikku/cli@0.12.23
|
||||
*/
|
||||
import { addSchema } from '@pikku/core/schema'
|
||||
|
||||
import * as PikkuConsoleSetSecretInput from './schemas/PikkuConsoleSetSecretInput.schema.json' with { type: 'json' }
|
||||
addSchema('PikkuConsoleSetSecretInput', PikkuConsoleSetSecretInput)
|
||||
|
||||
|
||||
import * as PikkuConsoleSetSecretOutput from './schemas/PikkuConsoleSetSecretOutput.schema.json' with { type: 'json' }
|
||||
addSchema('PikkuConsoleSetSecretOutput', PikkuConsoleSetSecretOutput)
|
||||
|
||||
|
||||
import * as PikkuConsoleGetVariableInput from './schemas/PikkuConsoleGetVariableInput.schema.json' with { type: 'json' }
|
||||
addSchema('PikkuConsoleGetVariableInput', PikkuConsoleGetVariableInput)
|
||||
|
||||
|
||||
import * as PikkuConsoleGetVariableOutput from './schemas/PikkuConsoleGetVariableOutput.schema.json' with { type: 'json' }
|
||||
addSchema('PikkuConsoleGetVariableOutput', PikkuConsoleGetVariableOutput)
|
||||
|
||||
|
||||
import * as PikkuConsoleSetVariableInput from './schemas/PikkuConsoleSetVariableInput.schema.json' with { type: 'json' }
|
||||
addSchema('PikkuConsoleSetVariableInput', PikkuConsoleSetVariableInput)
|
||||
|
||||
|
||||
import * as PikkuConsoleSetVariableOutput from './schemas/PikkuConsoleSetVariableOutput.schema.json' with { type: 'json' }
|
||||
addSchema('PikkuConsoleSetVariableOutput', PikkuConsoleSetVariableOutput)
|
||||
|
||||
|
||||
import * as PikkuConsoleHasSecretInput from './schemas/PikkuConsoleHasSecretInput.schema.json' with { type: 'json' }
|
||||
addSchema('PikkuConsoleHasSecretInput', PikkuConsoleHasSecretInput)
|
||||
|
||||
|
||||
import * as PikkuConsoleHasSecretOutput from './schemas/PikkuConsoleHasSecretOutput.schema.json' with { type: 'json' }
|
||||
addSchema('PikkuConsoleHasSecretOutput', PikkuConsoleHasSecretOutput)
|
||||
|
||||
|
||||
import * as PikkuConsoleGetSecretInput from './schemas/PikkuConsoleGetSecretInput.schema.json' with { type: 'json' }
|
||||
addSchema('PikkuConsoleGetSecretInput', PikkuConsoleGetSecretInput)
|
||||
|
||||
|
||||
import * as PikkuConsoleGetSecretOutput from './schemas/PikkuConsoleGetSecretOutput.schema.json' with { type: 'json' }
|
||||
addSchema('PikkuConsoleGetSecretOutput', PikkuConsoleGetSecretOutput)
|
||||
|
||||
|
||||
import * as StreamWorkflowRunInput from './schemas/StreamWorkflowRunInput.schema.json' with { type: 'json' }
|
||||
addSchema('StreamWorkflowRunInput', StreamWorkflowRunInput)
|
||||
|
||||
|
||||
import * as RemoteRPCHandlerInput from './schemas/RemoteRPCHandlerInput.schema.json' with { type: 'json' }
|
||||
addSchema('RemoteRPCHandlerInput', RemoteRPCHandlerInput)
|
||||
|
||||
|
||||
import * as WorkflowStarterInput from './schemas/WorkflowStarterInput.schema.json' with { type: 'json' }
|
||||
addSchema('WorkflowStarterInput', WorkflowStarterInput)
|
||||
|
||||
|
||||
import * as WorkflowStarterOutput from './schemas/WorkflowStarterOutput.schema.json' with { type: 'json' }
|
||||
addSchema('WorkflowStarterOutput', WorkflowStarterOutput)
|
||||
|
||||
|
||||
import * as WorkflowRunnerInput from './schemas/WorkflowRunnerInput.schema.json' with { type: 'json' }
|
||||
addSchema('WorkflowRunnerInput', WorkflowRunnerInput)
|
||||
|
||||
|
||||
import * as WorkflowStatusCheckerInput from './schemas/WorkflowStatusCheckerInput.schema.json' with { type: 'json' }
|
||||
addSchema('WorkflowStatusCheckerInput', WorkflowStatusCheckerInput)
|
||||
|
||||
|
||||
import * as WorkflowRunStatus from './schemas/WorkflowRunStatus.schema.json' with { type: 'json' }
|
||||
addSchema('WorkflowRunStatus', WorkflowRunStatus)
|
||||
|
||||
|
||||
import * as WorkflowStatusStreamInput from './schemas/WorkflowStatusStreamInput.schema.json' with { type: 'json' }
|
||||
addSchema('WorkflowStatusStreamInput', WorkflowStatusStreamInput)
|
||||
|
||||
|
||||
import * as WorkflowStatusStreamFullInput from './schemas/WorkflowStatusStreamFullInput.schema.json' with { type: 'json' }
|
||||
addSchema('WorkflowStatusStreamFullInput', WorkflowStatusStreamFullInput)
|
||||
|
||||
|
||||
import * as GraphStarterInput from './schemas/GraphStarterInput.schema.json' with { type: 'json' }
|
||||
addSchema('GraphStarterInput', GraphStarterInput)
|
||||
|
||||
|
||||
import * as GraphStarterOutput from './schemas/GraphStarterOutput.schema.json' with { type: 'json' }
|
||||
addSchema('GraphStarterOutput', GraphStarterOutput)
|
||||
|
||||
|
||||
import * as RpcCallerInput from './schemas/RpcCallerInput.schema.json' with { type: 'json' }
|
||||
addSchema('RpcCallerInput', RpcCallerInput)
|
||||
|
||||
|
||||
import * as AgentCallerInput from './schemas/AgentCallerInput.schema.json' with { type: 'json' }
|
||||
addSchema('AgentCallerInput', AgentCallerInput)
|
||||
|
||||
|
||||
import * as AgentStreamCallerInput from './schemas/AgentStreamCallerInput.schema.json' with { type: 'json' }
|
||||
addSchema('AgentStreamCallerInput', AgentStreamCallerInput)
|
||||
|
||||
|
||||
import * as AgentApproveCallerInput from './schemas/AgentApproveCallerInput.schema.json' with { type: 'json' }
|
||||
addSchema('AgentApproveCallerInput', AgentApproveCallerInput)
|
||||
|
||||
|
||||
import * as AgentResumeCallerInput from './schemas/AgentResumeCallerInput.schema.json' with { type: 'json' }
|
||||
addSchema('AgentResumeCallerInput', AgentResumeCallerInput)
|
||||
|
||||
|
||||
import * as GetAgentThreadsInput from './schemas/GetAgentThreadsInput.schema.json' with { type: 'json' }
|
||||
addSchema('GetAgentThreadsInput', GetAgentThreadsInput)
|
||||
|
||||
|
||||
import * as GetAgentThreadsOutput from './schemas/GetAgentThreadsOutput.schema.json' with { type: 'json' }
|
||||
addSchema('GetAgentThreadsOutput', GetAgentThreadsOutput)
|
||||
|
||||
|
||||
import * as GetAgentThreadMessagesInput from './schemas/GetAgentThreadMessagesInput.schema.json' with { type: 'json' }
|
||||
addSchema('GetAgentThreadMessagesInput', GetAgentThreadMessagesInput)
|
||||
|
||||
|
||||
import * as GetAgentThreadMessagesOutput from './schemas/GetAgentThreadMessagesOutput.schema.json' with { type: 'json' }
|
||||
addSchema('GetAgentThreadMessagesOutput', GetAgentThreadMessagesOutput)
|
||||
|
||||
|
||||
import * as GetAgentThreadRunsInput from './schemas/GetAgentThreadRunsInput.schema.json' with { type: 'json' }
|
||||
addSchema('GetAgentThreadRunsInput', GetAgentThreadRunsInput)
|
||||
|
||||
|
||||
import * as GetAgentThreadRunsOutput from './schemas/GetAgentThreadRunsOutput.schema.json' with { type: 'json' }
|
||||
addSchema('GetAgentThreadRunsOutput', GetAgentThreadRunsOutput)
|
||||
|
||||
|
||||
import * as DeleteAgentThreadInput from './schemas/DeleteAgentThreadInput.schema.json' with { type: 'json' }
|
||||
addSchema('DeleteAgentThreadInput', DeleteAgentThreadInput)
|
||||
|
||||
|
||||
import * as DeleteAgentThreadOutput from './schemas/DeleteAgentThreadOutput.schema.json' with { type: 'json' }
|
||||
addSchema('DeleteAgentThreadOutput', DeleteAgentThreadOutput)
|
||||
@@ -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":{}}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user