improved a lot of things

This commit is contained in:
SinachPat
2026-04-26 09:09:55 +01:00
parent 2911f22df8
commit 97315846be
70 changed files with 4967 additions and 10 deletions
+2 -1
View File
@@ -1 +1,2 @@
export {};
export * from './types.js';
export * from './queries.js';
+200
View File
@@ -0,0 +1,200 @@
// ── Query interface ───────────────────────────────────────────────────────────
// These functions describe the Origin Graph query surface. They accept a
// generic `db` client (matching Supabase's SupabaseClient shape) so the
// package doesn't take a hard dependency on @supabase/supabase-js. Wire them
// to TanStack Query's queryFn in the app layer.
import type {
Artboard,
Workspace,
IntentDiff,
DesignLanguageFile,
AgentSession,
TeamMember,
InsertArtboard,
InsertIntentDiff,
InsertAgentSession,
DiffStatus,
ArtboardAncestry,
} from './types.js';
// ── Minimal Supabase client interface ────────────────────────────────────────
export interface DbClient {
from(table: string): {
select(cols?: string): DbQuery;
insert(row: unknown): DbMutation;
update(row: unknown): DbMutation;
delete(): DbMutation;
};
rpc(fn: string, args?: unknown): Promise<{ data: unknown; error: DbError | null }>;
}
export interface DbQuery {
eq(col: string, val: unknown): DbQuery;
in(col: string, vals: unknown[]): DbQuery;
order(col: string, opts?: { ascending?: boolean }): DbQuery;
limit(n: number): DbQuery;
single(): Promise<{ data: unknown; error: DbError | null }>;
then(resolve: (result: { data: unknown[]; error: DbError | null }) => void): void;
}
export interface DbMutation {
eq(col: string, val: unknown): DbMutation;
select(cols?: string): DbMutation;
single(): Promise<{ data: unknown; error: DbError | null }>;
then(resolve: (result: { data: unknown; error: DbError | null }) => void): void;
}
export interface DbError {
message: string;
code?: string;
}
// ── Artboard queries ──────────────────────────────────────────────────────────
export async function getArtboards(db: DbClient, workspaceId: string): Promise<Artboard[]> {
const { data, error } = await (db
.from('artboards')
.select('*')
.eq('workspace_id', workspaceId)
.order('created_at', { ascending: false }) as unknown as Promise<{ data: Artboard[]; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
export async function getArtboard(db: DbClient, id: string): Promise<Artboard> {
const { data, error } = await (db
.from('artboards')
.select('*')
.eq('id', id)
.single() as Promise<{ data: Artboard; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
export async function createArtboard(db: DbClient, row: InsertArtboard): Promise<Artboard> {
const { data, error } = await (db
.from('artboards')
.insert(row)
.select()
.single() as Promise<{ data: Artboard; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
export async function getArtboardAncestors(db: DbClient, artboardId: string): Promise<ArtboardAncestry[]> {
const { data, error } = await (db
.from('artboard_ancestry')
.select('*')
.eq('artboard_id', artboardId)
.order('depth', { ascending: true }) as unknown as Promise<{ data: ArtboardAncestry[]; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
// ── Intent diff queries ───────────────────────────────────────────────────────
export async function getDiffs(db: DbClient, artboardId: string): Promise<IntentDiff[]> {
const { data, error } = await (db
.from('intent_diffs')
.select('*')
.eq('artboard_id', artboardId)
.order('created_at', { ascending: false }) as unknown as Promise<{ data: IntentDiff[]; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
export async function getDiffsByStatus(db: DbClient, workspaceId: string, status: DiffStatus): Promise<IntentDiff[]> {
// Join through artboards for workspace scoping
const { data, error } = await (db.rpc('get_diffs_by_status', { p_workspace_id: workspaceId, p_status: status }) as Promise<{ data: IntentDiff[]; error: DbError | null }>);
if (error) throw new Error(error.message);
return data as IntentDiff[];
}
export async function createDiff(db: DbClient, row: InsertIntentDiff): Promise<IntentDiff> {
const { data, error } = await (db
.from('intent_diffs')
.insert(row)
.select()
.single() as Promise<{ data: IntentDiff; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
export async function getDiff(db: DbClient, id: string): Promise<IntentDiff> {
const { data, error } = await (db
.from('intent_diffs')
.select('*')
.eq('id', id)
.single() as Promise<{ data: IntentDiff; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
export async function updateDiffStatus(
db: DbClient,
id: string,
status: DiffStatus,
notes?: string
): Promise<IntentDiff> {
const { data, error } = await (db
.from('intent_diffs')
.update({ status, ...(notes !== undefined ? { notes } : {}) })
.eq('id', id)
.select()
.single() as Promise<{ data: IntentDiff; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
// ── Design language file queries ──────────────────────────────────────────────
export async function getActiveDesignLanguageFile(
db: DbClient,
workspaceId: string
): Promise<DesignLanguageFile | null> {
const { data, error } = await (db
.from('design_language_files')
.select('*')
.eq('workspace_id', workspaceId)
.order('version', { ascending: false })
.limit(1)
.single() as Promise<{ data: DesignLanguageFile | null; error: DbError | null }>);
if (error?.code === 'PGRST116') return null; // No rows found
if (error) throw new Error(error.message);
return data;
}
// ── Agent session queries ─────────────────────────────────────────────────────
export async function createAgentSession(db: DbClient, row: InsertAgentSession): Promise<AgentSession> {
const { data, error } = await (db
.from('agent_sessions')
.insert(row)
.select()
.single() as Promise<{ data: AgentSession; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
// ── Workspace queries ─────────────────────────────────────────────────────────
export async function getWorkspace(db: DbClient, id: string): Promise<Workspace> {
const { data, error } = await (db
.from('workspaces')
.select('*')
.eq('id', id)
.single() as Promise<{ data: Workspace; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
export async function getTeamMembers(db: DbClient, workspaceId: string): Promise<TeamMember[]> {
const { data, error } = await (db
.from('team_members')
.select('*')
.eq('workspace_id', workspaceId) as unknown as Promise<{ data: TeamMember[]; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
+135
View File
@@ -0,0 +1,135 @@
import { z } from 'zod';
// ── Enums ─────────────────────────────────────────────────────────────────────
export const OriginTypeSchema = z.enum([
'GIT_COMMIT',
'LINEAR_ISSUE',
'SLACK_MESSAGE',
'URL',
'FORK',
]);
export type OriginType = z.infer<typeof OriginTypeSchema>;
export const DiffStatusSchema = z.enum([
'DRAFT',
'EXPORTED',
'IMPLEMENTED',
'BLOCKED',
]);
export type DiffStatus = z.infer<typeof DiffStatusSchema>;
export const TeamRoleSchema = z.enum([
'OWNER',
'DESIGNER',
'ENGINEER',
'PM',
'VIEWER',
]);
export type TeamRole = z.infer<typeof TeamRoleSchema>;
export const AgentTypeSchema = z.enum(['CURSOR', 'CLAUDE_CODE', 'GENERIC']);
export type AgentType = z.infer<typeof AgentTypeSchema>;
export const WorkspacePlanSchema = z.enum(['FREE', 'TEAM', 'ENTERPRISE']);
export type WorkspacePlan = z.infer<typeof WorkspacePlanSchema>;
// ── Row types (match PostgreSQL columns 1:1) ──────────────────────────────────
export const WorkspaceSchema = z.object({
id: z.string().uuid(),
name: z.string(),
owner_id: z.string(),
plan: WorkspacePlanSchema,
settings_jsonb: z.record(z.unknown()),
created_at: z.string().datetime(),
updated_at: z.string().datetime(),
});
export type Workspace = z.infer<typeof WorkspaceSchema>;
export const ArtboardSchema = z.object({
id: z.string().uuid(),
workspace_id: z.string().uuid(),
name: z.string(),
origin_id: z.string().uuid().nullable(),
parent_artboard_id: z.string().uuid().nullable(),
metadata_jsonb: z.record(z.unknown()),
created_at: z.string().datetime(),
updated_at: z.string().datetime(),
});
export type Artboard = z.infer<typeof ArtboardSchema>;
export const OriginSchema = z.object({
id: z.string().uuid(),
type: OriginTypeSchema,
source_ref: z.string(),
source_metadata_jsonb: z.record(z.unknown()),
created_at: z.string().datetime(),
updated_at: z.string().datetime(),
});
export type Origin = z.infer<typeof OriginSchema>;
export const IntentDiffSchema = z.object({
id: z.string().uuid(),
artboard_id: z.string().uuid(),
author_id: z.string(),
changes_jsonb: z.record(z.unknown()),
summary: z.string(),
status: DiffStatusSchema,
/** Free-text notes from the coding agent (e.g. why it was blocked) */
notes: z.string().optional(),
created_at: z.string().datetime(),
updated_at: z.string().datetime(),
});
export type IntentDiff = z.infer<typeof IntentDiffSchema>;
export const AgentSessionSchema = z.object({
id: z.string().uuid(),
artboard_id: z.string().uuid(),
diff_id: z.string().uuid().nullable(),
agent_type: AgentTypeSchema,
messages_jsonb: z.array(z.record(z.unknown())),
status: z.enum(['ACTIVE', 'COMPLETED', 'FAILED']),
created_at: z.string().datetime(),
updated_at: z.string().datetime(),
});
export type AgentSession = z.infer<typeof AgentSessionSchema>;
export const DesignLanguageFileSchema = z.object({
id: z.string().uuid(),
workspace_id: z.string().uuid(),
name: z.string(),
schema_jsonb: z.record(z.unknown()),
version: z.number().int(),
created_at: z.string().datetime(),
updated_at: z.string().datetime(),
});
export type DesignLanguageFile = z.infer<typeof DesignLanguageFileSchema>;
export const TeamMemberSchema = z.object({
id: z.string().uuid(),
workspace_id: z.string().uuid(),
user_id: z.string(),
role: TeamRoleSchema,
created_at: z.string().datetime(),
updated_at: z.string().datetime(),
});
export type TeamMember = z.infer<typeof TeamMemberSchema>;
// ── Ancestry (from materialized view) ────────────────────────────────────────
export interface ArtboardAncestry {
artboard_id: string;
ancestor_id: string;
depth: number;
}
// ── Insert types (omit server-set fields) ────────────────────────────────────
export type InsertWorkspace = Omit<Workspace, 'id' | 'created_at' | 'updated_at'>;
export type InsertArtboard = Omit<Artboard, 'id' | 'created_at' | 'updated_at'>;
export type InsertOrigin = Omit<Origin, 'id' | 'created_at' | 'updated_at'>;
export type InsertIntentDiff = Omit<IntentDiff, 'id' | 'created_at' | 'updated_at'>;
export type InsertAgentSession = Omit<AgentSession, 'id' | 'created_at' | 'updated_at'>;
export type InsertDesignLanguageFile = Omit<DesignLanguageFile, 'id' | 'created_at' | 'updated_at'>;
export type InsertTeamMember = Omit<TeamMember, 'id' | 'created_at' | 'updated_at'>;