@filipebraida/adonis-function-points 0.5.0 → 0.7.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +170 -0
  2. package/README.md +101 -292
  3. package/build/{calibration-8eV8CEix.js → calibration-DVIf8hcE.js} +42 -3
  4. package/build/commands/main.js +6 -6
  5. package/build/{fp_calibrate-DUbHiifm.js → fp_calibrate-3TxGdS1b.js} +1 -1
  6. package/build/{fp_count-ChtblhZV.js → fp_count-arGLnVlY.js} +1 -1
  7. package/build/{fp_diff-Dt7J4IWu.js → fp_diff-DBBvzq5x.js} +1 -1
  8. package/build/{fp_explain-DZJ--0-S.js → fp_explain-aNFApwiT.js} +1 -1
  9. package/build/{fp_inventory-CPtmuuke.js → fp_inventory-DIjKIC9t.js} +1 -1
  10. package/build/{fp_metrics-et8F1Wvt.js → fp_metrics-BpU61waG.js} +1 -1
  11. package/build/index.d.ts +8 -4
  12. package/build/index.js +4 -4
  13. package/build/{pipeline-CNTBhs6o.js → pipeline-DO2301fV.js} +2131 -389
  14. package/build/{resolvers-PJwo2Z8R.js → resolvers-DaU4uAqT.js} +603 -165
  15. package/build/{runners-DIt1G85i.js → runners-Dm7cWGa-.js} +6 -3
  16. package/build/src/albrecht/counter.d.ts +38 -5
  17. package/build/src/albrecht/data_functions.d.ts +49 -3
  18. package/build/src/albrecht/diff.d.ts +27 -0
  19. package/build/src/albrecht/index.d.ts +1 -0
  20. package/build/src/albrecht/opaque.d.ts +90 -0
  21. package/build/src/albrecht/technical_filter.d.ts +18 -11
  22. package/build/src/albrecht/transactional_functions.d.ts +7 -0
  23. package/build/src/cli.js +2 -2
  24. package/build/src/define_config.d.ts +55 -57
  25. package/build/src/inventory/graph/call_graph.d.ts +44 -0
  26. package/build/src/inventory/graph/deliveries.d.ts +88 -0
  27. package/build/src/inventory/graph/output_fields.d.ts +143 -0
  28. package/build/src/inventory/paths.d.ts +2 -0
  29. package/build/src/inventory/resolvers/index.d.ts +21 -0
  30. package/build/src/inventory/resolvers/index.js +2 -2
  31. package/build/src/inventory/resolvers/job_dispatch.d.ts +20 -0
  32. package/build/src/inventory/resolvers/local_function.d.ts +24 -0
  33. package/build/src/inventory/resolvers/transformer.d.ts +0 -23
  34. package/build/src/inventory/sources/commands.d.ts +14 -0
  35. package/build/src/inventory/sources/jobs.d.ts +27 -0
  36. package/build/src/pipeline.js +1 -1
  37. package/build/src/types.d.ts +49 -1
  38. package/build/stubs/config.stub +29 -16
  39. package/package.json +1 -1
