Skip to content

feat(service-worker): standalone service worker runtime#6022

Open
gxkl wants to merge 62 commits into
nextfrom
feat/service-worker
Open

feat(service-worker): standalone service worker runtime#6022
gxkl wants to merge 62 commits into
nextfrom
feat/service-worker

Conversation

@gxkl

@gxkl gxkl commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a standalone service worker runtime — an egg-free host that serves HTTP controllers and MCP tools from tegg modules, built on the module-plugin mechanism (#6021). Getting there refactored the controller stack into a host-agnostic core shared by two hosts (egg + fetch/service-worker), so the same declarative @HTTPController / @MCPController modules run in both.

Architecture

Four-package controller layering (mirrors the egg/standalone host boundary):

  • @eggjs/controller-runtime — host-agnostic library, never scanned (no egg dep): base register classes, RootProtoManager, ControllerRegisterFactory, the controller lifecycle protos, McpRouter/Router abstractions, collect-only MCPControllerRegister, MCPServerHelper.
  • @eggjs/controller-plugin (teggController) — the egg host transport.
  • @eggjs/service-worker-controller (serviceWorker module) — the fetch host transport.
  • Each host re-exports the runtime's decorated protos into its own scanned module (runtimeProtos.ts).

Standalone service worker (three packages over @eggjs/standalone):

  • @eggjs/service-worker-runtime — protocol-agnostic @Runner() entry + event dispatch, BackgroundTaskHelper, the logger/config inner-object contract.
  • @eggjs/service-worker-controller — fetch transport: FetchEventHandler, HTTP/MCP register providers, FetchRouter, streaming/SSE lifecycle, unified { code, message } errors.
  • @eggjs/service-worker — the ServiceWorkerApp facade: serve() (node:http bridge), embedded handleEvent(), and the raw Web Service Worker interface (self.addEventListener('fetch', e => e.respondWith(app.handleEvent(e)))). Framework modules are auto-discovered from the package's own deps — no hand-listed/ordered frameworkDeps.

Controllers as container citizens

The dual-host work moved the controller registers into the DI graph so egg and the service worker share one path:

  • egg HTTP/MCP registers are inner-object providers, not static TeggScope slots. EggHTTPControllerRegistrar plugs the HTTP creator into the factory and mounts controllers onto app.router via a @LoadUnitInstanceLifecycleProto hook when the CONTROLLER_LOAD_UNIT instance is created — no manual boot-time doRegister.
  • Inner objects inject app properties (router / logger / …) through the same () => app[name] compat protos business modules use (one PUBLIC copy, deduped); the imperative ControllerRegisterDefaults bypass is deleted. egg and the service worker now differ only in their Router / McpRouter implementations.
  • MCP is host-agnostic via an injected McpRouter boundary (EggMcpRouter / ServiceWorkerMcpRouter); the shared MCPControllerRegister only collects records. MCP stateless streamable HTTP uses the SDK's web-standard transport (SDK ≥ 1.29 forbids reuse — fresh server + transport per request) with an mcpAuthHandler extension point.

Notable fixes

  • StandaloneEggObjectFactory name pinned to eggObjectFactory, so ServiceWorkerRunner's inject resolves locally instead of the module-scan-order-sensitive global proto — this is what lets the service worker auto-discover its framework modules from its own package root.
  • Inject-proxy statics: the inject proxy binds function-valued properties and a bound class loses its statics; the HTTP/MCP registers now import EggContainerFactory directly instead of reading it off the app.

Testing

  • Multi-app isolation regression (test/MultiApp.test.ts), the full HTTP/MCP suites for both hosts, and a runnable examples/helloworld-service-worker (serve / embedded / fetch-event forms).
  • Green across tegg / controller / aop / eventbus / config / schedule / service-worker; dal suites need MySQL.

Docs

Wiki updated: wiki/packages/service-worker.md, wiki/concepts/tegg-module-plugin.md, wiki/workflows/local-ci.md, plus package READMEs.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added standalone Service Worker support for HTTP controllers and MCP tools, including fetch-event handling and streaming/SSE responses.
    • Introduced stateless MCP stream endpoints with a request authentication extension point.
    • Added a “hello world” Service Worker example (HTTP /hello + calculator add tool).
  • Documentation
    • Expanded Service Worker docs, example guidance, and updated local CI/architecture notes.
  • Bug Fixes
    • Improved host-aware routing/route-conflict detection across HTTP and MCP.
    • Fixed background task handling when timeout is set to 0.
  • Refactor
    • Reworked controller boot/registration to use shared runtime primitives for HTTP and MCP.
  • Tests
    • Added/expanded integration tests for routing, AOP/middleware, streaming lifecycle, auth, MCP calls, and multi-app isolation.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a host-agnostic controller runtime, refactors Egg controller registration, and introduces standalone service-worker runtime and HTTP/MCP transports. It also adds ServiceWorkerApp, integration tests, examples, package metadata, and documentation.

Changes

Standalone service-worker controller

Layer / File(s) Summary
Controller runtime contracts and registration
tegg/core/controller-runtime/...
Adds host-agnostic load-unit, metadata, HTTP, and MCP registration contracts, including route conflict detection and schema-loading hooks.
Egg controller integration and compatibility loading
tegg/plugin/controller/..., tegg/plugin/tegg/..., tegg/core/runtime/...
Moves Egg HTTP/MCP wiring into lifecycle registrars and providers, updates MCP hook ownership, and changes app-compatible prototype loading.
Standalone service-worker runtime and transports
tegg/standalone/service-worker*/..., tegg/core/standalone-decorator/...
Adds event dispatch, fetch contexts, HTTP routing, stateless MCP routing, authentication, response utilities, and streaming lifecycle handling.
Validation, examples, packaging, and documentation
tegg/standalone/service-worker/test/..., examples/helloworld-service-worker/..., wiki/...
Adds integration coverage, runnable examples, package/build metadata, module-plugin documentation, and stale-cache guidance.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: killagu, copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: introducing a standalone service worker runtime.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/service-worker

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 6, 2026

Copy link
Copy Markdown

Deploying egg with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6fd52da
Status: ✅  Deploy successful!
Preview URL: https://5d83a7ba.egg-cci.pages.dev
Branch Preview URL: https://feat-service-worker.egg-cci.pages.dev

View logs

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces standalone service worker support for @eggjs/tegg modules, allowing HTTP controllers and MCP tools to be served over a fetch event loop without requiring a full Egg application. It extracts the host-agnostic controller registration runtime into @eggjs/controller-runtime and adds the @eggjs/service-worker-runtime and @eggjs/service-worker packages, along with a runnable example. The review feedback suggests improving exception handling in FetchEventHandler by checking if caught errors are instances of Error before accessing their message, and expanding ResponseUtils to correctly handle and pass through standard web-standard BodyInit types (such as Uint8Array and Blob) instead of incorrectly serializing them as JSON.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread tegg/standalone/service-worker-controller/src/http/FetchEventHandler.ts Outdated
Comment thread tegg/standalone/service-worker-controller/src/utils/ResponseUtils.ts Outdated
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 6, 2026

Copy link
Copy Markdown

Deploying egg-v3 with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6fd52da
Status: ✅  Deploy successful!
Preview URL: https://55d33ef0.egg-v3.pages.dev
Branch Preview URL: https://feat-service-worker.egg-v3.pages.dev

View logs

@gxkl
gxkl force-pushed the feat/service-worker branch from 544b347 to 501a495 Compare July 6, 2026 00:25
@socket-security

socket-security Bot commented Jul 6, 2026

Copy link
Copy Markdown

Dependency limit exceeded — report not shown.

This pull request scan exceeded the 10,000-dependency limit applied to this scan, so the results are incomplete and may be inaccurate. To avoid reporting false positives, Socket has not posted a report.

Upgrade your plan to raise the dependency limit and get complete reports, or view the partial scan in the dashboard.

Socket is always free for open source. If this is a non-commercial open source project, contact us to request a free Team account.

@gxkl
gxkl force-pushed the feat/module-plugin-core branch from 5001fb6 to 79594a6 Compare July 6, 2026 01:39
@gxkl
gxkl force-pushed the feat/service-worker branch 4 times, most recently from bcb170d to 79d6bc3 Compare July 6, 2026 10:07
@gxkl
gxkl force-pushed the feat/module-plugin-core branch 8 times, most recently from b774e07 to 5c42a6d Compare July 9, 2026 15:46
@gxkl
gxkl force-pushed the feat/service-worker branch from 79d6bc3 to 27cb9ef Compare July 10, 2026 05:18
@gxkl
gxkl force-pushed the feat/module-plugin-core branch 9 times, most recently from 85a0476 to ad9acf9 Compare July 13, 2026 08:17
@gxkl
gxkl force-pushed the feat/service-worker branch from 9434e0b to 078a2d9 Compare July 13, 2026 08:24
gxkl and others added 4 commits July 19, 2026 18:11
Add a service-worker-format demo (worker-sw.ts + bundle-sw.mjs + run-sw.mjs) for
StandaloneWorkerBundler's `format: 'service-worker'` path: the entry uses
`addEventListener('fetch', ...)` and the bundler emits the CJS bundle as a classic
(non-module) script — no ESM wrapper, since a module wrapper would move the listener
to module scope where fetch events are not dispatched.

Verified under Node by shimming globalThis.addEventListener around the bundled
classic script (GET /hello + POST /mcp/calc/stream both 200). This format targets
Web Service Worker / edge runtimes, NOT Cloudflare workerd: workerd's nodejs_compat
(required for tegg's AsyncLocalStorage) only supports the module-worker format, and
wrangler rejects a service-worker-format script importing Node builtins. Documented
in the example README and egg-bundler wiki.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inline #createModuleLoader so the manifest descriptor is found once per module
(was found twice — once for precomputedFiles, once for the module name), and take
the bundle-mode unitName from moduleConfig.name (ModuleReference already carries
it) instead of a second manifest.moduleDescriptors.find. Matches the egg plugin's
EggModuleLoader, which passes moduleConfig.name gated on precomputed files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er.load

Pre-build a Map<unitPath, decoratedFiles> once before the load loop instead of a
per-module manifest.moduleDescriptors.find (O(n) -> O(1) per module), mirroring the
egg plugin's decoratedFilesMap. Behavior unchanged: an empty decoratedFiles array
is still truthy, so a module present in the manifest still takes the bundle branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Condense the injection-seam comments to the non-obvious rationale only — drop
comments that restate the code and merge duplicated bundle-mode explanations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 20, 2026 02:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread tools/egg-bundler/src/lib/importMetaPatch.ts Fixed
gxkl and others added 3 commits July 20, 2026 12:18
A loader created on the dynamic path (ModuleLoader.createModuleLoader, e.g. DAL's
multiInstance getObjects) globbed and loaded non-decorated files (an egg plugin's
app.ts), which aren't in the bundle map — the fallback dynamic require then failed
("Cannot find module as expression is too dynamic"). Now createModuleLoader reuses
the manifest's decorated files from globalThis.__EGG_BUNDLE_MANIFEST__ in bundle
mode, mirroring how the egg app bundle stays loadable; non-bundle runs are unchanged
(no global -> globbing as before). This lets a standalone service-worker bundle
include teggDal without excluding it.

Also: DataSource.getObjects returns early when a module has no dataSource config
(was loading every scanned module first); egg-bundler importMetaPatch exported
consts get explicit types and ServiceWorkerApp.loadMetadata an explicit return type
(isolatedDeclarations build); drop the unused @eggjs/service-worker-runtime dep from
the controller and ignore the scan-only @eggjs/service-worker-controller dep on the
host (unplugin-unused).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`egg-bin bundle` now bundles a standalone service worker in addition to an egg app.
A standalone bundle is selected by --entry (or --target standalone); --framework
names the app package, which must export loadMetadata (the standalone counterpart of
the egg app's framework specifier — no separate app-module/app-export). The command
runs loadMetadata + StandaloneWorkerBundler in one shot, mirroring how `egg-bin
bundle` hides the egg app's manifest-load-then-bundle.

@eggjs/service-worker gains a top-level loadMetadata export. The example drops the
hand-written bundle-cf.mjs/bundle-sw.mjs for `egg-bin bundle` scripts and no longer
excludes teggDal (the bundle-mode dynamic-loader fix makes it work). Verified: both
module and service-worker formats build via the CLI and run on Node + workerd.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document the bundle-mode dynamic-module-loading fix (createModuleLoader reuses the
manifest in bundle mode, aligning standalone with the egg app bundle), teggDal no
longer needing excludeModules, and the egg-bin bundle standalone target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 20, 2026 04:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

- js/stack-trace-exposure (FetchEventHandler): a 500 replied the raw thrown error
  message to the client. Reply a generic 'Internal Server Error' instead; the real
  error is still logged server-side and a host can surface details via
  errorResponseMapper.
- js/polynomial-redos (importMetaPatch): the Turbopack import.meta object regex used
  `[\s\S]*?` for the getter body, which backtracks polynomially over a large emitted
  bundle. The body is a single brace-free `return …F(…)`, so `[^}]*` keeps it linear.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 20, 2026 05:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The polynomial backtracking was in the identifier match, not the getter body: the
embedded `import$2e$meta__` literal is all word chars, so the surrounding `[\w$]*`
backtracked over it on a large bundle. Match the identifier as a plain `[\w$]*` and
move the import.meta marker check into the replace callback — the regex is now linear.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 20, 2026 05:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 20, 2026 08:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 20, 2026 08:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 20, 2026 16:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

4 participants