@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/CONTRIBUTING.md +58 -0
  3. package/CONTRIBUTION-TERMS.md +79 -0
  4. package/LICENSE +585 -0
  5. package/README.md +94 -0
  6. package/SECURITY.md +41 -0
  7. package/index.d.ts +86 -0
  8. package/index.js +87 -0
  9. package/package.json +75 -0
  10. package/src/BaseModel.d.ts +695 -0
  11. package/src/BaseModel.js +917 -0
  12. package/src/Graph.d.ts +215 -0
  13. package/src/Graph.js +257 -0
  14. package/src/decorators/AssociatedWith.d.ts +94 -0
  15. package/src/decorators/AssociatedWith.js +71 -0
  16. package/src/decorators/ColumnIndex.d.ts +206 -0
  17. package/src/decorators/ColumnIndex.js +98 -0
  18. package/src/decorators/CreatedBy.d.ts +27 -0
  19. package/src/decorators/CreatedBy.js +84 -0
  20. package/src/decorators/DeletedBy.d.ts +30 -0
  21. package/src/decorators/DeletedBy.js +89 -0
  22. package/src/decorators/DynamicView.d.ts +124 -0
  23. package/src/decorators/DynamicView.js +113 -0
  24. package/src/decorators/Emittable.d.ts +39 -0
  25. package/src/decorators/Emittable.js +42 -0
  26. package/src/decorators/NullableIndex.d.ts +77 -0
  27. package/src/decorators/NullableIndex.js +64 -0
  28. package/src/decorators/UpdatedBy.d.ts +27 -0
  29. package/src/decorators/UpdatedBy.js +105 -0
  30. package/src/decorators/View.d.ts +87 -0
  31. package/src/decorators/View.js +93 -0
  32. package/src/decorators/index.d.ts +32 -0
  33. package/src/decorators/index.js +33 -0
  34. package/src/helpers/index.d.ts +24 -0
  35. package/src/helpers/index.js +25 -0
  36. package/src/helpers/js.d.ts +61 -0
  37. package/src/helpers/js.js +88 -0
  38. package/src/helpers/query.d.ts +445 -0
  39. package/src/helpers/query.js +1095 -0
  40. package/src/index.d.ts +162 -0
  41. package/src/index.js +223 -0
  42. package/src/types/DataPage.d.ts +52 -0
  43. package/src/types/DataPage.js +2 -0
  44. package/src/types/FieldsInput.d.ts +41 -0
  45. package/src/types/FieldsInput.js +75 -0
  46. package/src/types/FilterInput.d.ts +136 -0
  47. package/src/types/FilterInput.js +291 -0
  48. package/src/types/JsonObject.d.ts +16 -0
  49. package/src/types/JsonObject.js +50 -0
  50. package/src/types/OrderByInput.d.ts +45 -0
  51. package/src/types/OrderByInput.js +80 -0
  52. package/src/types/PaginationInput.d.ts +44 -0
  53. package/src/types/PaginationInput.js +90 -0
  54. package/src/types/index.d.ts +30 -0
  55. package/src/types/index.js +31 -0
  56. package/src/types/ranges/DateRange.d.ts +27 -0
  57. package/src/types/ranges/DateRange.js +69 -0
  58. package/src/types/ranges/IRange.d.ts +47 -0
  59. package/src/types/ranges/IRange.js +2 -0
  60. package/src/types/ranges/NumericRange.d.ts +19 -0
  61. package/src/types/ranges/NumericRange.js +61 -0
  62. package/src/types/ranges/index.d.ts +26 -0
  63. package/src/types/ranges/index.js +27 -0