@@ -0,0 +1,88 @@
1
+ import { Node } from 'ts-morph';
2
+ import type { CallExpression, SourceFile } from 'ts-morph';
3
+ import type { RelationMap, StoreSymbols } from '../detectors/lucid.js';
4
+ import type { HandlerRef } from '../../types.js';
5
+ /**
6
+ * What a transaction DELIVERS — counting-decisions §6, plan 0.7 §A′.
7
+ *
8
+ * The DETs of an output are the fields that cross the boundary, and the place
9
+ * they cross is the delivery: the props handed to `inertia.render` /
10
+ * `inertia.modal` / `view.render`, the payload of `response.json|ok|created|send`,
11
+ * a literal the handler returns. Before this, "what leaves" was every column of
12
+ * every store the transaction touched — a `findOrFail` made to authorise handed
13
+ * the whole table to the screen, and a derived `total` handed nothing.
14
+ *
15
+ * Measured on three applications first: no delivered collection is a variable
16
+ * bound to a store; they come out of query objects the graph already follows.
17
+ * So a delivered value that comes from a followed call is resolved to what that
18
+ * body returns — the graph does that in `run()`, once the bodies are known; this
19
+ * module only says WHAT was delivered and where it came from.
20
+ */
21
+ export type Delivery =
22
+ /** a variable bound to a store: its columns leave */
23
+ {
24
+ kind: 'store';
25
+ store: string;
26
+ path: string;
27
+ }
28
+ /**
29
+ * The result of a call a strategy followed: what that body returns leaves.
30
+ * `args` are the call's arguments, classified — a body that returns no literal
31
+ * and reads no store (a CSV builder) delivers what was handed INTO it.
32
+ */
33
+ | {
34
+ kind: 'call';
35
+ refs: HandlerRef[];
36
+ path: string;
37
+ expression: string;
38
+ args: Delivery[];
39
+ /**
40
+ * One key of what the call returns — `const { data } = await q.handle()`,
41
+ * `relatorio.linhas` — rather than the whole result. Resolved against the
42
+ * body's classified return, keeping only that key.
43
+ */
44
+ pick?: string;
45
+ }
46
+ /** a scalar, a property, an expression: one DET */
47
+ | {
48
+ kind: 'scalar';
49
+ path: string;
50
+ }
51
+ /** an input echoed back — the validated payload, a field read off the request: counts once, on entry */
52
+ | {
53
+ kind: 'echo';
54
+ path: string;
55
+ }
56
+ /** something the classifier cannot read: one DET as a floor, reported */
57
+ | {
58
+ kind: 'opaque';
59
+ path: string;
60
+ expression: string;
61
+ };
62
+ /** keys of a result that carry its rows: picking one of these is not picking one value */
63
+ export declare const PASSES_ROWS: Set<string>;
64
+ export type DeliveryContext = {
65
+ body: Node;
66
+ file: SourceFile;
67
+ symbols: StoreSymbols;
68
+ relations: RelationMap;
69
+ followed: Map<CallExpression, HandlerRef[]>;
70
+ };
71
+ export type BodyDeliveries = {
72
+ /** props handed to a renderer or a response method, anywhere in the body */
73
+ calls: Delivery[];
74
+ /** a delivery call was found, even with nothing readable in it */
75
+ anyCall: boolean;
76
+ /**
77
+ * What a `return` hands back that is not one of those calls — meaningful for
78
+ * the ENTRY body only, where returning a value is delivering it; a followed
79
+ * body's return is read by `returnedLeavesOf` instead.
80
+ */
81
+ returns: Delivery[];
82
+ anyReturn: boolean;
83
+ };
84
+ /**
85
+ * The deliveries of a body: every props argument handed to a renderer or a
86
+ * response method, and what a `return` hands back that is not one of those calls.
87
+ */
88
+ export declare function deliveriesIn(ctx: DeliveryContext): BodyDeliveries;
@@ -0,0 +1,143 @@
1
+ import { Node } from 'ts-morph';
2
+ import type { CallExpression, ClassDeclaration, Expression, ObjectLiteralExpression } from 'ts-morph';
3
+ import type { CollectedDataStore } from '../sources/data_stores.js';
4
+ /**
5
+ * What an output transaction actually EMITS — counting-decisions §6.
6
+ *
7
+ * AFP §7.3 counts one DET per unique field that leaves the boundary. Without
8
+ * reading what leaves, the only repeatable answer is "every column of every
9
+ * table read", and that is what the count did: a detail screen passing through
10
+ * three transformers came out at 67 DET. The transformer is where the
11
+ * application says which fields cross, so it is read here.
12
+ *
13
+ * Two facts are extracted, and both are about the BODY, so they are cached with
14
+ * the rest of its facts:
15
+ *
16
+ * outputs the keys a transformer method returns — `LivroTransformer.titulo`
17
+ * selected the columns a query names in `.select()` — per store
18
+ *
19
+ * Everything the walker cannot read is a placeholder, never a guess: an
20
+ * unreadable spread counts 1 DET as a floor and is reported, the same treatment
21
+ * an open `vine.object` gets on the input side (§9).
22
+ */
23
+ export type OutputFacts = {
24
+ /** qualified keys emitted by this transformer body */
25
+ outputs: string[];
26
+ /** of those, the placeholders: a spread the walker could not read */
27
+ opaqueOutputs: string[];
28
+ /**
29
+ * The store this transformer is FOR — `BaseTransformer<Livro>` — when it is a
30
+ * known store. A transformer decides what leaves for its resource, not for the
31
+ * page: a store read beside it and passed raw is not covered.
32
+ */
33
+ resource: string | null;
34
+ };
35
+ /** how a body reads a store, per chain — what leaves when nothing transforms it */
36
+ export type StoreRead = {
37
+ store: string;
38
+ shape: 'whole' | 'select' | 'aggregate';
39
+ /** for `select`: the columns named */
40
+ columns: string[];
41
+ /**
42
+ * The store this one was preloaded THROUGH (`Livro.query().preload('autor')`),
43
+ * when it was not read by a chain of its own. A relation loaded for a
44
+ * transformer is consumed by it, not shown.
45
+ */
46
+ via?: string;
47
+ };
48
+ /**
49
+ * Does this class extend a transformer base from a package?
50
+ *
51
+ * Decided by the base's name AND by its import being a bare specifier, so an
52
+ * application class that merely happens to own a `transform` method is not
53
+ * mistaken for one. Shared with the `transformer` resolver: one definition of
54
+ * what a transformer is, or the resolver follows a body this walker refuses.
55
+ */
56
+ export declare function isTransformerClass(cls: ClassDeclaration): boolean;
57
+ /** `class X extends BaseTransformer<Livro>` -> 'Livro' */
58
+ export declare function transformerResourceOf(cls: ClassDeclaration): string | null;
59
+ /**
60
+ * The keys a transformer method returns.
61
+ *
62
+ * `followed` says whether a call inside the literal is a body the graph walks —
63
+ * `AutorTransformer.transform(x)`, `this.toObject()` — in which case its keys
64
+ * arrive through that body and the key holding it is not a DET of its own: the
65
+ * user sees the author's name, not an "autor" field.
66
+ *
67
+ * { titulo: l.titulo } 1 — `titulo`
68
+ * { autor: AutorTransformer.transform } 0 here; the nested body contributes
69
+ * { endereco: { rua, cidade } } leaves individually
70
+ * { tags: xs.map((t) => t.nome) } 1 — a repeating group of one attribute
71
+ * { itens: xs.map((i) => ({ a, b })) } the leaves, once
72
+ * ...this.pick(this.resource, [...]) the listed names
73
+ * ...this.toObject() 0 here; the followed body contributes
74
+ * ...anythingElse 1, opaque, reported
75
+ *
76
+ * A key that is the identifier of the transformer's resource is not a DET, for
77
+ * the same reason `isPrimary` is not one on the data function.
78
+ */
79
+ export declare function outputFieldsIn(body: Node, owner: ClassDeclaration | undefined, stores: Map<string, CollectedDataStore>, followed: (call: CallExpression) => boolean): OutputFacts;
80
+ /**
81
+ * The leaves of the object literal ANY body returns — a query object building a
82
+ * summary, a module function assembling a view model. Same walker as the
83
+ * transformer's, unqualified: the caller prefixes the path the value was
84
+ * delivered under. `null` when the body returns no literal.
85
+ */
86
+ export declare function returnedLeavesOf(body: Node, followed: (call: CallExpression) => boolean): {
87
+ leaves: string[];
88
+ opaque: string[];
89
+ } | null;
90
+ type LeafOptions = {
91
+ /** prefixed to every leaf, with a dot; empty for an unqualified walk */
92
+ qualifier: string;
93
+ /** top-level names that are not DETs (the resource's key, its stamps) */
94
+ excluded: Set<string>;
95
+ followed: (call: CallExpression) => boolean;
96
+ };
97
+ /**
98
+ * The leaves of object literals, by the §7 rules:
99
+ *
100
+ * { titulo: l.titulo } 1 — `titulo`
101
+ * { autor: AutorTransformer.transform } 0 here; the followed body contributes
102
+ * { endereco: { rua, cidade } } leaves individually
103
+ * { tags: xs.map((t) => t.nome) } 1 — a repeating group of one attribute
104
+ * { itens: xs.map((i) => ({ a, b })) } the leaves, once
105
+ * ...this.pick(this.resource, [...]) the listed names
106
+ * ...this.toObject() 0 here; the followed body contributes
107
+ * ...anythingElse 1, opaque, reported
108
+ */
109
+ export declare function collectLeaves(literals: ObjectLiteralExpression[], options: LeafOptions): {
110
+ leaves: string[];
111
+ opaque: string[];
112
+ };
113
+ /**
114
+ * Object literals the body itself returns — not the ones returned by arrow
115
+ * functions inside it, which belong to `.map()` callbacks and are read as
116
+ * repeating groups where they occur.
117
+ */
118
+ export declare function returnedLiteralsOf(body: Node): ObjectLiteralExpression[];
119
+ /** `xs.map((x) => ({ a, b }))` -> the literal; null for a scalar map or anything else */
120
+ export declare function mappedLiteralOf(value: Expression): ObjectLiteralExpression | null;
121
+ /** strips parentheses, `as`, `satisfies` and non-null assertions */
122
+ export declare function unwrap(node: Node | undefined | null): Expression | null;
123
+ /**
124
+ * The shape of the chain an access belongs to — read from the WHOLE chain, root
125
+ * to end, because every call on it is detected as an access and each must reach
126
+ * the same answer: `Livro.query().where(…).count()` is an aggregate whether the
127
+ * detector is looking at `query` or at `count`.
128
+ *
129
+ * whole rows leave: every column of the store (unless a transformer covers it)
130
+ * select only the columns named
131
+ * aggregate `.count()`, `.exists()`: one derived scalar leaves, not the table
132
+ */
133
+ export type ChainShape = {
134
+ selected: string[];
135
+ aggregate: boolean;
136
+ /** a `.select()` whose column list is not literal */
137
+ unreadable: {
138
+ line: number;
139
+ expression: string;
140
+ }[];
141
+ };
142
+ export declare function chainShapeOf(access: CallExpression): ChainShape;
143
+ export {};
@@ -37,4 +37,6 @@ export declare const toPosix: (value: string) => string;
37
37
  export declare const relativeTo: (root: string, value: string) => string;
