made tiny updates

This commit is contained in:
SinachPat
2026-05-04 12:30:56 +01:00
parent 3f029e15c2
commit 5b2d918c13
47 changed files with 2597 additions and 521 deletions
+27
View File
@@ -0,0 +1,27 @@
// ── Origin Graph client (spec Layer 4.2) ─────────────────────────────────────
// Thin typed wrapper around @supabase/supabase-js for the Origin Graph schema.
// The spec requires this file as the primary entry point for Supabase access
// in the origin-graph package.
//
// Usage in the app layer:
// import { createOriginGraphClient } from '@originmain/origin-graph';
// const db = createOriginGraphClient(supabaseUrl, supabaseKey);
//
// The returned client satisfies the `DbClient` interface used by all
// query functions in queries.ts — no type casting needed.
import { createClient } from '@supabase/supabase-js';
import type { DbClient } from './queries.js';
/**
* Creates a typed Supabase client configured for the Origin Graph schema.
* Pass `supabaseUrl` + `anonKey` for client-side usage (RLS enforced),
* or `supabaseUrl` + `serviceRoleKey` for server-side usage (bypasses RLS).
*/
export function createOriginGraphClient(
supabaseUrl: string,
supabaseKey: string,
options?: { auth?: { autoRefreshToken?: boolean; persistSession?: boolean } },
): DbClient {
return createClient(supabaseUrl, supabaseKey, options) as unknown as DbClient;
}
+1
View File
@@ -1,2 +1,3 @@
export * from './types.js';
export * from './queries.js';
export * from './client.js';
+85
View File
@@ -19,6 +19,7 @@ import type {
InsertAgentSession,
InsertTeamMember,
InsertProject,
InsertWorkspace,
DiffStatus,
ArtboardAncestry,
} from './types.js';
@@ -73,6 +74,17 @@ export async function getArtboards(
return data;
}
/**
* Spec Layer 4.2 canonical function name — delegates to getArtboards.
* Callers that use the spec-mandated name get the same result.
*/
export async function getArtboardsByWorkspace(
db: DbClient,
workspaceId: string,
): Promise<Artboard[]> {
return getArtboards(db, workspaceId);
}
export async function getArtboard(db: DbClient, id: string): Promise<Artboard> {
const { data, error } = await (db
.from('artboards')
@@ -126,6 +138,31 @@ export async function getArtboardAncestors(db: DbClient, artboardId: string): Pr
return data;
}
// ── Full-text artboard search (migration 006) ─────────────────────────────────
// Calls the search_artboards RPC which ranks artboards by relevance across name,
// metadata_jsonb, linked intent diff summaries, and origin source_ref.
// Returns up to `limit` results ordered by rank DESC.
export interface ArtboardSearchResult extends Artboard {
rank: number;
}
export async function searchArtboards(
db: DbClient,
workspaceId: string,
query: string,
limit = 20,
): Promise<ArtboardSearchResult[]> {
if (!query.trim()) return [];
const { data, error } = await (db.rpc('search_artboards', {
p_workspace_id: workspaceId,
p_query: query.trim(),
p_limit: limit,
}) as Promise<{ data: ArtboardSearchResult[]; 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[]> {
@@ -201,6 +238,16 @@ export async function getActiveDesignLanguageFile(
// ── Origin queries ────────────────────────────────────────────────────────────
export async function getOrigin(db: DbClient, id: string): Promise<Origin> {
const { data, error } = await (db
.from('origins')
.select('*')
.eq('id', id)
.single() as Promise<{ data: Origin; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
export async function createOrigin(db: DbClient, row: InsertOrigin): Promise<Origin> {
const { data, error } = await (db
.from('origins')
@@ -211,6 +258,34 @@ export async function createOrigin(db: DbClient, row: InsertOrigin): Promise<Ori
return data;
}
// ── Origin Graph aggregate query ─────────────────────────────────────────────
// Returns the complete provenance picture for a single artboard: the artboard
// row, its linked origin (if any), and all intent diffs ever recorded against
// it. This is the primary read path described in Layer 4.2 of the spec.
export async function getOriginGraph(
db: DbClient,
artboardId: string,
): Promise<{ artboard: Artboard; origin: Origin | null; diffs: IntentDiff[] }> {
// Fetch artboard and diffs in parallel for minimum latency.
const [artboard, diffs] = await Promise.all([
getArtboard(db, artboardId),
getDiffs(db, artboardId),
]);
let origin: Origin | null = null;
if (artboard.origin_id) {
try {
origin = await getOrigin(db, artboard.origin_id);
} catch {
// Origin may have been deleted (ON DELETE SET NULL on the FK) — treat as
// missing rather than throwing, since the artboard itself is valid.
}
}
return { artboard, origin, diffs };
}
// ── Agent session queries ─────────────────────────────────────────────────────
export async function createAgentSession(db: DbClient, row: InsertAgentSession): Promise<AgentSession> {
@@ -225,6 +300,16 @@ export async function createAgentSession(db: DbClient, row: InsertAgentSession):
// ── Workspace queries ─────────────────────────────────────────────────────────
export async function createWorkspace(db: DbClient, row: InsertWorkspace): Promise<Workspace> {
const { data, error } = await (db
.from('workspaces')
.insert(row)
.select()
.single() as Promise<{ data: Workspace; error: DbError | null }>);
if (error) throw new Error(error.message);
return data;
}
export async function getWorkspace(db: DbClient, id: string): Promise<Workspace> {
const { data, error } = await (db
.from('workspaces')
+102 -76
View File
@@ -3,122 +3,143 @@ import { z } from 'zod';
// ── Enums ─────────────────────────────────────────────────────────────────────
export const OriginTypeSchema = z.enum([
'GIT_COMMIT',
'LINEAR_ISSUE',
'SLACK_MESSAGE',
'URL',
'FORK',
// Spec Layer 4 lowercase set (canonical)
'route', 'linear', 'git', 'slack', 'feedback', 'fork', 'manual',
// Legacy uppercase set (migration 001) — kept for backward compat
'GIT_COMMIT', 'LINEAR_ISSUE', 'SLACK_MESSAGE', 'URL', 'FORK',
// Canonical uppercase aliases (migration 007)
'ROUTE', 'FEEDBACK', 'MANUAL',
]);
export type OriginType = z.infer<typeof OriginTypeSchema>;
export const DiffStatusSchema = z.enum([
'DRAFT',
'EXPORTED',
'IMPLEMENTED',
'BLOCKED',
// Spec Layer 4 lowercase set (canonical)
'draft', 'exported', 'acknowledged', 'implemented', 'rejected',
// Legacy uppercase set — kept for backward compat
'DRAFT', 'EXPORTED', 'IMPLEMENTED', 'BLOCKED',
// UI-driven additions
'ACKNOWLEDGED', 'REJECTED', 'REVIEWED', 'APPLIED',
]);
export type DiffStatus = z.infer<typeof DiffStatusSchema>;
export const TeamRoleSchema = z.enum([
'OWNER',
'DESIGNER',
'ENGINEER',
'PM',
'VIEWER',
]);
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 const AgentTypeSchema = z.enum([
// Spec lowercase (canonical)
'cursor', 'claude-code', 'generic',
// Legacy uppercase
'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) ─────────────────────────────────
// ── 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,
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(),
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(),
project_id: z.string().uuid().nullable(),
name: z.string(),
origin_id: z.string().uuid().nullable(),
id: z.string().uuid(),
workspace_id: z.string().uuid(),
project_id: z.string().uuid().nullable(),
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(),
/** Spec alias added migration 008 — mirrors parent_artboard_id */
parent_id: z.string().uuid().nullable().optional(),
metadata_jsonb: z.record(z.unknown()),
route: z.string().nullable().optional(),
remote_url: z.string().nullable().optional(),
/** Spec: NOT NULL DEFAULT 1440 (migration 008) */
width: z.number().int().default(1440),
/** Spec: NOT NULL DEFAULT 900 (migration 008) */
height: z.number().int().default(900),
created_by: z.string().optional(),
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(),
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(),
/** Spec Layer 4 FK: origins.artboard_id (added migration 008) */
artboard_id: z.string().uuid().nullable().optional(),
source_id: z.string().nullable().optional(),
source_url: z.string().nullable().optional(),
screenshot_url: z.string().nullable().optional(),
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(),
id: z.string().uuid(),
artboard_id: z.string().uuid(),
author_id: z.string(),
/** Spec column name: changes (renamed from changes_jsonb in migration 008) */
changes: z.record(z.unknown()),
/** Spec column name: aggregate_summary (renamed from summary in migration 008) */
aggregate_summary: z.string(),
status: DiffStatusSchema,
notes: z.string().optional(),
session_id: z.string().nullable().optional(),
before_screenshot: z.string().nullable().optional(),
after_screenshot: z.string().nullable().optional(),
exported_code: z.string().nullable().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,
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(),
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(),
id: z.string().uuid(),
workspace_id: z.string().uuid(),
name: z.string(),
name: z.string(),
schema_jsonb: z.record(z.unknown()),
version: z.number().int(),
created_at: z.string().datetime(),
updated_at: z.string().datetime(),
version: z.number().int(),
is_active: z.boolean().optional(),
created_by: z.string().nullable().optional(),
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(),
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(),
user_id: z.string(),
role: TeamRoleSchema,
created_at: z.string().datetime(),
updated_at: z.string().datetime(),
});
export type TeamMember = z.infer<typeof TeamMemberSchema>;
// ── Project ───────────────────────────────────────────────────────────────────
export const ProjectSchema = z.object({
id: z.string().uuid(),
workspace_id: z.string().uuid(),
@@ -131,7 +152,7 @@ export const ProjectSchema = z.object({
});
export type Project = z.infer<typeof ProjectSchema>;
// ── Ancestry (from materialized view) ────────────────────────────────────────
// ── Ancestry (from closure table) ────────────────────────────────────────────
export interface ArtboardAncestry {
artboard_id: string;
@@ -139,13 +160,18 @@ export interface ArtboardAncestry {
depth: number;
}
// ── Insert types (omit server-set fields) ────────────────────────────────────
// ── 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'>;
export type InsertProject = Omit<Project, 'id' | 'created_at' | 'updated_at'>;
export type InsertWorkspace = Omit<Workspace, 'id' | 'created_at' | 'updated_at'>;
/**
* width/height are optional on insert because the DB column defaults are
* 1440 and 900 respectively (migration 008). Callers may omit them.
*/
export type InsertArtboard = Omit<Artboard, 'id' | 'created_at' | 'updated_at' | 'width' | 'height'>
& { width?: number; height?: number };
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'>;
export type InsertProject = Omit<Project, 'id' | 'created_at' | 'updated_at'>;