Skip to content

feat(functions-compiler): carry the invocation-logs response channel - #628

Merged
yurynix merged 1 commit into
mainfrom
feat/functions-compiler-invocation-logs
Sep 17, 2026
Merged

yurynix merged 1 commit into
mainfrom
feat/functions-compiler-invocation-logs

Conversation

@yurynix

@yurynix yurynix commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Note

Description

Ports the invocation-logs response channel into @base44/functions-compiler. It was added to apper on 14 September 2026 by base44-dev/apper#22656feat(builder): test_backend_function returns its own invocation's logs, flag TEST_FUNCTION_RESPONSE_LOGS, commit 9d315265b06 — closing the last behavioural gap between this package and the compiler engine apper runs in production. When the server sets X-B44-Capture-Logs: 1, the per-app router buffers that invocation's own console.* lines on the request store and returns them on the X-B44-Invocation-Logs response header — covering the normal reply, the 404 / init-500 / 503 early returns, and the crash path. Without it, adopting 0.1.0 as-is would compile user workers that silently drop the feature, so this unblocks the apper swap.

Related Issue

None (ports base44-dev/apper#22656; enables retiring apper's in-tree base44-userapp-bundler engine)

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Other (please describe):

Changes Made

  • New src/invocation-logs.ts — the injected runtime snippets: CAPTURE_PATCH (per-line buffering with budgets), INVOCATION_LOGS_PATCH (_b44CaptureStore / _b44AttachInvocationLogs / _b44CrashResponse), and ASCII_ESCAPE, plus the header names and caps (40 lines, 6 KB of captured payload, 7 KB response-header budget).
  • Console patch (src/worker-entry.ts) — _b44Wrap now formats once and captures the line via _b44Capture before forwarding it to the original sink, so captured and shipped logs cannot diverge.
  • Per-app entry generationbuildAppEntrySource injects INVOCATION_LOGS_PATCH, seeds the request store with ..._b44CaptureStore(request), and routes every reply through _b44AttachInvocationLogs (telemetry stays the inner wrap so its header survives); a thrown error answers through _b44CrashResponse, which mirrors the dispatcher's user-exception 500 body but rethrows CPU-limit / subrequest-quota errors so the dispatcher's own classification stands.
  • Safety properties baked in — non-ASCII is \\uXXXX-escaped (workerd's headers.set throws above 0xff), overflow is reported as a dropped count rather than hidden, and when the function's own headers leave no room the payload degrades to drop-count-only and then to no header at all, never breaking a response that works with capture off. A silent invocation still gets an empty-list header, so "no header" stays the unambiguous "app has not rebundled" signal.
  • Scope — per-function entries are deliberately untouched; the channel is per-app router scope only.
  • Tests — new test/invocation-logs.test.ts (12 tests running the real patches in workerd via Miniflare: levels, line cap, byte budget under adversarial escaping, header squeeze, non-ASCII, passthrough of status/body/headers, crash body, quota rethrow) and 3 added assertions in test/worker-entry.test.ts on the generated entry.
  • package.json — version 0.1.0 → 0.1.1, since 0.1.0 is already published.

What is verbatim from apper, and what is new

All of it is a port of apper's files at 9d315265b06 (the tip of base44-dev/apper#22656), with four deliberate deltas and nothing else. Every row below is reproducible with a plain diff against that commit.

File Verbatim? The delta
test/invocation-logs.test.ts byte-identical none
test/worker-entry.test.ts byte-identical none
src/invocation-logs.ts verbatim except 2 lines MAX_CAPTURED_BYTES and RESPONSE_HEADER_BUDGET lost their export. They are used only inside the module and knip fails the build on unused exports — the same treatment #621 gave four internal types
src/worker-entry.ts every ported line verbatim except 1 the new import reads from "./invocation-logs.js"; apper's has no extension. This repo resolves NodeNext-style
package.json new 0.1.0 → 0.1.1, since 0.1.0 is already published

No formatting or restructuring rode along. The diff on src/worker-entry.ts is 8 hunks and every one of them is the feature. The file still differs from apper's copy in .js import extensions, import ordering and Biome line wrapping — all of that is pre-existing from #621 and untouched here.

To repeat the check: pull every line in either copy of worker-entry.ts mentioning ASCII_ESCAPE, CAPTURE_PATCH, INVOCATION_LOGS_PATCH, _b44Wrap, _b44CaptureStore, _b44AttachInvocationLogs or _b44CrashResponse — 20 lines on each side — and diff them. The only difference is the .js extension above.

Nothing was renamed, re-commented or otherwise improved on the way over. The behaviour is apper's; a divergence would be a bug that only surfaces once apper compiles through this package.

Testing

  • I have tested these changes locally
  • I have added/updated tests as needed
  • All tests pass (`npm test`)

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (if applicable)
  • My changes generate no new warnings
  • I have updated `docs/` (AGENTS.md) if I made architectural changes

How the gap opened

When
apper commit this package was snapshotted from (b27ce1f6) 2026-09-10 12:11 +03:00
#621 merged here, extracting the engine without the feature 2026-09-14 11:01 +03:00
base44-dev/apper#22656 merged, adding it (9d315265b06) 2026-09-14 11:19 +03:00

Eighteen minutes after the extraction landed, and four days after the commit it was snapshotted from. The extraction was verbatim against a commit that predated the feature, and the feature landed in apper while the extraction was in review — which is exactly why apper cannot adopt 0.1.0: the package's worker-entry.ts is the older one.

While both copies exist, the check before any swap is a directory diff of the two src/ trees, not a read of either changelog.

Additional Notes

  • Do not reformat `src/invocation-logs.ts`. Its exported strings are injected verbatim into every compiled user worker, so its text is part of the emitted bytes.
  • Behaviour is additive and opt-in per request. With no `X-B44-Capture-Logs` header the store carries no buffer, capture is a no-op, and crashes rethrow exactly as before — verified by two dedicated tests.
  • Known gap (documented in-code): lines logged after the response body starts streaming land past the header, the same frame boundary that already drops those events from per-function log views.
  • Publishing 0.1.1 is manual. `manual-publish.yml` is hardcoded to `packages/cli` and npm trusted publishing keys on the workflow filename, so this package has no release lane yet; adding one needs its own trusted-publisher registration.
  • Test-suite results above (252 tests green, typecheck/lint/knip clean) are as reported by the author's local run — the full suite was not re-executed in this description-generation environment; CI covers it.

🤖 Generated by Claude | 2026-09-16 17:14 UTC | ddce31b

Ports apper #22656 into the extracted engine. The per-app router buffers
an invocation's console lines on the request store when the server-injected
X-B44-Capture-Logs header is set, and returns them on the response — the
404 / init-500 / 503 early returns and the crash path included. Without
this the package compiles a worker that silently drops the feature, so
apper's bundler service cannot adopt it.

src/invocation-logs.ts is apper's file verbatim except for two constants
that lost `export` (used only inside the module; knip rejects the rest).
test/ is byte-identical to apper's copy, as the extraction requires.
Version 0.1.1, since 0.1.0 is already published.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Package Preview Available!


Install this PR's preview build with npm:

npm i @base44-preview/cli@0.1.15-pr.628.ddce31b

Prefer not to change any import paths? Install using npm alias so your code still imports base44:

npm i "base44@npm:@base44-preview/cli@0.1.15-pr.628.ddce31b"

Or add it to your package.json dependencies:

{
  "dependencies": {
    "base44": "npm:@base44-preview/cli@0.1.15-pr.628.ddce31b"
  }
}

Preview published to npm registry — try new features instantly!

@yurynix
yurynix merged commit b2f4699 into main Sep 17, 2026
18 checks passed
@yurynix
yurynix deleted the feat/functions-compiler-invocation-logs branch September 17, 2026 15:40
yurynix added a commit that referenced this pull request Sep 17, 2026
main has been red since #623 landed: it added src/version.ts pinned to 0.1.0
and the test that guards it, having branched before #628 bumped package.json to
0.1.1. Setting the literal to 0.1.1 is the whole of that fix.

The publish workflow would have reintroduced the drift on every release. `npm
version` rewrites package.json alone, and the literal cannot be read from
package.json at run time — a host that bundles this module ships none beside it
(that is what src/version.ts documents). So a release would have published a
banner naming the previous version and left main red again.

scripts/sync-version.ts rewrites the literal from package.json and throws if it
matches nothing, because a silent no-op sed is precisely the failure being
prevented. The workflow runs it between the bump and the build, and the release
commit now carries both files. Bumping by hand runs the same script.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
yurynix added a commit that referenced this pull request Sep 17, 2026
* ci(functions-compiler): add a manual npm publish workflow

The CLI's manual-publish.yml cannot be reused for @base44/functions-compiler.
Most of it is CLI-specific — standalone binaries, the Homebrew tap, PostHog
sourcemaps, the skills-repo dispatch, the GitHub Release that carries the
tarballs — and every one of those steps would need an `if:` guard on a package
input, in the one job that must not break. The two trains also tag differently:
`v<version>` is the CLI's series, so this one tags `functions-compiler-v<version>`.

Sharing the file would buy nothing anyway. npm trusted publishing keys a
publisher on the repo *and* the workflow filename, so @base44/functions-compiler
needs its own registry entry either way; a separate file makes that entry
narrower — it can publish this package and nothing else.

Two differences from the CLI workflow worth naming:

- Nothing is stripped from package.json before publish. The CLI deletes
  devDependencies because everything is bundled; here esbuild, @deno/loader and
  zod are real runtime dependencies consumers install.
- The job only builds and publishes. The packaging proof that hits the registry
  (scripts/verify-package.ts) stays in functions-compiler.yml, behind the Wix
  gateway, where it runs on every push to main — so this job still resolves
  nothing, which is what its gateway exemption rests on.

Before the first run, a trusted publisher for @base44/functions-compiler must be
registered on npmjs.com against this repo and this filename.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* ci(functions-compiler): declare the repository for trusted publishing

npm's trusted-publisher validation requires package.json's repository URL to
match the GitHub repository the OIDC token comes from. packages/cli already
declares it; this package did not, so the first publish would have failed the
check with nothing in the workflow to explain why.

`directory` points at the package inside the monorepo, which is also what makes
the npm page link to the right subtree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(functions-compiler): keep COMPILER_VERSION in step with package.json

main has been red since #623 landed: it added src/version.ts pinned to 0.1.0
and the test that guards it, having branched before #628 bumped package.json to
0.1.1. Setting the literal to 0.1.1 is the whole of that fix.

The publish workflow would have reintroduced the drift on every release. `npm
version` rewrites package.json alone, and the literal cannot be read from
package.json at run time — a host that bundles this module ships none beside it
(that is what src/version.ts documents). So a release would have published a
banner naming the previous version and left main red again.

scripts/sync-version.ts rewrites the literal from package.json and throws if it
matches nothing, because a silent no-op sed is precisely the failure being
prevented. The workflow runs it between the bump and the build, and the release
commit now carries both files. Bumping by hand runs the same script.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <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.

2 participants