/**
* @packageDocumentation
* Grouped example API for Gantt persistence, reporting, and framework metadata.
*
* The recipes wrap REST, GraphQL, Supabase/PostgreSQL, JSON snapshot, and print
* helpers with injected state and transport dependencies. They are designed as
* copyable integration patterns for applications that own the backend, file
* picker, print shell, or framework-specific demo catalog.
*/
import type { ProjectSnapshot } from '@revolist/gantt';
import {
cloneProjectSnapshot,
exportProjectSnapshot,
parseProjectSnapshotJson,
type ProjectSnapshotParseResult,
} from '@revolist/gantt';
import {
ProjectSnapshotGraphqlAdapter,
type ProjectSnapshotGraphqlAdapterOptions,
type ProjectSnapshotGraphqlRequestOptions,
} from './graphql-adapter';
import {
buildCreateProjectSnapshotTableSql,
buildLoadProjectSnapshotSql,
buildSaveProjectSnapshotSql,
ProjectSnapshotSupabaseAdapter,
type PostgresStatement,
type ProjectSnapshotPostgresOptions,
type ProjectSnapshotPostgresRow,
type SupabaseLikeClient,
} from './postgres-persistence';
import {
printGanttContainer,
type GanttPrintDateRange,
type GanttPrintOptions,
type GanttPrintResult,
} from './print-pdf-recipe';
import {
ProjectSnapshotRestAdapter,
type ProjectSnapshotRestAdapterOptions,
type ProjectSnapshotRestRequestOptions,
} from './rest-adapter';
export type GanttPersistenceRecipeId =
| 'rest-project-snapshot'
| 'graphql-project-snapshot'
| 'supabase-postgres-project-snapshot'
| 'postgres-sql-project-snapshot'
| 'print-pdf-export'
| 'json-snapshot-helpers'
| 'portal-svelte-gantt'
| 'portal-resource-filter';
export type GanttPersistenceRecipeChannel =
| 'persistence'
| 'export'
| 'reporting'
| 'framework';
export type GanttPersistenceRecipeFramework =
| 'framework-neutral'
| 'svelte'
| 'vanilla';
export interface GanttPersistenceRecipeMetadata {
readonly id: GanttPersistenceRecipeId;
readonly title: string;
readonly channel: GanttPersistenceRecipeChannel;
readonly frameworks: readonly GanttPersistenceRecipeFramework[];
readonly sourceFiles: readonly string[];
readonly dependencies: readonly string[];
readonly summary: string;
}
export interface ProjectSnapshotState {
getProject(): ProjectSnapshot;
setProject(project: ProjectSnapshot): void;
}
export interface ProjectSnapshotPersistenceRecipe {
readonly metadata: GanttPersistenceRecipeMetadata;
load(request?: ProjectSnapshotRestRequestOptions | ProjectSnapshotGraphqlRequestOptions): Promise<ProjectSnapshot>;
save(request?: ProjectSnapshotRestRequestOptions | ProjectSnapshotGraphqlRequestOptions): Promise<void>;
}
export interface ProjectSnapshotStorageRecipe {
readonly metadata: GanttPersistenceRecipeMetadata;
load(projectId: string): Promise<ProjectSnapshot | null>;
save(): Promise<ProjectSnapshot>;
}
export interface ProjectSnapshotSqlExecutor {
query<T>(statement: PostgresStatement): Promise<{ readonly rows: readonly T[] }>;
}
export interface ProjectSnapshotPostgresRecipe {
readonly metadata: GanttPersistenceRecipeMetadata;
createTable(): Promise<void>;
load(projectId: string): Promise<ProjectSnapshot | null>;
save(): Promise<ProjectSnapshot>;
buildReportingStatement(projectId: string): PostgresStatement;
}
export interface GanttPrintPdfRecipeOptions {
readonly target: Element | null | undefined;
readonly document?: Document;
readonly window?: GanttPrintOptions['window'];
readonly now?: () => Date | string;
}
export interface GanttPrintPdfRecipe {
readonly metadata: GanttPersistenceRecipeMetadata;
exportProject(project: ProjectSnapshot, range?: GanttPrintDateRange): GanttPrintResult;
}
export interface ProjectSnapshotJsonRecipeOptions {
readonly readText?: () => string | Promise<string>;
readonly writeText?: (fileName: string, contents: string) => void | Promise<void>;
readonly fileName?: (project: ProjectSnapshot) => string;
}
export interface ProjectSnapshotJsonRecipe {
readonly metadata: GanttPersistenceRecipeMetadata;
clone(project: ProjectSnapshot): ProjectSnapshot;
export(project: ProjectSnapshot, space?: number | string): string;
parse(json: string): ProjectSnapshotParseResult;
load(): Promise<ProjectSnapshot>;
save(project: ProjectSnapshot, space?: number | string): Promise<string>;
}
export const GANTT_PERSISTENCE_QUICK_WIN_EXAMPLES: readonly GanttPersistenceRecipeMetadata[] = [
{
id: 'rest-project-snapshot',
title: 'REST project snapshot persistence',
channel: 'persistence',
frameworks: ['framework-neutral'],
sourceFiles: [
'examples/components/src/components/gantt/recipes/rest-adapter.ts',
'packages/gantt/src/gantt/core/project-snapshot-json.ts',
],
dependencies: ['fetch'],
summary: 'Loads and saves a serialized ProjectSnapshot through an injected fetch implementation.',
},
{
id: 'graphql-project-snapshot',
title: 'GraphQL project snapshot persistence',
channel: 'persistence',
frameworks: ['framework-neutral'],
sourceFiles: [
'examples/components/src/components/gantt/recipes/graphql-adapter.ts',
'packages/gantt/src/gantt/core/project-snapshot-json.ts',
],
dependencies: ['fetch'],
summary: 'Uses configurable GraphQL operations and variables to load and save ProjectSnapshot JSON.',
},
{
id: 'supabase-postgres-project-snapshot',
title: 'Supabase/PostgreSQL project snapshot persistence',
channel: 'persistence',
frameworks: ['framework-neutral'],
sourceFiles: [
'examples/components/src/components/gantt/recipes/postgres-persistence.ts',
'packages/gantt/src/gantt/core/project-snapshot-json.ts',
],
dependencies: ['Supabase-like client'],
summary: 'Persists JSONB snapshots with a minimal Supabase-style client shape.',
},
{
id: 'postgres-sql-project-snapshot',
title: 'PostgreSQL SQL project snapshot persistence',
channel: 'reporting',
frameworks: ['framework-neutral'],
sourceFiles: [
'examples/components/src/components/gantt/recipes/postgres-persistence.ts',
],
dependencies: ['PostgreSQL query executor'],
summary: 'Builds parameterized DDL, load, save, and reporting SQL without owning the database client.',
},
{
id: 'print-pdf-export',
title: 'Print/PDF export',
channel: 'export',
frameworks: ['framework-neutral', 'vanilla'],
sourceFiles: [
'examples/components/src/components/gantt/recipes/print-pdf-recipe.ts',
],
dependencies: ['DOM print API'],
summary: 'Prepares a printable Gantt container that users can route to browser print or Save as PDF.',
},
{
id: 'json-snapshot-helpers',
title: 'JSON snapshot helpers',
channel: 'persistence',
frameworks: ['framework-neutral'],
sourceFiles: [
'packages/gantt/src/gantt/core/project-snapshot-json.ts',
],
dependencies: ['JSON'],
summary: 'Clones, serializes, parses, imports, and exports ProjectSnapshot payloads.',
},
{
id: 'portal-svelte-gantt',
title: 'Portal Svelte Gantt demo',
channel: 'framework',
frameworks: ['svelte'],
sourceFiles: [
'apps/portal/src/components/gantt/GanttSvelteExample.svelte',
'apps/portal/src/components/gantt/GanttSvelteExample.md',
],
dependencies: ['@revolist/svelte-datagrid'],
summary: 'Existing portal reference showing Gantt usage through the Svelte wrapper.',
},
{
id: 'portal-resource-filter',
title: 'Portal resource filter demo',
channel: 'framework',
frameworks: ['framework-neutral', 'vanilla'],
sourceFiles: [
'apps/portal/src/components/gantt/GanttResourceFilterExample.ts',
],
dependencies: ['resourceFilterIds'],
summary: 'Existing portal reference for filtering projected Gantt rows by assigned resource IDs.',
},
];
export function createRestProjectPersistenceRecipe(
options: ProjectSnapshotRestAdapterOptions,
state: ProjectSnapshotState,
): ProjectSnapshotPersistenceRecipe {
const adapter = new ProjectSnapshotRestAdapter(options);
return {
metadata: getPersistenceRecipeMetadata('rest-project-snapshot'),
async load(request) {
const project = await adapter.load(request as ProjectSnapshotRestRequestOptions | undefined);
state.setProject(project);
return project;
},
async save(request) {
await adapter.save(state.getProject(), request as ProjectSnapshotRestRequestOptions | undefined);
},
};
}
export function createGraphqlProjectPersistenceRecipe(
options: ProjectSnapshotGraphqlAdapterOptions,
state: ProjectSnapshotState,
): ProjectSnapshotPersistenceRecipe {
const adapter = new ProjectSnapshotGraphqlAdapter(options);
return {
metadata: getPersistenceRecipeMetadata('graphql-project-snapshot'),
async load(request) {
const project = await adapter.load(request as ProjectSnapshotGraphqlRequestOptions | undefined);
state.setProject(project);
return project;
},
async save(request) {
await adapter.save(state.getProject(), request as ProjectSnapshotGraphqlRequestOptions | undefined);
},
};
}
export function createSupabasePostgresPersistenceRecipe(
client: SupabaseLikeClient<ProjectSnapshotPostgresRow>,
state: ProjectSnapshotState,
options: ProjectSnapshotPostgresOptions = {},
): ProjectSnapshotStorageRecipe {
const adapter = new ProjectSnapshotSupabaseAdapter(client, options);
return {
metadata: getPersistenceRecipeMetadata('supabase-postgres-project-snapshot'),
async load(projectId) {
const project = await adapter.load(projectId);
if (project) {
state.setProject(project);
}
return project;
},
async save() {
const project = await adapter.save(state.getProject());
state.setProject(project);
return project;
},
};
}
export function createPostgresSqlPersistenceRecipe(
executor: ProjectSnapshotSqlExecutor,
state: ProjectSnapshotState,
options: ProjectSnapshotPostgresOptions = {},
): ProjectSnapshotPostgresRecipe {
return {
metadata: getPersistenceRecipeMetadata('postgres-sql-project-snapshot'),
async createTable() {
await executor.query(buildCreateProjectSnapshotTableSql(options));
},
async load(projectId) {
const result = await executor.query<{ readonly snapshot: unknown }>(
buildLoadProjectSnapshotSql(projectId, options),
);
const snapshot = result.rows[0]?.snapshot;
if (snapshot === undefined) {
return null;
}
const parsed = parseProjectSnapshotJson(
typeof snapshot === 'string' ? snapshot : JSON.stringify(snapshot),
);
if (parsed.ok === false) {
throw new Error(parsed.error);
}
state.setProject(parsed.project);
return parsed.project;
},
async save() {
const project = state.getProject();
const result = await executor.query<ProjectSnapshotPostgresRow>(
buildSaveProjectSnapshotSql(project, options),
);
const persisted = result.rows[0]?.snapshot ?? project;
const parsed = parseProjectSnapshotJson(
typeof persisted === 'string' ? persisted : JSON.stringify(persisted),
);
if (parsed.ok === false) {
throw new Error(parsed.error);
}
state.setProject(parsed.project);
return parsed.project;
},
buildReportingStatement(projectId) {
return {
text: [
'select',
" snapshot->>'id' as project_id,",
" snapshot->>'name' as project_name,",
" jsonb_array_length(snapshot->'tasks') as task_count,",
" jsonb_array_length(snapshot->'resources') as resource_count,",
" jsonb_array_length(snapshot->'dependencies') as dependency_count",
`from ${quoteQualifiedIdentifier(options.tableName ?? 'gantt_project_snapshots')}`,
'where project_id = $1;',
].join('\n'),
values: [projectId],
};
},
};
}
export function createPrintPdfExportRecipe(
options: GanttPrintPdfRecipeOptions,
): GanttPrintPdfRecipe {
return {
metadata: getPersistenceRecipeMetadata('print-pdf-export'),
exportProject(project, range) {
return printGanttContainer(options.target, {
document: options.document,
window: options.window,
metadata: {
title: project.name,
subtitle: `${project.tasks.length} tasks | ${project.resources.length} resources`,
range,
printedAt: options.now?.() ?? new Date(),
},
});
},
};
}
export function createJsonSnapshotRecipe(
options: ProjectSnapshotJsonRecipeOptions = {},
): ProjectSnapshotJsonRecipe {
return {
metadata: getPersistenceRecipeMetadata('json-snapshot-helpers'),
clone: cloneProjectSnapshot,
export(project, space) {
return exportProjectSnapshot(project, { space });
},
parse: parseProjectSnapshotJson,
async load() {
if (!options.readText) {
throw new Error('Project snapshot JSON load requires an injected readText function.');
}
const parsed = parseProjectSnapshotJson(await options.readText());
if (parsed.ok === false) {
throw new Error(parsed.error);
}
return parsed.project;
},
async save(project, space) {
if (!options.writeText) {
throw new Error('Project snapshot JSON save requires an injected writeText function.');
}
const contents = exportProjectSnapshot(project, { space });
const fileName = options.fileName?.(project) ?? `${project.id}-gantt-project.json`;
await options.writeText(fileName, contents);
return contents;
},
};
}
export function getPersistenceRecipeMetadata(
id: GanttPersistenceRecipeId,
): GanttPersistenceRecipeMetadata {
const metadata = GANTT_PERSISTENCE_QUICK_WIN_EXAMPLES.find(example => example.id === id);
if (!metadata) {
throw new Error(`Unknown Gantt persistence recipe "${id}".`);
}
return metadata;
}
function quoteQualifiedIdentifier(identifier: string): string {
const parts = identifier.split('.');
if (parts.some(part => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(part))) {
throw new Error(`PostgreSQL identifier "${identifier}" is not supported by this example.`);
}
return parts.map(part => `"${part}"`).join('.');
}