@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,113 @@
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 { addOptions, setModelName } from 'sequelize-typescript';
24
+ import {} from './View.js';
25
+ /**
26
+ * The placeholder pattern as a source string: `@{name}`, where the name is letters,
27
+ * digits and underscores.
28
+ *
29
+ * @remarks
30
+ * Exported so a service can compile its own matcher — to validate a definition of its
31
+ * own, say — rather than hard-coding the syntax a second time.
32
+ */
33
+ export const MATCHER = '@\\{([a-z0-9_]+?)\\}';
34
+ /** {@link MATCHER} compiled to find every placeholder in a definition. */
35
+ export const RX_MATCHER = new RegExp(MATCHER, 'gi');
36
+ /** {@link MATCHER} compiled to pull the name out of a single placeholder. */
37
+ export const RX_NAME_MATCHER = new RegExp(MATCHER, 'i');
38
+ /**
39
+ * Declares a model to be a view whose definition is parameterised per query.
40
+ *
41
+ * @remarks
42
+ * Everything `View` does, plus placeholders. The definition may carry `@{name}`
43
+ * markers, every finder accepts `viewParams` to fill them, and the select-query
44
+ * generator substitutes them and splices the resulting statement into the query — as
45
+ * the `FROM` target, or as a joined subquery when the view is reached through an
46
+ * `include`. One model then serves a family of views that differ only by a constant,
47
+ * which is the alternative to defining one view per variant in a migration.
48
+ *
49
+ * Every placeholder must have a default in `viewParams`. That is checked while the
50
+ * class is being defined, so a missing one is an error at import rather than a
51
+ * malformed statement at query time.
52
+ *
53
+ * Values are escaped, so a parameter can carry a caller's input. Anything that is not
54
+ * a number or a string becomes `NULL`.
55
+ *
56
+ * @param options - Model options carrying the definition and the parameter defaults.
57
+ * @returns A class decorator.
58
+ * @throws TypeError when the definition is missing or blank, or when it names a
59
+ * placeholder that `viewParams` does not.
60
+ * @example
61
+ * ```typescript
62
+ * @DynamicView({
63
+ * viewDefinition: `
64
+ * CREATE OR REPLACE VIEW "ProductRevenue" AS
65
+ * SELECT "productId" AS "id", SUM("payment") AS "revenue"
66
+ * FROM "Order"
67
+ * WHERE "currency" = @{currency}
68
+ * GROUP BY "productId"
69
+ * `,
70
+ * viewParams: { currency: 'USD' },
71
+ * freezeTableName: true,
72
+ * timestamps: false,
73
+ * })
74
+ * export class ProductRevenue extends BaseModel<ProductRevenue> {
75
+ * @PrimaryKey
76
+ * @Column(DataType.BIGINT)
77
+ * declare public id: number;
78
+ * }
79
+ *
80
+ * // the same model, read in another currency
81
+ * const rows = await ProductRevenue.findAll({
82
+ * viewParams: { currency: 'EUR' },
83
+ * });
84
+ * ```
85
+ */
86
+ export function DynamicView(options) {
87
+ if (!options || !options.viewDefinition || !options.viewDefinition.trim()) {
88
+ throw new TypeError('View definition is missing!');
89
+ }
90
+ // we are dynamic, no choice here!
91
+ options.isDynamicView = true;
92
+ const viewDef = options.viewDefinition || '';
93
+ const viewParams = options.viewParams || {};
94
+ (viewDef.match(RX_MATCHER) || []).forEach(param => {
95
+ const [, name] = param.match(RX_NAME_MATCHER) || ['', ''];
96
+ if (typeof viewParams[name] !== 'string') {
97
+ throw new TypeError(`View definition contains param '${name}', but it was not provided`);
98
+ }
99
+ });
100
+ return (target) => annotate(target, options);
101
+ }
102
+ /**
103
+ * Registers the model under its own name and records the view options on it.
104
+ *
105
+ * @param target - Model class being decorated.
106
+ * @param options - View definition options, marked as a view in place.
107
+ */
108
+ function annotate(target, options) {
109
+ Object.assign(options, { treatAsView: true });
110
+ setModelName(target.prototype, options.modelName || target.name);
111
+ addOptions(target.prototype, options);
112
+ }
113
+ //# sourceMappingURL=DynamicView.js.map
@@ -0,0 +1,39 @@
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
+ * Placeholder for change notifications, which are not implemented.
27
+ *
28
+ * @remarks
29
+ * The intent was to attach a trigger that publishes row changes through Postgres
30
+ * `NOTIFY`. The body is empty: applying this to a model does nothing whatever, and
31
+ * nothing about a model changes by carrying it. It is unfinished work that happens to
32
+ * be exported, not a switch that is off.
33
+ *
34
+ * What exists today: `@imqueue/pg-pubsub` is the `LISTEN`/`NOTIFY` client, and
35
+ * `@imqueue/pg-prisma` ships the change-notify triggers for the Prisma stack.
36
+ *
37
+ * @param _target - The model class, which is ignored.
38
+ */
39
+ export declare function Emittable(_target: any): void;
@@ -0,0 +1,42 @@
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
+ * Placeholder for change notifications, which are not implemented.
27
+ *
28
+ * @remarks
29
+ * The intent was to attach a trigger that publishes row changes through Postgres
30
+ * `NOTIFY`. The body is empty: applying this to a model does nothing whatever, and
31
+ * nothing about a model changes by carrying it. It is unfinished work that happens to
32
+ * be exported, not a switch that is off.
33
+ *
34
+ * What exists today: `@imqueue/pg-pubsub` is the `LISTEN`/`NOTIFY` client, and
35
+ * `@imqueue/pg-prisma` ships the change-notify triggers for the Prisma stack.
36
+ *
37
+ * @param _target - The model class, which is ignored.
38
+ */
39
+ export function Emittable(_target) {
40
+ // todo: implement
41
+ }
42
+ //# sourceMappingURL=Emittable.js.map
@@ -0,0 +1,77 @@
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 { type ColumnIndexOptions, type FunctionType } from './ColumnIndex.js';
25
+ /**
26
+ * What {@link (NullableIndex:1) | NullableIndex} accepts: every {@link ColumnIndexOptions} field except
27
+ * `expression`, which it sets itself.
28
+ */
29
+ export type NullableColumnIndexOptions = Omit<ColumnIndexOptions, 'expression'>;
30
+ /**
31
+ * Declares a pair of partial indices on a nullable column, one for the rows where it
32
+ * is null and one for the rows where it is not.
33
+ *
34
+ * @remarks
35
+ * Two narrow indices instead of one wide one. Each covers only its half of the table,
36
+ * so `WHERE "col" IS NULL` and `WHERE "col" IS NOT NULL` each get an index sized to
37
+ * the rows they actually match — which is the point on a column where one of the two
38
+ * halves is much smaller than the other.
39
+ *
40
+ * Usable bare or as a factory, exactly like {@link (ColumnIndex:1) | ColumnIndex}. The factory form used
41
+ * to build both halves from one options object that it mutated in place, and
42
+ * {@link (ColumnIndex:1) | ColumnIndex} stores that object by reference — so both declarations ended up
43
+ * pointing at the same object, both saying `IS NOT NULL`, and neither carrying a
44
+ * predicate at all. What you got was two identical non-partial indices. It now builds
45
+ * a fresh options object per half, keeps a predicate of your own by combining it with
46
+ * the null test, and suffixes a name of your own so the two halves cannot collide.
47
+ *
48
+ * @param args - Nothing, when used bare. The options, when used as a factory.
49
+ * @returns A property decorator, when used as a factory.
50
+ * @example
51
+ * ```typescript
52
+ * @Table
53
+ * export class Lead extends BaseModel<Lead> {
54
+ * @NullableIndex
55
+ * @Column(DataType.DATE)
56
+ * public closedAt: Date;
57
+ *
58
+ * @NullableIndex({ name: 'lead_assignee', concurrently: true })
59
+ * @Column(DataType.BIGINT)
60
+ * public assigneeId: number;
61
+ * }
62
+ * ```
63
+ */
64
+ export declare function NullableIndex(options: Partial<NullableColumnIndexOptions>): FunctionType;
65
+ /**
66
+ * Declares the pair of partial indices with no options of their own.
67
+ *
68
+ * @remarks
69
+ * The bare form. Each half is a partial index keyed on the null test itself, so it
70
+ * serves `IS NULL` and `IS NOT NULL` lookups; use the factory form to add a method,
71
+ * a name, or a predicate of your own.
72
+ *
73
+ * @param target - Model prototype the property belongs to.
74
+ * @param propertyName - Nullable column to split.
75
+ * @param propertyDescriptor - Unused; present because a decorator receives it.
76
+ */
77
+ export declare function NullableIndex(target: any, propertyName: string, propertyDescriptor?: PropertyDescriptor): void;
@@ -0,0 +1,64 @@
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 { ColumnIndex, } from './ColumnIndex.js';
25
+ export function NullableIndex(...args) {
26
+ if (args.length >= 2) {
27
+ const [target, propertyName, propertyDescriptor] = args;
28
+ annotate(target, propertyName, propertyDescriptor);
29
+ return;
30
+ }
31
+ return (target, propertyName, propertyDescriptor) => {
32
+ annotate(target, propertyName, propertyDescriptor, args[0]);
33
+ };
34
+ }
35
+ /**
36
+ * Declares the two halves of the pair.
37
+ *
38
+ * @param target - Model prototype the property belongs to.
39
+ * @param propertyName - Column the pair is declared on.
40
+ * @param propertyDescriptor - Passed through to {@link (ColumnIndex:1) | ColumnIndex}.
41
+ * @param options - Options to apply to both halves.
42
+ */
43
+ function annotate(target, propertyName, propertyDescriptor, options = {}) {
44
+ for (const isNull of [true, false]) {
45
+ const test = `"${propertyName}" IS ${isNull ? '' : 'NOT '}NULL`;
46
+ // A fresh object per half: ColumnIndex keeps what it is given by
47
+ // reference, so two halves sharing one object are two halves with
48
+ // whatever the second one wrote.
49
+ const half = {
50
+ ...options,
51
+ expression: test,
52
+ // A predicate of the caller's own is kept rather than replaced —
53
+ // splitting the rows is what this adds, not what it overrides.
54
+ predicate: options.predicate
55
+ ? `(${options.predicate}) AND ${test}`
56
+ : test,
57
+ };
58
+ if (options.name) {
59
+ half.name = `${options.name}${isNull ? '' : '_not'}_null`;
60
+ }
61
+ ColumnIndex(half)(target, propertyName, propertyDescriptor);
62
+ }
63
+ }
64
+ //# sourceMappingURL=NullableIndex.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Stamps the decorated column with the acting user id on INSERT and UPDATE,
3
+ * taken from the in-flight IMQ request metadata (`currentMetadata()?.userId`).
4
+ *
5
+ * A property decorator, reusable on any model field. It mirrors `@UpdatedAt`:
6
+ * - on INSERT it is set to the creating actor (just as `updatedAt` is set equal
7
+ * to `createdAt` on insert), without overwriting an explicit value;
8
+ * - on UPDATE it always overwrites with the current actor — "last modified by"
9
+ * must reflect who actually ran the update and not be caller-spoofable.
10
+ *
11
+ * Four hooks are registered, because models are written in four ways:
12
+ * - single INSERT → `beforeCreate` (set-if-empty)
13
+ * - `Model.bulkCreate(records, …)` → `beforeBulkCreate` (set-if-empty on the
14
+ * built instances; a plain `bulkCreate` does not fire `beforeCreate`, so the
15
+ * per-instance hook alone would be bypassed)
16
+ * - instance `save()` / `update()` → `beforeUpdate` (receives the instance)
17
+ * - static `Model.update(values, …)` → `beforeBulkUpdate` (receives options;
18
+ * the values to write live on `options.attributes`, and Sequelize filters the
19
+ * written columns down to `options.fields`, computed before this hook from the
20
+ * caller's values — so an injected field is dropped unless also added there).
21
+ *
22
+ * No-op when there is no acting user (system / unattributed writes).
23
+ *
24
+ * @param target - the decorated model's prototype
25
+ * @param propertyName - the decorated column property name
26
+ */
27
+ export declare function UpdatedBy(target: any, propertyName: string): void;
@@ -0,0 +1,105 @@
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, BeforeBulkUpdate, BeforeCreate, BeforeUpdate, } from 'sequelize-typescript';
26
+ /**
27
+ * Stamps the decorated column with the acting user id on INSERT and UPDATE,
28
+ * taken from the in-flight IMQ request metadata (`currentMetadata()?.userId`).
29
+ *
30
+ * A property decorator, reusable on any model field. It mirrors `@UpdatedAt`:
31
+ * - on INSERT it is set to the creating actor (just as `updatedAt` is set equal
32
+ * to `createdAt` on insert), without overwriting an explicit value;
33
+ * - on UPDATE it always overwrites with the current actor — "last modified by"
34
+ * must reflect who actually ran the update and not be caller-spoofable.
35
+ *
36
+ * Four hooks are registered, because models are written in four ways:
37
+ * - single INSERT → `beforeCreate` (set-if-empty)
38
+ * - `Model.bulkCreate(records, …)` → `beforeBulkCreate` (set-if-empty on the
39
+ * built instances; a plain `bulkCreate` does not fire `beforeCreate`, so the
40
+ * per-instance hook alone would be bypassed)
41
+ * - instance `save()` / `update()` → `beforeUpdate` (receives the instance)
42
+ * - static `Model.update(values, …)` → `beforeBulkUpdate` (receives options;
43
+ * the values to write live on `options.attributes`, and Sequelize filters the
44
+ * written columns down to `options.fields`, computed before this hook from the
45
+ * caller's values — so an injected field is dropped unless also added there).
46
+ *
47
+ * No-op when there is no acting user (system / unattributed writes).
48
+ *
49
+ * @param target - the decorated model's prototype
50
+ * @param propertyName - the decorated column property name
51
+ */
52
+ export function UpdatedBy(target, propertyName) {
53
+ const ctor = target.constructor;
54
+ const createHook = `__stampUpdatedByOnCreate$${propertyName}`;
55
+ const bulkCreateHook = `__stampUpdatedByOnBulkCreate$${propertyName}`;
56
+ const singleHook = `__stampUpdatedBy$${propertyName}`;
57
+ const bulkHook = `__stampBulkUpdatedBy$${propertyName}`;
58
+ if (ctor[singleHook]) {
59
+ return;
60
+ }
61
+ ctor[createHook] = function (instance) {
62
+ const userId = currentMetadata()?.userId;
63
+ if (userId != null && instance[propertyName] == null) {
64
+ instance[propertyName] = userId;
65
+ }
66
+ };
67
+ ctor[bulkCreateHook] = function (instances, options) {
68
+ const userId = currentMetadata()?.userId;
69
+ if (userId == null || !Array.isArray(instances)) {
70
+ return;
71
+ }
72
+ for (const instance of instances) {
73
+ if (instance && instance[propertyName] == null) {
74
+ instance[propertyName] = userId;
75
+ }
76
+ }
77
+ if (options &&
78
+ Array.isArray(options.fields) &&
79
+ !options.fields.includes(propertyName)) {
80
+ options.fields.push(propertyName);
81
+ }
82
+ };
83
+ ctor[singleHook] = function (instance) {
84
+ const userId = currentMetadata()?.userId;
85
+ if (userId != null) {
86
+ instance[propertyName] = userId;
87
+ }
88
+ };
89
+ ctor[bulkHook] = function (options) {
90
+ const userId = currentMetadata()?.userId;
91
+ if (userId == null || !options || !options.attributes) {
92
+ return;
93
+ }
94
+ options.attributes[propertyName] = userId;
95
+ if (Array.isArray(options.fields) &&
96
+ !options.fields.includes(propertyName)) {
97
+ options.fields.push(propertyName);
98
+ }
99
+ };
100
+ BeforeCreate(ctor, createHook);
101
+ BeforeBulkCreate(ctor, bulkCreateHook);
102
+ BeforeUpdate(ctor, singleHook);
103
+ BeforeBulkUpdate(ctor, bulkHook);
104
+ }
105
+ //# sourceMappingURL=UpdatedBy.js.map
@@ -0,0 +1,87 @@
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 { type ModelOptions } from 'sequelize';
26
+ /**
27
+ * Sequelize's model options, plus the SQL that defines the view.
28
+ */
29
+ export interface IViewDefineOptions extends ModelOptions {
30
+ /**
31
+ * The complete create statement for the view.
32
+ *
33
+ * @remarks
34
+ * Executed exactly as written, so it carries its own keywords — and writing it as
35
+ * `CREATE OR REPLACE VIEW` is what makes `sync({ withoutDrop: true })` an option
36
+ * later, which views that depend on each other need. The name it declares has to
37
+ * match the model's table name; that is checked before anything runs.
38
+ */
39
+ viewDefinition: string;
40
+ }
41
+ /**
42
+ * Declares a model to be a database view rather than a table.
43
+ *
44
+ * @remarks
45
+ * Does for a view what `Table` does for a table, and sets the flag the rest of the
46
+ * package keys off: the model is skipped by the table sync, created and dropped with
47
+ * view statements, and has its numeric columns re-cast after every finder, since a
48
+ * view returns them as strings.
49
+ *
50
+ * Two ordinary model options matter more here than they look. `freezeTableName` stops
51
+ * sequelize pluralising the model name, which has to match the name inside the
52
+ * definition. And `timestamps: false` stops it selecting `createdAt` and `updatedAt`,
53
+ * which a view has no reason to have. A view model may also extend another model
54
+ * instead of `BaseModel`, which is the shortest way to reuse a table's column
55
+ * declarations for a view over the same columns.
56
+ *
57
+ * Declared columns are a subset of what the view selects: anything the view returns
58
+ * and the model does not declare is simply not mapped.
59
+ *
60
+ * @param options - The definition SQL alone, or model options carrying it.
61
+ * @returns A class decorator.
62
+ * @throws TypeError when there is no definition, or it is blank. The string form used
63
+ * to skip that check, and an options object without the property threw an unhelpful
64
+ * error from reading it.
65
+ * @example
66
+ * ```typescript
67
+ * @View({
68
+ * viewDefinition: `
69
+ * CREATE OR REPLACE VIEW "ProductRevenue" AS
70
+ * SELECT "productId" AS "id", SUM("payment") AS "revenue"
71
+ * FROM "Order"
72
+ * GROUP BY "productId"
73
+ * `,
74
+ * freezeTableName: true,
75
+ * timestamps: false,
76
+ * })
77
+ * export class ProductRevenue extends BaseModel<ProductRevenue> {
78
+ * @PrimaryKey
79
+ * @Column(DataType.BIGINT)
80
+ * declare public id: number;
81
+ *
82
+ * @Column(DataType.DECIMAL(12, 2))
83
+ * declare public revenue: number;
84
+ * }
85
+ * ```
86
+ */
87
+ export declare function View(options: IViewDefineOptions | string): (target: any) => void;
@@ -0,0 +1,93 @@
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 {} from 'sequelize';
26
+ import { addOptions, setModelName } from 'sequelize-typescript';
27
+ /**
28
+ * Declares a model to be a database view rather than a table.
29
+ *
30
+ * @remarks
31
+ * Does for a view what `Table` does for a table, and sets the flag the rest of the
32
+ * package keys off: the model is skipped by the table sync, created and dropped with
33
+ * view statements, and has its numeric columns re-cast after every finder, since a
34
+ * view returns them as strings.
35
+ *
36
+ * Two ordinary model options matter more here than they look. `freezeTableName` stops
37
+ * sequelize pluralising the model name, which has to match the name inside the
38
+ * definition. And `timestamps: false` stops it selecting `createdAt` and `updatedAt`,
39
+ * which a view has no reason to have. A view model may also extend another model
40
+ * instead of `BaseModel`, which is the shortest way to reuse a table's column
41
+ * declarations for a view over the same columns.
42
+ *
43
+ * Declared columns are a subset of what the view selects: anything the view returns
44
+ * and the model does not declare is simply not mapped.
45
+ *
46
+ * @param options - The definition SQL alone, or model options carrying it.
47
+ * @returns A class decorator.
48
+ * @throws TypeError when there is no definition, or it is blank. The string form used
49
+ * to skip that check, and an options object without the property threw an unhelpful
50
+ * error from reading it.
51
+ * @example
52
+ * ```typescript
53
+ * @View({
54
+ * viewDefinition: `
55
+ * CREATE OR REPLACE VIEW "ProductRevenue" AS
56
+ * SELECT "productId" AS "id", SUM("payment") AS "revenue"
57
+ * FROM "Order"
58
+ * GROUP BY "productId"
59
+ * `,
60
+ * freezeTableName: true,
61
+ * timestamps: false,
62
+ * })
63
+ * export class ProductRevenue extends BaseModel<ProductRevenue> {
64
+ * @PrimaryKey
65
+ * @Column(DataType.BIGINT)
66
+ * declare public id: number;
67
+ *
68
+ * @Column(DataType.DECIMAL(12, 2))
69
+ * declare public revenue: number;
70
+ * }
71
+ * ```
72
+ */
73
+ export function View(options) {
74
+ if (typeof options === 'string') {
75
+ options = { viewDefinition: options };
76
+ }
77
+ if (!options || !options.viewDefinition || !options.viewDefinition.trim()) {
78
+ throw new TypeError('View definition is missing!');
79
+ }
80
+ return (target) => annotate(target, options);
81
+ }
82
+ /**
83
+ * Registers the model under its own name and records the view options on it.
84
+ *
85
+ * @param target - Model class being decorated.
86
+ * @param options - View definition options, marked as a view in place.
87
+ */
88
+ function annotate(target, options) {
89
+ Object.assign(options, { treatAsView: true });
90
+ setModelName(target.prototype, options.modelName || target.name);
91
+ addOptions(target.prototype, options);
92
+ }
93
+ //# sourceMappingURL=View.js.map