Analyzer::Javascript::Feathers
Inherits Analyzer::Javascript::JavascriptEngine < Analyzer < FileHelper < Reference < Object
Feathers.js (https://feathersjs.com) is service-based rather than route-based: registering a service at a path auto-generates REST verbs from whichever of the standard CRUD methods the service implements.
app.use('/messages', new MessageService())
app.service('messages').hooks({ ... })
find(params) -> GET /messages get(id, params) -> GET /messages/:id create(data, params) -> POST /messages update(id, data, params) -> PUT /messages/:id patch(id, data, params) -> PATCH /messages/:id remove(id, params) -> DELETE /messages/:id
app.use(path, service, options) accepts an optional third
argument whose methods: array is the authoritative list of
externally exposed methods when present (v5 "Dove" API) — it is
honoured here as an intersection against whatever methods were
otherwise detected/assumed. The path and methods: value are
both frequently bare identifiers pointing at a sibling
<name>.shared.ts module in the current (v5/"Dove") CLI generator
layout (export const messagePath = 'messages', export const messageMethods = ['find', 'get', ...] as const) rather than
inline literals — both are resolved the same one-import-hop way
as the service class itself.
False-positive risk
app.use('/path', someExpression) is also the generic Express
middleware/router-mount idiom, and Express coexists with Feathers
in the same JS/Node ecosystem noir already supports. To avoid
stealing routes from a plain Express app (or a sibling Express
app in a monorepo), a .use() call is only treated as a Feathers
service registration when the second argument is structurally
service-shaped:
new SomeClass(...)— Express never mounts a freshly-constructed instance this way; routers/middleware are always factory calls (express.Router(),cors(), ...) withoutnew.- an inline object literal
{ ... }. - a bare identifier that resolves (same-file or one
require/importhop, viaNoir::ImportGraph) to a class or object literal — but NOT toexpress.Router()/Router(). - a bare identifier that cannot be resolved at all, but the same
file also calls
app.service(<same path>)— an API Express apps never have, since.service()doesn't exist on a plain Expressapp.
A identifier(...) / member.expr(...) call (the shape of
express.Router(), cors(), express.static(...), ...) is never
accepted, so ordinary Express middleware mounting is left alone.
When the service expression resolves to a real class/object body,
only the CRUD methods that body actually defines are emitted — a
service that only implements find/get does not get create/
update/patch/remove fabricated for it. The one exception is
a class that extends one of the well-known Feathers database
adapters (KnexService, MongoDBService, MemoryService, ...) —
those always implement the full CRUD set themselves regardless of
which methods the subclass overrides, which is how the official
CLI generator's default <name>.class.ts looks
(export class MessageService extends KnexService<...> {}, no
method bodies at all).
When the expression can't be resolved to a body at all (external package, dynamic value, ...) but the structural evidence above is still strong enough to be confident this IS a Feathers registration, the full 6-verb CRUD set is emitted as a documented, deliberately conservative fallback — never for a body we DID inspect, found to extend nothing adapter-like, and found zero CRUD-shaped methods in, which is treated as a real negative (nothing emitted) rather than a fallback trigger.
Constants
method name => {HTTP verb, needs a trailing /:id segment}
The officially documented Feathers database-adapter service base
classes (https://feathersjs.com/api/databases/adapters) — every
one of these implements the full CRUD set itself, so a subclass
that overrides none (or only some) of them still exposes all six
externally, unless narrowed by an explicit methods: option.
Crystal recompiles an interpolated regex literal on every
evaluation; these are keyed by the (small, fixed) CRUD method
name set, so build them once at load time rather than per call.
m makes ^ match at each line start within a multi-line body,
not just the start of the whole string.
Class methods
Instance methods
Instance-side view of the same declaration. The per-file rescues live on
this base class, which has no way to name the analyzer that is running
inside them, so a skipped file could not be attributed to a tech.
Deriving it from analyzer_for keeps the name written exactly once.