Skip to content

feat: comprehensive core tasks for March 2026 release#36

Merged
weroperking merged 24 commits intomainfrom
feature/core-tasks-march-2026
Mar 21, 2026
Merged

feat: comprehensive core tasks for March 2026 release#36
weroperking merged 24 commits intomainfrom
feature/core-tasks-march-2026

Conversation

@weroperking
Copy link
Owner

@weroperking weroperking commented Mar 21, 2026

Summary

  • Add auth providers command and enhance CLI auth functionality
  • Improve dev server with RLS testing commands and migrate utilities
  • Enhance webhook dispatcher with advanced event handling
  • Add S3 storage adapter, image transformer, and type definitions
  • Improve GraphQL resolvers with realtime bridge and schema generator
  • Add local function runtime support for development
  • Implement request logger middleware and logger module
  • Enhance auto-rest and migration functionality
  • Update client realtime with better event handling
  • Add comprehensive test coverage for CLI and core packages
  • Add new features documentation

Commits (23 total)

This PR consolidates 23 categorized commits covering:

  • CLI Commands (auth, dev, migrate, webhook)
  • Core Webhooks, Storage, GraphQL, Functions
  • Middleware & Logger
  • Client Realtime
  • Tests and Documentation

Type of Change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Pre-flight Checklist

  • I have tested my changes locally
  • I have added/updated tests
  • My code follows the project's coding style

Summary by CodeRabbit

  • New Features

    • Local functions runtime for development with /functions/* routing
    • Real-time presence and channel-based messaging (client channel API)
    • On-demand image transformations via URL query parameters
    • OAuth social provider helpers and CLI add-provider
    • Migration rollback/history and RLS policy testing CLI tools
    • Advanced Auto-REST filtering, GraphQL subscriptions, structured logging
    • Webhook delivery logging and new webhook deliveries endpoints
  • Documentation

    • Added comprehensive feature specification docs and index
  • Chores

    • New CLI subcommands and extensive test suites; removed outdated docs

- Add new auth-providers command for managing authentication providers
- Enhance existing auth command with additional features
- Add tests for auth and login commands
- Improve dev server with better function lifecycle management
- Add RLS test command for row-level security testing
- Add branch commands tests
- Implement new migrate command for database migrations
- Add migrate-schema.sql with schema definitions
- Add migrate-utils for migration helpers
- Add tests for migrate utilities
- Improve webhook command with better event handling
- Add webhook command tests
- Enhance CLI main index with improved command registration
- Update logger with additional utility functions
- Update CLI package.json with new dependencies
- Update logger tests
- Improve webhook dispatcher with better event processing
- Add support for multiple webhook handlers
- Enhance error handling and retry logic
- Enhance webhook startup initialization
- Add schema.sql for webhook configurations
- Update webhook index exports
- Implement S3 adapter for cloud storage operations
- Add comprehensive storage type definitions
- Support for multiple storage backends
- Add storage module index with exports
- Implement image transformer for on-the-fly image processing
- Add support for image optimization and resizing
- Improve GraphQL resolvers with better query handling
- Enhance GraphQL server with additional features
- Add support for complex data types
- Add schema generator for dynamic GraphQL schemas
- Implement realtime-bridge for real-time GraphQL subscriptions
- Enhance GraphQL index exports
- Implement local-runtime for local function execution
- Enhance functions index with additional exports
- Support for local development and testing
- Implement request-logger for HTTP request logging
- Add comprehensive logger module
- Enhance middleware index with new exports
- Improve auto-rest with better CRUD operations
- Enhance migration system with improved schema handling
- Update migration tests
- Enhance branching module with improved storage handling
- Add configuration schema updates
- Improve postgres provider implementations
- Enhance core index with additional exports
- Add new realtime module for real-time functionality
- Improve client realtime module with better event handling
- Add support for real-time subscriptions
- Enhance connection management
- Enhance test project with improved realtime support
- Add webhooks route
- Add hello function example
- Update routes configuration
- Add tests for function commands
- Add graphql type map tests
- Add storage command tests
- Add tests for auto-rest functions
- Add chain code maps tests
- Add functions runtime tests
- Add image transformer tests
- Add logger functions tests
- Add middleware functions tests
- Add realtime channel manager tests
- Add vector search tests
- Add webhook functions tests
- Add new-features-docs with updated documentation
- Remove deprecated issues.md and core task issues 2.md
- Update bun.lock with new dependency versions
- Update packages/core/package.json with new dependencies
@coderabbitai

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

- Fix postgres import in rls-test.ts
- Fix quote formatting in client realtime.ts
- Fix Handler type in local-runtime.ts
- Fix WebhookDbClient adapter in test-project
- Simplify webhook routes to avoid type errors
- Add test summary script with pass/fail/skip counts
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (6)
scripts/test-summary.ts (1)

1-58: Duplicated test summary logic with package.json test script.

This script duplicates the test summary parsing logic found in the root package.json test script (same regex patterns, same output format). Consider either:

  1. Using this script from package.json: "test": "bun scripts/test-summary.ts"
  2. Removing this script if the inline shell approach in package.json is preferred
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-summary.ts` around lines 1 - 58, This file duplicates the
test-summary parsing/printing logic (the Bun spawn and regex parsing using proc,
fullOutput, passMatch/failMatch/testsMatch and the final exit handling), so
either remove this standalone script and call the same logic from package.json
(e.g. replace the inline shell summary with "bun scripts/test-summary.ts") or
consolidate into a single shared module used by both locations; update
package.json to point to the script if keeping it, or delete the script and
remove the duplicate regex/summary code from package.json if preferring the
inline approach, ensuring proc exit handling and regex patterns (/\d+ pass/g,
/\d+ fail/g, /Ran (\d+) tests?/g) remain consistent.
apps/test-project/src/routes/webhooks.ts (1)

21-25: Unused variable deliveryId.

The deliveryId parameter is extracted but never used before returning the 404 response.

🔧 Suggested fix
 webhooksRoute.get("/deliveries/:deliveryId", async (c) => {
-	const deliveryId = c.req.param("deliveryId");
+	const _deliveryId = c.req.param("deliveryId");
 
 	return c.json({ error: "Delivery not found" }, 404);
 });

Alternatively, remove the extraction entirely until the implementation is ready, or prefix with underscore to indicate intentionally unused.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/test-project/src/routes/webhooks.ts` around lines 21 - 25, The route
handler webhooksRoute.get("/deliveries/:deliveryId", async (c) => { ... })
extracts deliveryId but never uses it; either remove the unused extraction or
rename it to _deliveryId to indicate intentional non‑use, or implement the
lookup using deliveryId (e.g., fetch delivery by id and return it or 404) and
then return the appropriate c.json response — update the handler body in the
webhooksRoute.get callback accordingly.
apps/test-project/src/index.ts (2)

14-19: Stub adapter silently discards webhook delivery logs.

The dbAdapter always returns empty rows without actually persisting anything. While this is intentional for the stub phase, consider adding a TODO or logging when deliveries would be logged.

💡 Suggestion for visibility
 // Create an adapter to make drizzle SQLite compatible with WebhookDbClient interface
+// TODO: Implement actual persistence once migration is applied
 const dbAdapter: WebhookDbClient = {
 	async execute(_args: { sql: string; args: unknown[] }) {
+		// Silently drop webhook delivery logs until migration is applied
 		return { rows: [] };
 	},
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/test-project/src/index.ts` around lines 14 - 19, The stub dbAdapter
(object named dbAdapter implementing WebhookDbClient) currently returns empty
rows and silently discards webhook delivery logs; update the execute method to
at minimum log a clear warning (including the passed _args.sql/_args.args or a
summary) and add a TODO comment indicating persistence should be implemented,
while preserving the method signature and return shape ({ rows: [] }) so callers
continue to work; reference dbAdapter.execute and WebhookDbClient when making
the change.

116-118: Type assertion on process.env may propagate undefined values.

process.env has type NodeJS.ProcessEnv where values are string | undefined. Casting to Record<string, string> masks the potential for undefined values, which could cause issues in functions expecting all env vars to be defined.

♻️ Safer approach
-			const functionsRuntime = await initializeFunctionsRuntime(
-				".",
-				process.env as Record<string, string>,
-			);
+			// Filter to only defined env vars
+			const envVars = Object.fromEntries(
+				Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined)
+			);
+			const functionsRuntime = await initializeFunctionsRuntime(".", envVars);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/test-project/src/index.ts` around lines 116 - 118, The call is
force-casting process.env to Record<string,string>, which hides undefined
values; update the call site around initializeFunctionsRuntime and process.env
to pass a correctly-typed object (either change initializeFunctionsRuntime to
accept Record<string,string|undefined> or construct a sanitized env object by
iterating process.env and including only keys with defined string values or
supplying explicit defaults or validation that throws on missing values) so
callers and callee no longer rely on an unsafe type assertion.
packages/core/src/functions/local-runtime.ts (2)

80-113: Consider validating function name at load boundary as defense-in-depth.

While the middleware should validate input, loadFunction directly constructs filesystem paths from name. Adding validation here provides defense-in-depth against internal misuse.

🛡️ Suggested addition
 async loadFunction(name: string): Promise<LoadedFunction> {
+	if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
+		throw new Error(`Invalid function name: ${name}`);
+	}
 	const functionPath = path.join(this.functionsDir, name, "index.ts");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/functions/local-runtime.ts` around lines 80 - 113, Validate
the incoming name inside loadFunction before constructing functionPath: reject
names containing path separators or traversal patterns (e.g., '/', '\', '..')
and enforce a safe whitelist regex such as /^[A-Za-z0-9_-]+$/; if validation
fails, throw a clear Error (e.g., "Invalid function name") so functionsDir +
name cannot be abused, and keep the rest of loadFunction (statSync, import,
caching) unchanged.

171-182: File watcher callback should handle edge cases.

The watcher callback splits on path.sep but doesn't account for:

  1. Files directly in functionsDir (not in subdirectories)
  2. Platform differences where filename may use different separators
♻️ More robust extraction
 		this.watcher = watch(this.functionsDir, { recursive: true }, (eventType, filename) => {
 			if (filename && filename.endsWith(".ts")) {
-				// Extract function name from path (first segment)
-				const parts = filename.split(path.sep);
-				const functionName = parts[0];
+				// Extract function name from path (first directory segment)
+				// Normalize to handle mixed separators on Windows
+				const normalized = filename.replace(/\\/g, "/");
+				const parts = normalized.split("/");
+				const functionName = parts.length > 1 ? parts[0] : null;
 
-				if (functionName && functionName !== "functions") {
+				if (functionName) {
 					logger.info({ msg: `File changed, invalidating cache`, file: filename });
 					this.functions.delete(functionName);
 				}
 			}
 		});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/functions/local-runtime.ts` around lines 171 - 182, The
watcher callback can mis-extract the function name for files at the functionsDir
root and on platforms with different path separators; update the callback in the
watch handler to (1) normalize the incoming filename separators (e.g.
filename.replace(/[/\\]/g, path.sep)), (2) compute the path relative to
this.functionsDir using path.relative(this.functionsDir,
path.resolve(this.functionsDir, normalizedFilename)) to ensure we get a
consistent relative path, (3) split that relative path on path.sep and pick the
first non-empty segment (fall back to path.basename if necessary) as the
functionName, and (4) keep the existing guard (functionName && functionName !==
"functions") before calling this.functions.delete(functionName) so root-level
files and cross-platform separators are handled safely.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/test-project/src/index.ts`:
- Line 9: Remove the unused import of db from the top of the file: locate the
import statement "import { db } from \"./db\";" and delete it (or remove db from
the import list if other imports are present) so the module no longer imports an
unused symbol named db.

In `@package.json`:
- Line 46: Update the "test" npm script in package.json (the "test" script
string) to avoid GNU-specific grep -P usage: replace all grep -oP '\\d+'
patterns with grep -oE '[0-9]+' and change instances like '\\d+ pass' to '[0-9]+
pass', '\\d+ fail' to '[0-9]+ fail', '\\d+ skip' to '[0-9]+ skip', and 'Ran \\d+
tests?' to 'Ran [0-9]+ tests?' so macOS/BSD grep (using -oE) works
cross-platform.
- Line 46: The "test" npm script in package.json currently chains commands with
semicolons so test failures are lost; update the "test" script to capture the
exit code from the test runner (the bunx turbo run test invocation) into a
variable (or use &&/|| logic) before running the summary commands and then exit
with that captured code so CI sees failures, and also replace GNU-only grep -oP
usages with POSIX-friendly grep -oE in the summary pipeline to improve
portability; locate the "test" script string in package.json to implement these
changes.

In `@packages/client/src/realtime.ts`:
- Line 342: The JSDoc closing token is on the same line as the method
declaration for off(eventType: string, callback: (data: unknown) => void): void;
move the closing comment delimiter (*/) to its own line immediately above the
method signature to separate the JSDoc block from the function declaration and
restore consistent formatting in the off method in
packages/client/src/realtime.ts.
- Around line 306-322: The onPresence and onBroadcast methods currently register
anonymous listeners with this.on and never remove them; change each to create a
named handler (e.g., presenceHandler / broadcastHandler) that casts the incoming
data to PresenceEvent/BroadcastEvent, register it via this.on("presence",
presenceHandler) / this.on("broadcast", broadcastHandler), and return an
unsubscribe function that calls this.off("presence", presenceHandler) /
this.off("broadcast", broadcastHandler). Ensure the handler still checks
event.channel === channelName before invoking the callback so behavior is
unchanged.
- Around line 271-283: The channel subscription code can drop subscribe messages
because channel().subscribe() calls connect() then immediately this.send(),
which will be ignored if the socket is still CONNECTING; fix by tracking channel
subscriptions and re-sending them on reconnect like table subscriptions. Modify
channel(channelName).subscribe / unsubscribe to add/remove channelName (and
options if needed) to a tracked map or Set (e.g., trackedChannelSubscriptions)
and call this.sendSubscribeAll-style logic from the socket onopen handler to
iterate and re-send all tracked channel subscribe messages; also ensure
immediate subscribe attempts send if socket.readyState === WebSocket.OPEN,
otherwise rely on the tracked set so the onopen replay will deliver the
subscribe. Ensure unsubscribe removes from the tracked set and sends an
unsubscribe if socket is open.

In `@packages/core/src/functions/local-runtime.ts`:
- Around line 259-262: The function initializeFunctionsRuntime currently
defaults envVars to process.env which can leak secrets; change the signature and
callers so envVars is required (remove the default) or accept an explicit
allowlist parameter and filter process.env before use; update references to
initializeFunctionsRuntime and any call sites to pass a controlled
Record<string,string> (or an allowlist array) and implement the filtering logic
inside initializeFunctionsRuntime (or a helper like filterEnvForFunctions) to
only include allowed keys.
- Around line 147-158: The catch block currently returns error.message to
clients which can leak internals; update the catch in localRuntime (around
func.handler invocation) to keep logger.error({ msg: ..., function: name, error
}) for full diagnostics but replace the response body with a generic
production-safe message (e.g. "Internal Server Error" or "An internal error
occurred") and avoid including error.message or stack; optionally gate returning
the actual error text behind a debug/dev flag (NODE_ENV !== 'production') if you
need verboseness in development.

In `@scripts/test-summary.ts`:
- Around line 55-58: The closing separator console.log call is skipped when
tests fail because process.exit(1) is called before it; modify the control flow
around totalFail and proc.exitCode so the separator is printed regardless of
failure — e.g., call console.log("━━━━━━━━...") before invoking process.exit(1)
or ensure a single exit path prints the separator then exits; update the logic
that checks totalFail and proc.exitCode (the block that currently calls
process.exit) so console.log always runs prior to process.exit.
- Around line 3-11: The current code awaits proc.exited before consuming
proc.stdout/proc.stderr, which can deadlock if the child fills the pipe buffer;
change to consume streams while the process runs (or set Bun.spawn(..., { stdio:
"inherit" }) if you don't need to parse output). Specifically, when calling
Bun.spawn(...) (symbol: proc) either 1) start streaming proc.stdout and
proc.stderr to process.stdout/process.stderr using async iteration or stream
piping immediately after spawn and only await proc.exited after those consumers
are started, or 2) pass the option stdio: "inherit" to Bun.spawn to forward
output directly; ensure any parsing logic adapts if you choose inherit.

