TypeScript/Bun project scaffold

- Stack: Bun, Hono, Drizzle ORM, Zod, Handlebars, Pino
- Models: ticket, queue, transaction, scrip, template, custom_field, user, lifecycle
- Scrip engine: prepare/commit two-phase dispatch, template rendering, mock actions
- Lifecycle validator: state machine transition validation with wildcard support
- Routes: health, tickets (full CRUD + preview + transactions), queues, scrips, custom-fields, lifecycles
- Middleware: Pino logging, error handler
- Database: Drizzle ORM schema + initial migration (10 tables)
- Type-check: passes (tsc --noEmit, zero errors)
This commit is contained in:
Gjermund Høsøien Wiggen
2026-06-07 21:21:50 +02:00
parent 7be1810162
commit 1136227510
35 changed files with 2595 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
import type { InferSelectModel } from 'drizzle-orm';
import { customFields } from '../db/schema.ts';
export type CustomField = InferSelectModel<typeof customFields>;
export const CustomFieldType = {
SelectOne: 'SelectOne',
SelectMultiple: 'SelectMultiple',
Text: 'Text',
Date: 'Date',
} as const;
export type CustomFieldType = (typeof CustomFieldType)[keyof typeof CustomFieldType];

4
src/models/lifecycle.ts Normal file
View File

@@ -0,0 +1,4 @@
import type { InferSelectModel } from 'drizzle-orm';
import { lifecycles } from '../db/schema.ts';
export type Lifecycle = InferSelectModel<typeof lifecycles>;

11
src/models/queue.ts Normal file
View File

@@ -0,0 +1,11 @@
import type { InferSelectModel } from 'drizzle-orm';
import { z } from 'zod/v4';
import { queues } from '../db/schema.ts';
export type Queue = InferSelectModel<typeof queues>;
export const CreateQueueSchema = z.object({
name: z.string().min(1),
description: z.string().optional(),
lifecycle_id: z.string().uuid().optional(),
});

26
src/models/scrip.ts Normal file
View File

@@ -0,0 +1,26 @@
import type { InferSelectModel } from 'drizzle-orm';
import { z } from 'zod/v4';
import { scrips } from '../db/schema.ts';
export type Scrip = InferSelectModel<typeof scrips>;
export const ScripStage = {
TransactionCreate: 'TransactionCreate',
TransactionBatch: 'TransactionBatch',
} as const;
export type ScripStage = (typeof ScripStage)[keyof typeof ScripStage];
export const CreateScripSchema = z.object({
queue_id: z.string().uuid().nullable().optional(),
name: z.string().min(1),
description: z.string().optional(),
condition_type: z.string().min(1),
condition_config: z.record(z.string(), z.unknown()).default({}),
action_type: z.string().min(1),
action_config: z.record(z.string(), z.unknown()).default({}),
template_id: z.string().uuid().optional(),
stage: z.enum(['TransactionCreate', 'TransactionBatch']).default('TransactionCreate'),
sort_order: z.number().int().default(0),
disabled: z.boolean().default(false),
});

16
src/models/ticket.ts Normal file
View File

@@ -0,0 +1,16 @@
import type { InferSelectModel } from 'drizzle-orm';
import { z } from 'zod/v4';
import { tickets } from '../db/schema.ts';
export type Ticket = InferSelectModel<typeof tickets>;
export const CreateTicketSchema = z.object({
subject: z.string().min(1),
queue_id: z.string().uuid(),
});
export const UpdateTicketSchema = z.object({
subject: z.string().min(1).optional(),
status: z.string().min(1).optional(),
owner_id: z.string().uuid().optional(),
});

16
src/models/transaction.ts Normal file
View File

@@ -0,0 +1,16 @@
import type { InferSelectModel } from 'drizzle-orm';
import { transactions } from '../db/schema.ts';
export type Transaction = InferSelectModel<typeof transactions>;
export const TransactionType = {
Create: 'Create',
StatusChange: 'StatusChange',
SetOwner: 'SetOwner',
AddWatcher: 'AddWatcher',
Comment: 'Comment',
CustomField: 'CustomField',
Correspond: 'Correspond',
} as const;
export type TransactionType = (typeof TransactionType)[keyof typeof TransactionType];

4
src/models/user.ts Normal file
View File

@@ -0,0 +1,4 @@
import type { InferSelectModel } from 'drizzle-orm';
import { users } from '../db/schema.ts';
export type User = InferSelectModel<typeof users>;