This commit is contained in:
Philipinho
2026-03-09 01:08:15 +00:00
parent 4ff13cef62
commit 084746e65a
12 changed files with 667 additions and 105 deletions
+8 -2
View File
@@ -97,6 +97,12 @@ export const emailTypeOptionsSchema = z
})
.passthrough();
export const personTypeOptionsSchema = z
.object({
allowMultiple: z.boolean().default(true),
})
.passthrough();
export const emptyTypeOptionsSchema = z.object({}).passthrough();
const typeOptionsSchemaMap: Record<BasePropertyTypeValue, z.ZodType> = {
@@ -106,7 +112,7 @@ const typeOptionsSchemaMap: Record<BasePropertyTypeValue, z.ZodType> = {
[BasePropertyType.STATUS]: selectTypeOptionsSchema,
[BasePropertyType.MULTI_SELECT]: selectTypeOptionsSchema,
[BasePropertyType.DATE]: dateTypeOptionsSchema,
[BasePropertyType.PERSON]: emptyTypeOptionsSchema,
[BasePropertyType.PERSON]: personTypeOptionsSchema,
[BasePropertyType.FILE]: emptyTypeOptionsSchema,
[BasePropertyType.CHECKBOX]: checkboxTypeOptionsSchema,
[BasePropertyType.URL]: urlTypeOptionsSchema,
@@ -145,7 +151,7 @@ const cellValueSchemaMap: Partial<Record<BasePropertyTypeValue, z.ZodType>> = {
[BasePropertyType.STATUS]: z.string().uuid(),
[BasePropertyType.MULTI_SELECT]: z.array(z.string().uuid()),
[BasePropertyType.DATE]: z.string(),
[BasePropertyType.PERSON]: z.array(z.string().uuid()),
[BasePropertyType.PERSON]: z.union([z.string().uuid(), z.array(z.string().uuid())]),
[BasePropertyType.FILE]: z.array(z.object({
id: z.string().uuid(),
fileName: z.string(),
+144 -30
View File
@@ -1,13 +1,12 @@
import * as path from 'path';
import * as dotenv from 'dotenv';
import { Kysely, sql } from 'kysely';
import { Kysely } from 'kysely';
import { PostgresJSDialect } from 'kysely-postgres-js';
import postgres from 'postgres';
import { v7 as uuid7 } from 'uuid';
import { generateJitteredKeyBetween } from 'fractional-indexing-jittered';
const BASE_ID = '019c69a5-1d84-7985-a7f6-8ee2871d8669';
const TOTAL_ROWS = 100_000;
const TOTAL_ROWS = 1500;
const BATCH_SIZE = 2000;
const envFilePath = path.resolve(process.cwd(), '..', '..', '.env');
@@ -48,6 +47,10 @@ const WORDS = [
'Timeline', 'Milestone', 'Objective', 'Strategy', 'Initiative',
];
const COLORS = [
'red', 'orange', 'yellow', 'green', 'blue', 'purple', 'pink', 'gray',
];
function randomWords(min: number, max: number): string {
const count = min + Math.floor(Math.random() * (max - min + 1));
const result: string[] = [];
@@ -57,6 +60,60 @@ function randomWords(min: number, max: number): string {
return result.join(' ');
}
function makeChoices(names: string[], category?: string) {
return names.map((name, i) => ({
id: uuid7(),
name,
color: COLORS[i % COLORS.length],
...(category ? {} : {}),
}));
}
function makeStatusChoices() {
const todo = [{ id: uuid7(), name: 'Not Started', color: 'gray', category: 'todo' }];
const inProgress = [
{ id: uuid7(), name: 'In Progress', color: 'blue', category: 'inProgress' },
{ id: uuid7(), name: 'In Review', color: 'purple', category: 'inProgress' },
];
const complete = [
{ id: uuid7(), name: 'Done', color: 'green', category: 'complete' },
{ id: uuid7(), name: 'Cancelled', color: 'red', category: 'complete' },
];
const all = [...todo, ...inProgress, ...complete];
return { choices: all, choiceOrder: all.map((c) => c.id) };
}
type PropertyDef = {
name: string;
type: string;
isPrimary?: boolean;
typeOptions?: any;
};
function buildPropertyDefinitions(): PropertyDef[] {
const priorityChoices = makeChoices(['Low', 'Medium', 'High', 'Critical']);
const categoryChoices = makeChoices(['Engineering', 'Design', 'Marketing', 'Sales', 'Support', 'Operations']);
const tagChoices = makeChoices(['Bug', 'Feature', 'Improvement', 'Documentation', 'Research']);
const statusOpts = makeStatusChoices();
return [
{ name: 'Title', type: 'text', isPrimary: true },
{ name: 'Status', type: 'status', typeOptions: statusOpts },
{ name: 'Priority', type: 'select', typeOptions: { choices: priorityChoices, choiceOrder: priorityChoices.map((c) => c.id) } },
{ name: 'Category', type: 'select', typeOptions: { choices: categoryChoices, choiceOrder: categoryChoices.map((c) => c.id) } },
{ name: 'Tags', type: 'multiSelect', typeOptions: { choices: tagChoices, choiceOrder: tagChoices.map((c) => c.id) } },
{ name: 'Due Date', type: 'date', typeOptions: { dateFormat: 'YYYY-MM-DD', includeTime: false } },
{ name: 'Estimate', type: 'number', typeOptions: { format: 'plain', precision: 1 } },
{ name: 'Budget', type: 'number', typeOptions: { format: 'currency', precision: 2, currencySymbol: '$' } },
{ name: 'Approved', type: 'checkbox' },
{ name: 'Website', type: 'url' },
{ name: 'Contact Email', type: 'email' },
{ name: 'Notes', type: 'text' },
{ name: 'Created', type: 'createdAt' },
{ name: 'Last Edited', type: 'lastEditedAt' },
];
}
type CellGenerator = () => unknown;
function buildCellGenerator(property: any): CellGenerator | null {
@@ -108,17 +165,80 @@ function buildCellGenerator(property: any): CellGenerator | null {
}
}
async function main() {
console.log(`Seeding ${TOTAL_ROWS.toLocaleString()} rows for base ${BASE_ID}\n`);
async function createBase(workspaceId: string, spaceId: string, creatorId: string | null): Promise<string> {
const baseId = uuid7();
const baseName = `Seed Base ${new Date().toISOString().slice(0, 16)}`;
const base = await db
.selectFrom('bases')
.selectAll()
.where('id', '=', BASE_ID)
await db.insertInto('bases').values({
id: baseId,
name: baseName,
space_id: spaceId,
workspace_id: workspaceId,
creator_id: creatorId,
created_at: new Date(),
updated_at: new Date(),
}).execute();
console.log(`Created base: ${baseName}`);
console.log(`Base ID: ${baseId}\n`);
// Create properties
const propertyDefs = buildPropertyDefinitions();
let propPosition: string | null = null;
const insertedProperties: any[] = [];
for (const def of propertyDefs) {
propPosition = generateJitteredKeyBetween(propPosition, null);
const prop = {
id: uuid7(),
base_id: baseId,
name: def.name,
type: def.type,
position: propPosition,
type_options: def.typeOptions ? JSON.stringify(def.typeOptions) : null,
is_primary: def.isPrimary ?? false,
workspace_id: workspaceId,
created_at: new Date(),
updated_at: new Date(),
};
insertedProperties.push(prop);
}
await db.insertInto('base_properties').values(insertedProperties).execute();
console.log(`Created ${insertedProperties.length} properties:`);
for (const p of insertedProperties) {
console.log(` - ${p.name} (${p.type})${p.is_primary ? ' [primary]' : ''}${SKIP_TYPES.has(p.type) ? ' [system]' : ''}`);
}
// Create default view
const viewId = uuid7();
await db.insertInto('base_views').values({
id: viewId,
base_id: baseId,
name: 'Table View 1',
type: 'table',
position: generateJitteredKeyBetween(null, null),
config: JSON.stringify({}),
workspace_id: workspaceId,
creator_id: creatorId,
created_at: new Date(),
updated_at: new Date(),
}).execute();
console.log(`Created view: Table View 1\n`);
return baseId;
}
async function main() {
const spaceId = '019c69a3-dd47-7014-8b87-ec8f167577ee';
const space = await db
.selectFrom('spaces')
.select(['id', 'workspace_id'])
.where('id', '=', spaceId)
.executeTakeFirstOrThrow();
const workspaceId = base.workspace_id;
console.log(`Workspace: ${workspaceId}`);
const workspaceId = space.workspace_id;
const user = await db
.selectFrom('users')
@@ -127,19 +247,21 @@ async function main() {
.executeTakeFirst();
const creatorId = user?.id ?? null;
console.log(`Creator: ${creatorId ?? '(none)'}`);
console.log(`Workspace: ${workspaceId}`);
console.log(`Space: ${spaceId}`);
console.log(`Creator: ${creatorId ?? '(none)'}\n`);
// Create the base with properties and view
const baseId = await createBase(workspaceId, spaceId, creatorId);
// Load the created properties for cell generation
const properties = await db
.selectFrom('base_properties')
.selectAll()
.where('base_id', '=', BASE_ID)
.where('base_id', '=', baseId)
.execute();
console.log(`Properties: ${properties.length}`);
for (const p of properties) {
console.log(` - ${p.name} (${p.type})${SKIP_TYPES.has(p.type) ? ' [skipped]' : ''}`);
}
const generators: Array<{ propertyId: string; generate: CellGenerator }> = [];
for (const prop of properties) {
const gen = buildCellGenerator(prop);
@@ -148,18 +270,9 @@ async function main() {
}
}
console.log(`\nGenerating ${TOTAL_ROWS.toLocaleString()} positions...`);
console.log(`Generating ${TOTAL_ROWS.toLocaleString()} positions...`);
const lastRow = await db
.selectFrom('base_rows')
.select('position')
.where('base_id', '=', BASE_ID)
.where('deleted_at', 'is', null)
.orderBy(sql`position COLLATE "C"`, sql`desc`)
.limit(1)
.executeTakeFirst();
let lastPosition: string | null = lastRow?.position ?? null;
let lastPosition: string | null = null;
const positions: string[] = new Array(TOTAL_ROWS);
for (let i = 0; i < TOTAL_ROWS; i++) {
lastPosition = generateJitteredKeyBetween(lastPosition, null);
@@ -182,7 +295,7 @@ async function main() {
rows.push({
id: uuid7(),
base_id: BASE_ID,
base_id: baseId,
cells,
position: positions[i],
creator_id: creatorId,
@@ -201,6 +314,7 @@ async function main() {
const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1);
console.log(`\nDone. Inserted ${TOTAL_ROWS.toLocaleString()} rows in ${totalElapsed}s`);
console.log(`\nBase ID: ${baseId}`);
await db.destroy();
process.exit(0);