38
38
  /** Compares two paths that may have come from different sources. */
39
39
  export declare const samePath: (a: string | undefined, b: string | undefined) => boolean;
40
+ /** a seeder, by the directory `make:seeder` writes to — scaffolding, but a fact the report uses */
41
+ export declare function isSeeder(root: string, file: string): boolean;
40
42
  export declare function isApplicationCode(root: string, file: string): boolean;
@@ -28,3 +28,24 @@ export declare function resolveCall(call: import('ts-morph').CallExpression, ctx
28
28
  by: string;
29
29
  refs: import('../../types.js').HandlerRef[];
30
30
  } | null;
31
+ export type IgnoreCallsOptions = {
32
+ /** the strategy's name — printed in the report beside the volume it declared data-free */
33
+ name: string;
34
+ /** method names, matched on the callee: `getUrl` matches `x.getUrl(...)` */
35
+ methods?: string[];
36
+ /** a pattern over the callee's text: `/\bauthz\.can$/` */
37
+ matching?: RegExp;
38
+ /** lower runs first; defaults to 1, before every built-in */
39
+ order?: number;
40
+ };
41
+ /**
42
+ * A strategy that recognises a family of calls and knows they reach no data
43
+ * store — a rate limiter, an attachment's URL, an authorisation check.
44
+ *
45
+ * Read from a real configuration, every such strategy was the same eight lines:
46
+ * a helper to get the method name off the ts-morph node (the app does not depend
47
+ * on ts-morph), a `resolve` that returns nothing, and one comparison. What the
48
+ * design wants is kept — it is still a NAMED strategy, and `fp:count` still
49
+ * reports the volume it declared data-free — and the ceremony is not.
50
+ */
51
+ export declare function ignoreCalls(options: IgnoreCallsOptions): CallResolver;
@@ -1,2 +1,2 @@
1
- import { n as isTechnicalWrite, r as resolveCall, t as BUILTIN_CALL_RESOLVERS } from "../../../resolvers-PJwo2Z8R.js";
2
- export { BUILTIN_CALL_RESOLVERS, isTechnicalWrite, resolveCall };
1
+ import { i as resolveCall, n as ignoreCalls, r as isTechnicalWrite, t as BUILTIN_CALL_RESOLVERS } from "../../../resolvers-DaU4uAqT.js";
2
+ export { BUILTIN_CALL_RESOLVERS, ignoreCalls, isTechnicalWrite, resolveCall };
@@ -1,4 +1,24 @@
1
1
  import type { CallResolver } from './types.js';
