Skip to main content

PHDocument Migration Guide

tip

This guide covers the breaking changes introduced in Powerhouse v4.0.0 related to PHDocument structure changes. If you're upgrading from v3.2.0 or earlier, this migration is required and document models must be regenerated.

Overview

Version 4.0.0 introduced a significant refactor of the PHDocument structure that consolidates document metadata into a header field. This change enables signed and unsigned documents with cryptographic verification capabilities, but requires updating all code that accesses document properties.

What Changed

Document Structure Refactor

The most significant change is the consolidation of document metadata into a header field. Previously, document properties were scattered at the root level of the document object.

Before (v3.2.0 and earlier):

const document = {
id: "doc-123",
created: "2023-01-01T00:00:00.000Z",
lastModified: "2023-01-01T12:00:00.000Z",
revision: 5,
documentType: "powerhouse/todolist",
name: "My Todo List",
slug: "my-todo-list",
// ... other properties
};

After (v4.0.0):

const document = {
header: {
id: "doc-123",
createdAtUtcIso: "2023-01-01T00:00:00.000Z",
lastModifiedAtUtcIso: "2023-01-01T12:00:00.000Z",
revision: { global: 5, local: 0 },
documentType: "powerhouse/todolist",
name: "My Todo List",
slug: "my-todo-list",
branch: "main",
sig: { nonce: "", publicKey: {} },
meta: {},
},
// ... other properties
};

Complete Property Migration Map

Old PropertyNew PropertyAdditional Changes
document.iddocument.header.idNow an Ed25519 signature for signed documents
document.createddocument.header.createdAtUtcIsoRenamed to include UTC ISO specification
document.lastModifieddocument.header.lastModifiedAtUtcIsoRenamed to include UTC ISO specification
document.revisiondocument.header.revisionNow an object with scope keys (e.g., { global: 5, local: 0 })
document.documentTypedocument.header.documentTypeNo additional changes
document.namedocument.header.nameNo additional changes
document.slugdocument.header.slugNo additional changes
document.branchdocument.header.branchNow explicitly included
document.metadocument.header.metaNow explicitly included
N/Adocument.header.sigNew - Signature information for document verification

Step-by-Step Migration Guide

Step 1: Update Document Property Access

Replace all instances of direct property access with header-based access:

Common Property Access Patterns

Document ID Access:

// Before
const documentId = document.id;

// After
const documentId = document.header.id;

Document Name Access:

// Before
const documentName = document.name;

// After
const documentName = document.header.name;

Document Type Access:

// Before
const docType = document.documentType;

// After
const docType = document.header.documentType;

Timestamp Access:

// Before
const created = document.created;
const lastModified = document.lastModified;

// After
const created = document.header.createdAtUtcIso;
const lastModified = document.header.lastModifiedAtUtcIso;

Revision Access:

// Before
const revision = document.revision; // Was a number

// After
const globalRevision = document.header.revision.global; // Now an object
const localRevision = document.header.revision.local;
// Or get all revisions
const allRevisions = document.header.revision; // { global: 5, local: 0, ... }

Step 2: Update Component Code

React Components:

Example: Document List Component
// Before
function DocumentList({ documents }) {
return (
<div>
{documents.map((doc) => (
<div key={doc.id} className="document-item">
<h3>{doc.name}</h3>
<p>Type: {doc.documentType}</p>
<p>
Last modified: {new Date(doc.lastModified).toLocaleDateString()}
</p>
<p>Revision: {doc.revision}</p>
</div>
))}
</div>
);
}

// After
function DocumentList({ documents }) {
return (
<div>
{documents.map((doc) => (
<div key={doc.header.id} className="document-item">
<h3>{doc.header.name}</h3>
<p>Type: {doc.header.documentType}</p>
<p>
Last modified:{" "}
{new Date(doc.header.lastModifiedAtUtcIso).toLocaleDateString()}
</p>
<p>Global Revision: {doc.header.revision.global}</p>
</div>
))}
</div>
);
}

Step 3: Update Type Definitions

If you're using TypeScript, update your type definitions:

TypeScript Interface Updates
// Before
interface MyDocument {
id: string;
name: string;
documentType: string;
created: string;
lastModified: string;
revision: number;
// ... other properties
}

// After
interface MyDocument {
header: {
id: string;
name: string;
documentType: string;
createdAtUtcIso: string;
lastModifiedAtUtcIso: string;
revision: {
[scope: string]: number;
};
slug: string;
branch: string;
sig: {
nonce: string;
publicKey: any;
};
meta?: {
preferredEditor?: string;
};
};
// ... other properties
}

Step 4: Database Queries and APIs Compatibility

GraphQL Query Compatibility

GraphQL Queries:

# Your existing queries continue to work unchanged
query GetDocument($id: ID!) {
document(id: $id) {
id # Still works due to response transformation
name # Still works due to response transformation
documentType # Still works due to response transformation
created # Still works due to response transformation
lastModified # Still works due to response transformation
revision # Still works due to response transformation
}
}
tip

GraphQL Backward Compatibility: The GraphQL API maintains backward compatibility through response transformation. Your existing queries will continue to work without changes. However, when working with the raw document objects in your application code, you'll need to use the new header structure.

Common Migration Issues and Solutions

Issue 1: Undefined Property Errors

Problem: Getting undefined when accessing document properties.

Solution: Update property access to use the header structure:

// This will be undefined after migration
const name = document.name;

// Use this instead
const name = document.header.name;

Issue 2: Revision Type Mismatch

Problem: Code expecting revision to be a number but getting an object.

Solution: Update revision access to specify the scope:

// Before - revision was a number
if (document.revision > 5) { ... }

// After - revision is an object with scope keys
if (document.header.revision.global > 5) { ... }

Issue 3: Date Format Changes

Problem: Date parsing issues due to property name changes.

Solution: Update timestamp property names:

// Before
const createdDate = new Date(document.created);
const modifiedDate = new Date(document.lastModified);

// After
const createdDate = new Date(document.header.createdAtUtcIso);
const modifiedDate = new Date(document.header.lastModifiedAtUtcIso);

Testing Your Migration

Automated Testing

Create tests to verify your migration:

Migration Test Examples
// Test document property access
describe("Document Migration", () => {
it("should access document properties correctly", () => {
const mockDocument = {
header: {
id: "test-id",
name: "Test Document",
documentType: "powerhouse/test",
createdAtUtcIso: "2023-01-01T00:00:00.000Z",
lastModifiedAtUtcIso: "2023-01-01T12:00:00.000Z",
revision: { global: 5, local: 0 },
// ... other header properties
},
// ... other document properties
};

// Test property access
expect(mockDocument.header.id).toBe("test-id");
expect(mockDocument.header.name).toBe("Test Document");
expect(mockDocument.header.revision.global).toBe(5);
});
});

This migration guide covers the major changes in v4.0.0. For additional technical details, refer to the RELEASE-NOTES.md in the main repository.