@solidxai/core 0.1.11-beta.9 → 0.1.11
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 +456 -0
- package/dist/controllers/datasource-introspection.controller.d.ts +8 -0
- package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -1
- package/dist/controllers/media.controller.js +0 -2
- package/dist/controllers/media.controller.js.map +1 -1
- package/dist/helpers/environment.helper.d.ts +0 -1
- package/dist/helpers/environment.helper.d.ts.map +1 -1
- package/dist/helpers/environment.helper.js +0 -4
- package/dist/helpers/environment.helper.js.map +1 -1
- package/dist/helpers/model-metadata-helper.service.d.ts +1 -2
- package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
- package/dist/helpers/model-metadata-helper.service.js +2 -6
- package/dist/helpers/model-metadata-helper.service.js.map +1 -1
- package/dist/helpers/module-metadata-helper.service.js +1 -1
- package/dist/helpers/module-metadata-helper.service.js.map +1 -1
- package/dist/helpers/solid-registry.d.ts +1 -2
- package/dist/helpers/solid-registry.d.ts.map +1 -1
- package/dist/helpers/solid-registry.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +1 -0
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.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 +3 -6
- 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 +3 -6
- package/dist/repository/security-rule.repository.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +62 -33
- package/dist/services/authentication.service.d.ts +2 -0
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +32 -11
- package/dist/services/authentication.service.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 +5 -7
- 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 +7 -0
- package/dist/services/crud-helper.service.js.map +1 -1
- package/dist/services/datasource-introspection.service.d.ts +10 -0
- package/dist/services/datasource-introspection.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection.service.js +109 -17
- package/dist/services/datasource-introspection.service.js.map +1 -1
- package/dist/services/menu-item-metadata.service.d.ts.map +1 -1
- package/dist/services/menu-item-metadata.service.js +1 -0
- package/dist/services/menu-item-metadata.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts +2 -0
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +49 -16
- 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 +0 -28
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/services/settings/default-settings-provider.service.d.ts +212 -0
- package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
- package/dist/services/settings/default-settings-provider.service.js +106 -0
- package/dist/services/settings/default-settings-provider.service.js.map +1 -1
- package/dist/services/solid-ts-morph.service.d.ts +6 -0
- package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
- package/dist/services/solid-ts-morph.service.js +43 -0
- package/dist/services/solid-ts-morph.service.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +2 -8
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.d.ts +0 -8
- package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.js +3 -36
- package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
- package/dist/subscribers/created-by-updated-by.subscriber.d.ts.map +1 -1
- package/dist/subscribers/created-by-updated-by.subscriber.js.map +1 -1
- package/dist/subscribers/field-metadata.subscriber.d.ts.map +1 -1
- package/dist/subscribers/field-metadata.subscriber.js.map +1 -1
- package/dist/subscribers/scheduled-job.subscriber.d.ts.map +1 -1
- package/dist/subscribers/scheduled-job.subscriber.js +2 -3
- 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 +3 -5
- package/dist/subscribers/security-rule.subscriber.js.map +1 -1
- package/package.json +1 -1
- package/src/controllers/media.controller.ts +1 -1
- package/src/helpers/environment.helper.ts +0 -11
- package/src/helpers/model-metadata-helper.service.ts +2 -7
- package/src/helpers/module-metadata-helper.service.ts +1 -1
- package/src/helpers/solid-registry.ts +1 -7
- package/src/index.ts +0 -1
- package/src/interfaces.ts +1 -0
- package/src/repository/scheduled-job.repository.ts +4 -10
- package/src/repository/security-rule.repository.ts +4 -10
- package/src/seeders/seed-data/solid-core-metadata.json +62 -33
- package/src/services/authentication.service.ts +46 -12
- package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +5 -11
- package/src/services/crud-helper.service.ts +7 -0
- package/src/services/datasource-introspection.service.ts +176 -16
- package/src/services/menu-item-metadata.service.ts +2 -1
- package/src/services/model-metadata.service.ts +63 -22
- package/src/services/module-metadata.service.ts +0 -36
- package/src/services/settings/default-settings-provider.service.ts +106 -0
- package/src/services/solid-ts-morph.service.ts +61 -1
- package/src/subscribers/audit.subscriber.ts +2 -11
- package/src/subscribers/computed-entity-field.subscriber.ts +6 -63
- package/src/subscribers/created-by-updated-by.subscriber.ts +0 -3
- package/src/subscribers/field-metadata.subscriber.ts +0 -3
- package/src/subscribers/scheduled-job.subscriber.ts +2 -3
- package/src/subscribers/security-rule.subscriber.ts +3 -8
- package/dist/helpers/nodemon-heartbeat.d.ts +0 -5
- package/dist/helpers/nodemon-heartbeat.d.ts.map +0 -1
- package/dist/helpers/nodemon-heartbeat.js +0 -67
- package/dist/helpers/nodemon-heartbeat.js.map +0 -1
- package/src/helpers/nodemon-heartbeat.ts +0 -59
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Injectable, Logger } from "@nestjs/common";
|
|
3
3
|
import { join, dirname, normalize, isAbsolute, basename } from "node:path";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { Project, Node, ObjectLiteralExpression, ArrayLiteralExpression, QuoteKind, IndentationText } from "ts-morph";
|
|
5
|
+
import { Project, Node, ObjectLiteralExpression, ArrayLiteralExpression, QuoteKind, IndentationText, SyntaxKind } from "ts-morph";
|
|
6
6
|
import { MethodDeclarationStructure, StructureKind } from "ts-morph";
|
|
7
7
|
|
|
8
8
|
type Bucket = "imports" | "providers" | "exports";
|
|
@@ -468,6 +468,66 @@ export class SolidTsMorphService {
|
|
|
468
468
|
return { staged: true, overwritten: false, skipped: false };
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
+
cleanupTypeormDatasourceEntity(
|
|
472
|
+
filePath: string,
|
|
473
|
+
entityImportPath: string,
|
|
474
|
+
entityClassName: string,
|
|
475
|
+
): { staged: boolean; skipped: boolean; removedImport: boolean; removedEntity: boolean } {
|
|
476
|
+
const abs = this.resolveRepoPath(filePath);
|
|
477
|
+
if (!existsSync(abs)) {
|
|
478
|
+
this.logger.warn(`cleanupTypeormDatasourceEntity: file not found at ${filePath}, skipping.`);
|
|
479
|
+
return { staged: false, skipped: true, removedImport: false, removedEntity: false };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const existing = this.project.getSourceFile(abs);
|
|
483
|
+
const sourceFile = existing
|
|
484
|
+
? existing
|
|
485
|
+
: this.project.createSourceFile(abs, readFileSync(abs, "utf8"), { overwrite: true });
|
|
486
|
+
|
|
487
|
+
let removedImport = false;
|
|
488
|
+
let removedEntity = false;
|
|
489
|
+
|
|
490
|
+
const importDeclaration = sourceFile.getImportDeclarations().find((declaration) =>
|
|
491
|
+
declaration.getModuleSpecifierValue().replace(/\\/g, "/") === entityImportPath.replace(/\\/g, "/"),
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
if (importDeclaration) {
|
|
495
|
+
const namedImport = importDeclaration.getNamedImports().find((named) => named.getName() === entityClassName);
|
|
496
|
+
if (namedImport) {
|
|
497
|
+
namedImport.remove();
|
|
498
|
+
removedImport = true;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (
|
|
502
|
+
importDeclaration.getNamedImports().length === 0 &&
|
|
503
|
+
!importDeclaration.getDefaultImport() &&
|
|
504
|
+
!importDeclaration.getNamespaceImport()
|
|
505
|
+
) {
|
|
506
|
+
importDeclaration.remove();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const entitiesDeclaration = sourceFile.getVariableDeclaration("entities");
|
|
511
|
+
const entitiesArray = entitiesDeclaration?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);
|
|
512
|
+
if (entitiesArray) {
|
|
513
|
+
const elements = entitiesArray.getElements();
|
|
514
|
+
for (let i = elements.length - 1; i >= 0; i--) {
|
|
515
|
+
if (elements[i].getText().replace(/\s+/g, "") === entityClassName) {
|
|
516
|
+
entitiesArray.removeElement(i);
|
|
517
|
+
removedEntity = true;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!removedImport && !removedEntity) {
|
|
523
|
+
return { staged: false, skipped: true, removedImport, removedEntity };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
this.dirtySourceFiles.add(abs);
|
|
527
|
+
this.logger.log(`Staged datasource cleanup in ${this.rel(abs)} for entity ${entityClassName}`);
|
|
528
|
+
return { staged: true, skipped: false, removedImport, removedEntity };
|
|
529
|
+
}
|
|
530
|
+
|
|
471
531
|
|
|
472
532
|
|
|
473
533
|
}
|
|
@@ -4,7 +4,6 @@ import { lowerFirst } from 'src/helpers/string.helper';
|
|
|
4
4
|
import { SolidRegistry } from 'src/helpers/solid-registry';
|
|
5
5
|
import { DataSource, EntityMetadata, EntitySubscriberInterface, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm';
|
|
6
6
|
import { AuditQueuePayload } from 'src/interfaces';
|
|
7
|
-
import { isEmbeddedDb } from 'src/helpers/environment.helper';
|
|
8
7
|
import { RequestContextService } from 'src/services/request-context.service';
|
|
9
8
|
import { PublisherFactory } from 'src/services/queues/publisher-factory.service';
|
|
10
9
|
const AUDIT_BEFORE_SNAPSHOT = '__auditBeforeSnapshot';
|
|
@@ -89,10 +88,7 @@ export class AuditSubscriber implements EntitySubscriberInterface {
|
|
|
89
88
|
*/
|
|
90
89
|
private async prepareManyToManyAuditUpdateSnapshot(event: UpdateEvent<any>, entityId: number | null, updatedColumnNames: string[]): Promise<{ before: any; after: any } | null> {
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
// on the active connection. On regular Postgres, use the default DataSource.
|
|
94
|
-
const mgr = isEmbeddedDb() ? event.manager : this.dataSource;
|
|
95
|
-
const auditRelationFields = (await this.modelMetadataHelperService.loadFieldHierarchy(lowerFirst(event.metadata.name), isEmbeddedDb() ? event.manager : undefined)).filter(field =>
|
|
91
|
+
const auditRelationFields = (await this.modelMetadataHelperService.loadFieldHierarchy(lowerFirst(event.metadata.name))).filter(field =>
|
|
96
92
|
field.enableAuditTracking &&
|
|
97
93
|
field.type === 'relation' &&
|
|
98
94
|
field.relationType !== 'one-to-many'
|
|
@@ -110,7 +106,7 @@ export class AuditSubscriber implements EntitySubscriberInterface {
|
|
|
110
106
|
|
|
111
107
|
const relationBefore = event.entity?.[AUDIT_BEFORE_SNAPSHOT] ?? null;
|
|
112
108
|
|
|
113
|
-
const relationAfter = await
|
|
109
|
+
const relationAfter = await this.dataSource.getRepository(event.metadata.target as any).findOne({
|
|
114
110
|
where: { id: entityId } as any,
|
|
115
111
|
relations: relations as any,
|
|
116
112
|
});
|
|
@@ -155,11 +151,6 @@ export class AuditSubscriber implements EntitySubscriberInterface {
|
|
|
155
151
|
|
|
156
152
|
if (batch.length === 0) return;
|
|
157
153
|
|
|
158
|
-
if (isEmbeddedDb()) {
|
|
159
|
-
setImmediate(() => void this.publishAuditBatch(batch));
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
154
|
await this.publishAuditBatch(batch);
|
|
164
155
|
}
|
|
165
156
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { camelCase } from 'lodash';
|
|
2
2
|
import { forwardRef, Inject, Injectable, InternalServerErrorException, Logger, Scope } from "@nestjs/common";
|
|
3
3
|
import { ComputedFieldTriggerOperation } from "src/dtos/create-field-metadata.dto";
|
|
4
|
-
import { isEmbeddedDb } from "src/helpers/environment.helper";
|
|
5
4
|
import { ComputedFieldMetadata, SolidRegistry, TypeOrmEventContext } from "src/helpers/solid-registry";
|
|
6
5
|
import { IEntityPreComputeFieldProvider } from "src/interfaces";
|
|
7
6
|
import { PublisherFactory } from "src/services/queues/publisher-factory.service";
|
|
@@ -17,13 +16,6 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
|
|
|
17
16
|
private readonly logger = new Logger(this.constructor.name);
|
|
18
17
|
private dataSource: DataSource;
|
|
19
18
|
|
|
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
|
-
|
|
27
19
|
constructor(
|
|
28
20
|
private readonly solidRegistry: SolidRegistry,
|
|
29
21
|
@Inject(forwardRef(() => PublisherFactory))
|
|
@@ -68,18 +60,8 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
|
|
|
68
60
|
|
|
69
61
|
//FIXME: Need to add support for beforeRemove, beforeSoftRemove, afterSoftRemove, beforeRecover, afterRecover
|
|
70
62
|
|
|
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
63
|
private handlePostEventJobs(
|
|
82
|
-
|
|
64
|
+
_event: InsertEvent<any> | UpdateEvent<any> | RemoveEvent<any>,
|
|
83
65
|
entity: any,
|
|
84
66
|
currentOperation: ComputedFieldTriggerOperation,
|
|
85
67
|
modelName: string,
|
|
@@ -94,44 +76,11 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
|
|
|
94
76
|
);
|
|
95
77
|
if (computedFieldsTobeEvaluated.length === 0) return;
|
|
96
78
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
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);
|
|
79
|
+
for (const computedField of computedFieldsTobeEvaluated) {
|
|
80
|
+
this.enqueueComputedFieldEvaluationJob(
|
|
81
|
+
this.attachContext(computedField, eventContext),
|
|
82
|
+
entity,
|
|
83
|
+
);
|
|
135
84
|
}
|
|
136
85
|
}
|
|
137
86
|
|
|
@@ -199,10 +148,8 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
|
|
|
199
148
|
}
|
|
200
149
|
|
|
201
150
|
private enqueueComputedFieldEvaluationJob(computedField: ComputedFieldMetadata<any>, databaseEntity: any) {
|
|
202
|
-
const { manager: _manager, ...serializableEventContext } = computedField.eventContext ?? {};
|
|
203
151
|
const payload = {
|
|
204
152
|
...computedField,
|
|
205
|
-
eventContext: serializableEventContext,
|
|
206
153
|
databaseEntity,
|
|
207
154
|
};
|
|
208
155
|
this.publisherFactory.publish({ payload }, 'ComputedFieldEvaluationPublisher');
|
|
@@ -224,10 +171,6 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
|
|
|
224
171
|
const base: TypeOrmEventContext = {
|
|
225
172
|
metadataName: event.metadata?.name,
|
|
226
173
|
eventType: eventType,
|
|
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 } : {}),
|
|
231
174
|
};
|
|
232
175
|
if ("entityId" in event && event.entityId) {
|
|
233
176
|
base.entityId = event.entityId;
|
|
@@ -38,9 +38,6 @@ export class CreatedByUpdatedBySubscriber implements EntitySubscriberInterface {
|
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
// NOTE: If re-enabling loadUser below, query via `event.queryRunner.manager`, NOT
|
|
42
|
-
// `this.defaultDataSource.getRepository(...)`. A second connection opened mid-transaction
|
|
43
|
-
// deadlocks single-threaded engines (PGlite).
|
|
44
41
|
// const loadedUser = await this.loadUser(activeUserOrUndefined as unknown as ActiveUserData);
|
|
45
42
|
// if (isInsert) {
|
|
46
43
|
// event.entity.createdBy = loadedUser?.id;
|
|
@@ -21,9 +21,6 @@ export class FieldMetadataSubscriber implements EntitySubscriberInterface<FieldM
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
async afterUpdate(event: UpdateEvent<FieldMetadata>): Promise<void> {
|
|
24
|
-
// NOTE: If re-enabling the block below, use `event.queryRunner.manager` (the active
|
|
25
|
-
// transaction's connection), NOT `this.dataSource.getRepository(...)`. A second
|
|
26
|
-
// connection opened here mid-transaction deadlocks single-threaded engines (PGlite).
|
|
27
24
|
// If a field of type relation.many-to-one is marked for removal, and this field had an inverse relation created
|
|
28
25
|
// It means there is a field of type relation.one-to-many in the co-model, which needs to be removed
|
|
29
26
|
// if (event.entity && event.entity.isMarkedForRemoval && event.entity.type === 'relation' && event.entity.relationType === 'many-to-one' && event.entity.relationCreateInverse === true) {
|
|
@@ -4,7 +4,6 @@ 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";
|
|
8
7
|
import { ModuleMetadataHelperService } from "src/helpers/module-metadata-helper.service";
|
|
9
8
|
import { ScheduledJobRepository } from "src/repository/scheduled-job.repository";
|
|
10
9
|
import {
|
|
@@ -84,7 +83,7 @@ export class ScheduledJobSubscriber
|
|
|
84
83
|
return;
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
const moduleMetadataRepo =
|
|
86
|
+
const moduleMetadataRepo = this.dataSource.getRepository(ModuleMetadata);
|
|
88
87
|
const populatedModuleMetadata = await moduleMetadataRepo.findOne({
|
|
89
88
|
where: { id: moduleMetadata.id },
|
|
90
89
|
});
|
|
@@ -161,7 +160,7 @@ export class ScheduledJobSubscriber
|
|
|
161
160
|
(job) => job.scheduleName === jobName
|
|
162
161
|
);
|
|
163
162
|
// Insert or update job in metadata
|
|
164
|
-
const jobDto: CreateScheduledJobDto = await this.scheduledJobRepo.toDto(populatedScheduleJob as ScheduledJob
|
|
163
|
+
const jobDto: CreateScheduledJobDto = await this.scheduledJobRepo.toDto(populatedScheduleJob as ScheduledJob);
|
|
165
164
|
// Strip out bookkeeping/runtime fields before persisting to metadata JSON.
|
|
166
165
|
const {
|
|
167
166
|
moduleId,
|
|
@@ -4,7 +4,6 @@ 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";
|
|
8
7
|
import { ModuleMetadataHelperService } from "src/helpers/module-metadata-helper.service";
|
|
9
8
|
import { SecurityRuleRepository } from 'src/repository/security-rule.repository';
|
|
10
9
|
import { DataSource, EntitySubscriberInterface, InsertEvent, UpdateEvent } from "typeorm";
|
|
@@ -41,11 +40,7 @@ export class SecurityRuleSubscriber implements EntitySubscriberInterface<Securit
|
|
|
41
40
|
return;
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
|
|
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);
|
|
43
|
+
const modelMetadataRepo = this.dataSource.getRepository(ModelMetadata);
|
|
49
44
|
const populatedModelMetadata = await modelMetadataRepo.findOne({
|
|
50
45
|
where: {
|
|
51
46
|
id: modelMetadata.id
|
|
@@ -72,12 +67,12 @@ export class SecurityRuleSubscriber implements EntitySubscriberInterface<Securit
|
|
|
72
67
|
|
|
73
68
|
if (metaData.securityRules) {
|
|
74
69
|
const securityRuleIndex = metaData.securityRules?.findIndex((ruleFromFile: { name: string }) => ruleFromFile.name === securityRule.name);
|
|
75
|
-
const { id, roleId, modelMetadataId, ...requiredDto } = await this.securityRuleRepo.toDto(securityRule
|
|
70
|
+
const { id, roleId, modelMetadataId, ...requiredDto } = await this.securityRuleRepo.toDto(securityRule)
|
|
76
71
|
metaData.securityRules[securityRuleIndex] = { ...requiredDto, securityRuleConfig: JSON.parse(securityRule.securityRuleConfig) }
|
|
77
72
|
}
|
|
78
73
|
else {
|
|
79
74
|
const securityRules = []
|
|
80
|
-
const { id, roleId, modelMetadataId, ...requiredDto } = await this.securityRuleRepo.toDto(securityRule
|
|
75
|
+
const { id, roleId, modelMetadataId, ...requiredDto } = await this.securityRuleRepo.toDto(securityRule)
|
|
81
76
|
securityRules.push({ ...requiredDto, securityRuleConfig: JSON.parse(securityRule.securityRuleConfig) })
|
|
82
77
|
metaData.securityRules = securityRules
|
|
83
78
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nodemon-heartbeat.d.ts","sourceRoot":"","sources":["../../src/helpers/nodemon-heartbeat.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AASD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,sBAAsB,CA8B5E"}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.startNodemonHeartbeat = startNodemonHeartbeat;
|
|
37
|
-
const fsSync = __importStar(require("fs"));
|
|
38
|
-
const path = __importStar(require("path"));
|
|
39
|
-
const HEARTBEAT_FILENAME = 'solidx-nodemon-heartbeat.json';
|
|
40
|
-
const HEARTBEAT_INTERVAL_MS = 1500;
|
|
41
|
-
function startNodemonHeartbeat(srcDir) {
|
|
42
|
-
const filePath = path.join(srcDir, HEARTBEAT_FILENAME);
|
|
43
|
-
let stopped = false;
|
|
44
|
-
const touch = () => {
|
|
45
|
-
try {
|
|
46
|
-
fsSync.writeFileSync(filePath, String(Date.now()));
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
touch();
|
|
52
|
-
const timer = setInterval(touch, HEARTBEAT_INTERVAL_MS);
|
|
53
|
-
return {
|
|
54
|
-
stop() {
|
|
55
|
-
if (stopped)
|
|
56
|
-
return;
|
|
57
|
-
stopped = true;
|
|
58
|
-
clearInterval(timer);
|
|
59
|
-
try {
|
|
60
|
-
fsSync.unlinkSync(filePath);
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
//# sourceMappingURL=nodemon-heartbeat.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nodemon-heartbeat.js","sourceRoot":"","sources":["../../src/helpers/nodemon-heartbeat.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,sDA8BC;AA1DD,2CAA6B;AAC7B,2CAA6B;AAa7B,MAAM,kBAAkB,GAAG,+BAA+B,CAAC;AAC3D,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAanC,SAAgB,qBAAqB,CAAC,MAAc;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACvD,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,IAAI,CAAC;YACH,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;QAGT,CAAC;IACH,CAAC,CAAC;IAGF,KAAK,EAAE,CAAC;IAER,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC;IAExD,OAAO;QACL,IAAI;YACF,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,IAAI,CAAC;gBACH,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC;YAET,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import * as fsSync from 'fs';\nimport * as path from 'path';\n\n/**\n * The heartbeat file is placed inside the nodemon-watched src/ directory so\n * that touching it resets nodemon's restart delay. This prevents nodemon from\n * restarting the API server while in-process code generation is writing files\n * (which is especially important for embedded PGlite projects where the\n * generate-code endpoint runs schematics in-process).\n *\n * The file uses a .json extension because nodemon is configured to watch\n * ts,json files. It is placed at the src/ root (not inside any metadata\n * directory) so it is not caught by the metadata JSON ignore rule.\n */\nconst HEARTBEAT_FILENAME = 'solidx-nodemon-heartbeat.json';\nconst HEARTBEAT_INTERVAL_MS = 1500;\n\nexport interface NodemonHeartbeatHandle {\n stop: () => void;\n}\n\n/**\n * Starts a heartbeat that touches a file in the nodemon-watched directory\n * every ~1.5 s. Call `stop()` after the response has been flushed to allow\n * nodemon to restart.\n *\n * @param srcDir Absolute path to the `src/` directory nodemon watches.\n */\nexport function startNodemonHeartbeat(srcDir: string): NodemonHeartbeatHandle {\n const filePath = path.join(srcDir, HEARTBEAT_FILENAME);\n let stopped = false;\n\n const touch = () => {\n try {\n fsSync.writeFileSync(filePath, String(Date.now()));\n } catch {\n // best-effort — if the file can't be written, nodemon will simply\n // restart as usual, which is the safe fallback.\n }\n };\n\n // Write immediately so the delay resets before any schematic output lands.\n touch();\n\n const timer = setInterval(touch, HEARTBEAT_INTERVAL_MS);\n\n return {\n stop() {\n if (stopped) return;\n stopped = true;\n clearInterval(timer);\n try {\n fsSync.unlinkSync(filePath);\n } catch {\n // file may not exist — ignore\n }\n },\n };\n}\n"]}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import * as fsSync from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The heartbeat file is placed inside the nodemon-watched src/ directory so
|
|
6
|
-
* that touching it resets nodemon's restart delay. This prevents nodemon from
|
|
7
|
-
* restarting the API server while in-process code generation is writing files
|
|
8
|
-
* (which is especially important for embedded PGlite projects where the
|
|
9
|
-
* generate-code endpoint runs schematics in-process).
|
|
10
|
-
*
|
|
11
|
-
* The file uses a .json extension because nodemon is configured to watch
|
|
12
|
-
* ts,json files. It is placed at the src/ root (not inside any metadata
|
|
13
|
-
* directory) so it is not caught by the metadata JSON ignore rule.
|
|
14
|
-
*/
|
|
15
|
-
const HEARTBEAT_FILENAME = 'solidx-nodemon-heartbeat.json';
|
|
16
|
-
const HEARTBEAT_INTERVAL_MS = 1500;
|
|
17
|
-
|
|
18
|
-
export interface NodemonHeartbeatHandle {
|
|
19
|
-
stop: () => void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Starts a heartbeat that touches a file in the nodemon-watched directory
|
|
24
|
-
* every ~1.5 s. Call `stop()` after the response has been flushed to allow
|
|
25
|
-
* nodemon to restart.
|
|
26
|
-
*
|
|
27
|
-
* @param srcDir Absolute path to the `src/` directory nodemon watches.
|
|
28
|
-
*/
|
|
29
|
-
export function startNodemonHeartbeat(srcDir: string): NodemonHeartbeatHandle {
|
|
30
|
-
const filePath = path.join(srcDir, HEARTBEAT_FILENAME);
|
|
31
|
-
let stopped = false;
|
|
32
|
-
|
|
33
|
-
const touch = () => {
|
|
34
|
-
try {
|
|
35
|
-
fsSync.writeFileSync(filePath, String(Date.now()));
|
|
36
|
-
} catch {
|
|
37
|
-
// best-effort — if the file can't be written, nodemon will simply
|
|
38
|
-
// restart as usual, which is the safe fallback.
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Write immediately so the delay resets before any schematic output lands.
|
|
43
|
-
touch();
|
|
44
|
-
|
|
45
|
-
const timer = setInterval(touch, HEARTBEAT_INTERVAL_MS);
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
stop() {
|
|
49
|
-
if (stopped) return;
|
|
50
|
-
stopped = true;
|
|
51
|
-
clearInterval(timer);
|
|
52
|
-
try {
|
|
53
|
-
fsSync.unlinkSync(filePath);
|
|
54
|
-
} catch {
|
|
55
|
-
// file may not exist — ignore
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
}
|