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/todo-list",
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/todo-list",
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.