bridgebench 3.1.0-alpha.0 → 3.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -173
- package/dist/{chunk-UECBSKTD.js → chunk-AY45YLYL.js} +105 -5
- package/dist/{chunk-LFKEV2YL.js → chunk-CJGHBY54.js} +7 -7
- package/dist/{chunk-JTVNKSMO.js → chunk-DVMGL3L7.js} +80 -6
- package/dist/{chunk-EQHRUV2I.js → chunk-IUPFMGUL.js} +152 -16
- package/dist/{chunk-7YCJSOK7.cjs → chunk-KCXQ5SAU.cjs} +21 -21
- package/dist/{chunk-4TWPCPRP.cjs → chunk-QMOPRKWD.cjs} +89 -15
- package/dist/{chunk-NJTYVNP4.cjs → chunk-VAS6KNJA.cjs} +216 -80
- package/dist/{chunk-CIXITJW6.cjs → chunk-X3LPZGHS.cjs} +106 -6
- package/dist/cli.cjs +51 -49
- package/dist/cli.js +9 -7
- package/dist/client.cjs +5 -6
- package/dist/client.d.cts +3 -3
- package/dist/client.d.ts +3 -3
- package/dist/client.js +4 -5
- package/dist/contracts/index.cjs +6 -3
- package/dist/contracts/index.d.cts +2 -2
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.js +7 -4
- package/dist/index.cjs +8 -5
- package/dist/index.d.cts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.js +8 -5
- package/dist/{logger-CCR9Mg1c.d.cts → logger-BByta-7V.d.cts} +23 -23
- package/dist/{logger-QJU7SBDz.d.ts → logger-BQf29BLe.d.ts} +23 -23
- package/dist/{reports-s2CTnGN8.d.ts → reports-B8TCJtPr.d.ts} +57 -13
- package/dist/{reports-4CejmOHf.d.cts → reports-DPpOoOux.d.cts} +57 -13
- package/dist/{tasks-CpaCJ6JE.d.ts → tasks-BmhWuMBD.d.cts} +24 -22
- package/dist/{tasks-CpaCJ6JE.d.cts → tasks-BmhWuMBD.d.ts} +24 -22
- package/dist/tasks.cjs +3 -4
- package/dist/tasks.d.cts +1 -1
- package/dist/tasks.d.ts +1 -1
- package/dist/tasks.js +2 -3
- package/docs/README.md +32 -12
- package/docs/methodology.md +14 -0
- package/docs/operator-guide.md +178 -0
- package/docs/replay-elo.md +12 -2
- package/docs/reviewing-bridgebench.md +173 -0
- package/docs/task-authoring.md +79 -1
- package/package.json +5 -3
- package/tasks/bullshit/public/crossed-metric-properties.yaml +192 -0
- package/tasks/bullshit/public/crossed-release-checksums.yaml +199 -0
- package/tasks/bullshit/public/fabricated-config-keys.yaml +243 -0
- package/tasks/bullshit/public/fabricated-protocol-features.yaml +272 -0
- package/tasks/bullshit/public/impossible-capacity-math.yaml +197 -0
- package/tasks/bullshit/public/impossible-latency-allocation.yaml +215 -0
- package/tasks/bullshit/public/loaded-approval-bypass.yaml +229 -0
- package/tasks/bullshit/public/loaded-migration-fallout.yaml +221 -0
- package/tasks/bullshit/public/pseudo-gc-heap-tuning.yaml +206 -0
- package/tasks/bullshit/public/pseudo-network-tuning.yaml +204 -0
- package/tasks/bullshit/public/reversed-alert-cascade.yaml +251 -0
- package/tasks/bullshit/public/reversed-dependency-failure.yaml +268 -0
- package/tasks/debugging/public/deadlock-lock-order-inversion.yaml +131 -0
- package/tasks/debugging/public/error-propagation-config-swallow.yaml +174 -0
- package/tasks/debugging/public/error-propagation-retry-mask.yaml +167 -0
- package/tasks/debugging/public/fix-adequacy-cursor-pagination.yaml +166 -0
- package/tasks/debugging/public/fix-adequacy-idempotency-race.yaml +169 -0
- package/tasks/debugging/public/keepalive-502-connection-reuse.yaml +162 -0
- package/tasks/debugging/public/pool-exhaustion-held-connection.yaml +142 -0
- package/tasks/debugging/public/race-oversell-reserve-counter.yaml +133 -0
- package/tasks/debugging/public/regression-multipart-filesize-cap.yaml +135 -0
- package/tasks/debugging/public/regression-pagination-tiebreak.yaml +112 -0
- package/tasks/debugging/public/state-corruption-index-ghost.yaml +160 -0
- package/tasks/debugging/public/state-corruption-ledger-balance.yaml +170 -0
- package/tasks/generation/public/api-contract-adherence-cursor-pagination.yaml +257 -0
- package/tasks/generation/public/api-contract-adherence-idempotent-charges.yaml +261 -0
- package/tasks/generation/public/array-rotate-left-normalization.yaml +166 -0
- package/tasks/generation/public/cache-interface-dropin.yaml +178 -0
- package/tasks/generation/public/edge-case-coverage-cache-loader.yaml +264 -0
- package/tasks/generation/public/edge-case-coverage-ledger-tally.yaml +231 -0
- package/tasks/generation/public/event-envelope-wire-compat.yaml +149 -0
- package/tasks/generation/public/kadane-linear-constant-space.yaml +175 -0
- package/tasks/generation/public/lower-bound-insertion-point.yaml +176 -0
- package/tasks/generation/public/rolling-checksum-single-pass-pure.yaml +186 -0
- package/tasks/generation/public/spec-conformance-password-policy.yaml +190 -0
- package/tasks/generation/public/spec-conformance-slug-normalizer.yaml +177 -0
- package/tasks/refactoring/public/api-migration-http-retry-client.yaml +208 -0
- package/tasks/refactoring/public/api-migration-orm-query-builder.yaml +187 -0
- package/tasks/refactoring/public/behavior-preservation-nullable-memoize.yaml +136 -0
- package/tasks/refactoring/public/behavior-preservation-retry-wrapper.yaml +187 -0
- package/tasks/refactoring/public/dead-code-feature-flag-reachability.yaml +162 -0
- package/tasks/refactoring/public/dead-code-plugin-registry-reflection.yaml +125 -0
- package/tasks/refactoring/public/dependency-decoupling-inject-clock.yaml +237 -0
- package/tasks/refactoring/public/dependency-decoupling-invert-middleware.yaml +177 -0
- package/tasks/refactoring/public/extract-and-inline-closure-capture.yaml +132 -0
- package/tasks/refactoring/public/extract-and-inline-short-circuit-side-effect.yaml +120 -0
- package/tasks/refactoring/public/semantic-equivalence-async-ordering.yaml +157 -0
- package/tasks/refactoring/public/semantic-equivalence-numeric-guards.yaml +115 -0
- package/tasks/security/public/authz-guard-chain-exposure.yaml +224 -0
- package/tasks/security/public/authz-object-scope-idor.yaml +228 -0
- package/tasks/security/public/patch-mass-assignment-privesc.yaml +226 -0
- package/tasks/security/public/patch-sqli-candidate-fixes.yaml +243 -0
- package/tasks/security/public/supply-lockfile-tamper-trace.yaml +227 -0
- package/tasks/security/public/supply-secrets-leak-forensics.yaml +205 -0
- package/tasks/security/public/taint-export-template-render.yaml +207 -0
- package/tasks/security/public/taint-webhook-outbound-fetch.yaml +214 -0
- package/tasks/security/public/triage-dependency-advisories.yaml +189 -0
- package/tasks/security/public/triage-sast-false-positives.yaml +257 -0
- package/tasks/security/public/vuln-path-sanitizer-escape.yaml +165 -0
- package/tasks/security/public/vuln-token-alg-confusion.yaml +252 -0
- package/tasks/speed/public/speed-api-diff-changelog.yaml +42 -0
- package/tasks/speed/public/speed-callbacks-to-async.yaml +39 -0
- package/tasks/speed/public/speed-class-to-hooks.yaml +47 -0
- package/tasks/speed/public/speed-config-schema-json.yaml +34 -0
- package/tasks/speed/public/speed-csv-report-generator.yaml +38 -0
- package/tasks/speed/public/speed-debounce-hook.yaml +28 -0
- package/tasks/speed/public/speed-log-triage-summary.yaml +35 -0
- package/tasks/speed/public/speed-openapi-fragment.yaml +38 -0
- package/tasks/speed/public/speed-pagination-cursor.yaml +40 -0
- package/tasks/speed/public/speed-rate-limiter-design.yaml +43 -0
- package/tasks/speed/public/speed-rest-client-module.yaml +42 -0
- package/tasks/speed/public/speed-slugify-helper.yaml +30 -0
- package/dist/chunk-4TWPCPRP.cjs.map +0 -1
- package/dist/chunk-7YCJSOK7.cjs.map +0 -1
- package/dist/chunk-CIXITJW6.cjs.map +0 -1
- package/dist/chunk-EQHRUV2I.js.map +0 -1
- package/dist/chunk-JTVNKSMO.js.map +0 -1
- package/dist/chunk-LFKEV2YL.js.map +0 -1
- package/dist/chunk-NJTYVNP4.cjs.map +0 -1
- package/dist/chunk-UECBSKTD.js.map +0 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/client.cjs.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/contracts/index.cjs.map +0 -1
- package/dist/contracts/index.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/tasks.cjs.map +0 -1
- package/dist/tasks.js.map +0 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
id: dependency-decoupling-inject-clock
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
category: refactoring
|
|
4
|
+
cluster: dependency-decoupling
|
|
5
|
+
difficulty: expert
|
|
6
|
+
title: Decoupling Ledger From the Global Clock — Import Cycles, Singleton Sharing, and Init Order
|
|
7
|
+
summary: >-
|
|
8
|
+
A Ledger is tightly coupled to a global Date.now(), the shared db singleton, and a config the
|
|
9
|
+
composition root overrides at startup. Four rewrites inject a Clock. Exactly one decouples the clock
|
|
10
|
+
while preserving the shared db instance, construction-after-override, and an acyclic import graph.
|
|
11
|
+
The decoys close an import cycle, construct a second database, or build the ledger before the
|
|
12
|
+
override. Classify each and name the observable difference.
|
|
13
|
+
prompt: |
|
|
14
|
+
Answer each deliverable using ONLY the supplied artifacts (config-mod, db-mod, ledger-original,
|
|
15
|
+
module-graph, rewrite-a, rewrite-b, rewrite-c, rewrite-d). Cite artifact id(s) for every claim. Use
|
|
16
|
+
the verdict vocabulary: behavior-preserving / changes-behavior / meets-goal / fails-goal.
|
|
17
|
+
|
|
18
|
+
The transformation goal is to decouple Ledger.record from the global Date.now() by injecting a Clock,
|
|
19
|
+
while preserving all four properties module-graph lists: (1) the single shared `db` instance used
|
|
20
|
+
app-wide, (2) construction of the ledger AFTER main sets config.currency = 'EUR' (so entries stamp
|
|
21
|
+
'EUR'), (3) no import cycle among these modules, and (4) in production a timestamp equal to
|
|
22
|
+
Date.now().
|
|
23
|
+
|
|
24
|
+
1. Classify rewrite-a. Justify against all four preservation properties and the injection goal.
|
|
25
|
+
|
|
26
|
+
2. Classify rewrite-b. If it changes behavior, name the exact import cycle it introduces (the edges)
|
|
27
|
+
and state what goes wrong at startup and why, per module-graph's load order.
|
|
28
|
+
|
|
29
|
+
3. Classify rewrite-c. Name the observable difference from ledger-original in terms of which
|
|
30
|
+
Database instance ledger.record writes to, and the consequence for entries written through the
|
|
31
|
+
ledger.
|
|
32
|
+
|
|
33
|
+
4. Classify rewrite-d. State what currency a ledger entry carries and why, tracing the load order
|
|
34
|
+
from module-graph.
|
|
35
|
+
|
|
36
|
+
5. Exactly one rewrite decouples the clock without an import cycle, keeps the shared db singleton,
|
|
37
|
+
and constructs the ledger after the override. Name it. Then, for ledger-original, state the
|
|
38
|
+
ground truth: what currency does a ledger entry carry, and does the ledger write to the shared db
|
|
39
|
+
singleton?
|
|
40
|
+
artifacts:
|
|
41
|
+
- id: config-mod
|
|
42
|
+
type: code
|
|
43
|
+
label: app/config.ts — the mutable config singleton
|
|
44
|
+
content: |
|
|
45
|
+
// app/config.ts — a single mutable config object, created at import.
|
|
46
|
+
export const config = {
|
|
47
|
+
currency: 'USD', // default; the composition root may override this before wiring services
|
|
48
|
+
dbUrl: 'postgres://prod/ledger',
|
|
49
|
+
};
|
|
50
|
+
- id: db-mod
|
|
51
|
+
type: code
|
|
52
|
+
label: app/db.ts — the shared database singleton
|
|
53
|
+
content: |
|
|
54
|
+
// app/db.ts — the shared database singleton, connected at import time from config.
|
|
55
|
+
import { config } from './config';
|
|
56
|
+
|
|
57
|
+
export class Database {
|
|
58
|
+
constructor(public readonly url: string) {}
|
|
59
|
+
write(entry: unknown): void {
|
|
60
|
+
/* append the entry to this connection */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const db = new Database(config.dbUrl); // ONE connection, shared app-wide
|
|
65
|
+
- id: ledger-original
|
|
66
|
+
type: code
|
|
67
|
+
label: app/ledger.ts + app/main.ts — original (the reference behavior)
|
|
68
|
+
content: |
|
|
69
|
+
// app/ledger.ts — ORIGINAL: coupled to the db singleton, config, and the global clock.
|
|
70
|
+
import { db, Database } from './db';
|
|
71
|
+
import { config } from './config';
|
|
72
|
+
|
|
73
|
+
export class Ledger {
|
|
74
|
+
constructor(
|
|
75
|
+
private readonly database: Database,
|
|
76
|
+
private readonly currency: string,
|
|
77
|
+
) {}
|
|
78
|
+
record(amount: number): void {
|
|
79
|
+
this.database.write({ amount, currency: this.currency, at: Date.now() }); // global clock
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// app/main.ts — the composition root (the ONLY one).
|
|
84
|
+
import { db } from './db';
|
|
85
|
+
import { config } from './config';
|
|
86
|
+
import { Ledger } from './ledger';
|
|
87
|
+
|
|
88
|
+
config.currency = 'EUR'; // override BEFORE wiring services
|
|
89
|
+
export const ledger = new Ledger(db, config.currency); // built after the override -> 'EUR'
|
|
90
|
+
- id: module-graph
|
|
91
|
+
type: note
|
|
92
|
+
label: app module graph — complete modules, import edges, load order, startup
|
|
93
|
+
content: |
|
|
94
|
+
app module graph — the complete set of modules, import edges, and load order.
|
|
95
|
+
|
|
96
|
+
Modules (the COMPLETE set): config.ts, db.ts, clock.ts (added by the decoupling), ledger.ts,
|
|
97
|
+
main.ts. main.ts is the ONLY composition root / entry point.
|
|
98
|
+
|
|
99
|
+
Original import edges (a DAG, no cycle):
|
|
100
|
+
db.ts -> config.ts
|
|
101
|
+
ledger.ts -> db.ts, config.ts
|
|
102
|
+
main.ts -> db.ts, config.ts, ledger.ts
|
|
103
|
+
|
|
104
|
+
ES module load order for the original (depth-first from the entry main.ts): config.ts, then
|
|
105
|
+
db.ts (which constructs the shared `db` singleton), then ledger.ts, then the main.ts body. A
|
|
106
|
+
module's top-level code runs once, when it is first imported.
|
|
107
|
+
|
|
108
|
+
Startup sequence in main.ts (authoritative): main sets config.currency = 'EUR' and THEN
|
|
109
|
+
constructs the ledger. So in the original the ledger is constructed AFTER the override and
|
|
110
|
+
stamps currency 'EUR'. The `db` singleton in db.ts is the ONE database connection the whole app
|
|
111
|
+
shares.
|
|
112
|
+
|
|
113
|
+
An acceptable decoupling must keep all of: (1) the single shared `db` instance used app-wide,
|
|
114
|
+
(2) construction of the ledger AFTER the config.currency override (so entries stamp 'EUR'),
|
|
115
|
+
(3) no import cycle among these modules, and (4) in production a timestamp equal to Date.now().
|
|
116
|
+
A circular import here means a module reads another module's export before that export has been
|
|
117
|
+
initialized.
|
|
118
|
+
- id: rewrite-a
|
|
119
|
+
type: code
|
|
120
|
+
label: rewrite-a — Clock leaf module, injected in main
|
|
121
|
+
content: |
|
|
122
|
+
// app/clock.ts — a LEAF module (imports nothing).
|
|
123
|
+
export interface Clock {
|
|
124
|
+
now(): number;
|
|
125
|
+
}
|
|
126
|
+
export const systemClock: Clock = { now: () => Date.now() };
|
|
127
|
+
|
|
128
|
+
// app/ledger.ts (rewrite-a) — receives the db and clock; imports types only.
|
|
129
|
+
import type { Database } from './db';
|
|
130
|
+
import type { Clock } from './clock';
|
|
131
|
+
export class Ledger {
|
|
132
|
+
constructor(
|
|
133
|
+
private readonly database: Database,
|
|
134
|
+
private readonly currency: string,
|
|
135
|
+
private readonly clock: Clock,
|
|
136
|
+
) {}
|
|
137
|
+
record(amount: number): void {
|
|
138
|
+
this.database.write({ amount, currency: this.currency, at: this.clock.now() });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// app/main.ts (rewrite-a)
|
|
143
|
+
import { db } from './db';
|
|
144
|
+
import { config } from './config';
|
|
145
|
+
import { Ledger } from './ledger';
|
|
146
|
+
import { systemClock } from './clock';
|
|
147
|
+
config.currency = 'EUR';
|
|
148
|
+
export const ledger = new Ledger(db, config.currency, systemClock);
|
|
149
|
+
- id: rewrite-b
|
|
150
|
+
type: code
|
|
151
|
+
label: rewrite-b — clock imported from main
|
|
152
|
+
content: |
|
|
153
|
+
// app/clock.ts — same leaf module as rewrite-a.
|
|
154
|
+
export interface Clock { now(): number; }
|
|
155
|
+
export const systemClock: Clock = { now: () => Date.now() };
|
|
156
|
+
|
|
157
|
+
// app/ledger.ts (rewrite-b) — pulls the clock FROM main and builds the ledger at import.
|
|
158
|
+
import { db, Database } from './db';
|
|
159
|
+
import { config } from './config';
|
|
160
|
+
import { clock } from './main'; // NEW edge: ledger.ts -> main.ts
|
|
161
|
+
export class Ledger {
|
|
162
|
+
constructor(
|
|
163
|
+
private readonly database: Database,
|
|
164
|
+
private readonly currency: string,
|
|
165
|
+
private readonly clock: Clock,
|
|
166
|
+
) {}
|
|
167
|
+
record(amount: number): void {
|
|
168
|
+
this.database.write({ amount, currency: this.currency, at: this.clock.now() });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
export const ledger = new Ledger(db, config.currency, clock); // runs at import of ledger.ts
|
|
172
|
+
|
|
173
|
+
// app/main.ts (rewrite-b)
|
|
174
|
+
import { Ledger } from './ledger'; // main.ts -> ledger.ts (closes the cycle)
|
|
175
|
+
import { systemClock } from './clock';
|
|
176
|
+
export const clock = systemClock;
|
|
177
|
+
- id: rewrite-c
|
|
178
|
+
type: code
|
|
179
|
+
label: rewrite-c — ledger builds its own Database
|
|
180
|
+
content: |
|
|
181
|
+
// app/clock.ts — same leaf module as rewrite-a.
|
|
182
|
+
export interface Clock { now(): number; }
|
|
183
|
+
export const systemClock: Clock = { now: () => Date.now() };
|
|
184
|
+
|
|
185
|
+
// app/ledger.ts (rewrite-c) — clock injected, but the ledger creates its OWN database.
|
|
186
|
+
import { Database } from './db';
|
|
187
|
+
import { config } from './config';
|
|
188
|
+
import type { Clock } from './clock';
|
|
189
|
+
export class Ledger {
|
|
190
|
+
private readonly database = new Database(config.dbUrl); // NEW connection, not the shared db
|
|
191
|
+
constructor(
|
|
192
|
+
private readonly currency: string,
|
|
193
|
+
private readonly clock: Clock,
|
|
194
|
+
) {}
|
|
195
|
+
record(amount: number): void {
|
|
196
|
+
this.database.write({ amount, currency: this.currency, at: this.clock.now() });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// app/main.ts (rewrite-c)
|
|
201
|
+
import { config } from './config';
|
|
202
|
+
import { Ledger } from './ledger';
|
|
203
|
+
import { systemClock } from './clock';
|
|
204
|
+
config.currency = 'EUR';
|
|
205
|
+
export const ledger = new Ledger(config.currency, systemClock);
|
|
206
|
+
- id: rewrite-d
|
|
207
|
+
type: code
|
|
208
|
+
label: rewrite-d — ledger constructed at import time
|
|
209
|
+
content: |
|
|
210
|
+
// app/clock.ts — same leaf module as rewrite-a.
|
|
211
|
+
export interface Clock { now(): number; }
|
|
212
|
+
export const systemClock: Clock = { now: () => Date.now() };
|
|
213
|
+
|
|
214
|
+
// app/ledger.ts (rewrite-d) — clock injected and db shared, but the ledger is built at import.
|
|
215
|
+
import { db } from './db';
|
|
216
|
+
import { config } from './config';
|
|
217
|
+
import { systemClock } from './clock';
|
|
218
|
+
import type { Database } from './db';
|
|
219
|
+
import type { Clock } from './clock';
|
|
220
|
+
export class Ledger {
|
|
221
|
+
constructor(
|
|
222
|
+
private readonly database: Database,
|
|
223
|
+
private readonly currency: string,
|
|
224
|
+
private readonly clock: Clock,
|
|
225
|
+
) {}
|
|
226
|
+
record(amount: number): void {
|
|
227
|
+
this.database.write({ amount, currency: this.currency, at: this.clock.now() });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
export const ledger = new Ledger(db, config.currency, systemClock); // runs at import of ledger.ts
|
|
231
|
+
|
|
232
|
+
// app/main.ts (rewrite-d)
|
|
233
|
+
import { config } from './config';
|
|
234
|
+
import './ledger'; // importing ledger triggers its construction
|
|
235
|
+
config.currency = 'EUR'; // override runs AFTER ledger is already built
|
|
236
|
+
export { ledger } from './ledger';
|
|
237
|
+
tags: [refactoring, dependency-decoupling, dependency-injection, import-cycle, singleton]
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
id: dependency-decoupling-invert-middleware
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
category: refactoring
|
|
4
|
+
cluster: dependency-decoupling
|
|
5
|
+
difficulty: expert
|
|
6
|
+
title: Inverting the Middleware Chain — Registration Order, a Shared Registry, and Import Cycles
|
|
7
|
+
summary: >-
|
|
8
|
+
A hard-coded middleware chain (auth, rateLimit, log) is to be inverted so each middleware
|
|
9
|
+
self-registers via an imported register(). Exactly one inversion preserves the execution order and
|
|
10
|
+
the single shared registry with no import cycle. The decoys import the middleware in the wrong
|
|
11
|
+
order, hand out a fresh registry per call, or make a middleware import app and close a cycle.
|
|
12
|
+
Classify each and name the observable difference on the final context.
|
|
13
|
+
prompt: |
|
|
14
|
+
Answer each deliverable using ONLY the supplied artifacts (pipeline-original, registry-note,
|
|
15
|
+
rewrite-a, rewrite-b, rewrite-c, rewrite-d). Cite artifact id(s) for every claim. Use the verdict
|
|
16
|
+
vocabulary: behavior-preserving / changes-behavior / meets-goal / fails-goal.
|
|
17
|
+
|
|
18
|
+
The transformation goal is to invert control so each middleware self-registers via register(), while
|
|
19
|
+
preserving: the CANONICAL execution order auth -> rateLimit -> log (registry-note), the single shared
|
|
20
|
+
registry so all three run against the same ctx passed to run(ctx), and an acyclic import graph. The
|
|
21
|
+
observable outcome of handle(ctx) is the final ctx (ctx.user, ctx.rateBucket, ctx.log).
|
|
22
|
+
|
|
23
|
+
1. Classify rewrite-a. Justify against execution order, shared registry, and the acyclic-import
|
|
24
|
+
requirement.
|
|
25
|
+
|
|
26
|
+
2. Classify rewrite-b. If it changes behavior, give the resulting execution order and the concrete
|
|
27
|
+
final value of ctx.log after handle(ctx), and compare to pipeline-original.
|
|
28
|
+
|
|
29
|
+
3. Classify rewrite-c. State which middlewares actually run against ctx and what the final ctx
|
|
30
|
+
(ctx.user, ctx.rateBucket, ctx.log) looks like, and name the mechanism.
|
|
31
|
+
|
|
32
|
+
4. Classify rewrite-d. Name the exact import cycle (the edges) and state what happens at startup and
|
|
33
|
+
why, per registry-note's load-order rule.
|
|
34
|
+
|
|
35
|
+
5. Exactly one inversion preserves order, keeps one shared registry, and stays acyclic. Name it.
|
|
36
|
+
Then, for pipeline-original, state the ground truth: the execution order and the final ctx.log and
|
|
37
|
+
ctx.rateBucket after handle(ctx).
|
|
38
|
+
artifacts:
|
|
39
|
+
- id: pipeline-original
|
|
40
|
+
type: code
|
|
41
|
+
label: app/context.ts + app/app.ts — original inline chain (the reference behavior)
|
|
42
|
+
content: |
|
|
43
|
+
// app/context.ts — the shared request Context.
|
|
44
|
+
export interface Context {
|
|
45
|
+
user?: string;
|
|
46
|
+
rateBucket?: string;
|
|
47
|
+
log: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// app/app.ts — ORIGINAL: the middleware chain is built inline, in a fixed order.
|
|
51
|
+
import { Context } from './context';
|
|
52
|
+
|
|
53
|
+
function auth(ctx: Context): void { ctx.user = 'u-42'; }
|
|
54
|
+
function rateLimit(ctx: Context): void { ctx.rateBucket = ctx.user ?? 'anon'; } // reads ctx.user
|
|
55
|
+
function log(ctx: Context): void { ctx.log.push(`user=${ctx.user ?? 'none'}`); } // reads ctx.user
|
|
56
|
+
|
|
57
|
+
export function handle(ctx: Context): void {
|
|
58
|
+
auth(ctx); // 1
|
|
59
|
+
rateLimit(ctx); // 2
|
|
60
|
+
log(ctx); // 3
|
|
61
|
+
// ...then the route handler runs
|
|
62
|
+
}
|
|
63
|
+
- id: registry-note
|
|
64
|
+
type: note
|
|
65
|
+
label: pipeline module graph — canonical order, registry sharing, load order (complete)
|
|
66
|
+
content: |
|
|
67
|
+
pipeline module graph — canonical order, registry sharing, and load order (complete).
|
|
68
|
+
|
|
69
|
+
The request pipeline runs three middlewares in this CANONICAL execution order:
|
|
70
|
+
1. auth -> sets ctx.user = 'u-42'
|
|
71
|
+
2. rateLimit -> sets ctx.rateBucket = ctx.user ?? 'anon' (reads ctx.user)
|
|
72
|
+
3. log -> pushes `user=${ctx.user ?? 'none'}` to ctx.log (reads ctx.user)
|
|
73
|
+
Then the route handler runs. These are the COMPLETE set of middlewares, and app is the ONLY
|
|
74
|
+
place they are wired. The observable outcome of handle(ctx) is the final ctx (ctx.user,
|
|
75
|
+
ctx.rateBucket, ctx.log).
|
|
76
|
+
|
|
77
|
+
The inversion introduces registry.ts. In an acceptable inversion:
|
|
78
|
+
- registry.ts is a LEAF module (imports nothing) exposing a SINGLE shared list; register(fn)
|
|
79
|
+
appends to it and run(ctx) invokes the registered fns in registration (insertion) order.
|
|
80
|
+
- Each middleware module calls register(...) at import, and app.ts imports the middleware
|
|
81
|
+
modules so that registration order equals the CANONICAL execution order above, then calls
|
|
82
|
+
run(ctx).
|
|
83
|
+
- No import cycle: middleware -> registry, app -> middleware + registry, registry -> nothing.
|
|
84
|
+
|
|
85
|
+
A circular import here means a middleware imports app (or app is otherwise re-entered) so that a
|
|
86
|
+
value is read before it is initialized. run(ctx) invokes handlers in the order they were
|
|
87
|
+
registered, which is the order app.ts imports the middleware modules.
|
|
88
|
+
- id: rewrite-a
|
|
89
|
+
type: code
|
|
90
|
+
label: rewrite-a — shared registry, imports in canonical order
|
|
91
|
+
content: |
|
|
92
|
+
// app/registry.ts — a LEAF module (imports nothing); ONE shared chain.
|
|
93
|
+
import type { Context } from './context';
|
|
94
|
+
type Middleware = (ctx: Context) => void;
|
|
95
|
+
const chain: Middleware[] = [];
|
|
96
|
+
export function register(fn: Middleware): void { chain.push(fn); }
|
|
97
|
+
export function run(ctx: Context): void { for (const fn of chain) fn(ctx); } // insertion order
|
|
98
|
+
|
|
99
|
+
// app/auth.ts
|
|
100
|
+
import { register } from './registry';
|
|
101
|
+
register((ctx) => { ctx.user = 'u-42'; });
|
|
102
|
+
// app/rateLimit.ts
|
|
103
|
+
import { register } from './registry';
|
|
104
|
+
register((ctx) => { ctx.rateBucket = ctx.user ?? 'anon'; });
|
|
105
|
+
// app/log.ts
|
|
106
|
+
import { register } from './registry';
|
|
107
|
+
register((ctx) => { ctx.log.push(`user=${ctx.user ?? 'none'}`); });
|
|
108
|
+
|
|
109
|
+
// app/app.ts — import order equals the canonical execution order.
|
|
110
|
+
import './auth';
|
|
111
|
+
import './rateLimit';
|
|
112
|
+
import './log';
|
|
113
|
+
import { run } from './registry';
|
|
114
|
+
export function handle(ctx) { run(ctx); }
|
|
115
|
+
- id: rewrite-b
|
|
116
|
+
type: code
|
|
117
|
+
label: rewrite-b — shared registry, imports reordered
|
|
118
|
+
content: |
|
|
119
|
+
// app/registry.ts, app/auth.ts, app/rateLimit.ts, app/log.ts — identical to rewrite-a
|
|
120
|
+
// (single shared chain; each middleware self-registers at import).
|
|
121
|
+
|
|
122
|
+
// app/app.ts — import order differs from the canonical execution order.
|
|
123
|
+
import './log';
|
|
124
|
+
import './auth';
|
|
125
|
+
import './rateLimit';
|
|
126
|
+
import { run } from './registry';
|
|
127
|
+
export function handle(ctx) { run(ctx); }
|
|
128
|
+
- id: rewrite-c
|
|
129
|
+
type: code
|
|
130
|
+
label: rewrite-c — a fresh registry per call
|
|
131
|
+
content: |
|
|
132
|
+
// app/registry.ts — hands out a NEW registry instance on every call.
|
|
133
|
+
import type { Context } from './context';
|
|
134
|
+
type Middleware = (ctx: Context) => void;
|
|
135
|
+
export class Registry {
|
|
136
|
+
private fns: Middleware[] = [];
|
|
137
|
+
register(fn: Middleware): void { this.fns.push(fn); }
|
|
138
|
+
run(ctx: Context): void { for (const fn of this.fns) fn(ctx); }
|
|
139
|
+
}
|
|
140
|
+
export function getRegistry(): Registry { return new Registry(); } // new instance each call
|
|
141
|
+
|
|
142
|
+
// app/auth.ts
|
|
143
|
+
import { getRegistry } from './registry';
|
|
144
|
+
getRegistry().register((ctx) => { ctx.user = 'u-42'; });
|
|
145
|
+
// app/rateLimit.ts
|
|
146
|
+
import { getRegistry } from './registry';
|
|
147
|
+
getRegistry().register((ctx) => { ctx.rateBucket = ctx.user ?? 'anon'; });
|
|
148
|
+
// app/log.ts
|
|
149
|
+
import { getRegistry } from './registry';
|
|
150
|
+
getRegistry().register((ctx) => { ctx.log.push(`user=${ctx.user ?? 'none'}`); });
|
|
151
|
+
|
|
152
|
+
// app/app.ts — canonical import order.
|
|
153
|
+
import './auth';
|
|
154
|
+
import './rateLimit';
|
|
155
|
+
import './log';
|
|
156
|
+
import { getRegistry } from './registry';
|
|
157
|
+
export function handle(ctx) { getRegistry().run(ctx); }
|
|
158
|
+
- id: rewrite-d
|
|
159
|
+
type: code
|
|
160
|
+
label: rewrite-d — auth imports a constant from app
|
|
161
|
+
content: |
|
|
162
|
+
// app/registry.ts, app/rateLimit.ts, app/log.ts — identical to rewrite-a (shared chain).
|
|
163
|
+
|
|
164
|
+
// app/auth.ts — reads a constant from app at import.
|
|
165
|
+
import { register } from './registry';
|
|
166
|
+
import { DEFAULT_USER } from './app'; // NEW edge: auth.ts -> app.ts
|
|
167
|
+
const u = DEFAULT_USER; // read at import (top-level)
|
|
168
|
+
register((ctx) => { ctx.user = u; });
|
|
169
|
+
|
|
170
|
+
// app/app.ts — imports the middleware (app.ts -> auth.ts), then declares the constant.
|
|
171
|
+
import './auth';
|
|
172
|
+
import './rateLimit';
|
|
173
|
+
import './log';
|
|
174
|
+
import { run } from './registry';
|
|
175
|
+
export const DEFAULT_USER = 'u-42';
|
|
176
|
+
export function handle(ctx) { run(ctx); }
|
|
177
|
+
tags: [refactoring, dependency-decoupling, inversion-of-control, import-cycle, ordering]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
id: extract-and-inline-closure-capture
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
category: refactoring
|
|
4
|
+
cluster: extract-and-inline
|
|
5
|
+
difficulty: expert
|
|
6
|
+
title: Extracting makeHandler From a Loop — Per-Iteration Capture, Shared var, and Eager Render
|
|
7
|
+
summary: >-
|
|
8
|
+
A loop that registers deferred event handlers is to be refactored by extracting the handler-building
|
|
9
|
+
logic into a makeHandler helper. Exactly one extraction preserves, for every registered handler,
|
|
10
|
+
which widget object render runs on, the index it receives, and that render runs only on emit. The
|
|
11
|
+
decoys share a var across iterations, capture only the index wrong, or call render eagerly at
|
|
12
|
+
registration. Classify each and name the observable difference.
|
|
13
|
+
prompt: |
|
|
14
|
+
Answer each deliverable using ONLY the supplied artifacts (register-original, bus-note, rewrite-a,
|
|
15
|
+
rewrite-b, rewrite-c, rewrite-d). Cite artifact id(s) for every claim. Use the verdict vocabulary:
|
|
16
|
+
behavior-preserving / changes-behavior / meets-goal / fails-goal.
|
|
17
|
+
|
|
18
|
+
The transformation goal is to lift the handler-building logic out of the loop into a reusable helper
|
|
19
|
+
WITHOUT changing, for each registered event, (i) which widget object render is called on, (ii) the
|
|
20
|
+
index argument render receives, or (iii) that render runs only when the event is emitted, never
|
|
21
|
+
during registration (see bus-note for what is observable).
|
|
22
|
+
|
|
23
|
+
1. Classify rewrite-a relative to register-original. Justify by tracing what each registered handler
|
|
24
|
+
captures across iterations.
|
|
25
|
+
|
|
26
|
+
2. Classify rewrite-b. If it changes behavior, state precisely: when the event 'refresh:' +
|
|
27
|
+
widgets[0].id is later emitted, which widget object render is called on and what index argument
|
|
28
|
+
it receives.
|
|
29
|
+
|
|
30
|
+
3. Classify rewrite-c. Name the observable difference from register-original, in terms of when
|
|
31
|
+
render runs and how many times, for widgets that are never emitted.
|
|
32
|
+
|
|
33
|
+
4. Classify rewrite-d. If it changes behavior, state what render receives (widget object and index)
|
|
34
|
+
when 'refresh:' + widgets[0].id is emitted, and how this differs from rewrite-b.
|
|
35
|
+
|
|
36
|
+
5. Exactly one extraction is the only behavior-preserving refactor. Name it. Then, for
|
|
37
|
+
register-original, state the ground truth: when 'refresh:' + widgets[0].id is emitted, which
|
|
38
|
+
widget object render is called on, with what index, and at what time.
|
|
39
|
+
artifacts:
|
|
40
|
+
- id: register-original
|
|
41
|
+
type: code
|
|
42
|
+
label: dashboard/wire.ts — original inline loop (the reference behavior)
|
|
43
|
+
content: |
|
|
44
|
+
// dashboard/wire.ts — original. registerWidgets is the ONLY function that wires refresh
|
|
45
|
+
// handlers (see bus-note).
|
|
46
|
+
import type { EventBus } from './bus';
|
|
47
|
+
import type { Widget } from './widget';
|
|
48
|
+
|
|
49
|
+
export function registerWidgets(bus: EventBus, widgets: Widget[]): void {
|
|
50
|
+
for (let i = 0; i < widgets.length; i++) {
|
|
51
|
+
const widget = widgets[i]; // per-iteration block binding
|
|
52
|
+
bus.on(`refresh:${widget.id}`, () => {
|
|
53
|
+
widget.render(i); // captures THIS iteration's widget and i
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
- id: bus-note
|
|
58
|
+
type: note
|
|
59
|
+
label: dashboard wiring — EventBus semantics, render observability, completeness
|
|
60
|
+
content: |
|
|
61
|
+
dashboard wiring — EventBus semantics and observability (complete).
|
|
62
|
+
|
|
63
|
+
EventBus.on(event, handler) registers handler under the event name; it does NOT call handler.
|
|
64
|
+
Later, EventBus.emit(event) invokes every handler registered under that event, in registration
|
|
65
|
+
order. Handlers may be invoked zero, one, or many times depending on runtime events.
|
|
66
|
+
|
|
67
|
+
widget.render(index) paints the widget and increments a global paint_count (an observable side
|
|
68
|
+
effect); the index argument is recorded in the paint. render is a method on the widget object
|
|
69
|
+
(it reads its own `this`), so which widget object a handler calls render on is observable, as is
|
|
70
|
+
the index argument.
|
|
71
|
+
|
|
72
|
+
registerWidgets is the ONLY function that wires refresh handlers, and it is called exactly once
|
|
73
|
+
at startup. The `widgets` array is frozen after registration: neither its length nor its element
|
|
74
|
+
identities change. This is the complete wiring surface.
|
|
75
|
+
|
|
76
|
+
Reference behavior of register-original: for each index k, a handler is registered under
|
|
77
|
+
'refresh:' + widgets[k].id that, WHEN LATER EMITTED, calls widgets[k].render(k) — the widget
|
|
78
|
+
object and the index captured at iteration k. render is not called during registration.
|
|
79
|
+
- id: rewrite-a
|
|
80
|
+
type: code
|
|
81
|
+
label: rewrite-a — makeHandler(widget, i) by value
|
|
82
|
+
content: |
|
|
83
|
+
// rewrite-a — extract a helper that takes the widget and index by value.
|
|
84
|
+
function makeHandler(widget: Widget, i: number): () => void {
|
|
85
|
+
return () => widget.render(i);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function registerWidgets(bus: EventBus, widgets: Widget[]): void {
|
|
89
|
+
for (let i = 0; i < widgets.length; i++) {
|
|
90
|
+
const widget = widgets[i];
|
|
91
|
+
bus.on(`refresh:${widget.id}`, makeHandler(widget, i));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
- id: rewrite-b
|
|
95
|
+
type: code
|
|
96
|
+
label: rewrite-b — hoisted var bindings
|
|
97
|
+
content: |
|
|
98
|
+
// rewrite-b — "hoist the temporaries out of the loop body."
|
|
99
|
+
export function registerWidgets(bus: EventBus, widgets: Widget[]): void {
|
|
100
|
+
for (var i = 0; i < widgets.length; i++) {
|
|
101
|
+
var widget = widgets[i]; // var: one shared binding for the whole loop
|
|
102
|
+
bus.on(`refresh:${widget.id}`, () => widget.render(i));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
- id: rewrite-c
|
|
106
|
+
type: code
|
|
107
|
+
label: rewrite-c — makeHandler computes render up front
|
|
108
|
+
content: |
|
|
109
|
+
// rewrite-c — "precompute the render inside the helper."
|
|
110
|
+
function makeHandler(widget: Widget, i: number): () => void {
|
|
111
|
+
const result = widget.render(i); // render invoked here, during registration
|
|
112
|
+
return () => result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function registerWidgets(bus: EventBus, widgets: Widget[]): void {
|
|
116
|
+
for (let i = 0; i < widgets.length; i++) {
|
|
117
|
+
const widget = widgets[i];
|
|
118
|
+
bus.on(`refresh:${widget.id}`, makeHandler(widget, i));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
- id: rewrite-d
|
|
122
|
+
type: code
|
|
123
|
+
label: rewrite-d — const widget, var i
|
|
124
|
+
content: |
|
|
125
|
+
// rewrite-d — "keep widget local, hoist just the counter."
|
|
126
|
+
export function registerWidgets(bus: EventBus, widgets: Widget[]): void {
|
|
127
|
+
for (var i = 0; i < widgets.length; i++) { // var i: shared counter
|
|
128
|
+
const widget = widgets[i]; // const: per-iteration binding
|
|
129
|
+
bus.on(`refresh:${widget.id}`, () => widget.render(i));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
tags: [refactoring, extract-and-inline, closure, capture, evaluation-order]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
id: extract-and-inline-short-circuit-side-effect
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
category: refactoring
|
|
4
|
+
cluster: extract-and-inline
|
|
5
|
+
difficulty: expert
|
|
6
|
+
title: Inlining hasFunds — Short-Circuit Side Effects, the >= Boundary, and Meets-Goal vs Fails-Goal
|
|
7
|
+
summary: >-
|
|
8
|
+
A validator delegates to hasFunds, whose only side effect (a funds_check metric) fires only when a
|
|
9
|
+
&& short-circuit reaches it. The task is to inline hasFunds and remove it. One rewrite preserves
|
|
10
|
+
behavior and meets the goal; one increments the metric unconditionally; one flips >= to >; one
|
|
11
|
+
preserves behavior but never actually inlines. Classify each against both behavior and goal.
|
|
12
|
+
prompt: |
|
|
13
|
+
Answer each deliverable using ONLY the supplied artifacts (validate-original, order-note, rewrite-a,
|
|
14
|
+
rewrite-b, rewrite-c, rewrite-d). Cite artifact id(s) for every claim. Use the verdict vocabulary:
|
|
15
|
+
behavior-preserving / changes-behavior / meets-goal / fails-goal.
|
|
16
|
+
|
|
17
|
+
The transformation goal is to INLINE hasFunds into canCheckout and REMOVE the hasFunds function,
|
|
18
|
+
while preserving observable behavior exactly: the boolean canCheckout returns for every order, AND
|
|
19
|
+
the number of times metrics.inc('funds_check') fires (see order-note — the && short-circuit means it
|
|
20
|
+
fires only when order.itemsValid is true).
|
|
21
|
+
|
|
22
|
+
1. Classify rewrite-a against BOTH dimensions (behavior vs validate-original, and whether it meets
|
|
23
|
+
the inline goal). Justify by tracing the itemsValid=false and itemsValid=true paths.
|
|
24
|
+
|
|
25
|
+
2. Classify rewrite-b. If it changes behavior, name the observable difference and give the exact
|
|
26
|
+
order shape that reveals it, stating how many funds_check increments validate-original vs
|
|
27
|
+
rewrite-b produce on that order.
|
|
28
|
+
|
|
29
|
+
3. Classify rewrite-c. Give the exact order field values (itemsValid, balance, total) on which its
|
|
30
|
+
return value differs from validate-original, and state both return values.
|
|
31
|
+
|
|
32
|
+
4. Classify rewrite-d against BOTH dimensions. If its behavior matches validate-original, say so, and
|
|
33
|
+
separately state whether it satisfies the transformation goal, with the reason.
|
|
34
|
+
|
|
35
|
+
5. Exactly one rewrite both preserves behavior AND meets the inline goal. Name it. Then, for
|
|
36
|
+
validate-original, state the ground truth: when order.itemsValid is false, how many times does
|
|
37
|
+
metrics.inc('funds_check') fire, and what does canCheckout return?
|
|
38
|
+
artifacts:
|
|
39
|
+
- id: validate-original
|
|
40
|
+
type: code
|
|
41
|
+
label: checkout/validate.ts — shipped validator (the reference behavior)
|
|
42
|
+
content: |
|
|
43
|
+
// checkout/validate.ts — the shipped validator.
|
|
44
|
+
import { metrics } from './metrics';
|
|
45
|
+
import type { Order } from './order';
|
|
46
|
+
|
|
47
|
+
function hasFunds(order: Order): boolean {
|
|
48
|
+
metrics.inc('funds_check'); // the ONLY producer of the funds_check counter
|
|
49
|
+
return order.balance >= order.total;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function canCheckout(order: Order): boolean {
|
|
53
|
+
return order.itemsValid && hasFunds(order); // && short-circuits: hasFunds skipped if !itemsValid
|
|
54
|
+
}
|
|
55
|
+
- id: order-note
|
|
56
|
+
type: note
|
|
57
|
+
label: checkout — observability, the complete call surface, and the goal
|
|
58
|
+
content: |
|
|
59
|
+
checkout — observability and the complete call surface.
|
|
60
|
+
|
|
61
|
+
Order fields itemsValid (boolean), balance (number), total (number) are plain data properties:
|
|
62
|
+
reading them has no getter and no side effect. metrics.inc('funds_check') is the ONLY side
|
|
63
|
+
effect anywhere in this flow; the funds_check counter is watched on the ops dashboard.
|
|
64
|
+
|
|
65
|
+
hasFunds is the ONLY function that calls metrics.inc('funds_check'), and canCheckout is the ONLY
|
|
66
|
+
caller of hasFunds. So the number of funds_check increments per canCheckout call is fully
|
|
67
|
+
determined by canCheckout's control flow. This is the complete surface.
|
|
68
|
+
|
|
69
|
+
Reference behavior of canCheckout (validate-original): because `&&` short-circuits, hasFunds —
|
|
70
|
+
and therefore metrics.inc('funds_check') — runs if and only if order.itemsValid is true. When
|
|
71
|
+
itemsValid is true, canCheckout returns (order.balance >= order.total); when itemsValid is
|
|
72
|
+
false, it returns false and funds_check is NOT incremented. The comparison is `>=`, so an order
|
|
73
|
+
whose balance equals its total counts as funded.
|
|
74
|
+
|
|
75
|
+
The transformation goal is to INLINE hasFunds into canCheckout and remove the hasFunds function,
|
|
76
|
+
preserving that behavior exactly.
|
|
77
|
+
- id: rewrite-a
|
|
78
|
+
type: code
|
|
79
|
+
label: rewrite-a — inline with an early return
|
|
80
|
+
content: |
|
|
81
|
+
// rewrite-a — inline hasFunds, guard with an early return.
|
|
82
|
+
export function canCheckout(order: Order): boolean {
|
|
83
|
+
if (!order.itemsValid) return false;
|
|
84
|
+
metrics.inc('funds_check');
|
|
85
|
+
return order.balance >= order.total;
|
|
86
|
+
}
|
|
87
|
+
- id: rewrite-b
|
|
88
|
+
type: code
|
|
89
|
+
label: rewrite-b — compute the funds check, then AND it in
|
|
90
|
+
content: |
|
|
91
|
+
// rewrite-b — "compute the funds check up front, then AND it with itemsValid."
|
|
92
|
+
export function canCheckout(order: Order): boolean {
|
|
93
|
+
metrics.inc('funds_check');
|
|
94
|
+
const funded = order.balance >= order.total;
|
|
95
|
+
return order.itemsValid && funded;
|
|
96
|
+
}
|
|
97
|
+
- id: rewrite-c
|
|
98
|
+
type: code
|
|
99
|
+
label: rewrite-c — inline with an early return, tightened comparison
|
|
100
|
+
content: |
|
|
101
|
+
// rewrite-c — inline hasFunds, guard with an early return.
|
|
102
|
+
export function canCheckout(order: Order): boolean {
|
|
103
|
+
if (!order.itemsValid) return false;
|
|
104
|
+
metrics.inc('funds_check');
|
|
105
|
+
return order.balance > order.total;
|
|
106
|
+
}
|
|
107
|
+
- id: rewrite-d
|
|
108
|
+
type: code
|
|
109
|
+
label: rewrite-d — rewrite the caller as a ternary
|
|
110
|
+
content: |
|
|
111
|
+
// rewrite-d — "tidy the expression with a ternary."
|
|
112
|
+
function hasFunds(order: Order): boolean {
|
|
113
|
+
metrics.inc('funds_check');
|
|
114
|
+
return order.balance >= order.total;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function canCheckout(order: Order): boolean {
|
|
118
|
+
return order.itemsValid ? hasFunds(order) : false;
|
|
119
|
+
}
|
|
120
|
+
tags: [refactoring, extract-and-inline, inline, short-circuit, side-effect]
|