@@ -0,0 +1,206 @@
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
+ import 'reflect-metadata';
25
+ /**
26
+ * The property decorator a decorator factory hands back.
27
+ *
28
+ * @remarks
29
+ * Loose on purpose: it stands for the decorator returned by {@link (ColumnIndex:1) | ColumnIndex} and
30
+ * {@link (NullableIndex:1) | NullableIndex} when either is called with options, whose exact parameter list
31
+ * differs between TypeScript's legacy and current decorator emit.
32
+ */
33
+ export type FunctionType = (...args: any[]) => any;
34
+ /**
35
+ * The index methods Postgres offers, for {@link ColumnIndexOptions.method}.
36
+ *
37
+ * @remarks
38
+ * Postgres uses `BTREE` when nothing is given, and it is the only method with an
39
+ * ordered key — so it is the one to want unless the data has a shape another method
40
+ * is built for.
41
+ */
42
+ export declare enum IndexMethod {
43
+ /** Balanced tree: equality, ranges and ordering. Postgres's default. */
44
+ BTREE = "BTREE",
45
+ /** Hash: equality only, and smaller than a btree for that alone. */
46
+ HASH = "HASH",
47
+ /** Generalised search tree: geometric types, ranges, full-text. */
48
+ GIST = "GIST",
49
+ /** Space-partitioned GiST: quadtrees, tries and other unbalanced trees. */
50
+ SPGIST = "SPGIST",
51
+ /** Inverted index: containment in arrays, `jsonb` and full-text. */
52
+ GIN = "GIN",
53
+ /** Block range: huge tables whose values track their physical order. */
54
+ BRIN = "BRIN"
55
+ }
56
+ /**
57
+ * Sort direction of an index key, for {@link ColumnIndexOptions.order}.
58
+ *
59
+ * @remarks
60
+ * Rarely worth setting on its own, since a btree can be read backwards — it earns its
61
+ * keep when several keys sort in mixed directions, which a single-direction index
62
+ * cannot serve.
63
+ */
64
+ export declare enum SortOrder {
65
+ /** Ascending, which is Postgres's default. */
66
+ ASC = "ASC",
67
+ /** Descending. */
68
+ DESC = "DESC"
69
+ }
70
+ /**
71
+ * The clauses of the `CREATE INDEX` statement that a {@link (ColumnIndex:1) | ColumnIndex} declaration
72
+ * turns into.
73
+ *
74
+ * @remarks
75
+ * Every field is optional in practice — both decorators take a `Partial` of this, and
76
+ * an empty object declares a plain btree index on the column. The names follow
77
+ * Postgres's own `CREATE INDEX` syntax, and the values of the raw-SQL fields are
78
+ * emitted as written.
79
+ */
80
+ export interface ColumnIndexOptions {
81
+ /**
82
+ * Name of the index.
83
+ *
84
+ * @remarks
85
+ * Defaults to the table, the column and the declaration's position on the model,
86
+ * so two indices on one column cannot collide. Naming it yourself is what makes
87
+ * it recognisable later in `EXPLAIN` output and in `pg_indexes`.
88
+ */
89
+ name: string;
90
+ /** Index method. Postgres uses `BTREE` when this is left out. */
91
+ method: IndexMethod;
92
+ /**
93
+ * Builds the index without locking writes out of the table.
94
+ *
95
+ * @remarks
96
+ * Applied to the drop as well as the create. Postgres refuses to run either
97
+ * inside a transaction, and a concurrent build that fails leaves an invalid
98
+ * index behind to be dropped by hand — the price of not blocking a live table.
99
+ */
100
+ concurrently: boolean;
101
+ /**
102
+ * `true` sorts nulls first, `false` sorts them last, and leaving it out follows
103
+ * Postgres.
104
+ *
105
+ * @remarks
106
+ * Postgres's own default depends on the direction: nulls last for `ASC`, first
107
+ * for `DESC`. Set it only to match an `ORDER BY` that asks for the other one.
108
+ */
109
+ nullsFirst: boolean;
110
+ /** Sort direction of the index key. */
111
+ order: SortOrder;
112
+ /**
113
+ * Restricts the index to the rows this SQL condition matches.
114
+ *
115
+ * @remarks
116
+ * A partial index: smaller and cheaper to maintain, and used only where the
117
+ * planner can prove a query's own condition implies this one. Raw SQL, emitted
118
+ * as written, so the quoting is yours to get right.
119
+ */
120
+ predicate: string;
121
+ /**
122
+ * Indexes this SQL expression rather than the column.
123
+ *
124
+ * @remarks
125
+ * For filtering on a computed value — `lower("email")` being the standard case,
126
+ * which nothing but an expression index can serve. Raw SQL, and it replaces the
127
+ * column as the key rather than joining it.
128
+ */
129
+ expression: string;
130
+ /**
131
+ * Extra columns to carry in the index without indexing them.
132
+ *
133
+ * @remarks
134
+ * A covering index, so the planner can answer from the index alone instead of
135
+ * visiting the table. Btree only. Declared but never emitted in earlier
136
+ * versions.
137
+ */
138
+ include: string[];
139
+ /** Collation of the index key, where it differs from the column's own. */
140
+ collation: string;
141
+ /**
142
+ * Operator class for the key.
143
+ *
144
+ * @remarks
145
+ * How the method compares values: `text_pattern_ops` to make `LIKE 'x%'`
146
+ * indexable, `jsonb_path_ops` for a narrower GIN index. Emitted as written.
147
+ */
148
+ opClass: string;
149
+ /** Tablespace to build the index in, instead of the database default. */
150
+ tablespace: string;
151
+ /**
152
+ * Leaves an index that already exists alone rather than rebuilding it.
153
+ *
154
+ * @remarks
155
+ * Turns the pass into `CREATE INDEX IF NOT EXISTS` with no drop in front of it,
156
+ * so an existing index survives even if its declaration has changed since. The
157
+ * default rebuilds on every sync, which is always correct and always costs a
158
+ * full build.
159
+ */
160
+ safe: boolean;
161
+ /** Rejects duplicate values, enforcing uniqueness through the index. */
162
+ unique: boolean;
163
+ }
164
+ /**
165
+ * Declares an index on a model column, with Postgres's own index options.
166
+ *
167
+ * @remarks
168
+ * `Index` from `sequelize-typescript` is re-exported here and covers the ordinary
169
+ * cases through sequelize's own sync. This declares the index as a `CREATE INDEX`
170
+ * statement instead, which is what makes the Postgres-specific clauses reachable —
171
+ * partial indices, expression keys, operator classes, concurrent builds, covering
172
+ * columns. `Sequelize.sync()` runs them after the tables and views are in place.
173
+ *
174
+ * Declarations accumulate, so a column can carry several, and each is named by its
175
+ * position unless you name it.
176
+ *
177
+ * @param options - The clauses to build into the statement.
178
+ * @returns A property decorator.
179
+ * @example
180
+ * ```typescript
181
+ * @Table
182
+ * export class Lead extends BaseModel<Lead> {
183
+ * // case-insensitive lookups, over live rows only
184
+ * @ColumnIndex({
185
+ * expression: 'lower("email")',
186
+ * predicate: '"deletedAt" IS NULL',
187
+ * unique: true,
188
+ * })
189
+ * @Column(DataType.STRING)
190
+ * public email: string;
191
+ * }
192
+ * ```
193
+ */
194
+ export declare function ColumnIndex(options: Partial<ColumnIndexOptions>): FunctionType;
195
+ /**
196
+ * Declares a plain btree index on a model column.
197
+ *
198
+ * @remarks
199
+ * The bare form of the decorator, equivalent to calling it with no options. Use the
200
+ * factory form for anything Postgres-specific.
201
+ *
202
+ * @param target - Model prototype the property belongs to.
203
+ * @param propertyName - Column to index.
204
+ * @param propertyDescriptor - Unused; present because a decorator receives it.
205
+ */
206
+ export declare function ColumnIndex(target: any, propertyName: string, propertyDescriptor?: PropertyDescriptor): void;
@@ -0,0 +1,98 @@
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
+ import 'reflect-metadata';
25
+ import { addOptions, getOptions } from 'sequelize-typescript';
26
+ /**
27
+ * The index methods Postgres offers, for {@link ColumnIndexOptions.method}.
28
+ *
29
+ * @remarks
30
+ * Postgres uses `BTREE` when nothing is given, and it is the only method with an
31
+ * ordered key — so it is the one to want unless the data has a shape another method
32
+ * is built for.
33
+ */
34
+ export var IndexMethod;
35
+ (function (IndexMethod) {
36
+ /** Balanced tree: equality, ranges and ordering. Postgres's default. */
37
+ IndexMethod["BTREE"] = "BTREE";
38
+ /** Hash: equality only, and smaller than a btree for that alone. */
39
+ IndexMethod["HASH"] = "HASH";
40
+ /** Generalised search tree: geometric types, ranges, full-text. */
41
+ IndexMethod["GIST"] = "GIST";
42
+ /** Space-partitioned GiST: quadtrees, tries and other unbalanced trees. */
43
+ IndexMethod["SPGIST"] = "SPGIST";
44
+ /** Inverted index: containment in arrays, `jsonb` and full-text. */
45
+ IndexMethod["GIN"] = "GIN";
46
+ /** Block range: huge tables whose values track their physical order. */
47
+ IndexMethod["BRIN"] = "BRIN";
48
+ })(IndexMethod || (IndexMethod = {}));
49
+ /**
50
+ * Sort direction of an index key, for {@link ColumnIndexOptions.order}.
51
+ *
52
+ * @remarks
53
+ * Rarely worth setting on its own, since a btree can be read backwards — it earns its
54
+ * keep when several keys sort in mixed directions, which a single-direction index
55
+ * cannot serve.
56
+ */
57
+ export var SortOrder;
58
+ (function (SortOrder) {
59
+ /** Ascending, which is Postgres's default. */
60
+ SortOrder["ASC"] = "ASC";
61
+ /** Descending. */
62
+ SortOrder["DESC"] = "DESC";
63
+ })(SortOrder || (SortOrder = {}));
64
+ export function ColumnIndex(...args) {
65
+ if (args.length >= 2) {
66
+ const [target, propertyName, propertyDescriptor] = args;
67
+ return annotate(target, propertyName, propertyDescriptor);
68
+ }
69
+ return (target, propertyName, propertyDescriptor) => {
70
+ annotate(target, propertyName, propertyDescriptor, args[0]);
71
+ };
72
+ }
73
+ /**
74
+ * Appends one index declaration to the model's options.
75
+ *
76
+ * @param target - Model prototype the property belongs to.
77
+ * @param propertyName - Column to index.
78
+ * @param propertyDescriptor - Unused; present because a decorator receives it.
79
+ * @param options - The clauses to record for this index.
80
+ */
81
+ function annotate(target, propertyName, propertyDescriptor, options = {}) {
82
+ // Optional, because there may be no options bag yet. TypeScript applies
83
+ // property decorators BEFORE class decorators, so on an ordinary model this
84
+ // runs before `@Table` has created one, and reading `.indices` off undefined
85
+ // threw at import time — which made this decorator unusable exactly where it
86
+ // was meant to be used.
87
+ const indices = getOptions(target)?.indices || [];
88
+ addOptions(target, {
89
+ indices: [
90
+ ...indices,
91
+ {
92
+ column: propertyName,
93
+ options,
94
+ },
95
+ ],
96
+ });
97
+ }
98
+ //# sourceMappingURL=ColumnIndex.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Stamps the decorated column with the acting user id on INSERT, taken from the
3
+ * in-flight IMQ request metadata (`currentMetadata()?.userId`) — so the id never
4
+ * travels through method arguments and cannot be spoofed by a caller.
5
+ *
6
+ * A property decorator, reusable on any model field. The hook is a no-op when
7
+ * there is no acting user (system / unattributed writes) and never overwrites a
8
+ * value the application set explicitly.
9
+ *
10
+ * Two hooks are registered, because rows are inserted in two ways:
11
+ * - instance / single `create()` → `beforeCreate` (receives the instance)
12
+ * - static `Model.bulkCreate(records, …)` → `beforeBulkCreate` (receives the
13
+ * built instances; a plain `bulkCreate` does not fire `beforeCreate`, so the
14
+ * per-instance hook alone would be bypassed). Sequelize filters the written
15
+ * columns down to `options.fields` when the caller supplies it, so an injected
16
+ * field is dropped unless also added there.
17
+ *
18
+ * Mechanism: a property decorator receives the prototype, but Sequelize hook
19
+ * decorators must target a static method on the constructor, so generated static
20
+ * methods are attached to `target.constructor` and registered through the public
21
+ * `@BeforeCreate` / `@BeforeBulkCreate`. Hooks are installed by
22
+ * sequelize-typescript's `installHooks` during `Sequelize#addModels`.
23
+ *
24
+ * @param target - the decorated model's prototype
25
+ * @param propertyName - the decorated column property name
26
+ */
27
+ export declare function CreatedBy(target: any, propertyName: string): void;
@@ -0,0 +1,84 @@
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
+ import { currentMetadata } from '@imqueue/rpc';
25
+ import { BeforeBulkCreate, BeforeCreate } from 'sequelize-typescript';
26
+ /**
27
+ * Stamps the decorated column with the acting user id on INSERT, taken from the
28
+ * in-flight IMQ request metadata (`currentMetadata()?.userId`) — so the id never
29
+ * travels through method arguments and cannot be spoofed by a caller.
30
+ *
31
+ * A property decorator, reusable on any model field. The hook is a no-op when
32
+ * there is no acting user (system / unattributed writes) and never overwrites a
33
+ * value the application set explicitly.
34
+ *
35
+ * Two hooks are registered, because rows are inserted in two ways:
36
+ * - instance / single `create()` → `beforeCreate` (receives the instance)
37
+ * - static `Model.bulkCreate(records, …)` → `beforeBulkCreate` (receives the
38
+ * built instances; a plain `bulkCreate` does not fire `beforeCreate`, so the
39
+ * per-instance hook alone would be bypassed). Sequelize filters the written
40
+ * columns down to `options.fields` when the caller supplies it, so an injected
41
+ * field is dropped unless also added there.
42
+ *
43
+ * Mechanism: a property decorator receives the prototype, but Sequelize hook
44
+ * decorators must target a static method on the constructor, so generated static
45
+ * methods are attached to `target.constructor` and registered through the public
46
+ * `@BeforeCreate` / `@BeforeBulkCreate`. Hooks are installed by
47
+ * sequelize-typescript's `installHooks` during `Sequelize#addModels`.
48
+ *
49
+ * @param target - the decorated model's prototype
50
+ * @param propertyName - the decorated column property name
51
+ */
52
+ export function CreatedBy(target, propertyName) {
53
+ const ctor = target.constructor;
54
+ const hookName = `__stampCreatedBy$${propertyName}`;
55
+ const bulkHook = `__stampCreatedByOnBulkCreate$${propertyName}`;
56
+ if (ctor[hookName]) {
57
+ return;
58
+ }
59
+ ctor[hookName] = function (instance) {
60
+ const userId = currentMetadata()?.userId;
61
+ if (userId != null && instance[propertyName] == null) {
62
+ instance[propertyName] = userId;
63
+ }
64
+ };
65
+ ctor[bulkHook] = function (instances, options) {
66
+ const userId = currentMetadata()?.userId;
67
+ if (userId == null || !Array.isArray(instances)) {
68
+ return;
69
+ }
70
+ for (const instance of instances) {
71
+ if (instance && instance[propertyName] == null) {
72
+ instance[propertyName] = userId;
73
+ }
74
+ }
75
+ if (options &&
76
+ Array.isArray(options.fields) &&
77
+ !options.fields.includes(propertyName)) {
78
+ options.fields.push(propertyName);
79
+ }
80
+ };
81
+ BeforeCreate(ctor, hookName);
82
+ BeforeBulkCreate(ctor, bulkHook);
83
+ }
84
+ //# sourceMappingURL=CreatedBy.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Stamps the decorated column with the acting user id on soft-delete (and clears
3
+ * it on restore), taken from the in-flight IMQ request metadata
4
+ * (`currentMetadata()?.userId`). Mirrors `@DeletedAt`: set on delete, cleared on
5
+ * restore, and a delete touches neither `updatedAt` nor an `@UpdatedBy` column.
6
+ *
7
+ * A property decorator for paranoid models, reusable on any field. Soft-delete
8
+ * runs through `Model.destroy`, which Sequelize turns into a `deletedAt`-only
9
+ * UPDATE whose value hash is built internally and is unreachable from any hook.
10
+ * So instead of mutating that statement, the `beforeBulkDestroy` hook stamps the
11
+ * column with a sibling UPDATE over the same still-live rows (same `where`, same
12
+ * transaction) just before the soft-delete sets `deletedAt`; `beforeBulkRestore`
13
+ * does the inverse, clearing it as `deletedAt` is cleared.
14
+ *
15
+ * The sibling UPDATE runs on `this` — the concrete model the hook fires for,
16
+ * bound by Sequelize at call time (`runHooks` → `hook.apply(model, ...)`) —
17
+ * rather than the class captured at decoration time. That lets the decorator be
18
+ * declared on an abstract base model (e.g. a shared `BaseParanoid`) and still
19
+ * resolve to the real subclass; the captured constructor would be the
20
+ * (unregistered) base and break with "model not initialized".
21
+ *
22
+ * `hooks: false` avoids hook re-entrancy and keeps the delete from bumping an
23
+ * `@UpdatedBy` column; `silent: true` keeps it from bumping `updatedAt`, so the
24
+ * delete writes exactly `deletedBy` + `deletedAt` (like the native paranoid
25
+ * delete writes only `deletedAt`). No-op when there is no acting user.
26
+ *
27
+ * @param target - the decorated model's prototype
28
+ * @param propertyName - the decorated column property name
29
+ */
30
+ export declare function DeletedBy(target: any, propertyName: string): void;
@@ -0,0 +1,89 @@
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
+ import { currentMetadata } from '@imqueue/rpc';
25
+ import { BeforeBulkDestroy, BeforeBulkRestore } from 'sequelize-typescript';
26
+ /**
27
+ * Stamps the decorated column with the acting user id on soft-delete (and clears
28
+ * it on restore), taken from the in-flight IMQ request metadata
29
+ * (`currentMetadata()?.userId`). Mirrors `@DeletedAt`: set on delete, cleared on
30
+ * restore, and a delete touches neither `updatedAt` nor an `@UpdatedBy` column.
31
+ *
32
+ * A property decorator for paranoid models, reusable on any field. Soft-delete
33
+ * runs through `Model.destroy`, which Sequelize turns into a `deletedAt`-only
34
+ * UPDATE whose value hash is built internally and is unreachable from any hook.
35
+ * So instead of mutating that statement, the `beforeBulkDestroy` hook stamps the
36
+ * column with a sibling UPDATE over the same still-live rows (same `where`, same
37
+ * transaction) just before the soft-delete sets `deletedAt`; `beforeBulkRestore`
38
+ * does the inverse, clearing it as `deletedAt` is cleared.
39
+ *
40
+ * The sibling UPDATE runs on `this` — the concrete model the hook fires for,
41
+ * bound by Sequelize at call time (`runHooks` → `hook.apply(model, ...)`) —
42
+ * rather than the class captured at decoration time. That lets the decorator be
43
+ * declared on an abstract base model (e.g. a shared `BaseParanoid`) and still
44
+ * resolve to the real subclass; the captured constructor would be the
45
+ * (unregistered) base and break with "model not initialized".
46
+ *
47
+ * `hooks: false` avoids hook re-entrancy and keeps the delete from bumping an
48
+ * `@UpdatedBy` column; `silent: true` keeps it from bumping `updatedAt`, so the
49
+ * delete writes exactly `deletedBy` + `deletedAt` (like the native paranoid
50
+ * delete writes only `deletedAt`). No-op when there is no acting user.
51
+ *
52
+ * @param target - the decorated model's prototype
53
+ * @param propertyName - the decorated column property name
54
+ */
55
+ export function DeletedBy(target, propertyName) {
56
+ const ctor = target.constructor;
57
+ const bulkHook = `__stampBulkDeletedBy$${propertyName}`;
58
+ const restoreHook = `__clearBulkDeletedBy$${propertyName}`;
59
+ if (ctor[bulkHook]) {
60
+ return;
61
+ }
62
+ ctor[bulkHook] = async function (options) {
63
+ const userId = currentMetadata()?.userId;
64
+ if (userId == null || !options || !options.where) {
65
+ return;
66
+ }
67
+ await this.update({ [propertyName]: userId }, {
68
+ where: options.where,
69
+ transaction: options.transaction,
70
+ hooks: false,
71
+ silent: true,
72
+ });
73
+ };
74
+ ctor[restoreHook] = async function (options) {
75
+ if (!options || !options.where) {
76
+ return;
77
+ }
78
+ await this.update({ [propertyName]: null }, {
79
+ where: options.where,
80
+ transaction: options.transaction,
81
+ paranoid: false,
82
+ hooks: false,
83
+ silent: true,
84
+ });
85
+ };
86
+ BeforeBulkDestroy(ctor, bulkHook);
87
+ BeforeBulkRestore(ctor, restoreHook);
88
+ }
89
+ //# sourceMappingURL=DeletedBy.js.map
@@ -0,0 +1,124 @@
1
+ /*!
2
+ * I'm Queue Software Project
3
+ * Copyright (C) 2025 imqueue.com <support@imqueue.com>
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU General Public License
16
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ *
18
+ * If you want to use this code in a closed source (commercial) project, you can
19
+ * purchase a proprietary commercial license. Please contact us at
20
+ * <support@imqueue.com> to get commercial licensing options.
21
+ */
22
+ import 'reflect-metadata';
23
+ import { type IViewDefineOptions } from './View.js';
24
+ /**
25
+ * Values for a dynamic view's placeholders, keyed by placeholder name.
26
+ *
27
+ * @remarks
28
+ * Values are typed as strings and rendered as quoted SQL constants, so a numeric
29
+ * placeholder arrives as a quoted number and relies on Postgres casting it. They are
30
+ * escaped on the way in, which is what makes it safe for one to carry a caller's
31
+ * input.
32
+ */
33
+ export interface ViewParams {
34
+ [name: string]: string;
35
+ }
36
+ /**
37
+ * Options for a view whose definition is parameterised.
38
+ */
39
+ export interface IDynamicViewDefineOptions extends IViewDefineOptions {
40
+ /**
41
+ * A value for every placeholder the definition names.
42
+ *
43
+ * @remarks
44
+ * These are the defaults. A query may override any of them through
45
+ * `FindOptions.viewParams`, and what it does not override falls back to here.
46
+ * Every placeholder must appear in this map, which is checked when the class is
47
+ * defined.
48
+ */
49
+ viewParams: ViewParams;
50
+ /**
51
+ * The create statement, with placeholders in it.
52
+ *
53
+ * @remarks
54
+ * Same as the static case except that `@{name}` placeholders are substituted
55
+ * before the statement is used.
56
+ */
57
+ viewDefinition: string;
58
+ /**
59
+ * Marks the model as a dynamic view. Set by the decorator, not by you.
60
+ */
61
+ isDynamicView?: boolean;
62
+ }
63
+ /**
64
+ * The placeholder pattern as a source string: `@{name}`, where the name is letters,
65
+ * digits and underscores.
66
+ *
67
+ * @remarks
68
+ * Exported so a service can compile its own matcher — to validate a definition of its
69
+ * own, say — rather than hard-coding the syntax a second time.
70
+ */
71
+ export declare const MATCHER = "@\\{([a-z0-9_]+?)\\}";
72
+ /** {@link MATCHER} compiled to find every placeholder in a definition. */
73
+ export declare const RX_MATCHER: RegExp;
74
+ /** {@link MATCHER} compiled to pull the name out of a single placeholder. */
75
+ export declare const RX_NAME_MATCHER: RegExp;
76
+ /**
77
+ * Declares a model to be a view whose definition is parameterised per query.
78
+ *
79
+ * @remarks
80
+ * Everything `View` does, plus placeholders. The definition may carry `@{name}`
81
+ * markers, every finder accepts `viewParams` to fill them, and the select-query
82
+ * generator substitutes them and splices the resulting statement into the query — as
83
+ * the `FROM` target, or as a joined subquery when the view is reached through an
84
+ * `include`. One model then serves a family of views that differ only by a constant,
85
+ * which is the alternative to defining one view per variant in a migration.
86
+ *
87
+ * Every placeholder must have a default in `viewParams`. That is checked while the
88
+ * class is being defined, so a missing one is an error at import rather than a
89
+ * malformed statement at query time.
90
+ *
91
+ * Values are escaped, so a parameter can carry a caller's input. Anything that is not
92
+ * a number or a string becomes `NULL`.
93
+ *
94
+ * @param options - Model options carrying the definition and the parameter defaults.
95
+ * @returns A class decorator.
96
+ * @throws TypeError when the definition is missing or blank, or when it names a
97
+ * placeholder that `viewParams` does not.
98
+ * @example
99
+ * ```typescript
100
+ * @DynamicView({
101
+ * viewDefinition: `
102
+ * CREATE OR REPLACE VIEW "ProductRevenue" AS
103
+ * SELECT "productId" AS "id", SUM("payment") AS "revenue"
104
+ * FROM "Order"
105
+ * WHERE "currency" = @{currency}
106
+ * GROUP BY "productId"
107
+ * `,
108
+ * viewParams: { currency: 'USD' },
109
+ * freezeTableName: true,
110
+ * timestamps: false,
111
+ * })
112
+ * export class ProductRevenue extends BaseModel<ProductRevenue> {
113
+ * @PrimaryKey
114
+ * @Column(DataType.BIGINT)
115
+ * declare public id: number;
116
+ * }
117
+ *
118
+ * // the same model, read in another currency
119
+ * const rows = await ProductRevenue.findAll({
120
+ * viewParams: { currency: 'EUR' },
121
+ * });
122
+ * ```
123
+ */
124
+ export declare function DynamicView(options: IDynamicViewDefineOptions): (target: any) => void;