---

Nitpick comments:
In `@apps/test-project/src/index.ts`:
- Around line 14-19: The stub dbAdapter (object named dbAdapter implementing
WebhookDbClient) currently returns empty rows and silently discards webhook
delivery logs; update the execute method to at minimum log a clear warning
(including the passed _args.sql/_args.args or a summary) and add a TODO comment
indicating persistence should be implemented, while preserving the method
signature and return shape ({ rows: [] }) so callers continue to work; reference
dbAdapter.execute and WebhookDbClient when making the change.
- Around line 116-118: The call is force-casting process.env to
Record<string,string>, which hides undefined values; update the call site around
initializeFunctionsRuntime and process.env to pass a correctly-typed object
(either change initializeFunctionsRuntime to accept
Record<string,string|undefined> or construct a sanitized env object by iterating
process.env and including only keys with defined string values or supplying
explicit defaults or validation that throws on missing values) so callers and
callee no longer rely on an unsafe type assertion.

In `@apps/test-project/src/routes/webhooks.ts`:
- Around line 21-25: The route handler
webhooksRoute.get("/deliveries/:deliveryId", async (c) => { ... }) extracts
deliveryId but never uses it; either remove the unused extraction or rename it
to _deliveryId to indicate intentional non‑use, or implement the lookup using
deliveryId (e.g., fetch delivery by id and return it or 404) and then return the
appropriate c.json response — update the handler body in the webhooksRoute.get
callback accordingly.