2
+ export declare const DISPATCH_METHODS: Set<string>;
3
+ /**
4
+ * The method that actually runs the job, by queue package.
5
+ *
6
+ * There is no single name, and this list grew twice by measurement rather than by
7
+ * reasoning. `@rlanz/bull-queue` uses `handle`; `@nemoventures/adonis-jobs` calls
8
+ * it `process`; `@adonisjs/queue` — the official package — generates
9
+ * `async execute()` in its own `make:job` stub. Each omission cost the same: the
10
+ * file resolved, no body was found, the dispatch was reported as an unknown, and
11
+ * every write inside the job went uncounted.
12
+ *
13
+ * `execute` surfaced only once event dispatch started being followed, because the
14
+ * listener was what enqueued the job and that path had never been walked. Which is
15
+ * the argument for adding a name when a real application shows it: a list written
16
+ * from imagination would have missed this one too.
17
+ *
18
+ * Ordered: a class declaring more than one is answering the dispatcher with the
19
+ * first, and `handle` is the most common.
20
+ */
21
+ export declare const EXECUTION_METHODS: readonly ["handle", "execute", "process", "run", "perform"];
2
22
  /**
3
23
  * "Job" pattern: the write happens asynchronously.
4
24
  *
@@ -0,0 +1,24 @@
1
+ import type { CallExpression } from 'ts-morph';
2
+ import type { CallResolver } from './types.js';
3
+ /**
4
+ * "Local function" pattern: a helper declared in the same file, not imported.
5
+ *
6
+ * const lista = await proximos(id) // function proximos() { … }
7
+ * return rows.map(paraLinha) // const paraLinha = (row) => …
8
+ *
9
+ * A query object that keeps its helpers beside it is common, and before this
10
+ * every such call was unresolved: the store a helper read was reached by nobody
11
+ * from that route, and a value built by it left as a whole table handed in.
12
+ *
13
+ * Only module-level declarations: a closure declared inside a body is part of
14
+ * that body, and the graph already walks it.
15
+ */
16
+ export declare const localFunctionResolver: CallResolver;
17
+ /**
18
+ * The function a call names: `proximos(id)` names `proximos`; `rows.map(paraLinha)`
19
+ * names `paraLinha`, called once per row — the body the graph must read is the
20
+ * same, whichever way it was reached.
21
+ */
22
+ export declare function calledFunctionOf(call: CallExpression): import('ts-morph').Identifier | null;
23
+ /** `function name() {}` or `const name = () => {}` / `function () {}` at module level */
24
+ export declare function declaresFunction(file: import('ts-morph').SourceFile, name: string): boolean;
@@ -1,25 +1,2 @@
1
1
  import type { CallResolver } from './types.js';
