VOOZH about

URL: https://deepwiki.com/pcescato/ajc-bridge/2.2-sync-pipeline

⇱ Sync Pipeline | pcescato/ajc-bridge | DeepWiki


Loading...
Menu

Sync Pipeline

This page documents the end-to-end flow that takes a WordPress post save event and delivers content to one or more external platforms (GitHub or Dev.to). It covers the full chain from Queue_Manager through Sync_Runner, content adapters, media processing, and API clients.

  • For details on how Queue_Manager is instantiated and its hooks are registered at boot time, see Plugin Bootstrap.
  • For the Queue_Manager class in isolation (retry logic, lock mechanism, status state machine), see Queue Manager.
  • For Sync_Runner method details and error handling patterns, see Sync Runner.
  • For adapter implementations, see Content Adapters.

Overview

Every sync operation in AJC Bridge passes through the same two-stage pipeline:

  1. Enqueue — A WordPress hook triggers Queue_Manager::enqueue(), which schedules a background job via Action Scheduler (or WP Cron fallback) and sets the post's sync status to pending.
  2. Execute — The background job fires Queue_Manager::process_sync(), which acquires a transient-based concurrency lock (60s TTL) and delegates all real work to Sync_Runner::run().

Sync_Runner is the sole entry point for sync logic. It routes to platform-specific methods (sync_to_github() or sync_to_devto()) based on the publishing_strategy setting. Content adapters (Hugo_Adapter, Astro_Adapter, DevTo_Adapter) handle SSG-specific conversions and front matter generation.

Sources: core/class-queue-manager.php19-27 core/class-sync-runner.php17-21 core/class-queue-manager.php731-745


Pipeline Architecture

Diagram: Sync Pipeline — Code Entities


Sources: core/class-queue-manager.php506-617 core/class-sync-runner.php42-160


Sync Status State Machine

Each post carries a _ajc_sync_status post meta value that moves through the following states. Both Queue_Manager and Sync_Runner write to this meta key — Queue_Manager manages the outer lifecycle (pendingprocessingsuccess/error) while Sync_Runner manages the inner processing state.

Diagram: Sync Status States




















































StatusWritten ByMeaning
pendingQueue_ManagerScheduled via Action Scheduler, not yet started
processingQueue_Manager (line 544) / Sync_Runner (line 295)Background job running, lock held
successQueue_Manager (line 573) / Sync_Runner (line 486)Completed successfully, retry counter reset
errorQueue_Manager (line 560)Failed; eligible for retry (max 3 attempts)
cancelledQueue_Manager (line 247)Manually cancelled, lock released, retry counter reset
deletingQueue_Manager (line 273)Deletion job scheduled
deletedQueue_Manager (line 669)Files removed from GitHub
delete_errorQueue_Manager (line 658)Deletion failed

Sources: core/class-queue-manager.php60-72 core/class-queue-manager.php538-584 core/class-queue-manager.php633-703


Stage 1 — Enqueueing

When a post is published or updated, the Plugin registers a save_post hook (see Plugin Bootstrap) that calls Queue_Manager::enqueue().

Queue_Manager::enqueue() performs these checks before scheduling:

  1. Verifies post exists via get_post().
  2. Checks _ajc_sync_status — skips if already pending or processing.
  3. Reads _ajc_retry_count — aborts if >= MAX_RETRIES (3).
  4. Increments retry counter if previous status was error; resets it otherwise.
  5. Calls as_has_scheduled_action() to prevent duplicate Action Scheduler entries.
  6. Calls as_enqueue_async_action() (Action Scheduler) or wp_schedule_single_event() (WP Cron fallback).
  7. Sets _ajc_sync_status to pending.

Sources: core/class-queue-manager.php106-220


Stage 2 — Background Execution

