@solidxai/core 0.1.11-beta.8 → 0.1.11-beta.9
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/dist/helpers/environment.helper.d.ts +1 -0
- package/dist/helpers/environment.helper.d.ts.map +1 -1
- package/dist/helpers/environment.helper.js +4 -0
- package/dist/helpers/environment.helper.js.map +1 -1
- package/dist/helpers/model-metadata-helper.service.d.ts +2 -1
- package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
- package/dist/helpers/model-metadata-helper.service.js +6 -2
- package/dist/helpers/model-metadata-helper.service.js.map +1 -1
- package/dist/helpers/typeorm-db-helper.d.ts +1 -0
- package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
- package/dist/helpers/typeorm-db-helper.js +12 -0
- package/dist/helpers/typeorm-db-helper.js.map +1 -1
- package/dist/repository/scheduled-job.repository.d.ts +2 -2
- package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
- package/dist/repository/scheduled-job.repository.js +6 -3
- package/dist/repository/scheduled-job.repository.js.map +1 -1
- package/dist/repository/security-rule.repository.d.ts +2 -2
- package/dist/repository/security-rule.repository.d.ts.map +1 -1
- package/dist/repository/security-rule.repository.js +6 -3
- package/dist/repository/security-rule.repository.js.map +1 -1
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts.map +1 -1
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js +2 -1
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js.map +1 -1
- package/dist/services/crud-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +25 -12
- package/dist/services/crud-helper.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +2 -1
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/module-metadata.service.d.ts.map +1 -1
- package/dist/services/module-metadata.service.js +2 -1
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts +0 -1
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +5 -8
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.d.ts +9 -1
- package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.js +43 -12
- package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
- package/dist/subscribers/scheduled-job.subscriber.d.ts.map +1 -1
- package/dist/subscribers/scheduled-job.subscriber.js +3 -2
- package/dist/subscribers/scheduled-job.subscriber.js.map +1 -1
- package/dist/subscribers/security-rule.subscriber.d.ts.map +1 -1
- package/dist/subscribers/security-rule.subscriber.js +5 -3
- package/dist/subscribers/security-rule.subscriber.js.map +1 -1
- package/package.json +1 -1
- package/src/helpers/environment.helper.ts +11 -0
- package/src/helpers/model-metadata-helper.service.ts +7 -2
- package/src/helpers/typeorm-db-helper.ts +15 -0
- package/src/repository/scheduled-job.repository.ts +10 -4
- package/src/repository/security-rule.repository.ts +10 -4
- package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +5 -4
- package/src/services/crud-helper.service.ts +25 -12
- package/src/services/model-metadata.service.ts +2 -1
- package/src/services/module-metadata.service.ts +2 -1
- package/src/subscribers/audit.subscriber.ts +7 -9
- package/src/subscribers/computed-entity-field.subscriber.ts +79 -28
- package/src/subscribers/scheduled-job.subscriber.ts +3 -2
- package/src/subscribers/security-rule.subscriber.ts +8 -3
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { camelCase } from 'lodash';
|
|
2
|
-
import { Delete } from "@aws-sdk/client-s3";
|
|
3
2
|
import { forwardRef, Inject, Injectable, InternalServerErrorException, Logger, Scope } from "@nestjs/common";
|
|
4
|
-
import { model } from "mongoose";
|
|
5
3
|
import { ComputedFieldTriggerOperation } from "src/dtos/create-field-metadata.dto";
|
|
4
|
+
import { isEmbeddedDb } from "src/helpers/environment.helper";
|
|
6
5
|
import { ComputedFieldMetadata, SolidRegistry, TypeOrmEventContext } from "src/helpers/solid-registry";
|
|
7
6
|
import { IEntityPreComputeFieldProvider } from "src/interfaces";
|
|
8
7
|
import { PublisherFactory } from "src/services/queues/publisher-factory.service";
|
|
@@ -14,19 +13,22 @@ export interface ComputedFieldEvaluationPayload extends ComputedFieldMetadata {
|
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
@Injectable({ scope: Scope.TRANSIENT })
|
|
17
|
-
// @EventSubscriber()
|
|
18
16
|
export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface {
|
|
19
17
|
private readonly logger = new Logger(this.constructor.name);
|
|
20
18
|
private dataSource: DataSource;
|
|
19
|
+
|
|
20
|
+
// Per-transaction buffer for post-event evaluation jobs. Entries are
|
|
21
|
+
// flushed in afterTransactionCommit so the publish (which uses the
|
|
22
|
+
// default DataSource) never races the in-flight transaction's connection.
|
|
23
|
+
// On PGlite (pool size 1), the flush is deferred via setImmediate to
|
|
24
|
+
// ensure the transaction's connection has been released first.
|
|
25
|
+
private perTxn = new WeakMap<any, { computedField: ComputedFieldMetadata<any>; databaseEntity: any }[]>();
|
|
26
|
+
|
|
21
27
|
constructor(
|
|
22
|
-
// @InjectDataSource()
|
|
23
|
-
// private readonly dataSource: DataSource,
|
|
24
28
|
private readonly solidRegistry: SolidRegistry,
|
|
25
29
|
@Inject(forwardRef(() => PublisherFactory))
|
|
26
30
|
private readonly publisherFactory: PublisherFactory<ComputedFieldEvaluationPayload>
|
|
27
|
-
// private readonly computedFieldPublisher: ComputedFieldEvaluationPublisherDatabase,
|
|
28
31
|
) {
|
|
29
|
-
// this.dataSource.subscribers.push(this);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
bindToDataSource(dataSource: DataSource) {
|
|
@@ -43,47 +45,97 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
|
|
|
43
45
|
async beforeUpdate(event: UpdateEvent<any>): Promise<any> {
|
|
44
46
|
const modelName = camelCase(event.metadata?.name ?? event.entity?.constructor?.name ?? '');
|
|
45
47
|
const eventContext = this.sanitizeEventContext(event, 'beforeUpdate');
|
|
46
|
-
// await this.handleComputedFieldEvaluation(event.databaseEntity, ComputedFieldTriggerOperation.beforeUpdate, modelName, eventContext);
|
|
47
48
|
await this.handleComputedFieldEvaluation(event.entity, ComputedFieldTriggerOperation.beforeUpdate, modelName, eventContext);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
afterInsert(event: InsertEvent<any>) {
|
|
51
52
|
const modelName = camelCase(event.metadata?.name ?? event.entity?.constructor?.name ?? '');
|
|
52
53
|
const eventContext = this.sanitizeEventContext(event, 'afterInsert');
|
|
53
|
-
this.
|
|
54
|
+
this.handlePostEventJobs(event, event.entity, ComputedFieldTriggerOperation.afterInsert, modelName, eventContext);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
afterUpdate(event: UpdateEvent<any>) {
|
|
57
58
|
const modelName = camelCase(event.metadata?.name ?? event.entity?.constructor?.name ?? event.databaseEntity?.constructor?.name ?? '');
|
|
58
59
|
const eventContext = this.sanitizeEventContext(event, 'afterUpdate');
|
|
59
|
-
|
|
60
|
-
this.handleComputedFieldEvaluationJob(event.entity, ComputedFieldTriggerOperation.afterUpdate, modelName, eventContext);
|
|
60
|
+
this.handlePostEventJobs(event, event.entity, ComputedFieldTriggerOperation.afterUpdate, modelName, eventContext);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
afterRemove(event: RemoveEvent<any>) {
|
|
64
64
|
const modelName = camelCase(event.metadata?.name ?? event.entity?.constructor?.name ?? event.databaseEntity?.constructor?.name ?? '');
|
|
65
65
|
const eventContext = this.sanitizeEventContext(event, 'afterRemove');
|
|
66
|
-
this.
|
|
66
|
+
this.handlePostEventJobs(event, event.databaseEntity, ComputedFieldTriggerOperation.afterRemove, modelName, eventContext);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
//FIXME: Need to add support for beforeRemove, beforeSoftRemove, afterSoftRemove, beforeRecover, afterRecover
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
// --------- post-event dispatch (pglite vs non-pglite) ----------
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* On embedded PGlite (pool size 1): buffer payloads on the queryRunner and
|
|
75
|
+
* flush in afterTransactionCommit (deferred via setImmediate) so the publish
|
|
76
|
+
* never tries to get a second connection while the transaction is active.
|
|
77
|
+
*
|
|
78
|
+
* On regular Postgres: fire-and-forget publish immediately (original behaviour),
|
|
79
|
+
* since the pool has spare connections.
|
|
80
|
+
*/
|
|
81
|
+
private handlePostEventJobs(
|
|
82
|
+
event: InsertEvent<any> | UpdateEvent<any> | RemoveEvent<any>,
|
|
83
|
+
entity: any,
|
|
84
|
+
currentOperation: ComputedFieldTriggerOperation,
|
|
85
|
+
modelName: string,
|
|
86
|
+
eventContext?: TypeOrmEventContext,
|
|
87
|
+
) {
|
|
88
|
+
if (!entity) return;
|
|
89
|
+
|
|
75
90
|
const computedFieldsTobeEvaluated = this.getComputedFieldsForEvaluation(
|
|
76
91
|
this.solidRegistry.getComputedFieldMetadata(),
|
|
77
92
|
currentOperation,
|
|
78
93
|
modelName
|
|
79
94
|
);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
if (computedFieldsTobeEvaluated.length === 0) return;
|
|
96
|
+
|
|
97
|
+
if (isEmbeddedDb()) {
|
|
98
|
+
const qr = event.queryRunner;
|
|
99
|
+
const arr = this.perTxn.get(qr) ?? [];
|
|
100
|
+
for (const computedField of computedFieldsTobeEvaluated) {
|
|
101
|
+
arr.push({
|
|
102
|
+
computedField: this.attachContext(computedField, eventContext),
|
|
103
|
+
databaseEntity: entity,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
this.perTxn.set(qr, arr);
|
|
107
|
+
} else {
|
|
108
|
+
for (const computedField of computedFieldsTobeEvaluated) {
|
|
109
|
+
this.enqueueComputedFieldEvaluationJob(
|
|
110
|
+
this.attachContext(computedField, eventContext),
|
|
111
|
+
entity,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
83
114
|
}
|
|
84
115
|
}
|
|
85
116
|
|
|
86
|
-
|
|
117
|
+
// --------- transaction lifecycle (pglite only) ----------
|
|
118
|
+
async afterTransactionCommit(event: { queryRunner: any }) {
|
|
119
|
+
const batch = this.perTxn.get(event.queryRunner) ?? [];
|
|
120
|
+
this.perTxn.delete(event.queryRunner);
|
|
121
|
+
if (batch.length === 0) return;
|
|
122
|
+
|
|
123
|
+
// Deferred via setImmediate so the transaction's connection is released
|
|
124
|
+
// back to the (size-1) pool before the publish tries to acquire it.
|
|
125
|
+
setImmediate(() => void this.flushBatch(batch));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
afterTransactionRollback(event: { queryRunner: any }) {
|
|
129
|
+
this.perTxn.delete(event.queryRunner);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async flushBatch(batch: { computedField: ComputedFieldMetadata<any>; databaseEntity: any }[]) {
|
|
133
|
+
for (const { computedField, databaseEntity } of batch) {
|
|
134
|
+
this.enqueueComputedFieldEvaluationJob(computedField, databaseEntity);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private async handleComputedFieldEvaluation(entity: any, currentOperation: ComputedFieldTriggerOperation, modelName: string, eventContext?: TypeOrmEventContext): Promise<void> {
|
|
87
139
|
if (!entity) {
|
|
88
140
|
return;
|
|
89
141
|
}
|
|
@@ -92,9 +144,8 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
|
|
|
92
144
|
currentOperation,
|
|
93
145
|
modelName
|
|
94
146
|
);
|
|
95
|
-
//TODO: We can add a feature i.e dependsOn, where we can check if the computed field depends on other computed fields and evaluate them first
|
|
96
147
|
for (const computedField of computedFieldsTobeEvaluated) {
|
|
97
|
-
this.
|
|
148
|
+
await this.evaluateComputedField(this.attachContext(computedField, eventContext), entity, currentOperation);
|
|
98
149
|
}
|
|
99
150
|
}
|
|
100
151
|
|
|
@@ -147,17 +198,14 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
|
|
|
147
198
|
}
|
|
148
199
|
}
|
|
149
200
|
|
|
150
|
-
private enqueueComputedFieldEvaluationJob(computedField: ComputedFieldMetadata<any>, databaseEntity: any
|
|
201
|
+
private enqueueComputedFieldEvaluationJob(computedField: ComputedFieldMetadata<any>, databaseEntity: any) {
|
|
151
202
|
const { manager: _manager, ...serializableEventContext } = computedField.eventContext ?? {};
|
|
152
203
|
const payload = {
|
|
153
204
|
...computedField,
|
|
154
205
|
eventContext: serializableEventContext,
|
|
155
206
|
databaseEntity,
|
|
156
207
|
};
|
|
157
|
-
this.publisherFactory.publish({ payload }, 'ComputedFieldEvaluationPublisher')
|
|
158
|
-
// this.computedFieldPublisher.publish({
|
|
159
|
-
// payload
|
|
160
|
-
// });
|
|
208
|
+
this.publisherFactory.publish({ payload }, 'ComputedFieldEvaluationPublisher');
|
|
161
209
|
}
|
|
162
210
|
|
|
163
211
|
private attachContext<T extends ComputedFieldMetadata<any>>(computedField: T, eventContext?: any): T {
|
|
@@ -176,7 +224,10 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
|
|
|
176
224
|
const base: TypeOrmEventContext = {
|
|
177
225
|
metadataName: event.metadata?.name,
|
|
178
226
|
eventType: eventType,
|
|
179
|
-
|
|
227
|
+
// Only pass the transaction's EntityManager for embedded DB (PGlite),
|
|
228
|
+
// where a second pooled connection mid-transaction would deadlock.
|
|
229
|
+
// On regular Postgres, providers use their injected EntityManager.
|
|
230
|
+
...(isEmbeddedDb() ? { manager: event.manager } : {}),
|
|
180
231
|
};
|
|
181
232
|
if ("entityId" in event && event.entityId) {
|
|
182
233
|
base.entityId = event.entityId;
|
|
@@ -4,6 +4,7 @@ import * as fs from "fs/promises";
|
|
|
4
4
|
import { CreateScheduledJobDto } from "src/dtos/create-scheduled-job.dto";
|
|
5
5
|
import { ModuleMetadata } from "src/entities/module-metadata.entity";
|
|
6
6
|
import { ScheduledJob } from "src/entities/scheduled-job.entity";
|
|
7
|
+
import { isEmbeddedDb } from "src/helpers/environment.helper";
|
|
7
8
|
import { ModuleMetadataHelperService } from "src/helpers/module-metadata-helper.service";
|
|
8
9
|
import { ScheduledJobRepository } from "src/repository/scheduled-job.repository";
|
|
9
10
|
import {
|
|
@@ -83,7 +84,7 @@ export class ScheduledJobSubscriber
|
|
|
83
84
|
return;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
const moduleMetadataRepo = event.queryRunner.manager.getRepository(ModuleMetadata);
|
|
87
|
+
const moduleMetadataRepo = (isEmbeddedDb() ? event.queryRunner.manager : this.dataSource).getRepository(ModuleMetadata);
|
|
87
88
|
const populatedModuleMetadata = await moduleMetadataRepo.findOne({
|
|
88
89
|
where: { id: moduleMetadata.id },
|
|
89
90
|
});
|
|
@@ -160,7 +161,7 @@ export class ScheduledJobSubscriber
|
|
|
160
161
|
(job) => job.scheduleName === jobName
|
|
161
162
|
);
|
|
162
163
|
// Insert or update job in metadata
|
|
163
|
-
const jobDto: CreateScheduledJobDto = await this.scheduledJobRepo.toDto(populatedScheduleJob as ScheduledJob);
|
|
164
|
+
const jobDto: CreateScheduledJobDto = await this.scheduledJobRepo.toDto(populatedScheduleJob as ScheduledJob, isEmbeddedDb() ? entityManager : undefined);
|
|
164
165
|
// Strip out bookkeeping/runtime fields before persisting to metadata JSON.
|
|
165
166
|
const {
|
|
166
167
|
moduleId,
|
|
@@ -4,6 +4,7 @@ import * as fs from 'fs/promises'; // Use the Promise-based version of fs for as
|
|
|
4
4
|
import { SOLID_CORE_MODULE_NAME } from 'src/constants';
|
|
5
5
|
import { ModelMetadata } from 'src/entities/model-metadata.entity';
|
|
6
6
|
import { SecurityRule } from 'src/entities/security-rule.entity';
|
|
7
|
+
import { isEmbeddedDb } from "src/helpers/environment.helper";
|
|
7
8
|
import { ModuleMetadataHelperService } from "src/helpers/module-metadata-helper.service";
|
|
8
9
|
import { SecurityRuleRepository } from 'src/repository/security-rule.repository';
|
|
9
10
|
import { DataSource, EntitySubscriberInterface, InsertEvent, UpdateEvent } from "typeorm";
|
|
@@ -40,7 +41,11 @@ export class SecurityRuleSubscriber implements EntitySubscriberInterface<Securit
|
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
// On embedded PGlite, use the transaction's connection to avoid a
|
|
45
|
+
// second pooled connection mid-transaction. On regular Postgres, use
|
|
46
|
+
// the default DataSource (original behaviour).
|
|
47
|
+
const mgr = isEmbeddedDb() ? event.manager : this.dataSource;
|
|
48
|
+
const modelMetadataRepo = mgr.getRepository(ModelMetadata);
|
|
44
49
|
const populatedModelMetadata = await modelMetadataRepo.findOne({
|
|
45
50
|
where: {
|
|
46
51
|
id: modelMetadata.id
|
|
@@ -67,12 +72,12 @@ export class SecurityRuleSubscriber implements EntitySubscriberInterface<Securit
|
|
|
67
72
|
|
|
68
73
|
if (metaData.securityRules) {
|
|
69
74
|
const securityRuleIndex = metaData.securityRules?.findIndex((ruleFromFile: { name: string }) => ruleFromFile.name === securityRule.name);
|
|
70
|
-
const { id, roleId, modelMetadataId, ...requiredDto } = await this.securityRuleRepo.toDto(securityRule)
|
|
75
|
+
const { id, roleId, modelMetadataId, ...requiredDto } = await this.securityRuleRepo.toDto(securityRule, isEmbeddedDb() ? event.manager : undefined)
|
|
71
76
|
metaData.securityRules[securityRuleIndex] = { ...requiredDto, securityRuleConfig: JSON.parse(securityRule.securityRuleConfig) }
|
|
72
77
|
}
|
|
73
78
|
else {
|
|
74
79
|
const securityRules = []
|
|
75
|
-
const { id, roleId, modelMetadataId, ...requiredDto } = await this.securityRuleRepo.toDto(securityRule)
|
|
80
|
+
const { id, roleId, modelMetadataId, ...requiredDto } = await this.securityRuleRepo.toDto(securityRule, isEmbeddedDb() ? event.manager : undefined)
|
|
76
81
|
securityRules.push({ ...requiredDto, securityRuleConfig: JSON.parse(securityRule.securityRuleConfig) })
|
|
77
82
|
metaData.securityRules = securityRules
|
|
78
83
|
}
|