2
- /**
3
- * "Transformer" pattern: the package supplies the API, the application
4
- * supplies the body.
5
- *
6
- * class InviteTransformer extends BaseTransformer<Invite> {
7
- * toObject() { … }
8
- * }
9
- *
10
- * InviteTransformer.transform(invite)
11
- *
12
- * `transform()` and `paginate()` live in `@adonisjs/core`, so resolving the
13
- * symbol lands on the application file and finds no body there. The naive
14
- * reading is that the tracer must step into node_modules; it does not. Those
15
- * methods call BACK into `toObject()`, which the application writes, so the
16
- * body worth analysing was in the application all along.
17
- *
18
- * It is the same shape as `job-dispatch`, where `dispatch` enqueues and
19
- * `handle` executes.
20
- *
21
- * COUNTING DECISION: the write a transformer performs belongs to the
22
- * transaction that serialised through it. Without this, a table written only
23
- * inside `toObject()` is reached by nobody and drops out under AFP §6.5.4.
24
- */
25
2
  export declare const transformerResolver: CallResolver;
@@ -0,0 +1,14 @@
1
+ import type { ClassDeclaration } from 'ts-morph';
2
+ import type { AppContext } from '../app_context.js';
3
+ import type { CollectedEntryPoint } from './routes_ast.js';
4
+ export declare const GENERATES_DATA_HINT = "imports @faker-js/faker: generates data, probably a development tool";
5
+ export declare function collectCommands(app: AppContext): CollectedEntryPoint[];
6
+ /**
7
+ * The input DETs of a command: its `@flags.*` and `@args.*`, named as the
8
+ * operator types them — `flagName` / `argumentName` when given, the property
9
+ * otherwise. Returned as `flags.<name>` / `args.<name>` so the counter can say
10
+ * which is which.
11
+ */
12
+ export declare function commandFieldsOf(cls: ClassDeclaration): string[];
13
+ /** is this class an ace command? — the graph asks, to read its flags as input */
14
+ export declare function isCommandClass(cls: ClassDeclaration): boolean;
@@ -0,0 +1,27 @@
1
+ import type { AppContext } from '../app_context.js';
2
+ /**
3
+ * The application's queue jobs, and who dispatches each — plan 0.7 §D.
4
+ *
5
+ * A job dispatched by a handler is part of that handler's transaction (§9): the
6
+ * user clicks, the effect happens, asynchronously or not. A job that NO
7
+ * transaction reaches is one of two things, and the code cannot say which:
8
+ *
9
+ * scheduled `PodarAuditoriaJob.schedule({}).cron('0 3 * * *')` in a start
10
+ * file — an elementary process nobody is counting
11
+ * dead code dispatched by nothing at all
12
+ *
13
+ * Reported, not counted: inventing an elementary process is the error this
14
+ * package exists to avoid. When a scheduler appears on a real application, it
15
+ * becomes an entry point `job:<Class>` with the identity §5 already decided.
16
+ */
17
+ export type CollectedJob = {
18
+ /** the class name */
19
+ name: string;
20
+ /** absolute, posix */
21
+ file: string;
22
+ /** files that dispatch it (`X.dispatch(…)`), absolute posix — inside a transaction or not */
23
+ dispatchedFrom: string[];
24
+ /** files that schedule it (`X.schedule({}).cron(…)`): a scheduler, which no transaction is */
25
+ scheduledFrom: string[];
26
+ };
27
+ export declare function collectJobs(app: AppContext): CollectedJob[];
@@ -1,2 +1,2 @@
1
- import { n as analyze, t as CoverageTooLowError } from "../pipeline-CNTBhs6o.js";
1
+ import { n as analyze, t as CoverageTooLowError } from "../pipeline-DO2301fV.js";
2
2
  export { CoverageTooLowError, analyze };
