feat(steps): cross-tier channels — a client's fetch onto its own route, queue jobs onto consumers, bus and socket events onto handlers
- resolution/tier-synthesizer.ts: http-client (literal fetch/axios/ky/got/$fetch paths, axios.create baseURL instances, template holes as :params, base-URL holes by a two-segment tail; unique match only), queue-job (BullMQ/Bull add ↔ @Process/@Processor, WorkerHost process, new Worker, queue.process), event-bus (EventEmitter2 emit ↔ @OnEvent with globs; socket emit ↔ @SubscribeMessage / socket.on both ways with tier); channel, tier, callee, registeredAt on every edge; generic transport events never pair; test and generated files never sources; registered before the emitter pass
- steps.ts: crossing() reads tier/channel before languages; an endpoint reached across a tier is a bridge box and a boundary like a screen (through=1 enters it); a channel's call is not also an effect; sites read as written; a Next 'use server' action is a crossing by its directive (when.ts directive); a function-valued constant handler (asyncHandler(...)) is a route root and borrows the file-scope calls and refs within its lines
- express.ts: app.use('/prefix', router) mounts composed onto route names in postExtract (nested, by import or require); chained router.route('/x').get(h).put(h2) extracted, across lines
- frameworks/package-deps.ts: dependencies read from workspace package.json files too (Express, React, Expo Router, NestJS detect)
- routing manifest names constant handlers; e2e/ is a test directory; explore's Flow section labels the new channels
- tests: ui-steps-cross-tier (monorepo fixture: Next client + Express/Nest API), servers test updated for the queue landing
- docs: CHANGELOG, spec §3.13 cross-tier paragraph, CLAUDE.md, callback-edge-synthesis.md, plan P3 built
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
This commit is contained in:
co-authored by
Claude Fable 5
parent
950686def4
commit
b1f40c57dd
@@ -44,6 +44,7 @@ import type {
|
||||
UnresolvedRef,
|
||||
} from '../types';
|
||||
import { stripCommentsForRegex } from '../strip-comments';
|
||||
import { dependsOn } from './package-deps';
|
||||
|
||||
// =============================================================================
|
||||
// Route files
|
||||
@@ -143,8 +144,8 @@ export function navVerb(name: string): string | null {
|
||||
/** Lines a single navigation call is allowed to span. */
|
||||
const MAX_CALL_LINES = 12;
|
||||
|
||||
/** Placeholder for an interpolated `${…}` inside a template-literal href. */
|
||||
const HOLE = '\u0000';
|
||||
/** Placeholder for an interpolated `${…}` inside a template-literal href (shared with the cross-tier synthesizer). */
|
||||
export const HOLE = '\u0000';
|
||||
|
||||
/** Index of the `)` matching the `(` at `open`, skipping string bodies; -1 if unbalanced. */
|
||||
function matchParen(s: string, open: number): number {
|
||||
@@ -573,16 +574,7 @@ export const expoRouterResolver: FrameworkResolver = {
|
||||
languages: [...ROUTE_LANGUAGES],
|
||||
|
||||
detect(context: ResolutionContext): boolean {
|
||||
const packageJson = context.readFile('package.json');
|
||||
if (packageJson) {
|
||||
try {
|
||||
const pkg = JSON.parse(packageJson);
|
||||
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
||||
if (deps['expo-router']) return true;
|
||||
} catch {
|
||||
// Not JSON — fall through to the layout check.
|
||||
}
|
||||
}
|
||||
if (dependsOn(context, 'expo-router')) return true;
|
||||
const files = context.getAllFiles();
|
||||
const hasLayout = files.some((f) => /(?:^|\/)(?:src\/)?app\/_layout\.(?:tsx|jsx|ts|js)$/.test(f));
|
||||
const hasExpoConfig = files.some((f) => /^app\.(?:json|config\.(?:js|ts))$/.test(f));
|
||||
|
||||
Reference in New Issue
Block a user