Reset core/viewer/editor/mcp packages and apps/editor from private-editor
Wholesale swap of packages/{core,viewer,editor,mcp} and apps/editor with the
versions from the private editor repo, which is the production source of truth.
Setup changes:
- packages/{core,viewer,editor} versions held at 0.7.0 baseline (matching
the most recent published release) so a bump=minor publishes 0.8.0
- packages/mcp held at 0.1.1 (never published; first publish will go through
the new release.yml flow)
- peerDependencies and devDependencies for inter-package @pascal-app/*
references pinned to ^0.7.0 instead of '*' / 'workspace:*' so they are
valid for npm consumers
- Root package.json: TypeScript bumped to 6.0.2, added overrides for
@types/react, @types/react-dom, @types/three to prevent JSX namespace
fragmentation across the workspace
- release.yml extended to also publish editor and mcp; 'both' option renamed
to 'all'; added a sync step that updates inter-package peerDeps/devDeps to
match the new versions on every bump (so viewer/editor/mcp tarballs always
reference the version of core they were built against)
- Root scripts gained release:editor and release:mcp shortcuts
Verification:
- bun install --frozen-lockfile is consistent
- packages/{core,viewer,mcp} build cleanly, dist/index.d.ts emitted
- packages/editor check-types reports 21 pre-existing errors, identical to
what private-editor currently reports
Open PRs against editor-v2 will need rebasing/conflict resolution.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { mkdirSync } from 'node:fs'
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
@@ -6,6 +7,8 @@ import { z } from 'zod'
|
||||
import { generateSlug, isValidSlug, sanitizeSlug } from './slug'
|
||||
import { openSqliteDatabase, type SqliteDatabase } from './sqlite-driver'
|
||||
import {
|
||||
type ProjectCreateOptions,
|
||||
type ProjectStatus,
|
||||
type SceneEvent,
|
||||
type SceneEventAppendOptions,
|
||||
type SceneEventListOptions,
|
||||
@@ -58,6 +61,15 @@ interface SceneEventRow {
|
||||
graph_json: string
|
||||
}
|
||||
|
||||
interface ProjectPlaceholder {
|
||||
id: string
|
||||
name: string
|
||||
ownerId: string | null
|
||||
thumbnailUrl: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
const GraphSchema = z.object({
|
||||
nodes: z.record(z.string(), z.unknown()),
|
||||
rootNodeIds: z.array(z.string()),
|
||||
@@ -116,6 +128,7 @@ function resolveMaxSceneBytes(
|
||||
}
|
||||
|
||||
function rowToMeta(row: SceneRow): SceneMeta {
|
||||
const editorUrl = editorUrlForScene(row.id)
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
@@ -127,6 +140,66 @@ function rowToMeta(row: SceneRow): SceneMeta {
|
||||
updatedAt: row.updated_at,
|
||||
sizeBytes: row.size_bytes,
|
||||
nodeCount: row.node_count,
|
||||
editorUrl,
|
||||
url: editorUrl,
|
||||
published: true,
|
||||
graphHash: hashGraphJson(row.graph_json),
|
||||
}
|
||||
}
|
||||
|
||||
function editorUrlForScene(id: string): string {
|
||||
return `/editor/${id}`
|
||||
}
|
||||
|
||||
function hashGraphJson(graphJson: string): string {
|
||||
return createHash('sha256').update(graphJson).digest('hex')
|
||||
}
|
||||
|
||||
function rowToProjectStatus(row: SceneRow): ProjectStatus {
|
||||
const editorUrl = editorUrlForScene(row.id)
|
||||
return {
|
||||
id: row.id,
|
||||
projectId: row.project_id ?? row.id,
|
||||
name: row.name,
|
||||
editorUrl,
|
||||
url: editorUrl,
|
||||
ownerId: row.owner_id,
|
||||
thumbnailUrl: row.thumbnail_url,
|
||||
publishedVersion: row.version,
|
||||
latestVersion: row.version,
|
||||
draftVersion: null,
|
||||
browserVisibleVersion: row.version,
|
||||
version: row.version,
|
||||
isEmpty: row.node_count === 0,
|
||||
sizeBytes: row.size_bytes,
|
||||
nodeCount: row.node_count,
|
||||
graphHash: hashGraphJson(row.graph_json),
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
function placeholderToProjectStatus(project: ProjectPlaceholder): ProjectStatus {
|
||||
const editorUrl = editorUrlForScene(project.id)
|
||||
return {
|
||||
id: project.id,
|
||||
projectId: project.id,
|
||||
name: project.name,
|
||||
editorUrl,
|
||||
url: editorUrl,
|
||||
ownerId: project.ownerId,
|
||||
thumbnailUrl: project.thumbnailUrl,
|
||||
publishedVersion: null,
|
||||
latestVersion: null,
|
||||
draftVersion: null,
|
||||
browserVisibleVersion: null,
|
||||
version: 0,
|
||||
isEmpty: true,
|
||||
sizeBytes: 0,
|
||||
nodeCount: 0,
|
||||
graphHash: null,
|
||||
createdAt: project.createdAt,
|
||||
updatedAt: project.updatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +278,7 @@ export class SqliteSceneStore implements SceneStore {
|
||||
readonly databasePath: string
|
||||
|
||||
private readonly maxSceneBytes: number
|
||||
private readonly projectPlaceholders = new Map<string, ProjectPlaceholder>()
|
||||
private db: SqliteDatabase | null = null
|
||||
private dbPromise: Promise<SqliteDatabase> | null = null
|
||||
|
||||
@@ -214,6 +288,38 @@ export class SqliteSceneStore implements SceneStore {
|
||||
this.maxSceneBytes = resolveMaxSceneBytes(env, opts.maxSceneBytes)
|
||||
}
|
||||
|
||||
async createProject(opts: ProjectCreateOptions): Promise<ProjectStatus> {
|
||||
const db = await this.database()
|
||||
assertValidName(opts.name)
|
||||
const id = opts.id ? sanitizeSlug(opts.id) : this.generateUniqueId(db)
|
||||
if (!isValidSlug(id)) {
|
||||
throw new SceneInvalidError(`Invalid project id after sanitization: "${id}"`)
|
||||
}
|
||||
if (this.getRow(db, id)) {
|
||||
throw new SceneInvalidError(`Project with id "${id}" already exists`)
|
||||
}
|
||||
const now = new Date().toISOString()
|
||||
const project: ProjectPlaceholder = {
|
||||
id,
|
||||
name: opts.name,
|
||||
ownerId: opts.ownerId ?? null,
|
||||
thumbnailUrl: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
this.projectPlaceholders.set(id, project)
|
||||
return placeholderToProjectStatus(project)
|
||||
}
|
||||
|
||||
async getProjectStatus(id: string): Promise<ProjectStatus | null> {
|
||||
const db = await this.database()
|
||||
const safeId = sanitizeSlug(id)
|
||||
const row = this.getRow(db, safeId)
|
||||
if (row) return rowToProjectStatus(row)
|
||||
const placeholder = this.projectPlaceholders.get(safeId)
|
||||
return placeholder ? placeholderToProjectStatus(placeholder) : null
|
||||
}
|
||||
|
||||
async save(opts: SceneSaveOptions): Promise<SceneMeta> {
|
||||
return this.withWriteTransaction((db) => {
|
||||
assertValidName(opts.name)
|
||||
@@ -228,6 +334,7 @@ export class SqliteSceneStore implements SceneStore {
|
||||
}
|
||||
|
||||
const existing = this.getRow(db, id)
|
||||
const placeholder = this.projectPlaceholders.get(id)
|
||||
|
||||
if (existing && providedId !== undefined && opts.expectedVersion === undefined) {
|
||||
throw new SceneInvalidError(
|
||||
@@ -254,8 +361,12 @@ export class SqliteSceneStore implements SceneStore {
|
||||
|
||||
const now = new Date().toISOString()
|
||||
const version = (existing?.version ?? 0) + 1
|
||||
const createdAt = existing?.created_at ?? now
|
||||
const createdAt = existing?.created_at ?? placeholder?.createdAt ?? now
|
||||
const nodeCount = Object.keys(opts.graph.nodes ?? {}).length
|
||||
const projectId = opts.projectId ?? existing?.project_id ?? (placeholder ? id : null)
|
||||
const ownerId = opts.ownerId ?? existing?.owner_id ?? placeholder?.ownerId ?? null
|
||||
const thumbnailUrl =
|
||||
opts.thumbnailUrl ?? existing?.thumbnail_url ?? placeholder?.thumbnailUrl ?? null
|
||||
|
||||
if (existing) {
|
||||
db.query(
|
||||
@@ -272,9 +383,9 @@ export class SqliteSceneStore implements SceneStore {
|
||||
WHERE id = ?`,
|
||||
).run(
|
||||
opts.name,
|
||||
opts.projectId ?? null,
|
||||
opts.ownerId ?? null,
|
||||
opts.thumbnailUrl ?? null,
|
||||
projectId,
|
||||
ownerId,
|
||||
thumbnailUrl,
|
||||
version,
|
||||
now,
|
||||
sizeBytes,
|
||||
@@ -291,9 +402,9 @@ export class SqliteSceneStore implements SceneStore {
|
||||
).run(
|
||||
id,
|
||||
opts.name,
|
||||
opts.projectId ?? null,
|
||||
opts.ownerId ?? null,
|
||||
opts.thumbnailUrl ?? null,
|
||||
projectId,
|
||||
ownerId,
|
||||
thumbnailUrl,
|
||||
version,
|
||||
createdAt,
|
||||
now,
|
||||
@@ -307,19 +418,25 @@ export class SqliteSceneStore implements SceneStore {
|
||||
`INSERT INTO scene_revisions (
|
||||
scene_id, version, graph_json, author_kind, author_id, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
).run(id, version, graphJson, 'mcp', opts.ownerId ?? null, now)
|
||||
).run(id, version, graphJson, 'mcp', ownerId, now)
|
||||
|
||||
this.projectPlaceholders.delete(id)
|
||||
|
||||
return {
|
||||
id,
|
||||
name: opts.name,
|
||||
projectId: opts.projectId ?? null,
|
||||
ownerId: opts.ownerId ?? null,
|
||||
thumbnailUrl: opts.thumbnailUrl ?? null,
|
||||
projectId,
|
||||
ownerId,
|
||||
thumbnailUrl,
|
||||
version,
|
||||
createdAt,
|
||||
updatedAt: now,
|
||||
sizeBytes,
|
||||
nodeCount,
|
||||
editorUrl: editorUrlForScene(id),
|
||||
url: editorUrlForScene(id),
|
||||
published: true,
|
||||
graphHash: hashGraphJson(graphJson),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface SceneMeta {
|
||||
name: string
|
||||
projectId: string | null
|
||||
thumbnailUrl: string | null
|
||||
/** Monotonic, incremented on every save. */
|
||||
/** Browser-visible model version. Draft saves may update the same version repeatedly. */
|
||||
version: number
|
||||
/** ISO 8601 timestamp. */
|
||||
createdAt: string
|
||||
@@ -19,6 +19,18 @@ export interface SceneMeta {
|
||||
ownerId: string | null
|
||||
sizeBytes: number
|
||||
nodeCount: number
|
||||
/** Browser route agents should return to users. Hosted apps should prefer /editor/<projectId>. */
|
||||
editorUrl?: string
|
||||
/** Backward-compatible alias for clients that still read url. */
|
||||
url?: string
|
||||
/** True when this save is browser-visible without a separate publish call. */
|
||||
published?: boolean
|
||||
/** True when the saved graph is still the mutable browser-visible draft. */
|
||||
isDraft?: boolean
|
||||
/** How the scene was saved. Draft saves should not create meaningful history versions. */
|
||||
saveMode?: SceneSaveMode
|
||||
/** Stable hash of the graph payload used for save/load/status matching. */
|
||||
graphHash?: string
|
||||
}
|
||||
|
||||
export interface SceneWithGraph extends SceneMeta {
|
||||
@@ -43,8 +55,18 @@ export interface SceneSaveOptions {
|
||||
thumbnailUrl?: string | null
|
||||
/** When set, save fails with `SceneVersionConflictError` on mismatch. */
|
||||
expectedVersion?: number
|
||||
/** `draft` updates the browser-visible working model; `checkpoint` records version history. */
|
||||
saveMode?: SceneSaveMode
|
||||
/** Whether a checkpoint should become the published/browser-visible head. */
|
||||
publish?: boolean
|
||||
/** Optional hosted MCP session id for project presence/debug metadata. */
|
||||
agentSessionId?: string
|
||||
/** Optional high-level operation name for presence/debug metadata. */
|
||||
operation?: string
|
||||
}
|
||||
|
||||
export type SceneSaveMode = 'draft' | 'checkpoint'
|
||||
|
||||
export interface SceneListOptions {
|
||||
projectId?: string
|
||||
ownerId?: string
|
||||
@@ -67,8 +89,39 @@ export interface SceneEventListOptions {
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface ProjectCreateOptions {
|
||||
id?: SceneId
|
||||
name: string
|
||||
ownerId?: string | null
|
||||
isPrivate?: boolean
|
||||
}
|
||||
|
||||
export interface ProjectStatus {
|
||||
id: SceneId
|
||||
projectId: string
|
||||
name: string
|
||||
editorUrl: string
|
||||
url: string
|
||||
ownerId: string | null
|
||||
thumbnailUrl: string | null
|
||||
publishedVersion: number | null
|
||||
latestVersion: number | null
|
||||
draftVersion: number | null
|
||||
browserVisibleVersion: number | null
|
||||
/** Alias for the browser-visible/latest meaningful version. */
|
||||
version: number
|
||||
isEmpty: boolean
|
||||
sizeBytes: number
|
||||
nodeCount: number
|
||||
graphHash: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface SceneStore {
|
||||
readonly backend: 'sqlite'
|
||||
readonly backend: 'sqlite' | 'supabase'
|
||||
createProject?(opts: ProjectCreateOptions): Promise<ProjectStatus>
|
||||
getProjectStatus?(id: SceneId): Promise<ProjectStatus | null>
|
||||
save(opts: SceneSaveOptions): Promise<SceneMeta>
|
||||
load(id: SceneId): Promise<SceneWithGraph | null>
|
||||
list(opts?: SceneListOptions): Promise<SceneMeta[]>
|
||||
|
||||
Reference in New Issue
Block a user