When the Action Scheduler fires the ajc_bridge_sync_post hook, Queue_Manager::process_sync() runs:

  1. Verifies post still exists via get_post() (line 508).
  2. Calls acquire_lock($post_id), which attempts to set transient jamstack_lock_{post_id} with 60-second expiration (line 735) — returns false if lock already held by another worker.
  3. If lock acquisition fails, logs warning and returns immediately (line 518-524).
  4. Sets _ajc_sync_status to processing (line 544).
  5. Delegates to Sync_Runner::run($post_id) (line 556).
  6. On WP_Error return: sets status to error (line 560), logs error details.
  7. On success: sets status to success (line 573), updates timestamp, deletes retry counter.
  8. In finally block: always calls release_lock() (line 614), ensuring lock is freed even on PHP fatal errors.

The lock mechanism prevents race conditions when multiple background workers might attempt to process the same post concurrently.

Sources: core/class-queue-manager.php506-617 core/class-queue-manager.php731-745 core/class-queue-manager.php754-762


Stage 3 — Sync Runner Routing

Sync_Runner::run() is a public static method. It is the single entry point for all sync logic.

Pre-flight checks (executed before routing):

  1. Writes _ajc_sync_start_time to post meta.
  2. Calls check_safety_timeout() — if a previous run left _ajc_sync_start_time set and more than 300 seconds have elapsed, forces status to failed and clears the timestamp.
  3. Calls get_post() — returns WP_Error if not found.
  4. Checks post_status === 'publish' — returns WP_Error if not published.
  5. Reads publishing_strategy from ajc_bridge_settings option.
  6. Runs migrate_old_settings() if legacy adapter_type setting is present.

Routing by publishing_strategy:

StrategySync TargetDev.to Checkbox Required?
wordpress_onlyNone (skipped)
wordpress_devtoDev.to onlyYes
github_onlyGitHub onlyNo
devto_onlyDev.to onlyNo
dual_github_devtoGitHub always + Dev.to optionallyYes (for Dev.to)

Sources: core/class-sync-runner.php42-160 core/class-sync-runner.php787-809


Stage 4a — GitHub Sync Pipeline

Sync_Runner::sync_to_github() is a private static method called for strategies that target GitHub.

Diagram: GitHub Sync Detailed Flow


Adapter selection is based on the ssg_type setting:

  • ssg_type === 'astro'Astro_Adapter
  • ssg_type === 'hugo' (default) → Hugo_Adapter

Atomic commit payload structure built before sending:

payload = {
 "content/posts/YYYY-MM-DD-slug.md" => markdown_string,
 "static/images/{post_id}/featured.webp" => binary,
 "static/images/{post_id}/featured.avif" => binary,
 "static/images/{post_id}/{image}.webp" => binary,
 ...
}

A warning is logged if the total payload exceeds 10 MB, though the commit is still attempted.

Sources: core/class-sync-runner.php512-731 core/class-sync-runner.php594-630


Stage 4b — Dev.to Sync Pipeline

Sync_Runner::sync_to_devto() handles publishing to Dev.to. It accepts an optional $canonical_url string, which is set based on the publishing strategy:

  • wordpress_devto: canonical URL points to WordPress site ($settings['devto_site_url'] or get_site_url())
  • dual_github_devto: canonical URL points to GitHub static site ($settings['github_site_url'] + /posts/{slug})
  • devto_only: no canonical URL (Dev.to is primary)

Published status resolution logic (critical for preventing accidental unpublishing):

Scenariopublished value passed to adapterOutcome
New article (no _ajc_bridge_devto_id)false (line 328)Article created as draft
Existing article, DevTo_API::get_article() succeeds, published_timestamp is non-emptytrue (line 343)Article stays published
Existing article, DevTo_API::get_article() succeeds, published_timestamp is emptyfalse (line 345)Article stays draft
Existing article, DevTo_API::get_article() returns WP_Errornull (line 365)Front matter controls state; no override sent

The null case is critical — it prevents the plugin from accidentally unpublishing articles when the Dev.to API is temporarily unavailable.

Diagram: Dev.to Sync Flow


Sources: core/class-sync-runner.php283-501 core/class-sync-runner.php327-367 core/class-devto-api.php221-269


Stage 5 — Media Processing

Media_Processor is used only in the GitHub sync path. It converts WordPress images into commit-ready binary payloads.