In `@packages/core/src/functions/local-runtime.ts`:
- Around line 80-113: Validate the incoming name inside loadFunction before
constructing functionPath: reject names containing path separators or traversal
patterns (e.g., '/', '\', '..') and enforce a safe whitelist regex such as
/^[A-Za-z0-9_-]+$/; if validation fails, throw a clear Error (e.g., "Invalid
function name") so functionsDir + name cannot be abused, and keep the rest of
loadFunction (statSync, import, caching) unchanged.
- Around line 171-182: The watcher callback can mis-extract the function name
for files at the functionsDir root and on platforms with different path
separators; update the callback in the watch handler to (1) normalize the
incoming filename separators (e.g. filename.replace(/[/\\]/g, path.sep)), (2)
compute the path relative to this.functionsDir using
path.relative(this.functionsDir, path.resolve(this.functionsDir,
normalizedFilename)) to ensure we get a consistent relative path, (3) split that
relative path on path.sep and pick the first non-empty segment (fall back to
path.basename if necessary) as the functionName, and (4) keep the existing guard
(functionName && functionName !== "functions") before calling
this.functions.delete(functionName) so root-level files and cross-platform
separators are handled safely.

In `@scripts/test-summary.ts`:
- Around line 1-58: This file duplicates the test-summary parsing/printing logic
(the Bun spawn and regex parsing using proc, fullOutput,
passMatch/failMatch/testsMatch and the final exit handling), so either remove
this standalone script and call the same logic from package.json (e.g. replace
the inline shell summary with "bun scripts/test-summary.ts") or consolidate into
a single shared module used by both locations; update package.json to point to
the script if keeping it, or delete the script and remove the duplicate
regex/summary code from package.json if preferring the inline approach, ensuring
proc exit handling and regex patterns (/\d+ pass/g, /\d+ fail/g, /Ran (\d+)
tests?/g) remain consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 150cca6a-2035-4419-8353-7397815df3b8

📥 Commits

Reviewing files that changed from the base of the PR and between ed98b12 and 55c6724.

📒 Files selected for processing (7)
  • apps/test-project/src/index.ts
  • apps/test-project/src/routes/webhooks.ts
  • package.json
  • packages/cli/src/commands/rls-test.ts
  • packages/client/src/realtime.ts
  • packages/core/src/functions/local-runtime.ts
  • scripts/test-summary.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/cli/src/commands/rls-test.ts

import { upgradeWebSocket, websocket } from "hono/bun";
import config from "../betterbase.config";
import { auth } from "./auth";
import { db } from "./db";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if db is actually used in this file
rg -n '\bdb\b' apps/test-project/src/index.ts | grep -v "import.*db" | grep -v "dbAdapter" | grep -v "dbEventEmitter"

Repository: weroperking/Betterbase

Length of output: 48


🏁 Script executed:

cat -n apps/test-project/src/index.ts

Repository: weroperking/Betterbase

Length of output: 5788


Remove the unused db import.

The db import on line 9 is not used anywhere in this file and should be removed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/test-project/src/index.ts` at line 9, Remove the unused import of db
from the top of the file: locate the import statement "import { db } from
\"./db\";" and delete it (or remove db from the import list if other imports are
present) so the module no longer imports an unused symbol named db.

"files": [".", "!node_modules", "!.git"],
"scripts": {
"test": "bunx turbo run test",
"test": "bunx turbo run test 2>&1 | tee /tmp/test.log; echo ''; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo '📋 TEST SUMMARY'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; grep -oP '\\d+ pass' /tmp/test.log | awk '{sum+=$1} END {print \"✅ Passed: \" sum}'; grep -oP '\\d+ fail' /tmp/test.log | awk '{sum+=$1} END {print \"❌ Failed: \" sum}'; grep -oP '\\d+ skip' /tmp/test.log | awk '{sum+=$1} END {if (sum>0) print \"⏭️ Skipped: \" sum}'; grep -oP 'Ran \\d+ tests?' /tmp/test.log | grep -oP '\\d+' | awk '{sum+=$1} END {print \"📝 Total Tests: \" sum}'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

grep -oP is not portable to macOS.

The -P flag (Perl regex) is GNU grep-specific and unavailable on macOS's BSD grep. Developers on macOS will see grep: invalid option -- P. Use -oE (extended regex) with [0-9]+ instead of \d+ for cross-platform compatibility.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 46, Update the "test" npm script in package.json (the
"test" script string) to avoid GNU-specific grep -P usage: replace all grep -oP
'\\d+' patterns with grep -oE '[0-9]+' and change instances like '\\d+ pass' to
'[0-9]+ pass', '\\d+ fail' to '[0-9]+ fail', '\\d+ skip' to '[0-9]+ skip', and
'Ran \\d+ tests?' to 'Ran [0-9]+ tests?' so macOS/BSD grep (using -oE) works
cross-platform.

⚠️ Potential issue | 🔴 Critical

Test failures are not propagated to the exit code.

The script uses semicolons to chain commands, so the exit code is always from the last echo command (0), regardless of whether tests passed. This means CI pipelines won't detect test failures.

🔧 Proposed fix: capture and propagate exit code
-"test": "bunx turbo run test 2>&1 | tee /tmp/test.log; echo ''; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo '📋 TEST SUMMARY'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; grep -oP '\\d+ pass' /tmp/test.log | awk '{sum+=$1} END {print \"✅ Passed: \" sum}'; grep -oP '\\d+ fail' /tmp/test.log | awk '{sum+=$1} END {print \"❌ Failed: \" sum}'; grep -oP '\\d+ skip' /tmp/test.log | awk '{sum+=$1} END {if (sum>0) print \"⏭️  Skipped: \" sum}'; grep -oP 'Ran \\d+ tests?' /tmp/test.log | grep -oP '\\d+' | awk '{sum+=$1} END {print \"📝 Total Tests: \" sum}'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'",
+"test": "bunx turbo run test 2>&1 | tee /tmp/test.log; EXIT_CODE=${PIPESTATUS[0]}; echo ''; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo '📋 TEST SUMMARY'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; grep -oE '[0-9]+ pass' /tmp/test.log | awk '{sum+=$1} END {print \"✅ Passed: \" sum}'; grep -oE '[0-9]+ fail' /tmp/test.log | awk '{sum+=$1} END {print \"❌ Failed: \" sum}'; grep -oE '[0-9]+ skip' /tmp/test.log | awk '{sum+=$1} END {if (sum>0) print \"⏭️  Skipped: \" sum}'; grep -oE 'Ran [0-9]+ tests?' /tmp/test.log | grep -oE '[0-9]+' | awk '{sum+=$1} END {print \"📝 Total Tests: \" sum}'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; exit $EXIT_CODE",

Note: This also switches from grep -oP (GNU-only) to grep -oE (POSIX extended regex) for better portability.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"test": "bunx turbo run test 2>&1 | tee /tmp/test.log; echo ''; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo '📋 TEST SUMMARY'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; grep -oP '\\d+ pass' /tmp/test.log | awk '{sum+=$1} END {print \"✅ Passed: \" sum}'; grep -oP '\\d+ fail' /tmp/test.log | awk '{sum+=$1} END {print \"❌ Failed: \" sum}'; grep -oP '\\d+ skip' /tmp/test.log | awk '{sum+=$1} END {if (sum>0) print \"⏭️ Skipped: \" sum}'; grep -oP 'Ran \\d+ tests?' /tmp/test.log | grep -oP '\\d+' | awk '{sum+=$1} END {print \"📝 Total Tests: \" sum}'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'",
"test": "bunx turbo run test 2>&1 | tee /tmp/test.log; EXIT_CODE=${PIPESTATUS[0]}; echo ''; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo '📋 TEST SUMMARY'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; grep -oE '[0-9]+ pass' /tmp/test.log | awk '{sum+=$1} END {print \"✅ Passed: \" sum}'; grep -oE '[0-9]+ fail' /tmp/test.log | awk '{sum+=$1} END {print \"❌ Failed: \" sum}'; grep -oE '[0-9]+ skip' /tmp/test.log | awk '{sum+=$1} END {if (sum>0) print \"⏭️ Skipped: \" sum}'; grep -oE 'Ran [0-9]+ tests?' /tmp/test.log | grep -oE '[0-9]+' | awk '{sum+=$1} END {print \"📝 Total Tests: \" sum}'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; exit $EXIT_CODE",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 46, The "test" npm script in package.json currently
chains commands with semicolons so test failures are lost; update the "test"
script to capture the exit code from the test runner (the bunx turbo run test
invocation) into a variable (or use &&/|| logic) before running the summary
commands and then exit with that captured code so CI sees failures, and also
replace GNU-only grep -oP usages with POSIX-friendly grep -oE in the summary
pipeline to improve portability; locate the "test" script string in package.json
to implement these changes.

Comment on lines +271 to +283
channel(channelName: string) {
// Ensure connection is established
if (!this.disabled) {
this.connect();
}

return {
subscribe: (options?: ChannelSubscribeOptions) => {
this.send({
type: "subscribe",
channel: channelName,
payload: options,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Race condition: subscribe message may be silently dropped.

When channel().subscribe() is called, connect() initiates an async WebSocket connection. The immediate send() call on line 279 checks readyState === WebSocket.OPEN, but the socket is likely still CONNECTING. The subscribe message is silently dropped.

Unlike table subscriptions which are re-sent via sendSubscribeAll() in the onopen handler, channel subscriptions have no recovery mechanism. Users will believe they're subscribed but won't receive messages.

Consider tracking channel subscriptions and re-sending them on reconnect, similar to how table subscriptions work.

Proposed approach
 export class RealtimeClient {
 	private ws: WebSocket | null = null;
 	private subscriptions = new Map<string, Map<string, SubscriberEntry>>();
+	private channelSubscriptions = new Map<string, ChannelSubscribeOptions | undefined>();
 	private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;

Then in onopen, after re-sending table subscriptions:

 		for (const table of this.subscriptions.keys()) {
 			this.sendSubscribeAll(table);
 		}
+
+		for (const [channelName, options] of this.channelSubscriptions) {
+			this.send({ type: "subscribe", channel: channelName, payload: options });
+		}

And track/untrack in subscribe/unsubscribe:

 		subscribe: (options?: ChannelSubscribeOptions) => {
+			this.channelSubscriptions.set(channelName, options);
 			this.send({
 				type: "subscribe",
 				channel: channelName,
 				payload: options,
 			});
 				unsubscribe: () => {
+					this.channelSubscriptions.delete(channelName);
 					this.send({ type: "unsubscribe", channel: channelName });
 				},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/client/src/realtime.ts` around lines 271 - 283, The channel
subscription code can drop subscribe messages because channel().subscribe()
calls connect() then immediately this.send(), which will be ignored if the
socket is still CONNECTING; fix by tracking channel subscriptions and re-sending
them on reconnect like table subscriptions. Modify
channel(channelName).subscribe / unsubscribe to add/remove channelName (and
options if needed) to a tracked map or Set (e.g., trackedChannelSubscriptions)
and call this.sendSubscribeAll-style logic from the socket onopen handler to
iterate and re-send all tracked channel subscribe messages; also ensure
immediate subscribe attempts send if socket.readyState === WebSocket.OPEN,
otherwise rely on the tracked set so the onopen replay will deliver the
subscribe. Ensure unsubscribe removes from the tracked set and sends an
unsubscribe if socket is open.

Comment on lines +306 to +322
onPresence: (callback: (event: PresenceEvent) => void) => {
this.on("presence", (data) => {
const event = data as PresenceEvent;
if (event.channel === channelName) {
callback(event);
}
});
},

onBroadcast: (callback: (event: string, data: unknown) => void) => {
this.on("broadcast", (data) => {
const event = data as BroadcastEvent;
if (event.channel === channelName) {
callback(event.event, event.payload);
}
});
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Memory leak: onPresence and onBroadcast handlers cannot be removed.

These methods call this.on() to register handlers but don't return a cleanup function. Handlers accumulate across the lifecycle and continue firing even after unsubscribe() is called. Additionally, calling onPresence/onBroadcast multiple times adds duplicate handlers.

Return an unsubscribe function to allow cleanup.

Proposed fix
-				onPresence: (callback: (event: PresenceEvent) => void) => {
-					this.on("presence", (data) => {
-						const event = data as PresenceEvent;
-						if (event.channel === channelName) {
-							callback(event);
-						}
-					});
-				},
+				onPresence: (callback: (event: PresenceEvent) => void) => {
+					const handler = (data: unknown) => {
+						const event = data as PresenceEvent;
+						if (event.channel === channelName) {
+							callback(event);
+						}
+					};
+					this.on("presence", handler);
+					return () => this.off("presence", handler);
+				},
 
-				onBroadcast: (callback: (event: string, data: unknown) => void) => {
-					this.on("broadcast", (data) => {
-						const event = data as BroadcastEvent;
-						if (event.channel === channelName) {
-							callback(event.event, event.payload);
-						}
-					});
-				},
+				onBroadcast: (callback: (event: string, data: unknown) => void) => {
+					const handler = (data: unknown) => {
+						const event = data as BroadcastEvent;
+						if (event.channel === channelName) {
+							callback(event.event, event.payload);
+						}
+					};
+					this.on("broadcast", handler);
+					return () => this.off("broadcast", handler);
+				},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onPresence: (callback: (event: PresenceEvent) => void) => {
this.on("presence", (data) => {
const event = data as PresenceEvent;
if (event.channel === channelName) {
callback(event);
}
});
},
onBroadcast: (callback: (event: string, data: unknown) => void) => {
this.on("broadcast", (data) => {
const event = data as BroadcastEvent;
if (event.channel === channelName) {
callback(event.event, event.payload);
}
});
},
onPresence: (callback: (event: PresenceEvent) => void) => {
const handler = (data: unknown) => {
const event = data as PresenceEvent;
if (event.channel === channelName) {
callback(event);
}
};
this.on("presence", handler);
return () => this.off("presence", handler);
},
onBroadcast: (callback: (event: string, data: unknown) => void) => {
const handler = (data: unknown) => {
const event = data as BroadcastEvent;
if (event.channel === channelName) {
callback(event.event, event.payload);
}
};
this.on("broadcast", handler);
return () => this.off("broadcast", handler);
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/client/src/realtime.ts` around lines 306 - 322, The onPresence and
onBroadcast methods currently register anonymous listeners with this.on and
never remove them; change each to create a named handler (e.g., presenceHandler
/ broadcastHandler) that casts the incoming data to
PresenceEvent/BroadcastEvent, register it via this.on("presence",
presenceHandler) / this.on("broadcast", broadcastHandler), and return an
unsubscribe function that calls this.off("presence", presenceHandler) /
this.off("broadcast", broadcastHandler). Ensure the handler still checks
event.channel === channelName before invoking the callback so behavior is
unchanged.


/**
* Remove an event handler
*/ off(eventType: string, callback: (data: unknown) => void): void {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Formatting: JSDoc closing and function signature on same line.

The closing */ and function declaration should be on separate lines for readability and consistency.

Proposed fix
-	 */ off(eventType: string, callback: (data: unknown) => void): void {
+	 */
+	off(eventType: string, callback: (data: unknown) => void): void {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
*/ off(eventType: string, callback: (data: unknown) => void): void {
*/
off(eventType: string, callback: (data: unknown) => void): void {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/client/src/realtime.ts` at line 342, The JSDoc closing token is on
the same line as the method declaration for off(eventType: string, callback:
(data: unknown) => void): void; move the closing comment delimiter (*/) to its
own line immediately above the method signature to separate the JSDoc block from
the function declaration and restore consistent formatting in the off method in
packages/client/src/realtime.ts.

Comment on lines +147 to +158
try {
return await func.handler(ctx);
} catch (error) {
logger.error({ msg: `Function execution error`, function: name, error });
return new Response(
JSON.stringify({
error: "Internal Server Error",
message: error instanceof Error ? error.message : String(error),
}),
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handler errors expose internal message to clients.

The error response includes error.message which may leak internal details (stack traces, file paths, etc.) in the response body.

🔧 Suggested fix for production safety
 		} catch (error) {
 			logger.error({ msg: `Function execution error`, function: name, error });
+			const isDev = process.env.NODE_ENV === "development";
 			return new Response(
 				JSON.stringify({
 					error: "Internal Server Error",
-					message: error instanceof Error ? error.message : String(error),
+					...(isDev && { message: error instanceof Error ? error.message : String(error) }),
 				}),
 				{ status: 500, headers: { "Content-Type": "application/json" } },
 			);
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
return await func.handler(ctx);
} catch (error) {
logger.error({ msg: `Function execution error`, function: name, error });
return new Response(
JSON.stringify({
error: "Internal Server Error",
message: error instanceof Error ? error.message : String(error),
}),
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
try {
return await func.handler(ctx);
} catch (error) {
logger.error({ msg: `Function execution error`, function: name, error });
const isDev = process.env.NODE_ENV === "development";
return new Response(
JSON.stringify({
error: "Internal Server Error",
...(isDev && { message: error instanceof Error ? error.message : String(error) }),
}),
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/functions/local-runtime.ts` around lines 147 - 158, The
catch block currently returns error.message to clients which can leak internals;
update the catch in localRuntime (around func.handler invocation) to keep
logger.error({ msg: ..., function: name, error }) for full diagnostics but
replace the response body with a generic production-safe message (e.g. "Internal
Server Error" or "An internal error occurred") and avoid including error.message
or stack; optionally gate returning the actual error text behind a debug/dev
flag (NODE_ENV !== 'production') if you need verboseness in development.

Comment on lines +259 to +262
export async function initializeFunctionsRuntime(
projectRoot: string,
envVars: Record<string, string> = process.env as Record<string, string>,
): Promise<LocalFunctionsRuntime | null> {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Default envVars exposes entire process.env to functions.

Defaulting to process.env may inadvertently pass sensitive environment variables (secrets, API keys, internal configs) to user-defined functions.

🔧 Suggested fix - require explicit env allowlist
 export async function initializeFunctionsRuntime(
 	projectRoot: string,
-	envVars: Record<string, string> = process.env as Record<string, string>,
+	envVars: Record<string, string> = {},
 ): Promise<LocalFunctionsRuntime | null> {

Consider requiring callers to explicitly pass a filtered subset of environment variables, or implement an allowlist pattern.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function initializeFunctionsRuntime(
projectRoot: string,
envVars: Record<string, string> = process.env as Record<string, string>,
): Promise<LocalFunctionsRuntime | null> {
export async function initializeFunctionsRuntime(
projectRoot: string,
envVars: Record<string, string> = {},
): Promise<LocalFunctionsRuntime | null> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/functions/local-runtime.ts` around lines 259 - 262, The
function initializeFunctionsRuntime currently defaults envVars to process.env
which can leak secrets; change the signature and callers so envVars is required
(remove the default) or accept an explicit allowlist parameter and filter
process.env before use; update references to initializeFunctionsRuntime and any
call sites to pass a controlled Record<string,string> (or an allowlist array)
and implement the filtering logic inside initializeFunctionsRuntime (or a helper
like filterEnvForFunctions) to only include allowed keys.

Comment on lines +3 to +11
const proc = Bun.spawn(["bunx", "turbo", "run", "test"]);

await proc.exited;

const output = await new Response(proc.stdout).text();
const errorOutput = await new Response(proc.stderr).text();

process.stdout.write(output);
process.stderr.write(errorOutput);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential deadlock: awaiting process exit before consuming stdout/stderr.

Bun.spawn defaults to piped stdio. If the child produces more output than the pipe buffer (typically 64KB), it will block waiting for the parent to read. Since the parent awaits proc.exited before reading, this creates a deadlock when output is large.

Either stream output while the process runs, or use stdio: "inherit" to forward directly (though you'd lose the ability to parse output).

🔧 Proposed fix: stream output incrementally
-const proc = Bun.spawn(["bunx", "turbo", "run", "test"]);
+const proc = Bun.spawn(["bunx", "turbo", "run", "test"], {
+  stdout: "pipe",
+  stderr: "pipe",
+});
 
-await proc.exited;
+// Read streams concurrently while process runs to avoid deadlock
+const [output, errorOutput] = await Promise.all([
+  new Response(proc.stdout).text(),
+  new Response(proc.stderr).text(),
+]);
 
-const output = await new Response(proc.stdout).text();
-const errorOutput = await new Response(proc.stderr).text();
+await proc.exited;
 
 process.stdout.write(output);
 process.stderr.write(errorOutput);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const proc = Bun.spawn(["bunx", "turbo", "run", "test"]);
await proc.exited;
const output = await new Response(proc.stdout).text();
const errorOutput = await new Response(proc.stderr).text();
process.stdout.write(output);
process.stderr.write(errorOutput);
const proc = Bun.spawn(["bunx", "turbo", "run", "test"], {
stdout: "pipe",
stderr: "pipe",
});
// Read streams concurrently while process runs to avoid deadlock
const [output, errorOutput] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
]);
await proc.exited;
process.stdout.write(output);
process.stderr.write(errorOutput);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-summary.ts` around lines 3 - 11, The current code awaits
proc.exited before consuming proc.stdout/proc.stderr, which can deadlock if the
child fills the pipe buffer; change to consume streams while the process runs
(or set Bun.spawn(..., { stdio: "inherit" }) if you don't need to parse output).
Specifically, when calling Bun.spawn(...) (symbol: proc) either 1) start
streaming proc.stdout and proc.stderr to process.stdout/process.stderr using
async iteration or stream piping immediately after spawn and only await
proc.exited after those consumers are started, or 2) pass the option stdio:
"inherit" to Bun.spawn to forward output directly; ensure any parsing logic
adapts if you choose inherit.

Comment on lines +55 to +58
if (totalFail > 0 || proc.exitCode !== 0) {
process.exit(1);
}
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Closing separator not printed on failure.

When tests fail, process.exit(1) on line 56 prevents the closing separator on line 58 from printing. This results in inconsistent output formatting between success and failure cases.

🛠️ Proposed fix
+console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
+
 if (totalFail > 0 || proc.exitCode !== 0) {
 	process.exit(1);
 }
-console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (totalFail > 0 || proc.exitCode !== 0) {
process.exit(1);
}
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
if (totalFail > 0 || proc.exitCode !== 0) {
process.exit(1);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-summary.ts` around lines 55 - 58, The closing separator
console.log call is skipped when tests fail because process.exit(1) is called
before it; modify the control flow around totalFail and proc.exitCode so the
separator is printed regardless of failure — e.g., call
console.log("━━━━━━━━...") before invoking process.exit(1) or ensure a single
exit path prints the separator then exits; update the logic that checks
totalFail and proc.exitCode (the block that currently calls process.exit) so
console.log always runs prior to process.exit.

@weroperking weroperking merged commit 0f23a0f into main Mar 21, 2026
4 checks passed
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