Files
outline/shared/schema.ts
T
Tom Moor 82d7041b6b chore: Refactor Markdown importer to use new import pipeline (#12361)
* chore: Refactor Markdown importer to use new import pipeline

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 14:10:15 -04:00

156 lines
4.9 KiB
TypeScript

import { z } from "zod";
import type { IntegrationService, ProsemirrorDoc } from "./types";
import {
CollectionPermission,
type ImportableIntegrationService,
IssueTrackerIntegrationService,
} from "./types";
import { PageType } from "plugins/notion/shared/types";
const BaseImportInputItemSchema = z.object({
permission: z.enum(CollectionPermission).optional(),
});
export type BaseImportInput = z.infer<typeof BaseImportInputItemSchema>[];
export const NotionImportInputItemSchema = BaseImportInputItemSchema.extend({
type: z.enum(PageType).optional(),
externalId: z.string().optional(),
});
export type NotionImportInput = z.infer<typeof NotionImportInputItemSchema>[];
export const MarkdownImportInputItemSchema = BaseImportInputItemSchema.extend({
externalId: z.string(),
});
export type MarkdownImportInput = z.infer<
typeof MarkdownImportInputItemSchema
>[];
export type ImportInput<T extends ImportableIntegrationService> =
T extends IntegrationService.Notion
? NotionImportInput
: T extends IntegrationService.Markdown
? MarkdownImportInput
: BaseImportInput;
export const BaseImportTaskInputItemSchema = z.object({
externalId: z.string(),
parentExternalId: z.string().optional(),
collectionExternalId: z.string().optional(),
});
export type BaseImportTaskInput = z.infer<
typeof BaseImportTaskInputItemSchema
>[];
export const NotionImportTaskInputItemSchema =
BaseImportTaskInputItemSchema.extend({
type: z.enum(PageType),
});
export type NotionImportTaskInput = z.infer<
typeof NotionImportTaskInputItemSchema
>[];
/**
* Manifest entry describing a single attachment discovered during the
* Markdown zip bootstrap phase. The `id` is a pre-assigned UUID used both
* as the attachment node id in per-page prosemirror output and as the
* Attachment row id created during completion.
*/
export const MarkdownAttachmentManifestItemSchema = z.object({
id: z.uuid(),
name: z.string(),
mimeType: z.string(),
pathInZip: z.string(),
});
export type MarkdownAttachmentManifestItem = z.infer<
typeof MarkdownAttachmentManifestItemSchema
>;
/**
* Markdown importer scratch state. `storageKey` is set at import creation
* (it's the only durable handle on the uploaded zip). `manifest` is added
* by the bootstrap phase so the completion phase can re-download the zip
* and create Attachment rows without re-walking the tree.
*/
export interface MarkdownImportScratch {
storageKey: string;
manifest?: MarkdownAttachmentManifestItem[];
}
/**
* Per-importer scratch shape stored on `Import.scratch`. Holds cross-phase
* state that the importer needs between bootstrap and completion but that
* isn't part of any single task's input. Cleared when the import flips to
* `Processed`.
*/
export type ImportScratch<T extends ImportableIntegrationService> =
T extends IntegrationService.Markdown ? MarkdownImportScratch : never;
/**
* Per-page task input. Generated by the bootstrap task and consumed by
* subsequent MarkdownAPIImportTask runs. `children` carries this document's
* direct descendants so that each level of the document tree is scheduled
* as a separate task wave; this preserves parent-before-child ordering
* during persistence (createdAt of child tasks is strictly later than
* parents'). The type is defined as a TypeScript interface rather than via
* z.infer because it is only consumed internally — never validated at an
* API boundary — and zod's recursive-schema ergonomics aren't worth the
* cost here.
*/
export interface MarkdownPageImportTaskInputItem {
externalId: string;
parentExternalId?: string;
collectionExternalId?: string;
title: string;
path: string;
markdownText: string;
attachmentMap: MarkdownAttachmentManifestItem[];
docMap: Record<string, string>;
children?: MarkdownPageImportTaskInputItem[];
}
/**
* Markdown import task input — a bootstrap row carrying only the base
* placeholder item (the zip's `storageKey` lives on `Import.scratch`), or a
* page row carrying per-document content.
*/
export type MarkdownImportTaskInput = (
| BaseImportTaskInput[number]
| MarkdownPageImportTaskInputItem
)[];
export type ImportTaskInput<T extends ImportableIntegrationService> =
T extends IntegrationService.Notion
? NotionImportTaskInput
: T extends IntegrationService.Markdown
? MarkdownImportTaskInput
: BaseImportTaskInput;
// No reason to be here except for co-location with import task input.
export type ImportTaskOutput = {
externalId: string;
title: string;
icon?: string;
author?: string;
content: ProsemirrorDoc;
createdAt?: Date;
updatedAt?: Date;
}[];
export const IssueSource = z.object({
id: z.string().nonempty(),
name: z.string().nonempty(),
owner: z.object({
id: z.string().nonempty(),
name: z.string().nonempty(),
}),
service: z.enum(IssueTrackerIntegrationService),
});
export type IssueSource = z.infer<typeof IssueSource>;