For each content image:

  1. extract_image_urls() — regex extracts src from <img> tags, filters to local URLs only.
  2. download_image() — uses wp_remote_get() to save to a per-post temp directory under sys_get_temp_dir()/ajc-bridge-images/{post_id}/.
  3. generate_webp() — Intervention Image at 85% quality.
  4. generate_avif() — only if Imagick extension is loaded and reports AVIF support via queryFormats('AVIF').
  5. Returns binary content and relative path mappings for URL substitution.

For the featured image:

  • Same download/encode flow, but filenames are fixed as featured.webp and featured.avif (for Hugo) or {original}.webp/{original}.avif (for Astro), determined by the adapter's get_featured_image_name() method.

The finally block in sync_to_github() guarantees cleanup_temp_files() runs even if an exception occurs.

Sources: core/class-media-processor.php136-206 core/class-media-processor.php371-496 core/class-media-processor.php509-574 core/class-media-processor.php1169-1196


Deletion Pipeline

When a post is trashed or deleted, Queue_Manager::enqueue_deletion() cancels any pending sync tasks, then schedules ajc_bridge_delete_post. The background handler Queue_Manager::process_deletion() delegates to Sync_Runner::delete().

Sync_Runner::delete() workflow:

  1. Reads _ajc_file_path from post meta (cached path).
  2. If no cache exists and post is still available: calls adapter.get_file_path(post) and caches it.
  3. Calls Git_API::delete_file() for the Markdown file.
  4. Calls Git_API::list_directory() on the images directory from adapter.get_images_dir(post_id).
  5. Calls Git_API::delete_file() for each image file (non-blocking — errors are logged, not fatal).
  6. Returns { post_id, success: true, deleted: [...paths] }.

Sources: core/class-sync-runner.php828-1025 core/class-queue-manager.php266-297


Post Meta Keys

Both Queue_Manager and Sync_Runner write to post meta. Here is the full set of keys managed by the pipeline:

Meta KeyWriterRead/Written AtDescription
_ajc_sync_statusBothQueue_Manager (182, 544, 573), Sync_Runner (56, 295, 486)Current sync status string
_ajc_sync_lastSync_Runner(774)Unix timestamp of last sync completion
_ajc_sync_timestampQueue_Manager(183, 248, 574)Unix timestamp of status update
_ajc_sync_start_timeSync_RunnerWritten (46), cleared (492, 729)Safety timeout tracking; cleared in finally
_ajc_file_pathSync_RunnerWritten (656), read (871, 887)Cached GitHub file path for deletion
_ajc_last_commit_urlSync_Runner(664)Full GitHub commit URL
_ajc_retry_countQueue_ManagerRead (143, 528), written (158), deleted (169, 251, 575)Number of retry attempts (max 3)
_ajc_bridge_devto_idSync_RunnerRead (314), written (413-418)Dev.to article ID (set on first create)
_ajc_bridge_devto_urlSync_Runner(421-427)Dev.to article URL
_ajc_bridge_devto_sync_timeSync_Runner(429-433)Unix timestamp of last Dev.to sync
_ajc_bridge_devto_publishedSync_Runner(435-447)'1' or '0' reflecting Dev.to published state
_ajc_bridge_publish_devtoAdmin UIRead (97, 134)'1' if user checked the Dev.to syndication checkbox

Sources: core/class-queue-manager.php42-58 core/class-sync-runner.php46 core/class-sync-runner.php656-666 core/class-sync-runner.php413-447


Error Handling Summary

LocationMechanismOutcome
Queue_Manager::process_sync()try/catch Exception and catch ThrowableSets error status; lock always released in finally
Sync_Runner::sync_to_github()try/catch/finallyfinally cleans temp files and writes final status
Sync_Runner::sync_to_devto()try/catch/finallyfinally writes failed or success status; clears start time
Sync_Runner::check_safety_timeout()Reads _ajc_sync_start_time; if > 300s, forces failedPrevents permanently stuck processing state
Media_Processor image loopcontinue on per-image errorPipeline proceeds with remaining images
Sync_Runner::delete() image loopLogs warning, continuesMarkdown deletion failure is hard-stop; image failures are soft

Sources: core/class-sync-runner.php681-730 core/class-sync-runner.php461-494 core/class-sync-runner.php787-809 core/class-queue-manager.php588-616