Skip to content

feat(plugins): Phase 3 — ordering + cron liveness + reconciliation (T3.1–T3.6)#852

Open
lane711 wants to merge 1 commit into
lane711/plugin-system-definefrom
lane711/plugin-system-phase3
Open

feat(plugins): Phase 3 — ordering + cron liveness + reconciliation (T3.1–T3.6)#852
lane711 wants to merge 1 commit into
lane711/plugin-system-definefrom
lane711/plugin-system-phase3

Conversation

@lane711

@lane711 lane711 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Phase 3 of the plugin framework overhaul — takes the system to production-ready by making dependency ordering, cron cold-start wiring, delivery reconciliation, and the Worker entry real.

Stacked on: #844 (lane711/plugin-system-define)

T3.1 — Dependency topo-sort + cycle detection

  • plugins/topo-sort.ts: DFS topo-sort with visiting stack, throws PluginDependencyCycleError on cycles, warns/throws on missing dep IDs (strict/non-strict).
  • MountablePlugin + WirablePlugin extended with id? + dependencies?.
  • Both registerPluginRoutes and wireRegisteredPlugins now sort by dependencies before mounting/wiring. Old-style PluginBuilder plugins (no dependencies) keep their original order.

T3.2 — bootIsolate extraction

  • HTTP middleware body factored into boot: BootIsolateFn — the same once-guarded initEmailService + wirePlugins call, now exposed on SonicJSApp.
  • SonicJSApp.boot(env): callable from cron handlers, tests, or any non-HTTP context. Idempotent.

T3.3 — Wire scheduled() end-to-end

  • createScheduledHandler gains optional boot? parameter: called before the first dispatch so cron-first cold isolates have a populated hook bus.
  • my-sonicjs-app/src/index.ts restructured to export { fetch, scheduled } — a proper Worker object that wires plugins even when cron fires first.

T3.4 — wrangler.toml [triggers] codegen

  • plugins/generate-triggers.ts: parseCronTriggers, updateWranglerTriggers, generateTriggersComment.
  • my-sonicjs-app/scripts/generate-cron-triggers.ts: --check mode for CI sync guard.

T3.5 — Per-provider reconciliation + observability migration

  • EmailProvider.reconcile?() optional method; EmailLogRow type.
  • Migration 038: adds user_id, context_type, context_id, tenant_id, delivery_state, delivery_synced_at to email_log (all nullable, no defaults). Partial index for reconciliation; per-user history index.

T3.6 — CloudflareEmailProvider

  • services/email/providers/cloudflare.ts: wraps the send_email Workers binding (MailChannels / CF Email Routing). Configurable defaultFrom.

Test plan

  • npm test --workspace=@sonicjs-cms/core → 1647 passed, 0 failed
  • tsc --noEmit → clean
  • eslint src/ → 0 errors
  • Topo-sort: 11 tests (ordering, cycle detection, strict/non-strict, id preference)
  • boot-isolate: 8 tests (exposed, idempotent, shared once-guard, disableAll, cron handler)
  • generate-triggers: 6 tests (parse, update, roundtrip)

🤖 Generated with Claude Code

…observability (T3.1–T3.6)

T3.1 — Dependency topo-sort + cycle detection:
- `plugins/topo-sort.ts`: DFS topo-sort with `visiting` stack, throws
  `PluginDependencyCycleError` on cycles, warns/throws on missing dep ids (strict).
- `MountablePlugin` + `WirablePlugin` extended with optional `id` + `dependencies`.
- `registerPluginRoutes` + `wireRegisteredPlugins` now sort by dependencies by
  default (sortByDependencies=true); old-style plugins without `dependencies`
  keep their original declaration order.

T3.2 — bootIsolate extraction:
- HTTP middleware body factored into `boot: BootIsolateFn` closure — the same
  once-guarded `initEmailService` + `wirePlugins` call, now exposed on the
  returned `SonicJSApp` object.
- `SonicJSApp` type extended with `readonly boot: BootIsolateFn`.
- The HTTP middleware now calls `boot(c.env)`, sharing the once-guard with any
  other caller (cron, test harness).

T3.3 — Wire scheduled() end-to-end:
- `createScheduledHandler` gains optional `boot?` parameter. Called before the
  first cron dispatch so a cron-first cold isolate has a populated hook bus and
  reachable email service; warm isolates return instantly (once-guard).
- `my-sonicjs-app/src/index.ts` restructured to export `{ fetch, scheduled }` —
  a proper Worker object instead of a bare Hono app. `scheduled` wires through
  `app.boot`.

T3.4 — wrangler.toml [triggers] codegen:
- `plugins/generate-triggers.ts`: `parseCronTriggers`, `updateWranglerTriggers`,
  `generateTriggersComment` utilities.
- `my-sonicjs-app/scripts/generate-cron-triggers.ts`: a tsx script that reads
  plugin `crons[]` and writes the `[triggers]` section; `--check` mode for CI.

T3.5 — Per-provider reconciliation + observability migration:
- `EmailProvider.reconcile?()` optional method for delivery-state backfill.
- `EmailLogRow` type for reconciliation inputs.
- Migration `038_email_log_observability.sql`: adds `user_id`, `context_type`,
  `context_id`, `tenant_id`, `delivery_state`, `delivery_synced_at` (all
  nullable, no defaults — forward-only D1 / NULL-safe). Partial index for
  reconciliation queries; per-user history index.

T3.6 — CloudflareEmailProvider:
- `services/email/providers/cloudflare.ts`: `CloudflareEmailProvider` wraps the
  `send_email` Workers binding (MailChannels / CF Email Routing).

New exports: `createScheduledHandler`, `dispatchCronTick`, `collectCrons`,
`collectCronSchedules`, `getHookSystem`, `topoSort`, `PluginDependencyCycleError`,
`CloudflareEmailProvider`, `parseCronTriggers`, `updateWranglerTriggers`,
`BootIsolateFn`.

Tests: +25 (topo-sort: 11, boot-isolate: 8, generate-triggers: 6).
Full suite: 1647 passed, 0 failed; tsc + lint clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant