theokit 0.19.4 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/{build-6UDZJIHD.js → build-TBV4KVXI.js} +2 -2
  2. package/dist/chunk-5PY3QPVM.js +263 -0
  3. package/dist/chunk-5PY3QPVM.js.map +1 -0
  4. package/dist/cli/index.js +2 -2
  5. package/dist/{define-websocket-CdK94O-D.d.ts → define-websocket-CPQcQK9h.d.ts} +1 -19
  6. package/dist/{generate-OMKHQ7OM.js → generate-373DALPY.js} +62 -70
  7. package/dist/generate-373DALPY.js.map +1 -0
  8. package/dist/{index-B40qUSrQ.d.ts → index-C3ged4mn.d.ts} +2 -2
  9. package/dist/index.d.ts +26 -5
  10. package/dist/index.js +23 -3
  11. package/dist/index.js.map +1 -1
  12. package/dist/{plugin-runner-BGBkzgi0.d.ts → plugin-runner-CMprWWHZ.d.ts} +1 -1
  13. package/dist/{plugin-types-DNJGxr4Z.d.ts → plugin-types-L49QYMb5.d.ts} +1 -19
  14. package/dist/{registry-XSRSTH33.js → registry-3NB7KOUI.js} +2 -2
  15. package/dist/server/define/index.d.ts +270 -57
  16. package/dist/server/define/index.js +14 -16
  17. package/dist/server/http/index.d.ts +3 -3
  18. package/dist/server/index.d.ts +6 -6
  19. package/dist/server/index.js +20 -27
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/plugins/index.d.ts +3 -3
  22. package/dist/server/realtime/index.d.ts +1 -1
  23. package/dist/{static-CYG2WGYP.js → static-MB2CD5Y3.js} +1 -1
  24. package/dist/static-MB2CD5Y3.js.map +1 -0
  25. package/package.json +2 -2
  26. package/dist/chunk-CWVBDUDC.js +0 -108
  27. package/dist/chunk-CWVBDUDC.js.map +0 -1
  28. package/dist/generate-OMKHQ7OM.js.map +0 -1
  29. package/dist/static-CYG2WGYP.js.map +0 -1
  30. /package/dist/{build-6UDZJIHD.js.map → build-TBV4KVXI.js.map} +0 -0
  31. /package/dist/{registry-XSRSTH33.js.map → registry-3NB7KOUI.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/generate.ts","../src/cli/commands/generate-resource.ts","../src/cli/commands/generate-types.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\n\nimport { generateResource } from './generate-resource.js'\nimport {\n VALID_TYPES,\n type GeneratorType,\n type GenerateOptions,\n type GenerateStatus,\n type GenerateResult,\n} from './generate-types.js'\n\nexport {\n VALID_TYPES,\n type GeneratorType,\n type GenerateOptions,\n type GenerateStatus,\n type GenerateResult,\n}\n\nfunction toKebabCase(name: string): boolean {\n return /^[a-z][a-z0-9/-]*$/.test(name)\n}\n\n/**\n * Reserved JS identifier names that conflict with object prototype machinery\n * or would shadow built-in module shapes if used as action/route names.\n * Mirrors action-scan's RESERVED_NAMES (T1.4).\n */\nconst RESERVED_BASENAMES = new Set([\n 'index',\n 'constructor',\n '__proto__',\n 'prototype',\n 'hasOwnProperty',\n])\n\n/**\n * Validate that a name segment doesn't hit the reserved-identifier list.\n * Checks the BASENAME (last `/` segment) — nested `admin/constructor` still\n * rejected because the file `constructor.ts` would be the conflict.\n */\nfunction hasReservedSegment(name: string): boolean {\n const basename = name.includes('/') ? (name.split('/').pop() ?? name) : name\n return RESERVED_BASENAMES.has(basename)\n}\n\n/**\n * EC-4: validate that resolving `targetSubpath` under `cwd` stays inside `cwd`.\n * Returns `true` when path is safe (stays inside), `false` when it escapes via\n * `..`, absolute path, null byte, or similar traversal vector.\n */\nfunction isPathInside(cwd: string, targetSubpath: string): boolean {\n if (targetSubpath.includes('\\x00')) return false\n if (targetSubpath.startsWith('/') || targetSubpath.startsWith('\\\\')) return false\n const resolved = resolve(cwd, targetSubpath)\n const cwdResolved = resolve(cwd)\n const sep = process.platform === 'win32' ? '\\\\' : '/'\n return resolved === cwdResolved || resolved.startsWith(cwdResolved + sep)\n}\n\nfunction toPascalCase(name: string): string {\n return name\n .split(/[-/]/)\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join('')\n}\n\nfunction toCamelCase(name: string): string {\n const pascal = toPascalCase(name)\n return pascal.charAt(0).toLowerCase() + pascal.slice(1)\n}\n\nfunction generateRouteTemplate(name: string): string {\n return [\n `import { route } from 'theokit/server'`,\n `import { z } from 'zod'`,\n ``,\n `export const GET = route()`,\n ` .handler(({ ctx }) => {`,\n ` return { message: 'TODO: implement ${name} GET' }`,\n ` })`,\n ` .build()`,\n ``,\n ].join('\\n')\n}\n\nfunction generateControllerTemplate(name: string): string {\n const className = toPascalCase(name) + 'Controller'\n const baseName = name.split('/').pop() ?? name\n return [\n `// AUTO-GENERATED by \\`theokit generate controller ${name}\\``,\n `import { Controller, Get } from '@theokit/http'`,\n ``,\n `@Controller('${baseName}')`,\n `export class ${className} {`,\n ` @Get()`,\n ` findAll(): string {`,\n ` return 'This action returns all ${baseName}'`,\n ` }`,\n `}`,\n ``,\n ].join('\\n')\n}\n\nfunction generateActionTemplate(name: string): string {\n const camel = toCamelCase(name)\n return [\n `import { action } from 'theokit/server'`,\n `import { z } from 'zod'`,\n ``,\n `export const ${camel} = action()`,\n ` .input(z.object({}))`,\n ` .handler(({ input, ctx }) => {`,\n ` return { message: 'TODO: implement ${name}' }`,\n ` })`,\n ` .build()`,\n ``,\n ].join('\\n')\n}\n\n/**\n * Co-located test template for `theokit generate action <name>` (T5.2 +\n * plan Q4 cenário 1: roundtrip serialize). Skeleton uses vitest BDD shape\n * per testing.md.\n */\nfunction generateAgentTemplate(name: string): string {\n return [\n `import { agent } from '@theokit/agents'`,\n `import { z } from 'zod'`,\n ``,\n `// Zero-config: this file is auto-served at POST /api/agents/${name}.`,\n `// Add tools with tool('name')...build() and chain them via .tool(...).`,\n `export default agent()`,\n ` .input(z.object({ message: z.string() }))`,\n ` .model('openai/gpt-4o-mini')`,\n ` .system('You are a helpful ${name} assistant.')`,\n ` .build()`,\n ``,\n ].join('\\n')\n}\n\nfunction generateToolboxTemplate(name: string): string {\n const camel = toCamelCase(name)\n return [\n `import { tool } from 'theokit/server'`,\n `import { z } from 'zod'`,\n ``,\n `export const ${camel}Hello = tool('${name}_hello')`,\n ` .describe('Say hello')`,\n ` .input(z.object({ name: z.string() }))`,\n ` .execute(({ name }) => \\`Hello, \\${name}!\\`)`,\n ` .build()`,\n ``,\n ].join('\\n')\n}\n\nfunction resolveTemplate(\n cwd: string,\n type: GeneratorType,\n name: string,\n): { filePath: string; content: string } | null {\n switch (type) {\n case 'route':\n return {\n filePath: resolve(cwd, 'server/routes', `${name}.ts`),\n content: generateRouteTemplate(name),\n }\n case 'action':\n return {\n filePath: resolve(cwd, 'server/actions', `${name}.ts`),\n content: generateActionTemplate(name),\n }\n case 'page':\n return { filePath: resolve(cwd, `app/${name}/page.tsx`), content: generatePageTemplate(name) }\n case 'ws':\n return {\n filePath: resolve(cwd, 'server/ws', `${name}.ts`),\n content: generateWsTemplate(name),\n }\n case 'controller':\n return {\n filePath: resolve(cwd, 'server/controllers', `${name}.controller.ts`),\n content: generateControllerTemplate(name),\n }\n case 'agent':\n return {\n filePath: resolve(cwd, 'server/agents', `${name}.agent.ts`),\n content: generateAgentTemplate(name),\n }\n case 'toolbox':\n return {\n filePath: resolve(cwd, 'server/toolboxes', `${name}.tools.ts`),\n content: generateToolboxTemplate(name),\n }\n default:\n return null\n }\n}\n\nfunction generateActionTestTemplate(name: string): string {\n const camel = toCamelCase(name)\n return [\n `import { describe, it, expect } from 'vitest'`,\n ``,\n `import { ${camel} } from './${name.split('/').pop()}.js'`,\n ``,\n `describe('${name} action', () => {`,\n ` it('should accept a valid input shape', () => {`,\n ` expect(${camel}.input).toBeDefined()`,\n ` expect(typeof ${camel}.handler).toBe('function')`,\n ` })`,\n ``,\n ` it('should reject invalid input via zod schema', () => {`,\n ` const parsed = ${camel}.input.safeParse({ __invalid: true })`,\n ` // Empty object schema accepts {}; tighten this assertion when adding fields.`,\n ` expect(parsed.success).toBe(true)`,\n ` })`,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generatePageTemplate(name: string): string {\n const pascal = toPascalCase(name)\n return [`export default function ${pascal}Page() {`, ` return <h1>${pascal}</h1>`, `}`, ``].join(\n '\\n',\n )\n}\n\nfunction generateWsTemplate(_name: string): string {\n return [\n `import { defineWebSocket } from 'theokit/server'`,\n ``,\n `export default defineWebSocket({`,\n ` onMessage(ws, data) {`,\n ` ws.send(\\`echo: \\${data}\\`)`,\n ` },`,\n `})`,\n ``,\n ].join('\\n')\n}\n\n/**\n * Programmatic generate. Returns a structured result instead of throwing —\n * Studio (`theokit_generate` tool) consumes this directly. The CLI wrapper\n * below maps the structured result to console output + exit code semantics.\n */\n// eslint-disable-next-line @typescript-eslint/require-await\nexport async function generate(opts: GenerateOptions): Promise<GenerateResult> {\n const { cwd, type, name } = opts\n\n if (!existsSync(resolve(cwd, 'theo.config.ts')) && !existsSync(resolve(cwd, 'theo.config.js'))) {\n return {\n status: 'not_a_project',\n message: 'Not a Theo project. cwd has no theo.config.ts or theo.config.js',\n }\n }\n\n if (!VALID_TYPES.includes(type as GeneratorType)) {\n return {\n status: 'invalid_kind',\n message: `Invalid generator type \"${type}\". Available: ${VALID_TYPES.join(', ')}`,\n }\n }\n\n if (!name || !toKebabCase(name)) {\n return {\n status: 'invalid_name',\n message: `Invalid name \"${name}\". Use kebab-case: lowercase letters, numbers, hyphens.`,\n }\n }\n\n // EC-2-related: reject reserved JS identifier basenames (would shadow\n // built-in prototype machinery in virtual module emit or scan).\n if (hasReservedSegment(name)) {\n return {\n status: 'invalid_name',\n message: `Reserved name \"${name}\" — basename collides with built-in identifier (index/constructor/__proto__/prototype/hasOwnProperty).`,\n }\n }\n\n // Resource generates multiple files — handle separately\n if (type === 'resource') {\n return generateResource(cwd, name, opts.fields ?? [])\n }\n\n const resolved = resolveTemplate(cwd, type as GeneratorType, name)\n if (resolved === null) {\n return { status: 'invalid_kind', message: `Unknown type: ${type}` }\n }\n const { filePath, content } = resolved\n\n // EC-4: confirm the resolved filePath stays inside cwd. `toKebabCase`\n // already rejects most traversal vectors but a defense-in-depth check\n // against `..` slipping in via valid-looking segments is cheap.\n const relativeFromCwd = filePath.startsWith(resolve(cwd))\n ? filePath.slice(resolve(cwd).length + 1)\n : filePath\n if (!isPathInside(cwd, relativeFromCwd)) {\n return {\n status: 'invalid_name',\n message: `Path traversal denied: \"${name}\" would escape project root.`,\n }\n }\n\n if (existsSync(filePath)) {\n return { status: 'already_exists', filePath, kind: type as GeneratorType, name }\n }\n\n mkdirSync(dirname(filePath), { recursive: true })\n writeFileSync(filePath, content)\n\n // T5.2 + plan Q4: emit co-located test file alongside actions (only\n // for action type; routes/pages/ws keep prior single-file behavior).\n // Skip if existing test file present (preserve user-customized tests).\n if (type === 'action') {\n const testPath = filePath.replace(/\\.ts$/, '.test.ts')\n if (!existsSync(testPath)) {\n writeFileSync(testPath, generateActionTestTemplate(name))\n }\n }\n\n return { status: 'created', filePath, kind: type as GeneratorType, name }\n}\n\n/**\n * CLI entry point — preserves the original surface (throws + console.log).\n * Wraps the programmatic `generate` function.\n */\nexport async function generateCommand(\n type: string,\n name: string,\n fields?: string[],\n): Promise<void> {\n const result = await generate({ cwd: process.cwd(), type, name, fields })\n switch (result.status) {\n case 'not_a_project':\n throw new Error('Not a Theo project. Run this from a project root with theo.config.ts')\n case 'invalid_kind':\n throw new Error(result.message ?? 'Invalid kind')\n case 'invalid_name':\n throw new Error(\n `Invalid name \"${name}\". Use kebab-case: lowercase letters, numbers, hyphens. Example: my-route`,\n )\n case 'already_exists':\n console.log(`\\n ⚠ ${result.filePath} already exists. Skipping.\\n`)\n return\n case 'created':\n console.log(`\\n ✓ Created ${type}: ${result.filePath}\\n`)\n return\n }\n}\n","import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\n\nimport type { GenerateResult } from './generate-types.js'\n\n// --- Field parsing ---\n\nconst ALLOWED_FIELD_TYPES = new Set(['string', 'text', 'number', 'boolean'])\nconst RESERVED_FIELDS = new Set(['id', 'createdAt', 'created_at'])\n\nexport interface ResourceField {\n name: string\n type: 'string' | 'text' | 'number' | 'boolean'\n drizzleColumn: string\n zodType: string\n}\n\nexport function parseResourceFields(args: string[]): ResourceField[] {\n if (args.length === 0) {\n throw new Error(\n 'Resource requires at least one field. Example: theokit generate resource posts title:string',\n )\n }\n return args.map((arg) => {\n const parts = arg.split(':')\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n throw new Error(`Invalid field \"${arg}\". Use name:type format (e.g. title:string)`)\n }\n const [name, type] = parts\n if (!ALLOWED_FIELD_TYPES.has(type)) {\n throw new Error(\n `Unknown field type \"${type}\" in \"${arg}\". Valid types: ${[...ALLOWED_FIELD_TYPES].join(', ')}`,\n )\n }\n if (RESERVED_FIELDS.has(name)) {\n throw new Error(`Field \"${name}\" is reserved (auto-generated by the framework)`)\n }\n const fieldType = type as ResourceField['type']\n return {\n name,\n type: fieldType,\n drizzleColumn: mapDrizzleColumn(name, fieldType),\n zodType: mapZodType(fieldType),\n }\n })\n}\n\n// --- Column / type mapping ---\n\nfunction mapDrizzleColumn(name: string, type: ResourceField['type']): string {\n switch (type) {\n case 'string':\n case 'text':\n return `text('${name}').notNull()`\n case 'number':\n return `integer('${name}').notNull()`\n case 'boolean':\n return `integer('${name}', { mode: 'boolean' }).notNull().default(false)`\n }\n}\n\nfunction mapZodType(type: ResourceField['type']): string {\n switch (type) {\n case 'string':\n case 'text':\n return 'z.string()'\n case 'number':\n return 'z.number()'\n case 'boolean':\n return 'z.boolean()'\n }\n}\n\n// --- Template generators ---\n\nfunction generateSchemaEntry(resourceName: string, fields: ResourceField[]): string {\n const cols = [\n ` id: integer('id').primaryKey({ autoIncrement: true }),`,\n ...fields.map((f) => ` ${f.name}: ${f.drizzleColumn},`),\n ` createdAt: text('created_at')`,\n ` .notNull()`,\n ` .$defaultFn(() => new Date().toISOString().split('T')[0]),`,\n ]\n return [\n ``,\n `export const ${resourceName} = sqliteTable('${resourceName}', {`,\n ...cols,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generateRouteIndex(resourceName: string, fields: ResourceField[]): string {\n const bodyFields = fields.map((f) => ` ${f.name}: ${f.zodType},`).join('\\n')\n return [\n `import { route } from 'theokit/server/define'`,\n `import { z } from 'zod'`,\n `import { db } from '../../db/index.js'`,\n `import { ${resourceName} } from '../../db/schema.js'`,\n ``,\n `export const GET = route()`,\n ` .handler(() => db.select().from(${resourceName}).all())`,\n ` .build()`,\n ``,\n `export const POST = route()`,\n ` .body(`,\n ` z.object({`,\n bodyFields,\n ` }),`,\n ` )`,\n ` .status(201)`,\n ` .handler(({ body }) => {`,\n ` const result = db.insert(${resourceName}).values(body).returning().get()`,\n ` return result`,\n ` })`,\n ` .build()`,\n ``,\n ].join('\\n')\n}\n\nfunction generateRouteId(resourceName: string, fields: ResourceField[]): string {\n const updateFields = fields.map((f) => ` ${f.name}: ${f.zodType}.optional(),`).join('\\n')\n return [\n `import { route } from 'theokit/server/define'`,\n `import { z } from 'zod'`,\n `import { db } from '../../db/index.js'`,\n `import { ${resourceName} } from '../../db/schema.js'`,\n `import { eq } from 'drizzle-orm'`,\n ``,\n `export const GET = route()`,\n ` .params(z.object({ id: z.coerce.number() }))`,\n ` .handler(({ params }) => {`,\n ` const item = db.select().from(${resourceName}).where(eq(${resourceName}.id, params.id)).get()`,\n ` if (!item) return new Response(JSON.stringify({ error: 'Not found' }), { status: 404 })`,\n ` return item`,\n ` })`,\n ` .build()`,\n ``,\n `export const PUT = route()`,\n ` .params(z.object({ id: z.coerce.number() }))`,\n ` .body(`,\n ` z.object({`,\n updateFields,\n ` }),`,\n ` )`,\n ` .handler(({ params, body }) => {`,\n ` const result = db.update(${resourceName}).set(body).where(eq(${resourceName}.id, params.id)).returning().get()`,\n ` if (!result) return new Response(JSON.stringify({ error: 'Not found' }), { status: 404 })`,\n ` return result`,\n ` })`,\n ` .build()`,\n ``,\n `export const DELETE = route()`,\n ` .params(z.object({ id: z.coerce.number() }))`,\n ` .status(204)`,\n ` .handler(({ params }) => {`,\n ` db.delete(${resourceName}).where(eq(${resourceName}.id, params.id)).run()`,\n ` })`,\n ` .build()`,\n ``,\n ].join('\\n')\n}\n\nfunction generateTestTemplate(resourceName: string): string {\n return [\n `import { describe, it, expect } from 'vitest'`,\n ``,\n `describe('${resourceName} API', () => {`,\n ` it('should have a schema defined', async () => {`,\n ` const schema = await import('../server/db/schema.js')`,\n ` expect(schema.${resourceName}).toBeDefined()`,\n ` })`,\n ``,\n ` it('should have route handlers', async () => {`,\n ` const index = await import('../server/routes/${resourceName}/index.js')`,\n ` expect(index.GET).toBeDefined()`,\n ` expect(index.POST).toBeDefined()`,\n ` })`,\n `})`,\n ``,\n ].join('\\n')\n}\n\n// --- Orchestrator ---\n\nexport function generateResource(cwd: string, name: string, fieldArgs: string[]): GenerateResult {\n let fields: ResourceField[]\n try {\n fields = parseResourceFields(fieldArgs)\n } catch (err) {\n return { status: 'invalid_name', message: err instanceof Error ? err.message : String(err) }\n }\n\n const schemaPath = resolve(cwd, 'server/db/schema.ts')\n const routeIndexPath = resolve(cwd, `server/routes/${name}/index.ts`)\n const routeIdPath = resolve(cwd, `server/routes/${name}/[id].ts`)\n const testPath = resolve(cwd, `tests/${name}.test.ts`)\n\n if (!existsSync(schemaPath)) {\n return {\n status: 'not_a_project',\n message:\n 'server/db/schema.ts not found. Run this from a TheoKit project with database setup.',\n }\n }\n\n if (existsSync(routeIndexPath)) {\n return { status: 'already_exists', filePath: routeIndexPath, kind: 'resource', name }\n }\n\n const existingSchema = readFileSync(schemaPath, 'utf-8')\n if (existingSchema.includes(`export const ${name} =`)) {\n return {\n status: 'already_exists',\n filePath: schemaPath,\n kind: 'resource',\n name,\n message: `Table \"${name}\" already exists in schema.ts`,\n }\n }\n\n // 1. Append schema entry\n appendFileSync(schemaPath, generateSchemaEntry(name, fields))\n\n // 2. Create route files\n mkdirSync(dirname(routeIndexPath), { recursive: true })\n writeFileSync(routeIndexPath, generateRouteIndex(name, fields))\n writeFileSync(routeIdPath, generateRouteId(name, fields))\n\n // 3. Create test file\n mkdirSync(dirname(testPath), { recursive: true })\n if (!existsSync(testPath)) {\n writeFileSync(testPath, generateTestTemplate(name))\n }\n\n return { status: 'created', filePath: routeIndexPath, kind: 'resource', name }\n}\n","/**\n * Shared types for the `theokit generate` CLI command family.\n *\n * Extracted to break the circular dependency between generate.ts ↔ generate-resource.ts\n * (architecture-remediation plan T1.1, 2026-06-12).\n */\n\nexport const VALID_TYPES = [\n 'route',\n 'action',\n 'page',\n 'ws',\n 'controller',\n 'agent',\n 'toolbox',\n 'resource',\n] as const\nexport type GeneratorType = (typeof VALID_TYPES)[number]\n\nexport interface GenerateOptions {\n cwd: string\n type: string\n name: string\n fields?: string[]\n}\n\nexport type GenerateStatus =\n | 'created'\n | 'already_exists'\n | 'invalid_kind'\n | 'invalid_name'\n | 'not_a_project'\n\nexport interface GenerateResult {\n status: GenerateStatus\n filePath?: string\n kind?: GeneratorType\n name?: string\n message?: string\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","writeFileSync","resolve","dirname","resolve","existsSync","mkdirSync","dirname","writeFileSync"]}
@@ -1,7 +1,7 @@
1
1
  import { a as TheoErrorCode, T as TheoErrorEnvelope } from './error-envelope-BsNzzAV5.js';
2
2
  import { IncomingMessage, ServerResponse } from 'node:http';
3
- import { P as PluginContext } from './plugin-types-DNJGxr4Z.js';
4
- import { P as PluginRunner } from './plugin-runner-BGBkzgi0.js';
3
+ import { P as PluginContext } from './plugin-types-L49QYMb5.js';
4
+ import { P as PluginRunner } from './plugin-runner-CMprWWHZ.js';
5
5
  import { J as JobBackend } from './job-backend-CgC8Xf33.js';
6
6
  import { L as LoadModule, S as ServerRouteNode } from './match-CfbEFRG4.js';
7
7
  import { c as CsrfMode, D as DisallowedConfig } from './csrf-BBrEZSBW.js';
package/dist/index.d.ts CHANGED
@@ -280,12 +280,33 @@ declare const theoConfigSchema: z.ZodObject<{
280
280
  }, z.core.$strip>;
281
281
  type TheoConfig = z.infer<typeof theoConfigSchema>;
282
282
 
283
+ /** The fluent config builder. No field is required; `.set()` merges arbitrary config fields. */
284
+ interface ConfigBuilder {
285
+ /** Project identifier (DNS-1123) — used by `.theokit/services.json`. */
286
+ name(value: TheoConfig['name']): ConfigBuilder;
287
+ /** Backend root dir (route/action/agent discovery). Default `'server'`. */
288
+ serverDir(value: TheoConfig['serverDir']): ConfigBuilder;
289
+ /** Frontend root dir (file-based router). Default `'app'`. */
290
+ appDir(value: TheoConfig['appDir']): ConfigBuilder;
291
+ /** Agents root dir. Default `'agents'`. */
292
+ agentsDir(value: TheoConfig['agentsDir']): ConfigBuilder;
293
+ /** Build output dir. Default `'.theokit'`. */
294
+ distDir(value: TheoConfig['distDir']): ConfigBuilder;
295
+ /** Dev/prod port. Default `3000`. */
296
+ port(value: TheoConfig['port']): ConfigBuilder;
297
+ /** Bind host. Default `'localhost'`. */
298
+ host(value: TheoConfig['host']): ConfigBuilder;
299
+ /** Enable server-side rendering. Default `false`. */
300
+ ssr(value: TheoConfig['ssr']): ConfigBuilder;
301
+ /** Escape hatch — merge any config fields not covered by a dedicated setter (rate-limit, security…). */
302
+ set(partial: Partial<TheoConfig>): ConfigBuilder;
303
+ /** Resolve to the `Partial<TheoConfig>` — the SAME value `defineConfig({...})` returns. */
304
+ build(): Partial<TheoConfig>;
305
+ }
283
306
  /**
284
- * Define Theo framework configuration.
285
- * Identity function — provides type inference for theo.config.ts.
286
- * Runtime validation happens in loadConfig(), not here.
307
+ * Start a fluent config definition. Chain the common setters and/or `.set(partial)`, then `.build()`.
287
308
  */
288
- declare function defineConfig(config: Partial<TheoConfig>): Partial<TheoConfig>;
309
+ declare function config(): ConfigBuilder;
289
310
 
290
311
  /**
291
312
  * Deep merges two plain objects. Override values take precedence.
@@ -359,4 +380,4 @@ interface EntryClientOptions {
359
380
  }
360
381
  declare function generateEntryClient(ssr?: boolean, opts?: EntryClientOptions): string;
361
382
 
362
- export { type ConfigIssue, type RouteNode, type TheoConfig, TheoConfigError, TheoProjectError, deepMerge, defineConfig, generateEntryClient, generateRouteManifest, isRouteFile, loadConfig, scanRoutes, theoConfigSchema, validateProjectStructure };
383
+ export { type ConfigBuilder, type ConfigIssue, type RouteNode, type TheoConfig, TheoConfigError, TheoProjectError, config, deepMerge, generateEntryClient, generateRouteManifest, isRouteFile, loadConfig, scanRoutes, theoConfigSchema, validateProjectStructure };
package/dist/index.js CHANGED
@@ -35,8 +35,28 @@ import "./chunk-UVXB2ER7.js";
35
35
  import "./chunk-DGUM43GV.js";
36
36
 
37
37
  // src/config/define-config.ts
38
- function defineConfig(config) {
39
- return config;
38
+ function defineConfig(config2) {
39
+ return config2;
40
+ }
41
+
42
+ // src/config/config-builder.ts
43
+ function makeConfigBuilder(spec) {
44
+ const merge = (patch) => makeConfigBuilder({ ...spec, ...patch });
45
+ return {
46
+ name: (value) => merge({ name: value }),
47
+ serverDir: (value) => merge({ serverDir: value }),
48
+ appDir: (value) => merge({ appDir: value }),
49
+ agentsDir: (value) => merge({ agentsDir: value }),
50
+ distDir: (value) => merge({ distDir: value }),
51
+ port: (value) => merge({ port: value }),
52
+ host: (value) => merge({ host: value }),
53
+ ssr: (value) => merge({ ssr: value }),
54
+ set: (partial) => merge(partial),
55
+ build: () => defineConfig(spec)
56
+ };
57
+ }
58
+ function config() {
59
+ return makeConfigBuilder({});
40
60
  }
41
61
 
42
62
  // src/config/validate-structure.ts
@@ -100,8 +120,8 @@ function validateProjectStructure(rootDir, appDir = "app") {
100
120
  export {
101
121
  TheoConfigError,
102
122
  TheoProjectError,
123
+ config,
103
124
  deepMerge,
104
- defineConfig,
105
125
  generateEntryClient,
106
126
  generateRouteManifest,
107
127
  isRouteFile,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config/define-config.ts","../src/config/validate-structure.ts","../src/core/errors.ts"],"sourcesContent":["import type { TheoConfig } from './schema.js'\n\n/**\n * Define Theo framework configuration.\n * Identity function — provides type inference for theo.config.ts.\n * Runtime validation happens in loadConfig(), not here.\n */\nexport function defineConfig(config: Partial<TheoConfig>): Partial<TheoConfig> {\n return config\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Project-structure validator. Reads paths joined onto `projectRoot`,\n * with names from a fixed `ValidationRule[]` table. No HTTP input.\n */\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { TheoProjectError } from '../core/errors.js'\n\ninterface ValidationRule {\n path: string\n errorMessage: string\n}\n\nconst REQUIRED_FILES: ValidationRule[] = [\n {\n path: 'theo.config.ts',\n errorMessage: 'Missing required file: theo.config.ts',\n },\n {\n path: 'package.json',\n errorMessage: 'Missing required file: package.json',\n },\n]\n\n// #95 — honor config `appDir` (default \"app\") so a project that groups its frontend under a custom\n// directory (e.g. `apps/web`) is not rejected by a hardcoded `app/` requirement.\nexport function validateProjectStructure(rootDir: string, appDir = 'app'): void {\n if (!existsSync(rootDir)) {\n throw new TheoProjectError([`Project directory does not exist: ${rootDir}`], rootDir)\n }\n\n const errors: string[] = []\n\n const requiredDirs: ValidationRule[] = [\n { path: appDir, errorMessage: `Missing required directory: ${appDir}/` },\n ]\n\n for (const rule of requiredDirs) {\n if (!existsSync(join(rootDir, rule.path))) {\n errors.push(rule.errorMessage)\n }\n }\n\n for (const rule of REQUIRED_FILES) {\n if (!existsSync(join(rootDir, rule.path))) {\n errors.push(rule.errorMessage)\n }\n }\n\n if (errors.length > 0) {\n throw new TheoProjectError(errors, rootDir)\n }\n}\n","export class TheoProjectError extends Error {\n public readonly errors: string[]\n public readonly rootDir: string\n\n constructor(errors: string[], rootDir: string) {\n const errorLines = errors.map((e) => ` - ${e}`).join('\\n')\n\n super(\n `Invalid Theo project structure\\n\\n` +\n ` Root: ${rootDir}\\n\\n` +\n (errorLines ? ` Errors:\\n${errorLines}\\n` : ''),\n )\n\n this.name = 'TheoProjectError'\n this.errors = errors\n this.rootDir = rootDir\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOO,SAAS,aAAa,QAAkD;AAC7E,SAAO;AACT;;;ACLA,SAAS,kBAAkB;AAC3B,SAAS,YAAY;;;ACLd,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1B;AAAA,EACA;AAAA,EAEhB,YAAY,QAAkB,SAAiB;AAC7C,UAAM,aAAa,OAAO,IAAI,CAAC,MAAM,OAAO,CAAC,EAAE,EAAE,KAAK,IAAI;AAE1D;AAAA,MACE;AAAA;AAAA,UACa,OAAO;AAAA;AAAA,KACjB,aAAa;AAAA,EAAc,UAAU;AAAA,IAAO;AAAA,IACjD;AAEA,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,UAAU;AAAA,EACjB;AACF;;;ADHA,IAAM,iBAAmC;AAAA,EACvC;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAIO,SAAS,yBAAyB,SAAiB,SAAS,OAAa;AAC9E,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,UAAM,IAAI,iBAAiB,CAAC,qCAAqC,OAAO,EAAE,GAAG,OAAO;AAAA,EACtF;AAEA,QAAM,SAAmB,CAAC;AAE1B,QAAM,eAAiC;AAAA,IACrC,EAAE,MAAM,QAAQ,cAAc,+BAA+B,MAAM,IAAI;AAAA,EACzE;AAEA,aAAW,QAAQ,cAAc;AAC/B,QAAI,CAAC,WAAW,KAAK,SAAS,KAAK,IAAI,CAAC,GAAG;AACzC,aAAO,KAAK,KAAK,YAAY;AAAA,IAC/B;AAAA,EACF;AAEA,aAAW,QAAQ,gBAAgB;AACjC,QAAI,CAAC,WAAW,KAAK,SAAS,KAAK,IAAI,CAAC,GAAG;AACzC,aAAO,KAAK,KAAK,YAAY;AAAA,IAC/B;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,IAAI,iBAAiB,QAAQ,OAAO;AAAA,EAC5C;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/config/define-config.ts","../src/config/config-builder.ts","../src/config/validate-structure.ts","../src/core/errors.ts"],"sourcesContent":["import type { TheoConfig } from './schema.js'\n\n/**\n * Define Theo framework configuration.\n * Identity function — provides type inference for theo.config.ts.\n * Runtime validation happens in loadConfig(), not here.\n */\nexport function defineConfig(config: Partial<TheoConfig>): Partial<TheoConfig> {\n return config\n}\n","/**\n * M31 Phase 3 — `config()`, the fluent builder that replaces `defineConfig({...})`.\n *\n * Config is a ~30-field flat bag — a pure setter chain would be worse DX than an object literal\n * (Vite/Nuxt/Astro keep config as identity for this reason). So `config()` is HYBRID (ADR-M31-3):\n * chainable setters for the common fields PLUS a `.set(partial)` escape hatch for the long tail.\n * `.build()` delegates to the internal {@link defineConfig} (identity) — `loadConfig` is UNCHANGED.\n *\n * export default config()\n * .serverDir('core')\n * .agentsDir('core/agents')\n * .appDir('apps/web')\n * .set({ security: { csrf: 'strict' } })\n * .build()\n */\nimport { defineConfig } from './define-config.js'\nimport type { TheoConfig } from './schema.js'\n\n/** The fluent config builder. No field is required; `.set()` merges arbitrary config fields. */\nexport interface ConfigBuilder {\n /** Project identifier (DNS-1123) — used by `.theokit/services.json`. */\n name(value: TheoConfig['name']): ConfigBuilder\n /** Backend root dir (route/action/agent discovery). Default `'server'`. */\n serverDir(value: TheoConfig['serverDir']): ConfigBuilder\n /** Frontend root dir (file-based router). Default `'app'`. */\n appDir(value: TheoConfig['appDir']): ConfigBuilder\n /** Agents root dir. Default `'agents'`. */\n agentsDir(value: TheoConfig['agentsDir']): ConfigBuilder\n /** Build output dir. Default `'.theokit'`. */\n distDir(value: TheoConfig['distDir']): ConfigBuilder\n /** Dev/prod port. Default `3000`. */\n port(value: TheoConfig['port']): ConfigBuilder\n /** Bind host. Default `'localhost'`. */\n host(value: TheoConfig['host']): ConfigBuilder\n /** Enable server-side rendering. Default `false`. */\n ssr(value: TheoConfig['ssr']): ConfigBuilder\n /** Escape hatch — merge any config fields not covered by a dedicated setter (rate-limit, security…). */\n set(partial: Partial<TheoConfig>): ConfigBuilder\n /** Resolve to the `Partial<TheoConfig>` — the SAME value `defineConfig({...})` returns. */\n build(): Partial<TheoConfig>\n}\n\nfunction makeConfigBuilder(spec: Partial<TheoConfig>): ConfigBuilder {\n const merge = (patch: Partial<TheoConfig>): ConfigBuilder =>\n makeConfigBuilder({ ...spec, ...patch })\n return {\n name: (value) => merge({ name: value }),\n serverDir: (value) => merge({ serverDir: value }),\n appDir: (value) => merge({ appDir: value }),\n agentsDir: (value) => merge({ agentsDir: value }),\n distDir: (value) => merge({ distDir: value }),\n port: (value) => merge({ port: value }),\n host: (value) => merge({ host: value }),\n ssr: (value) => merge({ ssr: value }),\n set: (partial) => merge(partial),\n build: () => defineConfig(spec),\n }\n}\n\n/**\n * Start a fluent config definition. Chain the common setters and/or `.set(partial)`, then `.build()`.\n */\nexport function config(): ConfigBuilder {\n return makeConfigBuilder({})\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Project-structure validator. Reads paths joined onto `projectRoot`,\n * with names from a fixed `ValidationRule[]` table. No HTTP input.\n */\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { TheoProjectError } from '../core/errors.js'\n\ninterface ValidationRule {\n path: string\n errorMessage: string\n}\n\nconst REQUIRED_FILES: ValidationRule[] = [\n {\n path: 'theo.config.ts',\n errorMessage: 'Missing required file: theo.config.ts',\n },\n {\n path: 'package.json',\n errorMessage: 'Missing required file: package.json',\n },\n]\n\n// #95 — honor config `appDir` (default \"app\") so a project that groups its frontend under a custom\n// directory (e.g. `apps/web`) is not rejected by a hardcoded `app/` requirement.\nexport function validateProjectStructure(rootDir: string, appDir = 'app'): void {\n if (!existsSync(rootDir)) {\n throw new TheoProjectError([`Project directory does not exist: ${rootDir}`], rootDir)\n }\n\n const errors: string[] = []\n\n const requiredDirs: ValidationRule[] = [\n { path: appDir, errorMessage: `Missing required directory: ${appDir}/` },\n ]\n\n for (const rule of requiredDirs) {\n if (!existsSync(join(rootDir, rule.path))) {\n errors.push(rule.errorMessage)\n }\n }\n\n for (const rule of REQUIRED_FILES) {\n if (!existsSync(join(rootDir, rule.path))) {\n errors.push(rule.errorMessage)\n }\n }\n\n if (errors.length > 0) {\n throw new TheoProjectError(errors, rootDir)\n }\n}\n","export class TheoProjectError extends Error {\n public readonly errors: string[]\n public readonly rootDir: string\n\n constructor(errors: string[], rootDir: string) {\n const errorLines = errors.map((e) => ` - ${e}`).join('\\n')\n\n super(\n `Invalid Theo project structure\\n\\n` +\n ` Root: ${rootDir}\\n\\n` +\n (errorLines ? ` Errors:\\n${errorLines}\\n` : ''),\n )\n\n this.name = 'TheoProjectError'\n this.errors = errors\n this.rootDir = rootDir\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOO,SAAS,aAAaA,SAAkD;AAC7E,SAAOA;AACT;;;ACiCA,SAAS,kBAAkB,MAA0C;AACnE,QAAM,QAAQ,CAAC,UACb,kBAAkB,EAAE,GAAG,MAAM,GAAG,MAAM,CAAC;AACzC,SAAO;AAAA,IACL,MAAM,CAAC,UAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAAA,IACtC,WAAW,CAAC,UAAU,MAAM,EAAE,WAAW,MAAM,CAAC;AAAA,IAChD,QAAQ,CAAC,UAAU,MAAM,EAAE,QAAQ,MAAM,CAAC;AAAA,IAC1C,WAAW,CAAC,UAAU,MAAM,EAAE,WAAW,MAAM,CAAC;AAAA,IAChD,SAAS,CAAC,UAAU,MAAM,EAAE,SAAS,MAAM,CAAC;AAAA,IAC5C,MAAM,CAAC,UAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAAA,IACtC,MAAM,CAAC,UAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAAA,IACtC,KAAK,CAAC,UAAU,MAAM,EAAE,KAAK,MAAM,CAAC;AAAA,IACpC,KAAK,CAAC,YAAY,MAAM,OAAO;AAAA,IAC/B,OAAO,MAAM,aAAa,IAAI;AAAA,EAChC;AACF;AAKO,SAAS,SAAwB;AACtC,SAAO,kBAAkB,CAAC,CAAC;AAC7B;;;AC5DA,SAAS,kBAAkB;AAC3B,SAAS,YAAY;;;ACLd,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1B;AAAA,EACA;AAAA,EAEhB,YAAY,QAAkB,SAAiB;AAC7C,UAAM,aAAa,OAAO,IAAI,CAAC,MAAM,OAAO,CAAC,EAAE,EAAE,KAAK,IAAI;AAE1D;AAAA,MACE;AAAA;AAAA,UACa,OAAO;AAAA;AAAA,KACjB,aAAa;AAAA,EAAc,UAAU;AAAA,IAAO;AAAA,IACjD;AAEA,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,UAAU;AAAA,EACjB;AACF;;;ADHA,IAAM,iBAAmC;AAAA,EACvC;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAIO,SAAS,yBAAyB,SAAiB,SAAS,OAAa;AAC9E,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,UAAM,IAAI,iBAAiB,CAAC,qCAAqC,OAAO,EAAE,GAAG,OAAO;AAAA,EACtF;AAEA,QAAM,SAAmB,CAAC;AAE1B,QAAM,eAAiC;AAAA,IACrC,EAAE,MAAM,QAAQ,cAAc,+BAA+B,MAAM,IAAI;AAAA,EACzE;AAEA,aAAW,QAAQ,cAAc;AAC/B,QAAI,CAAC,WAAW,KAAK,SAAS,KAAK,IAAI,CAAC,GAAG;AACzC,aAAO,KAAK,KAAK,YAAY;AAAA,IAC/B;AAAA,EACF;AAEA,aAAW,QAAQ,gBAAgB;AACjC,QAAI,CAAC,WAAW,KAAK,SAAS,KAAK,IAAI,CAAC,GAAG;AACzC,aAAO,KAAK,KAAK,YAAY;AAAA,IAC/B;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,IAAI,iBAAiB,QAAQ,OAAO;AAAA,EAC5C;AACF;","names":["config"]}
@@ -1,4 +1,4 @@
1
- import { T as TheoPlugin, a as TheoApp, P as PluginContext, H as HookResult, R as RunHookOptions } from './plugin-types-DNJGxr4Z.js';
1
+ import { T as TheoPlugin, a as TheoApp, P as PluginContext, H as HookResult, R as RunHookOptions } from './plugin-types-L49QYMb5.js';
2
2
 
3
3
  declare class DuplicatePluginError extends Error {
4
4
  constructor(name: string);
@@ -29,24 +29,6 @@ interface TheoPlugin {
29
29
  name: string;
30
30
  register(app: TheoApp): void | Promise<void>;
31
31
  }
32
- /**
33
- * Identity function for plugin authors. Provides auto-completion + type
34
- * inference at the call site (TanStack/Vite/Astro pattern). Pure runtime
35
- * no-op — returns the input unchanged.
36
- *
37
- * @example
38
- * import { definePlugin } from 'theokit/server'
39
- * export default definePlugin({
40
- * name: 'my-plugin',
41
- * register(app) {
42
- * app.addHook('onRequest', (req) => { ... })
43
- * },
44
- * })
45
- *
46
- * Equivalent to `const p: TheoPlugin = {...}` but more ergonomic. See
47
- * ADR-0008 (D1 + D6) for the rationale.
48
- */
49
- declare function definePlugin(plugin: TheoPlugin): TheoPlugin;
50
32
  /**
51
33
  * Web-Standards plugin context. Available during all 4 hook lifecycle
52
34
  * stages (onRequest, preHandler, onResponse, onError).
@@ -76,4 +58,4 @@ type WebPreHandlerHook = (ctx: WebPluginContext) => void | Promise<void>;
76
58
  type WebOnResponseHook = (ctx: WebPluginContext) => void | Promise<void>;
77
59
  type WebOnErrorHook = (ctx: WebPluginErrorContext) => void | Promise<void>;
78
60
 
79
- export { type HookResult as H, type OnErrorHook as O, type PluginContext as P, type RunHookOptions as R, type TheoPlugin as T, type WebOnRequestHook as W, type TheoApp as a, type WebPreHandlerHook as b, type WebOnResponseHook as c, type WebOnErrorHook as d, type HookName as e, type OnRequestHook as f, type OnResponseHook as g, type PluginErrorContext as h, type PreHandlerHook as i, definePlugin as j };
61
+ export type { HookResult as H, OnErrorHook as O, PluginContext as P, RunHookOptions as R, TheoPlugin as T, WebOnRequestHook as W, TheoApp as a, WebPreHandlerHook as b, WebOnResponseHook as c, WebOnErrorHook as d, HookName as e, OnRequestHook as f, OnResponseHook as g, PluginErrorContext as h, PreHandlerHook as i };
@@ -6,7 +6,7 @@ var adapterRegistry = {
6
6
  node: async () => (await import("./node-BPJ3Z4DT.js")).nodeAdapter,
7
7
  vercel: async () => (await import("./vercel-J6G7ZHYQ.js")).vercelAdapter,
8
8
  cloudflare: async () => (await import("./cloudflare-C6E5SPAE.js")).cloudflareAdapter,
9
- static: async () => (await import("./static-CYG2WGYP.js")).staticAdapter,
9
+ static: async () => (await import("./static-MB2CD5Y3.js")).staticAdapter,
10
10
  bun: async () => (await import("./bun-KP2KES6S.js")).bunAdapter,
11
11
  "deno-deploy": async () => (await import("./deno-deploy-RFZN56X4.js")).denoDeployAdapter,
12
12
  netlify: async () => (await import("./netlify-PMLHVPN4.js")).netlifyAdapter,
@@ -21,4 +21,4 @@ export {
21
21
  adapterRegistry,
22
22
  resolveAdapter
23
23
  };
24
- //# sourceMappingURL=registry-XSRSTH33.js.map
24
+ //# sourceMappingURL=registry-3NB7KOUI.js.map
@@ -1,9 +1,9 @@
1
1
  import { z } from 'zod';
2
+ import { W as WebSocketHandler, b as WebSocketLike } from '../../define-websocket-CPQcQK9h.js';
3
+ export { a as WebSocketHandlerWeb } from '../../define-websocket-CPQcQK9h.js';
4
+ import { f as OnRequestHook, i as PreHandlerHook, g as OnResponseHook, O as OnErrorHook, T as TheoPlugin } from '../../plugin-types-L49QYMb5.js';
2
5
  import { UIMessageChunk } from 'ai';
3
- import { b as WebSocketLike } from '../../define-websocket-CdK94O-D.js';
4
- export { W as WebSocketHandler, a as WebSocketHandlerWeb, d as defineWebSocket, c as defineWebSocketWeb } from '../../define-websocket-CdK94O-D.js';
5
6
  import { IncomingMessage } from 'node:http';
6
- import { T as TheoPlugin } from '../../plugin-types-DNJGxr4Z.js';
7
7
  export { H as HEALTH_PATH, a as HealthRouteConfig, b as READY_PATH, c as ReadyRouteConfig, d as ReservedResponse, R as ReservedRoutes, e as defineHealthRoute, f as defineReadyRoute, s as serveReservedRoute } from '../../health-route-C0hk64_U.js';
8
8
 
9
9
  /**
@@ -58,12 +58,6 @@ interface RouteConfig<TQuery extends z.ZodType = z.ZodUndefined, TBody extends z
58
58
  }) => TResponse | Promise<TResponse>;
59
59
  }
60
60
 
61
- /**
62
- * Define a typed HTTP route.
63
- * Identity function — provides type inference for route handlers.
64
- */
65
- declare function defineRoute<TQuery extends z.ZodType = z.ZodUndefined, TBody extends z.ZodType = z.ZodUndefined, TParams extends z.ZodType = z.ZodUndefined, TCtx = unknown, TResponse = unknown>(config: RouteConfig<TQuery, TBody, TParams, TCtx, TResponse>): RouteConfig<TQuery, TBody, TParams, TCtx, TResponse>;
66
-
67
61
  /**
68
62
  * Action wire-protocol accept mode per plan g3-server-actions-and-useaction
69
63
  * v1.2 ADR D1. Default behavior (when omitted) is `'json'`. `'form'` opts the
@@ -98,28 +92,8 @@ interface ActionConfig<TInput extends z.ZodType, TCtx = unknown> {
98
92
  ctx: TCtx;
99
93
  }) => unknown;
100
94
  }
101
- /**
102
- * Define a typed server action.
103
- *
104
- * Identity function — provides type inference for action handlers. The
105
- * runtime that consumes the config (validation + invocation + serialization)
106
- * lives in `server/http/action-execute.ts`.
107
- *
108
- * Per plan g3-server-actions-and-useaction v1.2 § Phase 1 / T1.2: the new
109
- * `accept?: 'form' | 'json'` field is the only contract change vs the
110
- * pre-G3 identity. Existing callsites (`defineAction({input, handler})`)
111
- * continue to compile — `accept` is opt-in.
112
- */
113
- declare function defineAction<TInput extends z.ZodType, TCtx = unknown>(config: ActionConfig<TInput, TCtx>): ActionConfig<TInput, TCtx>;
114
95
 
115
96
  type MiddlewareHandler = (request: Request, next: (request: Request) => Promise<Response>) => Response | Promise<Response>;
116
- /**
117
- * Define a middleware handler.
118
- * Identity function — provides type annotation for middleware.
119
- */
120
- declare function defineMiddleware(handler: MiddlewareHandler): MiddlewareHandler;
121
-
122
- declare function uiMessageStreamResponse(chunks: AsyncIterable<UIMessageChunk>): Response;
123
97
 
124
98
  /**
125
99
  * Item #4 — `defineAgentTool`
@@ -201,29 +175,277 @@ interface DefineAgentToolSpec<T extends z.ZodType, R = string> {
201
175
  transform?: ToolTransform<R>;
202
176
  }
203
177
  /**
204
- * Build a {@link CustomTool} from a Zod 3 schema + handler.
178
+ * M18 — apply a tool's `transform` for a target (`display` / `transcript`). Returns the formatted
179
+ * value, or the raw `result` when the tool declares no transform for that target.
180
+ */
181
+ declare function applyTransform(tool: CustomTool, result: unknown, target: 'display' | 'transcript'): unknown;
182
+
183
+ /**
184
+ * M31 Phase 3 — `route()`, the fluent builder that replaces `defineRoute({...})`.
205
185
  *
206
- * Behavior:
207
- * - Validates `name` matches the LLM tool-name regex.
208
- * - Requires `inputSchema` to be a `ZodObject` (Anthropic + SDK contract).
209
- * - Warns (not throws) if `description` is empty empty descriptions
210
- * degrade LLM tool selection.
211
- * - Converts the Zod schema to JSON Schema 7 inline (no `$ref`s — LLMs handle
212
- * inline schemas more reliably).
213
- * - Strips the top-level `$schema` field (Anthropic rejects schemas with
214
- * `$schema` at root in some provider modes).
215
- * - Wraps the handler to parse the input via the Zod schema BEFORE invoking
216
- * the user code — bad LLM-supplied input throws `ZodError`, which the SDK
217
- * converts to `tool_result(isError)`.
186
+ * Pure type-state (mirrors `tool-builder.ts`). `.query/.body/.params` set Zod schemas whose
187
+ * `z.infer<>` flows into the handler's `ctx`; `.handler()` is required before `.build()`. `.build()`
188
+ * delegates to the internal {@link defineRoute} (an identity fn), emitting the identical
189
+ * `RouteConfig` the scan/execute path is UNCHANGED (identity-shape delegation, blueprint §2).
218
190
  *
219
- * @public
191
+ * export const POST = route()
192
+ * .params(z.object({ id: z.string() }))
193
+ * .body(z.object({ text: z.string() }))
194
+ * .handler(({ params, body }) => save(params.id, body.text))
195
+ * .build()
220
196
  */
221
- declare function defineAgentTool<T extends z.ZodType, R = string>(spec: DefineAgentToolSpec<T, R>): CustomTool;
197
+
198
+ /** Compile-error carrier: `.build()` called before `.handler()`. */
199
+ interface MissingHandlerError {
200
+ readonly __theokitError: 'a route needs .handler(fn) before .build()';
201
+ }
222
202
  /**
223
- * M18 apply a tool's `transform` for a target (`display` / `transcript`). Returns the formatted
224
- * value, or the raw `result` when the tool declares no transform for that target.
203
+ * The fluent route builder. `TQuery/TBody/TParams` track the input schemas (default `z.ZodUndefined`,
204
+ * matching {@link RouteConfig}); `TResponse` tracks the handler return; `THandlerSet` gates `.build()`.
225
205
  */
226
- declare function applyTransform(tool: CustomTool, result: unknown, target: 'display' | 'transcript'): unknown;
206
+ interface RouteBuilder<TQuery extends z.ZodType = z.ZodUndefined, TBody extends z.ZodType = z.ZodUndefined, TParams extends z.ZodType = z.ZodUndefined, TCtx = unknown, TResponse = unknown, THandlerSet extends boolean = false> {
207
+ /** Set the URL search-params schema. Inferred into `ctx.query`. */
208
+ query<S extends z.ZodType>(schema: S): RouteBuilder<S, TBody, TParams, TCtx, TResponse, THandlerSet>;
209
+ /** Set the request-body schema. Inferred into `ctx.body`. */
210
+ body<S extends z.ZodType>(schema: S): RouteBuilder<TQuery, S, TParams, TCtx, TResponse, THandlerSet>;
211
+ /** Set the path-params schema. Inferred into `ctx.params`. */
212
+ params<S extends z.ZodType>(schema: S): RouteBuilder<TQuery, TBody, S, TCtx, TResponse, THandlerSet>;
213
+ /** Runtime-only validation of the handler's plain-object return (500 on mismatch). Not inferred (YAGNI). */
214
+ response(schema: z.ZodType): RouteBuilder<TQuery, TBody, TParams, TCtx, TResponse, THandlerSet>;
215
+ /** Override the HTTP status for a plain-object return (default 200; 204 for void). */
216
+ status(code: number): RouteBuilder<TQuery, TBody, TParams, TCtx, TResponse, THandlerSet>;
217
+ /** Opt out of CSRF enforcement for this route (webhooks / OAuth callbacks). */
218
+ csrf(disabled: false): RouteBuilder<TQuery, TBody, TParams, TCtx, TResponse, THandlerSet>;
219
+ /**
220
+ * Set the handler. Its `ctx` infers `query/body/params` from the schemas set above. Required
221
+ * before `.build()`.
222
+ */
223
+ handler<R>(fn: (ctx: {
224
+ query: z.infer<TQuery>;
225
+ body: z.infer<TBody>;
226
+ params: z.infer<TParams>;
227
+ request: Request;
228
+ ctx: TCtx;
229
+ }) => R | Promise<R>): RouteBuilder<TQuery, TBody, TParams, TCtx, R, true>;
230
+ /**
231
+ * Resolve to the `RouteConfig` — the SAME value `defineRoute({...})` returns. COMPILE ERROR when
232
+ * `.handler()` was never called.
233
+ */
234
+ build(...guard: THandlerSet extends true ? [] : [error: MissingHandlerError]): RouteConfig<TQuery, TBody, TParams, TCtx, TResponse>;
235
+ }
236
+ /**
237
+ * Start a fluent route definition. Chain `.query/.body/.params/.response/.status/.csrf` (all
238
+ * optional), then `.handler()` (required) and `.build()` for the `RouteConfig`.
239
+ */
240
+ declare function route(): RouteBuilder;
241
+
242
+ /**
243
+ * M31 Phase 3 — `action()`, the fluent builder that replaces `defineAction({...})`.
244
+ *
245
+ * Pure type-state (mirrors `route-builder.ts`). `.input()` (required) sets the Zod schema whose
246
+ * `z.infer<>` types the handler's `ctx.input`; `.handler()` (required) closes the chain. `.build()`
247
+ * delegates to the internal {@link defineAction} (identity) — the action-execute path is UNCHANGED.
248
+ *
249
+ * export const createUser = action()
250
+ * .input(z.object({ email: z.string().email() }))
251
+ * .handler(({ input }) => createUser(input.email))
252
+ * .build()
253
+ */
254
+
255
+ /** Compile-error carrier: `.execute`/`.build()` reached before `.input()`. */
256
+ interface MissingInputError$1 {
257
+ readonly __theokitError: 'call .input(schema) before .handler(fn)';
258
+ }
259
+ /** Compile-error carrier: `.build()` before both `.input()` and `.handler()` are set. */
260
+ interface IncompleteActionError {
261
+ readonly __theokitError: 'an action needs .input(schema) and .handler(fn) before .build()';
262
+ }
263
+ /** A required-but-unset field. Branded so no ordinary value satisfies it (tRPC UnsetMarker). */
264
+ type UnsetMarker$1 = 'theokit.unset' & {
265
+ readonly __brand: 'theokit.unset';
266
+ };
267
+ /**
268
+ * The fluent action builder. `TInput` tracks the Zod schema (drives `ctx.input` inference);
269
+ * `THandlerSet` gates `.build()`.
270
+ */
271
+ interface ActionBuilder<TInput extends z.ZodType | UnsetMarker$1 = UnsetMarker$1, TCtx = unknown, THandlerSet extends boolean = false> {
272
+ /** Set the Zod input schema. Required — every action declares its input contract (zod-is-SSOT). */
273
+ input<S extends z.ZodType>(schema: S): ActionBuilder<S, TCtx, THandlerSet>;
274
+ /** Wire-protocol accept mode (`'json'` default, `'form'` for FormData multipart). */
275
+ accept(mode: ActionAccept): ActionBuilder<TInput, TCtx, THandlerSet>;
276
+ /** Opt out of CSRF enforcement for this action. */
277
+ csrf(disabled: false): ActionBuilder<TInput, TCtx, THandlerSet>;
278
+ /**
279
+ * Set the handler. COMPILE ERROR before `.input()` — the param type collapses to
280
+ * {@link MissingInputError}. `ctx.input` is inferred via `z.infer<TInput>`.
281
+ */
282
+ handler(fn: TInput extends z.ZodType ? (ctx: {
283
+ input: z.infer<TInput>;
284
+ ctx: TCtx;
285
+ }) => unknown : MissingInputError$1): ActionBuilder<TInput, TCtx, true>;
286
+ /**
287
+ * Resolve to the `ActionConfig` — the SAME value `defineAction({...})` returns. COMPILE ERROR when
288
+ * `.input()` or `.handler()` was never called.
289
+ */
290
+ build(...guard: THandlerSet extends true ? TInput extends z.ZodType ? [] : [error: IncompleteActionError] : [error: IncompleteActionError]): ActionConfig<TInput extends z.ZodType ? TInput : z.ZodType, TCtx>;
291
+ }
292
+ /**
293
+ * Start a fluent action definition. Chain `.input()` (required), optionally `.accept()` / `.csrf()`,
294
+ * then `.handler()` (required) and `.build()` for the `ActionConfig`.
295
+ */
296
+ declare function action(): ActionBuilder;
297
+
298
+ /**
299
+ * M31 Phase 3 — `websocket()`, the fluent builder that replaces `defineWebSocket({...})`.
300
+ *
301
+ * Lifecycle setters (`onOpen/onMessage/onClose/onError`), all optional; `.build()` delegates to the
302
+ * internal {@link defineWebSocket} (identity) — the ws handler loading path is UNCHANGED.
303
+ *
304
+ * export default websocket()
305
+ * .onOpen((ws) => ws.send('hi'))
306
+ * .onMessage((ws, data) => ws.send(`echo:${data}`))
307
+ * .build()
308
+ */
309
+
310
+ /** The fluent WebSocket builder. Each lifecycle hook is optional; `.build()` returns the handler. */
311
+ interface WebSocketBuilder {
312
+ onOpen(fn: NonNullable<WebSocketHandler['onOpen']>): WebSocketBuilder;
313
+ onMessage(fn: NonNullable<WebSocketHandler['onMessage']>): WebSocketBuilder;
314
+ onClose(fn: NonNullable<WebSocketHandler['onClose']>): WebSocketBuilder;
315
+ onError(fn: NonNullable<WebSocketHandler['onError']>): WebSocketBuilder;
316
+ /** Resolve to the `WebSocketHandler` — the SAME value `defineWebSocket({...})` returns. */
317
+ build(): WebSocketHandler;
318
+ }
319
+ /** Start a fluent WebSocket definition. Chain any of the lifecycle hooks, then `.build()`. */
320
+ declare function websocket(): WebSocketBuilder;
321
+
322
+ /**
323
+ * M31 Phase 3 — `middleware()`, the fluent builder that replaces `defineMiddleware(fn)`.
324
+ *
325
+ * A middleware IS a single function `(request, next) => Response`. The builder's `.handle()` sets it
326
+ * (required); `.build()` delegates to the internal {@link defineMiddleware} (identity) and returns
327
+ * the handler the runtime expects.
328
+ *
329
+ * export default middleware()
330
+ * .handle(async (request, next) => {
331
+ * const res = await next(request)
332
+ * res.headers.set('x-mw', '1')
333
+ * return res
334
+ * })
335
+ * .build()
336
+ */
337
+
338
+ /** Compile-error carrier: `.build()` called before `.handle()`. */
339
+ interface MissingHandleError {
340
+ readonly __theokitError: 'middleware needs .handle(fn) before .build()';
341
+ }
342
+ /** The fluent middleware builder. `THandleSet` gates `.build()`. */
343
+ interface MiddlewareBuilder<THandleSet extends boolean = false> {
344
+ /** Set the `(request, next) => Response` handler. Required before `.build()`. */
345
+ handle(fn: MiddlewareHandler): MiddlewareBuilder<true>;
346
+ /** Resolve to the `MiddlewareHandler`. COMPILE ERROR when `.handle()` was never called. */
347
+ build(...guard: THandleSet extends true ? [] : [error: MissingHandleError]): MiddlewareHandler;
348
+ }
349
+ /** Start a fluent middleware definition. Chain `.handle()` (required), then `.build()`. */
350
+ declare function middleware(): MiddlewareBuilder;
351
+
352
+ /**
353
+ * M31 Phase 1 — `tool()`, the fluent builder that replaces `defineAgentTool({...})`.
354
+ *
355
+ * Pure type-state (tRPC `UnsetMarker` technique, mirroring `agent-builder.ts`). The runtime is a
356
+ * plain accumulator; `.build()` delegates to the internal {@link defineAgentTool}, so the emitted
357
+ * `CustomTool` is byte-for-byte the legacy shape — the SDK/agent compile path is UNCHANGED
358
+ * (identity-shape delegation, blueprint §2).
359
+ *
360
+ * PURE metadata (G2 / sdk-runtime.md): a tool describes a capability; it NEVER calls an LLM.
361
+ *
362
+ * tool('read')
363
+ * .describe('Read a UTF-8 file')
364
+ * .input(z.object({ path: z.string() }))
365
+ * .execute(async ({ path }, ctx) => readFile(resolveInProject(ctx, path)))
366
+ * .build()
367
+ */
368
+
369
+ /** A required-but-unset builder field. Branded so no ordinary value satisfies it (tRPC UnsetMarker). */
370
+ type UnsetMarker = 'theokit.unset' & {
371
+ readonly __brand: 'theokit.unset';
372
+ };
373
+ /** Compile-error carrier: `.execute()` called before `.input()`. */
374
+ interface MissingInputError {
375
+ readonly __theokitError: 'call .input(schema) before .execute(handler)';
376
+ }
377
+ /** Compile-error carrier: `.build()` called before both `.input()` and `.execute()` are set. */
378
+ interface IncompleteToolError {
379
+ readonly __theokitError: 'a tool needs .input(schema) and .execute(handler) before .build()';
380
+ }
381
+ /** The run context a tool handler receives (M7). */
382
+ interface ToolCtx {
383
+ signal?: AbortSignal;
384
+ context?: unknown;
385
+ }
386
+ /**
387
+ * The fluent tool builder. Each method returns a NEW builder type with the relevant type parameter
388
+ * advanced. `TInput` tracks the Zod schema (drives `execute` input inference); `R` tracks the
389
+ * handler result; `THandlerSet` gates `.build()`.
390
+ */
391
+ interface ToolBuilder<TName extends string = string, TInput extends z.ZodType | UnsetMarker = UnsetMarker, R = string, THandlerSet extends boolean = false> {
392
+ /** Set the LLM-facing description. Optional; an empty description warns at build (LLM selection). */
393
+ describe(description: string): ToolBuilder<TName, TInput, R, THandlerSet>;
394
+ /** Set the Zod input schema (must be `z.object(...)` at the root). Required before `.execute()`. */
395
+ input<S extends z.ZodType>(schema: S): ToolBuilder<TName, S, R, THandlerSet>;
396
+ /**
397
+ * Set the handler. COMPILE ERROR when `.input()` was not called first — the parameter type
398
+ * collapses to {@link MissingInputError}. The `input` argument is inferred via `z.infer<TInput>`.
399
+ */
400
+ execute<R2>(handler: TInput extends z.ZodType ? (input: z.infer<TInput>, ctx?: ToolCtx) => R2 | Promise<R2> : MissingInputError): ToolBuilder<TName, TInput, R2, true>;
401
+ /** M18 — map a rich handler result to the model-visible string. */
402
+ toModelOutput(fn: (result: R) => string): ToolBuilder<TName, TInput, R, THandlerSet>;
403
+ /** M18 — per-target formatters (`display` / `transcript`) for the app UI/transcript. */
404
+ transform(t: ToolTransform<R>): ToolBuilder<TName, TInput, R, THandlerSet>;
405
+ /**
406
+ * Resolve to the `CustomTool` — the SAME value `defineAgentTool({...})` returns. COMPILE ERROR
407
+ * when `.input()` or `.execute()` was never called.
408
+ */
409
+ build(...guard: THandlerSet extends true ? TInput extends z.ZodType ? [] : [error: IncompleteToolError] : [error: IncompleteToolError]): CustomTool;
410
+ }
411
+ /**
412
+ * Start a fluent tool definition. Chain `.input()` + `.execute()` (both required), optionally
413
+ * `.describe()` / `.toModelOutput()` / `.transform()`, then `.build()` for the `CustomTool`.
414
+ */
415
+ declare function tool<TName extends string>(name: TName): ToolBuilder<TName>;
416
+
417
+ /**
418
+ * M31 Phase 3 — `plugin()`, the fluent builder that replaces `definePlugin({...})`.
419
+ *
420
+ * Collects lifecycle hooks + request decorations and SYNTHESIZES the `register(app)` function, so
421
+ * authors never write the imperative `register` body. `.build()` returns a `TheoPlugin` the plugin
422
+ * runner consumes UNCHANGED.
423
+ *
424
+ * export default plugin('request-id')
425
+ * .onRequest((ctx) => { ctx.ctx.requestId = crypto.randomUUID() })
426
+ * .onResponse((ctx) => { ctx.response.setHeader('x-request-id', String(ctx.ctx.requestId)) })
427
+ * .build()
428
+ */
429
+
430
+ /** The fluent plugin builder. `name` is set at entry; every hook is optional and may repeat. */
431
+ interface PluginBuilder {
432
+ /** Register an `onRequest` hook (runs before the CSRF gate). May be called multiple times. */
433
+ onRequest(fn: OnRequestHook): PluginBuilder;
434
+ /** Register a `preHandler` hook (after CSRF, before the route handler). */
435
+ preHandler(fn: PreHandlerHook): PluginBuilder;
436
+ /** Register an `onResponse` hook (after the handler returns). */
437
+ onResponse(fn: OnResponseHook): PluginBuilder;
438
+ /** Register an `onError` hook (error path). */
439
+ onError(fn: OnErrorHook): PluginBuilder;
440
+ /** Decorate every request with a key/value pair (available on `ctx.ctx[key]`). */
441
+ decorateRequest<T>(key: string, value: T): PluginBuilder;
442
+ /** Resolve to the `TheoPlugin` — a synthesized `{ name, register }` the runner consumes. */
443
+ build(): TheoPlugin;
444
+ }
445
+ /** Start a fluent plugin definition. `name` is required; chain hooks/decorations, then `.build()`. */
446
+ declare function plugin(name: string): PluginBuilder;
447
+
448
+ declare function uiMessageStreamResponse(chunks: AsyncIterable<UIMessageChunk>): Response;
227
449
 
228
450
  interface ChannelHandler<TMessage = unknown> {
229
451
  onSubscribe?: (ws: WebSocketLike, room: string, req: IncomingMessage) => void;
@@ -270,13 +492,4 @@ interface WebChannelHandler<TMessage = unknown> {
270
492
  */
271
493
  declare function defineWebChannel<TMessage = unknown>(handler: WebChannelHandler<TMessage>): WebChannelHandler<TMessage>;
272
494
 
273
- /**
274
- * Identity function for defining a Theo plugin.
275
- *
276
- * **Note:** Prefer `definePlugin` (shorter, canonical name per ADR-0008 D6).
277
- * Both functions are identical — `defineTheoPlugin` is kept as an alias for
278
- * existing in-tree consumers without forcing a migration sweep.
279
- */
280
- declare function defineTheoPlugin(plugin: TheoPlugin): TheoPlugin;
281
-
282
- export { type ActionAccept, type ActionConfig, type ChannelHandler, type CustomTool, type DefineAgentToolSpec, type MiddlewareHandler, type RouteConfig, type ToolTransform, type WebChannelHandler, WebSocketLike, applyTransform, defineAction, defineAgentTool, defineChannel, defineMiddleware, defineRoute, defineTheoPlugin, defineWebChannel, uiMessageStreamResponse };
495
+ export { type ActionAccept, type ActionBuilder, type ActionConfig, type ChannelHandler, type CustomTool, type DefineAgentToolSpec, type MiddlewareBuilder, type MiddlewareHandler, type PluginBuilder, type RouteBuilder, type RouteConfig, type ToolBuilder, type ToolTransform, type WebChannelHandler, type WebSocketBuilder, WebSocketHandler, WebSocketLike, action, applyTransform, defineChannel, defineWebChannel, middleware, plugin, route, tool, uiMessageStreamResponse, websocket };