@imqueue/pg-sequelize 4.2.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.
- package/CHANGELOG.md +50 -0
- package/CONTRIBUTING.md +58 -0
- package/CONTRIBUTION-TERMS.md +79 -0
- package/LICENSE +585 -0
- package/README.md +94 -0
- package/SECURITY.md +41 -0
- package/index.d.ts +86 -0
- package/index.js +87 -0
- package/package.json +75 -0
- package/src/BaseModel.d.ts +695 -0
- package/src/BaseModel.js +917 -0
- package/src/Graph.d.ts +215 -0
- package/src/Graph.js +257 -0
- package/src/decorators/AssociatedWith.d.ts +94 -0
- package/src/decorators/AssociatedWith.js +71 -0
- package/src/decorators/ColumnIndex.d.ts +206 -0
- package/src/decorators/ColumnIndex.js +98 -0
- package/src/decorators/CreatedBy.d.ts +27 -0
- package/src/decorators/CreatedBy.js +84 -0
- package/src/decorators/DeletedBy.d.ts +30 -0
- package/src/decorators/DeletedBy.js +89 -0
- package/src/decorators/DynamicView.d.ts +124 -0
- package/src/decorators/DynamicView.js +113 -0
- package/src/decorators/Emittable.d.ts +39 -0
- package/src/decorators/Emittable.js +42 -0
- package/src/decorators/NullableIndex.d.ts +77 -0
- package/src/decorators/NullableIndex.js +64 -0
- package/src/decorators/UpdatedBy.d.ts +27 -0
- package/src/decorators/UpdatedBy.js +105 -0
- package/src/decorators/View.d.ts +87 -0
- package/src/decorators/View.js +93 -0
- package/src/decorators/index.d.ts +32 -0
- package/src/decorators/index.js +33 -0
- package/src/helpers/index.d.ts +24 -0
- package/src/helpers/index.js +25 -0
- package/src/helpers/js.d.ts +61 -0
- package/src/helpers/js.js +88 -0
- package/src/helpers/query.d.ts +445 -0
- package/src/helpers/query.js +1095 -0
- package/src/index.d.ts +162 -0
- package/src/index.js +223 -0
- package/src/types/DataPage.d.ts +52 -0
- package/src/types/DataPage.js +2 -0
- package/src/types/FieldsInput.d.ts +41 -0
- package/src/types/FieldsInput.js +75 -0
- package/src/types/FilterInput.d.ts +136 -0
- package/src/types/FilterInput.js +291 -0
- package/src/types/JsonObject.d.ts +16 -0
- package/src/types/JsonObject.js +50 -0
- package/src/types/OrderByInput.d.ts +45 -0
- package/src/types/OrderByInput.js +80 -0
- package/src/types/PaginationInput.d.ts +44 -0
- package/src/types/PaginationInput.js +90 -0
- package/src/types/index.d.ts +30 -0
- package/src/types/index.js +31 -0
- package/src/types/ranges/DateRange.d.ts +27 -0
- package/src/types/ranges/DateRange.js +69 -0
- package/src/types/ranges/IRange.d.ts +47 -0
- package/src/types/ranges/IRange.js +2 -0
- package/src/types/ranges/NumericRange.d.ts +19 -0
- package/src/types/ranges/NumericRange.js +61 -0
- package/src/types/ranges/index.d.ts +26 -0
- package/src/types/ranges/index.js +27 -0
package/src/Graph.d.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @imqueue/pg-sequelize - Sequelize ORM refines for @imqueue
|
|
3
|
+
*
|
|
4
|
+
* I'm Queue Software Project
|
|
5
|
+
* Copyright (C) 2025 imqueue.com <support@imqueue.com>
|
|
6
|
+
*
|
|
7
|
+
* This program is free software: you can redistribute it and/or modify
|
|
8
|
+
* it under the terms of the GNU General Public License as published by
|
|
9
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
* (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* This program is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
* GNU General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* You should have received a copy of the GNU General Public License
|
|
18
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
19
|
+
*
|
|
20
|
+
* If you want to use this code in a closed source (commercial) project, you can
|
|
21
|
+
* purchase a proprietary commercial license. Please contact us at
|
|
22
|
+
* <support@imqueue.com> to get commercial licensing options.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* A graph as adjacency lists: each vertex mapped to the vertices it points at.
|
|
26
|
+
*/
|
|
27
|
+
export type GraphMap<T> = Map<T, T[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Called once per vertex during a traversal.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* Returning `false` prunes the walk at that vertex — its own edges are not
|
|
33
|
+
* followed — rather than ending the traversal. Anything else, `undefined`
|
|
34
|
+
* included, carries on. The second argument is the live visited map, so a
|
|
35
|
+
* callback can see what has already been reached, and add to it to steer the
|
|
36
|
+
* walk away from a vertex it does not want visited.
|
|
37
|
+
*/
|
|
38
|
+
export type GraphForeachCallback<T> = (vertex: T, visited: Map<T, boolean>) => false | void;
|
|
39
|
+
/**
|
|
40
|
+
* A directed, unweighted graph with depth-first traversal and cycle detection.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* Small on purpose. It exists so model associations can be walked as a graph —
|
|
44
|
+
* `BaseModel.toGraph()` builds one — and the question worth asking of that graph is
|
|
45
|
+
* whether it has a cycle, because a cycle is a chain of `include`s that can be asked
|
|
46
|
+
* to include itself.
|
|
47
|
+
*
|
|
48
|
+
* Directed, despite what this said for a long time: an edge is recorded only on the
|
|
49
|
+
* vertex it starts from, so `addEdge(a, b)` does not make `hasEdge(b, a)` true. The
|
|
50
|
+
* cycle detection is the standard directed-graph one, with a recursion stack
|
|
51
|
+
* alongside the visited set, and it would be wrong for an undirected graph.
|
|
52
|
+
*
|
|
53
|
+
* Vertices are used as `Map` keys, so identity is what distinguishes them — model
|
|
54
|
+
* classes work, structurally equal objects do not.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const graph = Lead.toGraph();
|
|
59
|
+
*
|
|
60
|
+
* if (graph.isCycled()) {
|
|
61
|
+
* // some association path leads back to where it started
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare class Graph<T> {
|
|
66
|
+
/** The adjacency lists backing this graph. */
|
|
67
|
+
private list;
|
|
68
|
+
/**
|
|
69
|
+
* Adds vertices with no edges.
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
* Not idempotent: a vertex that is already present has its edges RESET, so guard
|
|
73
|
+
* with {@link Graph.hasVertex} when a vertex may already be there. Adding an edge
|
|
74
|
+
* from an unknown vertex adds it for you, which is the usual way one appears.
|
|
75
|
+
*
|
|
76
|
+
* @param vertex - Vertices to add.
|
|
77
|
+
* @returns This graph, for chaining.
|
|
78
|
+
*/
|
|
79
|
+
addVertex(...vertex: T[]): Graph<T>;
|
|
80
|
+
/**
|
|
81
|
+
* Removes vertices along with the edges leading out of them.
|
|
82
|
+
*
|
|
83
|
+
* @remarks
|
|
84
|
+
* Edges pointing AT a removed vertex are left behind, so other vertices can go on
|
|
85
|
+
* naming one that is gone — and a walk that follows such an edge simply finds no
|
|
86
|
+
* further edges rather than failing.
|
|
87
|
+
*
|
|
88
|
+
* @param vertex - Vertices to remove.
|
|
89
|
+
* @returns This graph, for chaining.
|
|
90
|
+
*/
|
|
91
|
+
delVertex(...vertex: T[]): Graph<T>;
|
|
92
|
+
/**
|
|
93
|
+
* Adds edges from one vertex to others.
|
|
94
|
+
*
|
|
95
|
+
* @remarks
|
|
96
|
+
* Directed: only `fromVertex` records them. It is added to the graph first if it
|
|
97
|
+
* is not there yet, while the targets are not — an edge may point at a vertex the
|
|
98
|
+
* graph does not otherwise know. Duplicate edges are kept as duplicates.
|
|
99
|
+
*
|
|
100
|
+
* @param fromVertex - Vertex the edges start from.
|
|
101
|
+
* @param toVertex - Vertices they point at.
|
|
102
|
+
* @returns This graph, for chaining.
|
|
103
|
+
*/
|
|
104
|
+
addEdge(fromVertex: T, ...toVertex: T[]): Graph<T>;
|
|
105
|
+
/**
|
|
106
|
+
* Removes edges from one vertex to others.
|
|
107
|
+
*
|
|
108
|
+
* @remarks
|
|
109
|
+
* Every occurrence of each target is removed, so a duplicated edge goes entirely.
|
|
110
|
+
* A vertex with no edges, or one that is not in the graph, is left alone.
|
|
111
|
+
*
|
|
112
|
+
* @param fromVertex - Vertex to remove edges from.
|
|
113
|
+
* @param toVertex - Vertices to stop pointing at.
|
|
114
|
+
* @returns This graph, for chaining.
|
|
115
|
+
*/
|
|
116
|
+
delEdge(fromVertex: T, ...toVertex: T[]): Graph<T>;
|
|
117
|
+
/**
|
|
118
|
+
* Whether one vertex points at another.
|
|
119
|
+
*
|
|
120
|
+
* @remarks
|
|
121
|
+
* Directed, so the order of the arguments matters: this asks about an edge from
|
|
122
|
+
* `vertex` to `edge` and says nothing about the other direction.
|
|
123
|
+
*
|
|
124
|
+
* @param vertex - Vertex the edge would start from.
|
|
125
|
+
* @param edge - Vertex it would point at.
|
|
126
|
+
* @returns `true` when that edge is present.
|
|
127
|
+
*/
|
|
128
|
+
hasEdge(vertex: T, edge: T): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Whether a vertex is in this graph.
|
|
131
|
+
*
|
|
132
|
+
* @remarks
|
|
133
|
+
* By identity, since vertices are `Map` keys. A vertex that is only pointed at by
|
|
134
|
+
* an edge and never added is not in the graph.
|
|
135
|
+
*
|
|
136
|
+
* @param vertex - Vertex to look for.
|
|
137
|
+
* @returns `true` when the graph holds it.
|
|
138
|
+
*/
|
|
139
|
+
hasVertex(vertex: T): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Visits every vertex once, depth first.
|
|
142
|
+
*
|
|
143
|
+
* @remarks
|
|
144
|
+
* Walks from each vertex in turn, sharing one visited set across all of them — so
|
|
145
|
+
* the callback sees each vertex exactly once no matter how many paths reach it,
|
|
146
|
+
* and a disconnected part of the graph is covered too. A callback returning
|
|
147
|
+
* `false` prunes that branch; the traversal moves on to the next vertex rather
|
|
148
|
+
* than stopping.
|
|
149
|
+
*
|
|
150
|
+
* @param callback - Called once per vertex.
|
|
151
|
+
* @returns This graph, for chaining.
|
|
152
|
+
*/
|
|
153
|
+
forEach(callback: GraphForeachCallback<T>): Graph<T>;
|
|
154
|
+
/**
|
|
155
|
+
* Walks depth first from one vertex.
|
|
156
|
+
*
|
|
157
|
+
* @remarks
|
|
158
|
+
* Follows edges as far as they go, marking each vertex as it arrives and never
|
|
159
|
+
* arriving twice, which is what makes it safe on a cyclic graph. Passing a visited
|
|
160
|
+
* map of your own both continues an earlier walk and lets you exclude vertices by
|
|
161
|
+
* marking them before starting.
|
|
162
|
+
*
|
|
163
|
+
* @param vertex - Vertex to start from.
|
|
164
|
+
* @param callback - Called once per vertex reached; returning `false` prunes.
|
|
165
|
+
* @param visited - Vertices already reached. A fresh map by default.
|
|
166
|
+
* @returns This graph, for chaining.
|
|
167
|
+
*/
|
|
168
|
+
walk(vertex: T, callback?: GraphForeachCallback<T>, visited?: Map<T, boolean>): Graph<T>;
|
|
169
|
+
/**
|
|
170
|
+
* Every vertex reachable from one vertex, in the order a depth-first walk finds
|
|
171
|
+
* them.
|
|
172
|
+
*
|
|
173
|
+
* @remarks
|
|
174
|
+
* The reachable SET, not the longest path — each vertex appears once however many
|
|
175
|
+
* routes lead to it, and the starting vertex is the first entry. Reading it as a
|
|
176
|
+
* path is what makes a cyclic graph look as though it terminates.
|
|
177
|
+
*
|
|
178
|
+
* @param vertex - Vertex to start from.
|
|
179
|
+
* @returns An iterator over the reachable vertices.
|
|
180
|
+
*/
|
|
181
|
+
path(vertex: T): IterableIterator<T>;
|
|
182
|
+
/**
|
|
183
|
+
* Whether any path in this graph leads back to where it started.
|
|
184
|
+
*
|
|
185
|
+
* @remarks
|
|
186
|
+
* Checks from every vertex, so a cycle in a part of the graph nothing else reaches
|
|
187
|
+
* is still found. A self-edge counts. For model associations this is the question
|
|
188
|
+
* that matters: a cycle is an `include` chain that can be asked to include itself.
|
|
189
|
+
*
|
|
190
|
+
* @returns `true` when the graph contains a cycle.
|
|
191
|
+
*/
|
|
192
|
+
isCycled(): boolean;
|
|
193
|
+
/**
|
|
194
|
+
* The vertices in this graph, in insertion order.
|
|
195
|
+
*
|
|
196
|
+
* @returns An iterator over the vertices.
|
|
197
|
+
*/
|
|
198
|
+
vertices(): IterableIterator<T>;
|
|
199
|
+
/**
|
|
200
|
+
* Looks for a cycle reachable from one vertex.
|
|
201
|
+
*
|
|
202
|
+
* @remarks
|
|
203
|
+
* Depth-first with a recursion stack beside the visited set: reaching a vertex
|
|
204
|
+
* that is still on the stack means the path has come back on itself, whereas
|
|
205
|
+
* reaching one that is merely visited means it was explored already and holds no
|
|
206
|
+
* cycle. The stack entry is cleared on the way out, which is what keeps two
|
|
207
|
+
* separate paths through one vertex from reading as a cycle.
|
|
208
|
+
*
|
|
209
|
+
* @param vertex - Vertex to search from.
|
|
210
|
+
* @param visited - Vertices explored in this run, added to as it goes.
|
|
211
|
+
* @param stack - Vertices on the current path.
|
|
212
|
+
* @returns `true` when a cycle is reachable from `vertex`.
|
|
213
|
+
*/
|
|
214
|
+
private detectCycle;
|
|
215
|
+
}
|
package/src/Graph.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A directed, unweighted graph with depth-first traversal and cycle detection.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Small on purpose. It exists so model associations can be walked as a graph —
|
|
6
|
+
* `BaseModel.toGraph()` builds one — and the question worth asking of that graph is
|
|
7
|
+
* whether it has a cycle, because a cycle is a chain of `include`s that can be asked
|
|
8
|
+
* to include itself.
|
|
9
|
+
*
|
|
10
|
+
* Directed, despite what this said for a long time: an edge is recorded only on the
|
|
11
|
+
* vertex it starts from, so `addEdge(a, b)` does not make `hasEdge(b, a)` true. The
|
|
12
|
+
* cycle detection is the standard directed-graph one, with a recursion stack
|
|
13
|
+
* alongside the visited set, and it would be wrong for an undirected graph.
|
|
14
|
+
*
|
|
15
|
+
* Vertices are used as `Map` keys, so identity is what distinguishes them — model
|
|
16
|
+
* classes work, structurally equal objects do not.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const graph = Lead.toGraph();
|
|
21
|
+
*
|
|
22
|
+
* if (graph.isCycled()) {
|
|
23
|
+
* // some association path leads back to where it started
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class Graph {
|
|
28
|
+
/** The adjacency lists backing this graph. */
|
|
29
|
+
list = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* Adds vertices with no edges.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* Not idempotent: a vertex that is already present has its edges RESET, so guard
|
|
35
|
+
* with {@link Graph.hasVertex} when a vertex may already be there. Adding an edge
|
|
36
|
+
* from an unknown vertex adds it for you, which is the usual way one appears.
|
|
37
|
+
*
|
|
38
|
+
* @param vertex - Vertices to add.
|
|
39
|
+
* @returns This graph, for chaining.
|
|
40
|
+
*/
|
|
41
|
+
addVertex(...vertex) {
|
|
42
|
+
for (const v of vertex) {
|
|
43
|
+
this.list.set(v, []);
|
|
44
|
+
}
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Removes vertices along with the edges leading out of them.
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* Edges pointing AT a removed vertex are left behind, so other vertices can go on
|
|
52
|
+
* naming one that is gone — and a walk that follows such an edge simply finds no
|
|
53
|
+
* further edges rather than failing.
|
|
54
|
+
*
|
|
55
|
+
* @param vertex - Vertices to remove.
|
|
56
|
+
* @returns This graph, for chaining.
|
|
57
|
+
*/
|
|
58
|
+
delVertex(...vertex) {
|
|
59
|
+
for (const v of vertex) {
|
|
60
|
+
this.list.delete(v);
|
|
61
|
+
}
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Adds edges from one vertex to others.
|
|
66
|
+
*
|
|
67
|
+
* @remarks
|
|
68
|
+
* Directed: only `fromVertex` records them. It is added to the graph first if it
|
|
69
|
+
* is not there yet, while the targets are not — an edge may point at a vertex the
|
|
70
|
+
* graph does not otherwise know. Duplicate edges are kept as duplicates.
|
|
71
|
+
*
|
|
72
|
+
* @param fromVertex - Vertex the edges start from.
|
|
73
|
+
* @param toVertex - Vertices they point at.
|
|
74
|
+
* @returns This graph, for chaining.
|
|
75
|
+
*/
|
|
76
|
+
addEdge(fromVertex, ...toVertex) {
|
|
77
|
+
let edges = this.list.get(fromVertex);
|
|
78
|
+
if (!edges) {
|
|
79
|
+
this.addVertex(fromVertex);
|
|
80
|
+
edges = this.list.get(fromVertex);
|
|
81
|
+
}
|
|
82
|
+
edges.push(...toVertex);
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Removes edges from one vertex to others.
|
|
87
|
+
*
|
|
88
|
+
* @remarks
|
|
89
|
+
* Every occurrence of each target is removed, so a duplicated edge goes entirely.
|
|
90
|
+
* A vertex with no edges, or one that is not in the graph, is left alone.
|
|
91
|
+
*
|
|
92
|
+
* @param fromVertex - Vertex to remove edges from.
|
|
93
|
+
* @param toVertex - Vertices to stop pointing at.
|
|
94
|
+
* @returns This graph, for chaining.
|
|
95
|
+
*/
|
|
96
|
+
delEdge(fromVertex, ...toVertex) {
|
|
97
|
+
const edges = this.list.get(fromVertex);
|
|
98
|
+
if (!(edges && edges.length)) {
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
for (const vertex of toVertex) {
|
|
102
|
+
while (~edges.indexOf(vertex)) {
|
|
103
|
+
edges.splice(edges.indexOf(vertex), 1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Whether one vertex points at another.
|
|
110
|
+
*
|
|
111
|
+
* @remarks
|
|
112
|
+
* Directed, so the order of the arguments matters: this asks about an edge from
|
|
113
|
+
* `vertex` to `edge` and says nothing about the other direction.
|
|
114
|
+
*
|
|
115
|
+
* @param vertex - Vertex the edge would start from.
|
|
116
|
+
* @param edge - Vertex it would point at.
|
|
117
|
+
* @returns `true` when that edge is present.
|
|
118
|
+
*/
|
|
119
|
+
hasEdge(vertex, edge) {
|
|
120
|
+
return !!~(this.list.get(vertex) || []).indexOf(edge);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Whether a vertex is in this graph.
|
|
124
|
+
*
|
|
125
|
+
* @remarks
|
|
126
|
+
* By identity, since vertices are `Map` keys. A vertex that is only pointed at by
|
|
127
|
+
* an edge and never added is not in the graph.
|
|
128
|
+
*
|
|
129
|
+
* @param vertex - Vertex to look for.
|
|
130
|
+
* @returns `true` when the graph holds it.
|
|
131
|
+
*/
|
|
132
|
+
hasVertex(vertex) {
|
|
133
|
+
return this.list.has(vertex);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Visits every vertex once, depth first.
|
|
137
|
+
*
|
|
138
|
+
* @remarks
|
|
139
|
+
* Walks from each vertex in turn, sharing one visited set across all of them — so
|
|
140
|
+
* the callback sees each vertex exactly once no matter how many paths reach it,
|
|
141
|
+
* and a disconnected part of the graph is covered too. A callback returning
|
|
142
|
+
* `false` prunes that branch; the traversal moves on to the next vertex rather
|
|
143
|
+
* than stopping.
|
|
144
|
+
*
|
|
145
|
+
* @param callback - Called once per vertex.
|
|
146
|
+
* @returns This graph, for chaining.
|
|
147
|
+
*/
|
|
148
|
+
forEach(callback) {
|
|
149
|
+
const visited = new Map();
|
|
150
|
+
for (const node of this.list.keys()) {
|
|
151
|
+
this.walk(node, callback, visited);
|
|
152
|
+
}
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Walks depth first from one vertex.
|
|
157
|
+
*
|
|
158
|
+
* @remarks
|
|
159
|
+
* Follows edges as far as they go, marking each vertex as it arrives and never
|
|
160
|
+
* arriving twice, which is what makes it safe on a cyclic graph. Passing a visited
|
|
161
|
+
* map of your own both continues an earlier walk and lets you exclude vertices by
|
|
162
|
+
* marking them before starting.
|
|
163
|
+
*
|
|
164
|
+
* @param vertex - Vertex to start from.
|
|
165
|
+
* @param callback - Called once per vertex reached; returning `false` prunes.
|
|
166
|
+
* @param visited - Vertices already reached. A fresh map by default.
|
|
167
|
+
* @returns This graph, for chaining.
|
|
168
|
+
*/
|
|
169
|
+
walk(vertex, callback, visited = new Map()) {
|
|
170
|
+
if (!visited.get(vertex)) {
|
|
171
|
+
visited.set(vertex, true);
|
|
172
|
+
if (callback && callback(vertex, visited) === false) {
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
for (const neighbor of this.list.get(vertex) || []) {
|
|
176
|
+
this.walk(neighbor, callback, visited);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Every vertex reachable from one vertex, in the order a depth-first walk finds
|
|
183
|
+
* them.
|
|
184
|
+
*
|
|
185
|
+
* @remarks
|
|
186
|
+
* The reachable SET, not the longest path — each vertex appears once however many
|
|
187
|
+
* routes lead to it, and the starting vertex is the first entry. Reading it as a
|
|
188
|
+
* path is what makes a cyclic graph look as though it terminates.
|
|
189
|
+
*
|
|
190
|
+
* @param vertex - Vertex to start from.
|
|
191
|
+
* @returns An iterator over the reachable vertices.
|
|
192
|
+
*/
|
|
193
|
+
path(vertex) {
|
|
194
|
+
const visited = new Map();
|
|
195
|
+
this.walk(vertex, undefined, visited);
|
|
196
|
+
return visited.keys();
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Whether any path in this graph leads back to where it started.
|
|
200
|
+
*
|
|
201
|
+
* @remarks
|
|
202
|
+
* Checks from every vertex, so a cycle in a part of the graph nothing else reaches
|
|
203
|
+
* is still found. A self-edge counts. For model associations this is the question
|
|
204
|
+
* that matters: a cycle is an `include` chain that can be asked to include itself.
|
|
205
|
+
*
|
|
206
|
+
* @returns `true` when the graph contains a cycle.
|
|
207
|
+
*/
|
|
208
|
+
isCycled() {
|
|
209
|
+
const visited = new Map();
|
|
210
|
+
const stack = new Map();
|
|
211
|
+
for (const node of this.list.keys()) {
|
|
212
|
+
if (this.detectCycle(node, visited, stack)) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The vertices in this graph, in insertion order.
|
|
220
|
+
*
|
|
221
|
+
* @returns An iterator over the vertices.
|
|
222
|
+
*/
|
|
223
|
+
vertices() {
|
|
224
|
+
return this.list.keys();
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Looks for a cycle reachable from one vertex.
|
|
228
|
+
*
|
|
229
|
+
* @remarks
|
|
230
|
+
* Depth-first with a recursion stack beside the visited set: reaching a vertex
|
|
231
|
+
* that is still on the stack means the path has come back on itself, whereas
|
|
232
|
+
* reaching one that is merely visited means it was explored already and holds no
|
|
233
|
+
* cycle. The stack entry is cleared on the way out, which is what keeps two
|
|
234
|
+
* separate paths through one vertex from reading as a cycle.
|
|
235
|
+
*
|
|
236
|
+
* @param vertex - Vertex to search from.
|
|
237
|
+
* @param visited - Vertices explored in this run, added to as it goes.
|
|
238
|
+
* @param stack - Vertices on the current path.
|
|
239
|
+
* @returns `true` when a cycle is reachable from `vertex`.
|
|
240
|
+
*/
|
|
241
|
+
detectCycle(vertex, visited, stack) {
|
|
242
|
+
if (!visited.get(vertex)) {
|
|
243
|
+
visited.set(vertex, true);
|
|
244
|
+
stack.set(vertex, true);
|
|
245
|
+
for (const currentNode of this.list.get(vertex) || []) {
|
|
246
|
+
if ((!visited.get(currentNode) &&
|
|
247
|
+
this.detectCycle(currentNode, visited, stack)) ||
|
|
248
|
+
stack.get(currentNode)) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
stack.set(vertex, false);
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=Graph.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @imqueue/pg-sequelize - Sequelize ORM refines for @imqueue
|
|
3
|
+
*
|
|
4
|
+
* I'm Queue Software Project
|
|
5
|
+
* Copyright (C) 2025 imqueue.com <support@imqueue.com>
|
|
6
|
+
*
|
|
7
|
+
* This program is free software: you can redistribute it and/or modify
|
|
8
|
+
* it under the terms of the GNU General Public License as published by
|
|
9
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
* (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* This program is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
* GNU General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* You should have received a copy of the GNU General Public License
|
|
18
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
19
|
+
*
|
|
20
|
+
* If you want to use this code in a closed source (commercial) project, you can
|
|
21
|
+
* purchase a proprietary commercial license. Please contact us at
|
|
22
|
+
* <support@imqueue.com> to get commercial licensing options.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Describes the association a filter field stands for.
|
|
26
|
+
*/
|
|
27
|
+
export interface IAssociated {
|
|
28
|
+
/** The model on the other side of the association. */
|
|
29
|
+
model: any;
|
|
30
|
+
/**
|
|
31
|
+
* The input class describing that model's own filter fields.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* Read recursively, so a filter can nest as deep as the input classes do.
|
|
35
|
+
*/
|
|
36
|
+
input: any;
|
|
37
|
+
/**
|
|
38
|
+
* Association name on the model, when it differs from the field name.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* Recorded and then never read: nothing in this package consumes it today, so
|
|
42
|
+
* the field name and the association name have to match. Left in place because
|
|
43
|
+
* removing it from the public type would break callers that set it.
|
|
44
|
+
*/
|
|
45
|
+
modelFieldName?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Marks a field of a filter input as standing for an association rather than for a
|
|
49
|
+
* column.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* This is what lets a caller's filter reach through a relation. `query.toWhereOptions`
|
|
53
|
+
* instantiates the input class, and any property it finds carrying one of these
|
|
54
|
+
* descriptors becomes a required `include` on the associated model, with the nested
|
|
55
|
+
* filter resolved against that model's own input class — recursively, so the nesting
|
|
56
|
+
* can go as deep as the input classes do. Without it the nested object would be
|
|
57
|
+
* treated as a column filter and produce a where clause on a column that does not
|
|
58
|
+
* exist.
|
|
59
|
+
*
|
|
60
|
+
* Declare the field with `declare`, and do not instantiate the input class yourself.
|
|
61
|
+
* The descriptor lives on the prototype as a read-only, non-enumerable property, so a
|
|
62
|
+
* real field declaration would shadow it with `undefined` under
|
|
63
|
+
* `useDefineForClassFields` and the association would be silently ignored — and an
|
|
64
|
+
* assignment to it throws in strict mode. These classes are descriptions of a wire
|
|
65
|
+
* shape, not containers for one.
|
|
66
|
+
*
|
|
67
|
+
* The thunk is resolved on first read rather than at decoration time, which is what
|
|
68
|
+
* makes it worth being a thunk: input classes and models routinely import each other,
|
|
69
|
+
* and a decorator body runs while those modules are still initialising, so resolving
|
|
70
|
+
* eagerly could capture `undefined` as the model. First read happens on the first
|
|
71
|
+
* query, by which point every module is loaded.
|
|
72
|
+
*
|
|
73
|
+
* @param cb - Returns the association descriptor.
|
|
74
|
+
* @returns A property decorator.
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* export class PaymentListInput {
|
|
78
|
+
* @property(() => `number[] | ${FilterInput.name}`, true)
|
|
79
|
+
* declare public amount?: number[] | FilterInput;
|
|
80
|
+
*
|
|
81
|
+
* @property('PaymentTypeListInput', true)
|
|
82
|
+
* @AssociatedWith(() => ({
|
|
83
|
+
* model: PaymentType,
|
|
84
|
+
* input: PaymentTypeListInput,
|
|
85
|
+
* }))
|
|
86
|
+
* declare public paymentType?: PaymentTypeListInput;
|
|
87
|
+
* }
|
|
88
|
+
*
|
|
89
|
+
* // A filter of { paymentType: { name: 'card' } } now becomes a required join on
|
|
90
|
+
* // PaymentType with the name filter applied there.
|
|
91
|
+
* const options = query.toWhereOptions(filter, PaymentListInput);
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function AssociatedWith(cb: () => IAssociated): (target: any, key: string) => any;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks a field of a filter input as standing for an association rather than for a
|
|
3
|
+
* column.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* This is what lets a caller's filter reach through a relation. `query.toWhereOptions`
|
|
7
|
+
* instantiates the input class, and any property it finds carrying one of these
|
|
8
|
+
* descriptors becomes a required `include` on the associated model, with the nested
|
|
9
|
+
* filter resolved against that model's own input class — recursively, so the nesting
|
|
10
|
+
* can go as deep as the input classes do. Without it the nested object would be
|
|
11
|
+
* treated as a column filter and produce a where clause on a column that does not
|
|
12
|
+
* exist.
|
|
13
|
+
*
|
|
14
|
+
* Declare the field with `declare`, and do not instantiate the input class yourself.
|
|
15
|
+
* The descriptor lives on the prototype as a read-only, non-enumerable property, so a
|
|
16
|
+
* real field declaration would shadow it with `undefined` under
|
|
17
|
+
* `useDefineForClassFields` and the association would be silently ignored — and an
|
|
18
|
+
* assignment to it throws in strict mode. These classes are descriptions of a wire
|
|
19
|
+
* shape, not containers for one.
|
|
20
|
+
*
|
|
21
|
+
* The thunk is resolved on first read rather than at decoration time, which is what
|
|
22
|
+
* makes it worth being a thunk: input classes and models routinely import each other,
|
|
23
|
+
* and a decorator body runs while those modules are still initialising, so resolving
|
|
24
|
+
* eagerly could capture `undefined` as the model. First read happens on the first
|
|
25
|
+
* query, by which point every module is loaded.
|
|
26
|
+
*
|
|
27
|
+
* @param cb - Returns the association descriptor.
|
|
28
|
+
* @returns A property decorator.
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* export class PaymentListInput {
|
|
32
|
+
* @property(() => `number[] | ${FilterInput.name}`, true)
|
|
33
|
+
* declare public amount?: number[] | FilterInput;
|
|
34
|
+
*
|
|
35
|
+
* @property('PaymentTypeListInput', true)
|
|
36
|
+
* @AssociatedWith(() => ({
|
|
37
|
+
* model: PaymentType,
|
|
38
|
+
* input: PaymentTypeListInput,
|
|
39
|
+
* }))
|
|
40
|
+
* declare public paymentType?: PaymentTypeListInput;
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* // A filter of { paymentType: { name: 'card' } } now becomes a required join on
|
|
44
|
+
* // PaymentType with the name filter applied there.
|
|
45
|
+
* const options = query.toWhereOptions(filter, PaymentListInput);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function AssociatedWith(cb) {
|
|
49
|
+
return (target, key) => {
|
|
50
|
+
let resolved;
|
|
51
|
+
let done = false;
|
|
52
|
+
Object.defineProperty(target, key, {
|
|
53
|
+
get() {
|
|
54
|
+
if (!done) {
|
|
55
|
+
done = true;
|
|
56
|
+
const association = cb();
|
|
57
|
+
if (association) {
|
|
58
|
+
resolved = {
|
|
59
|
+
model: association.model,
|
|
60
|
+
input: association.input,
|
|
61
|
+
key: association.modelFieldName || key,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return resolved;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
return target;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=AssociatedWith.js.map
|