tina4-nodejs 3.13.131 → 3.13.133

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.
@@ -0,0 +1,101 @@
1
+ /**
2
+ * ModelCollection — a list of ORM models that also carries the query total.
3
+ *
4
+ * What the ORM read queries (`where` / `select` / `find` filter-form / `all` /
5
+ * `withTrashed`) return. It IS an Array — iterate it, index it, slice it, `.map`
6
+ * it, `.filter` it, read `.length`, `JSON.stringify` it — so every existing
7
+ * caller keeps working unchanged (`Array.isArray(coll) === true`). It adds one
8
+ * thing: the TOTAL number of rows matching the query's filter, independent of
9
+ * `limit` / `offset`.
10
+ *
11
+ * The total is free. Every one of those methods already runs the fetch COUNT
12
+ * probe (`db.fetch` / `probeTotal`) that computes `SELECT COUNT(*)` for the same
13
+ * filter; the ORM used to hydrate the page of models and throw that count away.
14
+ * This class carries it instead, so a caller with 20 models on the page can
15
+ * still learn there are 250 rows in the set. ZERO extra queries.
16
+ *
17
+ * Uniform across all four Tina4 frameworks (ADR-0064). Same concept, language-
18
+ * idiomatic accessor name:
19
+ *
20
+ * Python / Ruby : get_total_records() to_paginate()
21
+ * PHP / Node : getTotalRecords() toPaginate()
22
+ *
23
+ * The accessor is a METHOD, not a `.total` property, on purpose: `Array#count`
24
+ * exists in Ruby and `list.count()` in Python, so a `.count` would shadow a
25
+ * built-in. `DatabaseResult` keeps its `.count` property (it is not a list); both
26
+ * expose the identical seven-key `toPaginate()` envelope.
27
+ *
28
+ * ### Why a real Array subclass with `[Symbol.species] === Array`
29
+ *
30
+ * `class X extends Array` is the parity-faithful backing (the ADR-0064 table
31
+ * says Node is a "subclass of Array"), and it keeps `Array.isArray` true and
32
+ * `instanceof ModelCollection` true (mirroring Python's `isinstance`). BUT a bare
33
+ * Array subclass is a footgun: `map`/`filter`/`slice` build the result via
34
+ * `this.constructor[Symbol.species]`, calling the constructor with a single
35
+ * NUMBER (the length), and `new X(oneNumber)` means "length N", not "one
36
+ * element". Overriding `[Symbol.species]` to return `Array` makes every derived
37
+ * operation build a plain `Array` — so `.map`/`.filter`/`.slice`/spread never
38
+ * touch this constructor and can never explode. The constructor below is also
39
+ * defended against the numeric-length call directly, so even a path that ignores
40
+ * species is safe.
41
+ */
42
+ /** The canonical pagination envelope — seven snake_case keys (ADR-0043/0064). */
43
+ export interface PaginateEnvelope {
44
+ records: unknown[];
45
+ total: number;
46
+ page: number;
47
+ per_page: number;
48
+ total_pages: number;
49
+ limit: number;
50
+ offset: number;
51
+ }
52
+ export declare class ModelCollection<T = unknown> extends Array<T> {
53
+ /** Total rows matching the query's filter (ignores limit/offset). */
54
+ private _total;
55
+ /** The SQL limit that produced this page. */
56
+ private _limit;
57
+ /** The SQL offset that produced this page. */
58
+ private _offset;
59
+ /**
60
+ * Derived array operations (`map`, `filter`, `slice`, spread, …) build a plain
61
+ * `Array`, never another `ModelCollection`. This is what defuses the Array-
62
+ * subclass constructor/species trap: the engine never calls this constructor
63
+ * with a length during those operations.
64
+ */
65
+ static get [Symbol.species](): ArrayConstructor;
66
+ /**
67
+ * @param items the page of hydrated model instances (or the length, when the
68
+ * engine constructs a derived array — defended against here).
69
+ * @param total total rows matching the query's filter (ignores limit/offset).
70
+ * @param limit the SQL limit that produced this page.
71
+ * @param offset the SQL offset that produced this page.
72
+ */
73
+ constructor(items?: readonly T[] | number, total?: number, limit?: number, offset?: number);
74
+ /**
75
+ * Total rows matching the query's filter, ignoring limit/offset.
76
+ *
77
+ * This is the whole point of the collection: the page slice you are iterating
78
+ * is capped by `limit`, but this number is the full count of matching rows —
79
+ * what a pager needs to render "page 3 of 13".
80
+ */
81
+ getTotalRecords(): number;
82
+ /**
83
+ * The canonical pagination envelope — seven snake_case keys, identical to
84
+ * `DatabaseResult.toPaginate()` (ADR-0043) and to the other three frameworks'
85
+ * `toPaginate()` / `to_paginate()`.
86
+ *
87
+ * records the page's rows as plain objects (never re-sliced)
88
+ * total getTotalRecords() — the true total for the filter
89
+ * page floor(offset / per_page) + 1
90
+ * per_page the query's limit
91
+ * total_pages ceil(total / per_page)
92
+ * limit the SQL limit actually applied
93
+ * offset the SQL offset actually applied
94
+ *
95
+ * `records` are model dicts (via `toDict()`, the same serialisation the
96
+ * framework applies to a model in a JSON response), so the JSON a client sees
97
+ * matches `DatabaseResult` exactly — the result is uniform whether the route
98
+ * returned a raw `db.fetch()` or an ORM query.
99
+ */
100
+ toPaginate(): PaginateEnvelope;
101
+ }