@@ -45,6 +45,17 @@ export type Attribute = {
45
45
  name: string;
46
46
  type?: string;
47
47
  isIdentifier: boolean;
48
+ /**
49
+ * Maintained by the framework, not by the user: `autoCreate` / `autoUpdate`
50
+ * on a `@column.dateTime()`. Not a DET, on the same ground as the identifier —
51
+ * counting-decisions §6.
52
+ */
53
+ system?: boolean;
54
+ /**
55
+ * `serializeAs: null`: Lucid never serialises it, so it never leaves on an
56
+ * output. Still a DET of the data function — counting-decisions §6.
57
+ */
58
+ hidden?: boolean;
48
59
  provenance: Provenance;
49
60
  };
50
61
  /**
@@ -63,6 +74,12 @@ export type EntryPoint = {
63
74
  /** route name when present; not the identity used across versions */
64
75
  name?: string;
65
76
  handler: HandlerRef | null;
77
+ /**
78
+ * What the collector noticed and the counter cannot decide: an ace command that
79
+ * imports a data generator is probably a development tool. Printed beside the
80
+ * function, so a person excludes it with `boundary.ignoreEntryPoints`.
81
+ */
82
+ hints?: string[];
66
83
  provenance: Provenance;
67
84
  };
68
85
  export type HandlerRef = {
@@ -103,10 +120,41 @@ export type HandlerBehavior = {
103
120
  * user-recognisable field crossing the boundary, which is §7.2's definition.
104
121
  */
105
122
  requestFields: Field[];
123
+ /**
124
+ * The `@flags.*` / `@args.*` an ace command declares — its input DETs, exact.
125
+ * Named `flags.<name>` / `args.<name>`, as the operator types them. Absent on
126
+ * an HTTP transaction.
127
+ */
128
+ commandFields?: Field[];
106
129
  /** the transaction reads the request in a way that enumerates nothing */
107
130
  opaqueRequest: boolean;
108
- /** declared output fields (transformers, DTOs) */
131
+ /**
132
+ * What a transformer on the path emits — counting-decisions §6. Empty means no
133
+ * transformer was reached and the output is counted from the stores' columns.
134
+ */
109
135
  outputFields: Field[];
136
+ /** output spreads the analysis could not read: 1 DET each, a floor */
137
+ opaqueOutputFields: Field[];
138
+ /** stores a transformer on the path is for: their keys leave, not their columns */
139
+ transformedStores: string[];
140
+ /** what the transaction delivers: derived fields, raw stores, values nobody could read */
141
+ delivered: {
142
+ any: boolean;
143
+ fields: string[];
144
+ opaqueFields: string[];
145
+ stores: string[];
146
+ };
147
+ /**
148
+ * How each store was read: rows whole, `.select()` columns, or one aggregate
149
+ * scalar; by its own chain (`direct`) or preloaded through another store (`via`).
150
+ */
151
+ outputReads: Record<string, {
152
+ whole: boolean;
153
+ selected: string[];
154
+ aggregate: boolean;
155
+ direct: boolean;
156
+ via: string[];
157
+ }>;
110
158
  /** path walked through the call graph — what `fp:explain` prints */
111
159
  trace: TraceStep[];
112
160
  /** calls no resolver knew how to follow */
@@ -24,31 +24,44 @@ export default defineConfig({
24
24
  business: [],
25
25
  },
26
26
 
27
- /** Logical subgroups (RET). `constant` pins it at 1, which is honest. */
28
- retStrategy: 'constant',
27
+ /**
28
+ * How tables fold into data functions (RET). `usage`, the default: a
29
+ * `hasMany`/`hasOne` child no application code addresses directly is a RET of
30
+ * its parent, not an ILF of its own. `none` keeps every table apart, at RET 1,
31
+ * to compare against a count made before 0.6.0.
32
+ */
33
+ // dataFunctions: { grouping: 'usage' },
29
34
 
30
- /** How far to follow the call graph from the handler. */
31
- maxDepth: 3,
35
+ /** How far to follow the call graph from the handler (default 3). */
36
+ // maxDepth: 3,
32
37
 
33
38
  /** Refuses to emit a count if tracing covers less than this. */
34
39
  minCoverage: 0.85,
35
40
 
36
41
  /**
37
- * Facts static analysis cannot read, declared with a required justification.
38
- *
39
- * The case this exists for: fields the user fills that live in a JSON column
40
- * whose schema is stored in the database. See counting-decisions §8 — and use
41
- * it sparingly, because `fp:count` reports what share of the total came from
42
- * here.
42
+ * DETs static analysis cannot read — a JSON column, an open validator field —
43
+ * count 1 each, a floor, and `fp:count` names them. Answer them here by ORIGIN,
44
+ * with a required justification, and the answer reaches every function carrying
45
+ * the DET. See counting-decisions §8; use it sparingly, because `fp:count`
46
+ * reports what share of the total came from here.
43
47
  */
44
- // overrides: {
45
- // // one schema, or several whose fields are unioned by leaf path
46
- // Form: { detFromSchema: ['intakeSchema', 'reviewSchema'], reason: 'one per template' },
48
+ // opaque: {
49
+ // // the fields live in a JSON Schema declared in the code: name it (or several,
50
+ // // unioned by leaf path), and the count keeps coming from the code
51
+ // 'Survey.answers': { schemas: ['surveySchema', 'feedbackSchema'], reason: 'one schema per survey template' },
52
+ // 'answerSurveyValidator.answers': { schemas: 'surveySchema', reason: 'the same form, submitted' },
47
53
  //
48
54
  // // 1 DET is a floor, and `fp:count` says so on every run. When 1 IS the right
49
- // // answer, record that someone checked — otherwise the warning becomes noise
50
- // // the team learns to scroll past. It moves no number.
51
- // Petition: { opaqueReviewed: ['schema', 'uiSchema'], reason: 'metadata; one field each' },
55
+ // // answer, record that someone checked — it moves no number
56
+ // 'Attachment.metadata': { reviewed: true, reason: 'size and mime type: one bag, one field' },
57
+ // },
58
+
59
+ /**
60
+ * A declared NUMBER for one function — the last resort, for a schema that lives
61
+ * only in the database. It freezes: prefer `opaque.<origin>.schemas`.
62
+ */
63
+ // overrides: {
64
+ // 'POST /surveys/:id/answers': { det: 42, reason: 'the form has 42 fields, defined in the database' },
52
65
  // },
53
66
 
54
67
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@filipebraida/adonis-function-points",
3
3
  "description": "Automated function point counting and code metrics for AdonisJS applications.",
4
- "version": "0.5.0",
4
+ "version": "0.7.0",
5
5
  "engines": {
6
6
  "node": ">=24.0.0"
7
7
  },