You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: correct five stale CLAUDE.md claims from the guide behavioral audit (batch 2) (#3091)
* docs: correct five stale CLAUDE.md claims found by the guide behavioral audit
- Model Quick Reference: tableName() is a getter, the setter is table() (#3079)
- RateLimiter keyFunction: middleware context has no cgi key; use the real
cgi scope with a Len() guard (#3074)
- Anti-Pattern 5: execute() has no parameters argument; NOW() is not
portable (fails on SQLite and SQL Server) — use CURRENT_TIMESTAMP
- Anti-Pattern 8: Wheels.ActionNotAllowed currently surfaces as HTTP 500,
not the intended 404 (#3075)
- Background Jobs: the advertised wheels jobs worker CLI does not exist;
point at the programmatic queue API (#3090)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
* docs: hoist the keyFunction closure per Cross-Engine Invariant 5
The corrected RateLimiter snippet itself used an inline closure as a
constructor named argument — the Adobe CF ArrayStoreException pattern
this same file documents. Caught by the Reviewer; hoisted to match the
RIGHT form in .ai/wheels/cross-engine-compatibility.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
---------
Signed-off-by: Peter Amiri <peter@alurium.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+16-16Lines changed: 16 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -121,13 +121,14 @@ Model finders return query objects, not arrays. Loop accordingly.
121
121
```
122
122
123
123
### 5. Migration Seed Data — Direct SQL Only
124
-
Parameter binding in `execute()` is unreliable. Use inline SQL.
124
+
`execute()`accepts only a SQL string — there is no `parameters` argument (`Migration.cfc`: `execute(required string sql)`). Use inline SQL.
125
125
```cfm
126
126
// WRONG
127
127
execute(sql="INSERT INTO roles (name) VALUES (?)", parameters=[{value="admin"}]);
128
128
129
-
// RIGHT — and use NOW() for database-agnostic dates (MySQL/PG/MSSQL/H2/SQLite)
130
-
execute("INSERT INTO roles (name, createdAt, updatedAt) VALUES ('admin', NOW(), NOW())");
129
+
// RIGHT — and use CURRENT_TIMESTAMP for database-agnostic dates (MySQL/PG/MSSQL/H2/SQLite).
130
+
// NOW() fails on SQLite (the `wheels new` default DB) and SQL Server; no adapter rewrites it.
131
+
execute("INSERT INTO roles (name, createdAt, updatedAt) VALUES ('admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)");
131
132
```
132
133
133
134
### 6. Route Order Matters
@@ -149,7 +150,7 @@ function authenticate() { ... }
149
150
private function authenticate() { ... }
150
151
```
151
152
152
-
Conversely, public **framework helpers** mixed onto every controller (`env`, `model`, `redirectTo`, `linkTo`, the `is*` request predicates, the flash helpers, …) are auto-excluded from the routable surface. At app start `application.wheels.protectedControllerMethods` is built from the `wheels.Global` + `wheels.controller.*` + `wheels.view.*` mixin surface (the same `getMetaData().functions` set `$integrateComponents` mixes in), and `$callAction()` throws `Wheels.ActionNotAllowed` → 404 for any action whose name matches one. So a helper can't be invoked as an action — but you also **can't name a user action after a framework helper** (it 404s instead of dispatching). The standard REST action names (`index`, `show`, `new`, `edit`, `create`, `update`, `delete`) are not helpers, so they're unaffected ([#2845](https://github.com/wheels-dev/wheels/pull/2845)).
153
+
Conversely, public **framework helpers** mixed onto every controller (`env`, `model`, `redirectTo`, `linkTo`, the `is*` request predicates, the flash helpers, …) are auto-excluded from the routable surface. At app start `application.wheels.protectedControllerMethods` is built from the `wheels.Global` + `wheels.controller.*` + `wheels.view.*` mixin surface (the same `getMetaData().functions` set `$integrateComponents` mixes in), and `$callAction()` throws `Wheels.ActionNotAllowed` for any action whose name matches one — intended to fall through to the 404 path, but it currently surfaces as HTTP 500 in every environment ([#3075](https://github.com/wheels-dev/wheels/issues/3075)). So a helper can't be invoked as an action — but you also **can't name a user action after a framework helper** (it errors instead of dispatching). The standard REST action names (`index`, `show`, `new`, `edit`, `create`, `update`, `delete`) are not helpers, so they're unaffected ([#2845](https://github.com/wheels-dev/wheels/pull/2845)).
153
154
154
155
### 9. Always cfparam View Variables
155
156
Every variable passed from controller to view needs a cfparam at the top of the view file.
@@ -258,7 +259,7 @@ For new migrator helpers or anywhere you accept a column-name argument: declare
258
259
component extends="Model" {
259
260
function config() {
260
261
// Table/key (only if non-conventional)
261
-
tableName("tbl_users");
262
+
table("tbl_users"); // setter is table(); tableName() is a getter — tableName("x") is a silent no-op (#3079)
262
263
setPrimaryKey("userId");
263
264
264
265
// Associations — all named params when using options
@@ -422,11 +423,17 @@ new wheels.middleware.RateLimiter() /
422
423
new wheels.middleware.RateLimiter(maxRequests=100, windowSeconds=120, strategy="slidingWindow")
423
424
new wheels.middleware.RateLimiter(maxRequests=50, windowSeconds=60, strategy="tokenBucket")
424
425
new wheels.middleware.RateLimiter(storage="database") // auto-creates wheels_rate_limits
425
-
new wheels.middleware.RateLimiter(keyFunction=function(req) { // rate-limit per API key
426
-
return req.cgi.http_x_api_key ?: "anonymous";
427
-
})
426
+
// rate-limit per API key — hoist the closure first: an inline function literal
427
+
// as a constructor named arg crashes Adobe CF (Cross-Engine Invariant 5)
428
+
var apiKeyFn = function(req) {
429
+
var apiKey = cgi.http_x_api_key;
430
+
return Len(apiKey) ? apiKey : "anonymous";
431
+
};
432
+
new wheels.middleware.RateLimiter(keyFunction=apiKeyFn)
428
433
```
429
434
435
+
The `keyFunction` receives the dispatch middleware context `{params, route, pathInfo, method}` — it has **no `cgi` key** ([#3074](https://github.com/wheels-dev/wheels/issues/3074)), so `req.cgi.*` silently collapses every client into one bucket. Read the real `cgi` scope directly, and guard with `Len()` (a missing header reads as empty string, not undefined, so `?:` never fires).
436
+
430
437
Strategies: `fixedWindow` (default), `slidingWindow`, `tokenBucket`. Storage: `memory` or `database`. Emits `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`. Returns `429` with `Retry-After` when exceeded.
431
438
432
439
`windowSeconds` must be > 0; `maxRequests` must be >= 0. Invalid values throw `Wheels.RateLimiter.InvalidConfiguration` at construction. `maxRequests = 0` is a valid kill-switch.
@@ -649,14 +656,7 @@ result = (new wheels.Job()).processQueue(queue="mailers", limit=10);
649
656
stats = (new wheels.Job()).queueStats();
650
657
```
651
658
652
-
Worker CLI:
653
-
```bash
654
-
wheels jobs work --queue=mailers --interval=3
655
-
wheels jobs status [--format=json]
656
-
wheels jobs retry --queue=mailers
657
-
wheels jobs purge --completed --failed --older-than=30
658
-
wheels jobs monitor
659
-
```
659
+
Worker CLI: none yet — `wheels jobs work|status|retry|purge|monitor` do not exist (`cli/lucli/Module.cfc` has no `jobs` command; invoking one errors — [#3090](https://github.com/wheels-dev/wheels/issues/3090)). Drive queues programmatically via `processQueue()` / `queueStats()` (above), e.g. from a scheduled task or cron-invoked script.
660
660
661
661
Backoff: `this.baseDelay = 2`, `this.maxDelay = 3600` in `config()`. Formula: `Min(baseDelay * 2^attempt, maxDelay)`. The `wheels_jobs` table is auto-created on first enqueue/processing — no migration needed.
0 commit comments