@kinotic-ai/kinotic-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +594 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +6 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +6 -0
- package/dist/commands/generate.d.ts +11 -0
- package/dist/commands/generate.js +36 -0
- package/dist/commands/initialize.d.ts +12 -0
- package/dist/commands/initialize.js +102 -0
- package/dist/commands/synchronize.d.ts +17 -0
- package/dist/commands/synchronize.js +154 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/internal/CodeGenerationService.d.ts +24 -0
- package/dist/internal/CodeGenerationService.js +256 -0
- package/dist/internal/Logger.d.ts +27 -0
- package/dist/internal/Logger.js +35 -0
- package/dist/internal/ProjectMigrationService.d.ts +28 -0
- package/dist/internal/ProjectMigrationService.js +99 -0
- package/dist/internal/Utils.d.ts +66 -0
- package/dist/internal/Utils.js +349 -0
- package/dist/internal/converter/ConverterConstants.d.ts +3 -0
- package/dist/internal/converter/ConverterConstants.js +4 -0
- package/dist/internal/converter/DefaultConversionContext.d.ts +29 -0
- package/dist/internal/converter/DefaultConversionContext.js +112 -0
- package/dist/internal/converter/IConversionContext.d.ts +64 -0
- package/dist/internal/converter/IConversionContext.js +11 -0
- package/dist/internal/converter/IConverterStrategy.d.ts +35 -0
- package/dist/internal/converter/IConverterStrategy.js +1 -0
- package/dist/internal/converter/ITypeConverter.d.ts +28 -0
- package/dist/internal/converter/ITypeConverter.js +1 -0
- package/dist/internal/converter/SpecificTypesConverter.d.ts +12 -0
- package/dist/internal/converter/SpecificTypesConverter.js +24 -0
- package/dist/internal/converter/codegen/ArrayC3TypeToStatementMapper.d.ts +9 -0
- package/dist/internal/converter/codegen/ArrayC3TypeToStatementMapper.js +46 -0
- package/dist/internal/converter/codegen/ObjectC3TypeToStatementMapper.d.ts +13 -0
- package/dist/internal/converter/codegen/ObjectC3TypeToStatementMapper.js +67 -0
- package/dist/internal/converter/codegen/PrimitiveC3TypeToStatementMapper.d.ts +9 -0
- package/dist/internal/converter/codegen/PrimitiveC3TypeToStatementMapper.js +24 -0
- package/dist/internal/converter/codegen/StatementMapper.d.ts +40 -0
- package/dist/internal/converter/codegen/StatementMapper.js +132 -0
- package/dist/internal/converter/codegen/StatementMapperConversionState.d.ts +9 -0
- package/dist/internal/converter/codegen/StatementMapperConversionState.js +17 -0
- package/dist/internal/converter/codegen/StatementMapperConverterStrategy.d.ts +16 -0
- package/dist/internal/converter/codegen/StatementMapperConverterStrategy.js +30 -0
- package/dist/internal/converter/codegen/UnionC3TypeToStatementMapper.d.ts +9 -0
- package/dist/internal/converter/codegen/UnionC3TypeToStatementMapper.js +46 -0
- package/dist/internal/converter/common/BaseConversionState.d.ts +9 -0
- package/dist/internal/converter/common/BaseConversionState.js +13 -0
- package/dist/internal/converter/typescript/ArrayToC3Type.d.ts +9 -0
- package/dist/internal/converter/typescript/ArrayToC3Type.js +17 -0
- package/dist/internal/converter/typescript/ConverterUtils.d.ts +4 -0
- package/dist/internal/converter/typescript/ConverterUtils.js +261 -0
- package/dist/internal/converter/typescript/EnumToC3Type.d.ts +12 -0
- package/dist/internal/converter/typescript/EnumToC3Type.js +26 -0
- package/dist/internal/converter/typescript/ObjectLikeToC3Type.d.ts +15 -0
- package/dist/internal/converter/typescript/ObjectLikeToC3Type.js +111 -0
- package/dist/internal/converter/typescript/PrimitiveToC3Type.d.ts +10 -0
- package/dist/internal/converter/typescript/PrimitiveToC3Type.js +33 -0
- package/dist/internal/converter/typescript/QueryOptionsToC3Type.d.ts +9 -0
- package/dist/internal/converter/typescript/QueryOptionsToC3Type.js +9 -0
- package/dist/internal/converter/typescript/TenantSelectionToC3Type.d.ts +9 -0
- package/dist/internal/converter/typescript/TenantSelectionToC3Type.js +9 -0
- package/dist/internal/converter/typescript/TypescriptConversionState.d.ts +24 -0
- package/dist/internal/converter/typescript/TypescriptConversionState.js +26 -0
- package/dist/internal/converter/typescript/TypescriptConverterStrategy.d.ts +16 -0
- package/dist/internal/converter/typescript/TypescriptConverterStrategy.js +44 -0
- package/dist/internal/converter/typescript/UnionToC3Type.d.ts +15 -0
- package/dist/internal/converter/typescript/UnionToC3Type.js +184 -0
- package/dist/internal/state/Environment.d.ts +13 -0
- package/dist/internal/state/Environment.js +65 -0
- package/dist/internal/state/IStateManager.d.ts +19 -0
- package/dist/internal/state/IStateManager.js +41 -0
- package/dist/internal/state/KinoticProjectConfigUtil.d.ts +9 -0
- package/dist/internal/state/KinoticProjectConfigUtil.js +153 -0
- package/dist/templates/AdminEntityService.liquid +14 -0
- package/dist/templates/BaseAdminEntityService.liquid +19 -0
- package/dist/templates/BaseEntityService.liquid +48 -0
- package/dist/templates/EntityService.liquid +14 -0
- package/dist/templates/KinoticProjectConfig.ts.liquid +10 -0
- package/oclif.manifest.json +161 -0
- package/package.json +97 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Kinotic } from '@kinotic-ai/core';
|
|
2
|
+
import { readdir, readFile, access } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { constants } from 'fs';
|
|
5
|
+
/**
|
|
6
|
+
* Internal service for loading and applying migrations from the local filesystem.
|
|
7
|
+
* Looks for migration files in a 'migrations' folder in the project root.
|
|
8
|
+
*/
|
|
9
|
+
export class ProjectMigrationService {
|
|
10
|
+
migrationService;
|
|
11
|
+
logger;
|
|
12
|
+
constructor(logger) {
|
|
13
|
+
this.migrationService = Kinotic.migrations;
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Loads migration files from the migrations directory and applies any new ones.
|
|
18
|
+
*
|
|
19
|
+
* @param projectId the project identifier to apply migrations to
|
|
20
|
+
* @param migrationsDir the directory containing migration files (defaults to './migrations')
|
|
21
|
+
* @param verbose whether to enable verbose logging
|
|
22
|
+
*/
|
|
23
|
+
async applyMigrations(projectId, migrationsDir = './migrations', verbose = false) {
|
|
24
|
+
// Check if migrations directory exists
|
|
25
|
+
try {
|
|
26
|
+
await access(migrationsDir, constants.F_OK);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
this.logger.log('No migrations will be applied since no migrations directory found');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Get the last applied migration version
|
|
33
|
+
const lastAppliedVersion = await this.migrationService.getLastAppliedMigrationVersion(projectId);
|
|
34
|
+
// Load only the files that need to be applied
|
|
35
|
+
const migrationsToApply = await this.loadUnappliedMigrations(migrationsDir, lastAppliedVersion);
|
|
36
|
+
if (migrationsToApply.length === 0) {
|
|
37
|
+
this.logVerbose('No migration files found or all migrations already applied', verbose);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
this.logger.log(`Applying ${migrationsToApply.length} new migrations (starting from version ${lastAppliedVersion !== null ? lastAppliedVersion + 1 : 1})`);
|
|
41
|
+
// Apply the migrations
|
|
42
|
+
const migrationRequest = {
|
|
43
|
+
projectId,
|
|
44
|
+
migrations: migrationsToApply
|
|
45
|
+
};
|
|
46
|
+
const result = await this.migrationService.executeMigrations(migrationRequest);
|
|
47
|
+
if (result.success) {
|
|
48
|
+
this.logger.log(`Successfully applied ${result.migrationsProcessed} migrations`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
throw new Error(`Migration failed: ${result.errorMessage}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Loads only the migration files that need to be applied, checking everything in one pass.
|
|
56
|
+
*/
|
|
57
|
+
async loadUnappliedMigrations(migrationsDir, lastAppliedVersion) {
|
|
58
|
+
const migrations = [];
|
|
59
|
+
// Read directory and process files in one pass
|
|
60
|
+
const files = await readdir(migrationsDir);
|
|
61
|
+
for (const file of files) {
|
|
62
|
+
// Skip non-SQL files
|
|
63
|
+
if (!file.endsWith('.sql')) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Extract version from filename (e.g., V1__create_table.sql -> 1)
|
|
67
|
+
const version = this.extractVersionFromFilename(file);
|
|
68
|
+
if (version === null) {
|
|
69
|
+
throw new Error(`Invalid migration filename format: ${file}. Expected format: V{version}__{description}.sql`);
|
|
70
|
+
}
|
|
71
|
+
// Only load files that need to be applied
|
|
72
|
+
if (lastAppliedVersion === null || version > lastAppliedVersion) {
|
|
73
|
+
const filePath = join(migrationsDir, file);
|
|
74
|
+
const content = await readFile(filePath, 'utf8');
|
|
75
|
+
migrations.push({
|
|
76
|
+
version,
|
|
77
|
+
name: file,
|
|
78
|
+
content
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Sort by version to ensure proper order
|
|
83
|
+
migrations.sort((a, b) => a.version - b.version);
|
|
84
|
+
return migrations;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extracts the version number from a migration filename.
|
|
88
|
+
* Expected format: V{version}__{description}.sql
|
|
89
|
+
*/
|
|
90
|
+
extractVersionFromFilename(filename) {
|
|
91
|
+
const match = filename.match(/^V(\d+)__.*\.sql$/);
|
|
92
|
+
return match ? parseInt(match[1], 10) : null;
|
|
93
|
+
}
|
|
94
|
+
logVerbose(message, verbose) {
|
|
95
|
+
if (verbose) {
|
|
96
|
+
this.logger.log(message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { FunctionDefinition, ObjectC3Type } from '@kinotic-ai/idl';
|
|
2
|
+
import { Project } from 'ts-morph';
|
|
3
|
+
import { Logger } from './Logger.js';
|
|
4
|
+
export type GeneratedServiceInfo = {
|
|
5
|
+
entityServiceName: string;
|
|
6
|
+
namedQueries: FunctionDefinition[];
|
|
7
|
+
};
|
|
8
|
+
export declare class SessionMetadata {
|
|
9
|
+
sessionId: string;
|
|
10
|
+
replyToId: string;
|
|
11
|
+
constructor(sessionId: string, replyToId: string);
|
|
12
|
+
}
|
|
13
|
+
export declare function jsonStringifyReplacer(key: any, value: any): any;
|
|
14
|
+
/**
|
|
15
|
+
* Connects to the server and upgrades the session to a CLI session
|
|
16
|
+
* Currently this works by connecting and waiting for the clients session id on the event bus
|
|
17
|
+
* The cli then disconnects and reconnects using the clients' session.
|
|
18
|
+
* This will be replaced when the server supports a session upgrade command
|
|
19
|
+
* @param server the server to connect to
|
|
20
|
+
* @param logger the logger to use
|
|
21
|
+
* @param authHeadersFile path to a file containing the auth headers
|
|
22
|
+
* @return true if the session was upgraded successfully
|
|
23
|
+
*/
|
|
24
|
+
export declare function connectAndUpgradeSession(server: string, logger: Logger, authHeadersFile?: string): Promise<boolean>;
|
|
25
|
+
export type EntityInfo = {
|
|
26
|
+
exportedFromFile: string;
|
|
27
|
+
defaultExport: boolean;
|
|
28
|
+
entity: ObjectC3Type;
|
|
29
|
+
multiTenantSelectionEnabled: boolean;
|
|
30
|
+
};
|
|
31
|
+
export type ConversionConfiguration = {
|
|
32
|
+
application: string;
|
|
33
|
+
entitiesPath: string;
|
|
34
|
+
verbose: boolean;
|
|
35
|
+
logger: Logger;
|
|
36
|
+
};
|
|
37
|
+
export declare function pathToTsGlobPath(path: string): string;
|
|
38
|
+
export declare function createTsMorphProject(): Project;
|
|
39
|
+
export declare function convertAllEntities(config: ConversionConfiguration): EntityInfo[];
|
|
40
|
+
/**
|
|
41
|
+
* Will return the relative path from the 'from' path to the 'to' path
|
|
42
|
+
* @param from path to start from
|
|
43
|
+
* @param to path to end at
|
|
44
|
+
* @param fileExtensionForImports this is the file extension to append to the end of the relative path
|
|
45
|
+
*/
|
|
46
|
+
export declare function getRelativeImportPath(from: string, to: string, fileExtensionForImports?: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* Will return the name of the node module if the path is within a node module or null if not
|
|
49
|
+
* @param nodeModulePath to check
|
|
50
|
+
*/
|
|
51
|
+
export declare function tryGetNodeModuleName(nodeModulePath: string): string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Saves the C3Type to the local filesystem
|
|
54
|
+
* @param savePath to save the entities to
|
|
55
|
+
* @param entity to save
|
|
56
|
+
* @param logger to log to if desired, if null nothing will be logged
|
|
57
|
+
*/
|
|
58
|
+
export declare function writeEntityJsonToFilesystem(savePath: string, entity: ObjectC3Type, logger?: Logger): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Save the C3Type(s) to the local filesystem
|
|
61
|
+
* @param savePath to save the entities to
|
|
62
|
+
* @param entities to save
|
|
63
|
+
* @param logger to log to if desired, if null nothing will be logged
|
|
64
|
+
*/
|
|
65
|
+
export declare function writeEntitiesJsonToFilesystem(savePath: string, entities: ObjectC3Type[], logger?: Logger): Promise<void>;
|
|
66
|
+
export declare function writeGeneratedServiceInfoToFilesystem(savePath: string, info: GeneratedServiceInfo, logger?: Logger): Promise<void>;
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { Kinotic, Event, EventConstants, ParticipantConstants } from '@kinotic-ai/core';
|
|
2
|
+
import { ObjectC3Type } from '@kinotic-ai/idl';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import fsPromises from 'fs/promises';
|
|
5
|
+
import { confirm } from '@inquirer/prompts';
|
|
6
|
+
import open from 'open';
|
|
7
|
+
import pTimeout from 'p-timeout';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { IndentationText, Node, Project } from 'ts-morph';
|
|
10
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
11
|
+
import { createConversionContext } from './converter/IConversionContext.js';
|
|
12
|
+
import { TypescriptConversionState } from './converter/typescript/TypescriptConversionState.js';
|
|
13
|
+
import { TypescriptConverterStrategy } from './converter/typescript/TypescriptConverterStrategy.js';
|
|
14
|
+
export class SessionMetadata {
|
|
15
|
+
sessionId;
|
|
16
|
+
replyToId;
|
|
17
|
+
constructor(sessionId, replyToId) {
|
|
18
|
+
this.sessionId = sessionId;
|
|
19
|
+
this.replyToId = replyToId;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function isEmpty(value) {
|
|
23
|
+
if (value === null || value === undefined) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value.every(isEmpty);
|
|
28
|
+
}
|
|
29
|
+
else if (typeof (value) === 'object') {
|
|
30
|
+
return Object.values(value).every(isEmpty);
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
export function jsonStringifyReplacer(key, value) {
|
|
35
|
+
return isEmpty(value)
|
|
36
|
+
? undefined
|
|
37
|
+
: value;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Connects to the server and upgrades the session to a CLI session
|
|
41
|
+
* Currently this works by connecting and waiting for the clients session id on the event bus
|
|
42
|
+
* The cli then disconnects and reconnects using the clients' session.
|
|
43
|
+
* This will be replaced when the server supports a session upgrade command
|
|
44
|
+
* @param server the server to connect to
|
|
45
|
+
* @param logger the logger to use
|
|
46
|
+
* @param authHeadersFile path to a file containing the auth headers
|
|
47
|
+
* @return true if the session was upgraded successfully
|
|
48
|
+
*/
|
|
49
|
+
export async function connectAndUpgradeSession(server, logger, authHeadersFile) {
|
|
50
|
+
try {
|
|
51
|
+
const serverURL = new URL(server);
|
|
52
|
+
if (serverURL.protocol !== 'http:' && serverURL.protocol !== 'https:') {
|
|
53
|
+
logger.log('Invalid server URL, only http and https are supported');
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
let connectionInfo = { host: '' };
|
|
57
|
+
if (serverURL.hostname === 'localhost' || serverURL.hostname === '127.0.0.1') {
|
|
58
|
+
connectionInfo.host = serverURL.hostname;
|
|
59
|
+
connectionInfo.port = 58503;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
connectionInfo.host = serverURL.hostname;
|
|
63
|
+
if (serverURL.protocol === 'https:') {
|
|
64
|
+
connectionInfo.useSSL = true;
|
|
65
|
+
connectionInfo.port = 443;
|
|
66
|
+
}
|
|
67
|
+
if (serverURL.port) {
|
|
68
|
+
connectionInfo.port = Number(serverURL.port);
|
|
69
|
+
}
|
|
70
|
+
else if (!connectionInfo.useSSL) {
|
|
71
|
+
connectionInfo.port = 58503;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (authHeadersFile) {
|
|
75
|
+
if (!fs.existsSync(authHeadersFile)) {
|
|
76
|
+
logger.log(`Authentication header file ${authHeadersFile} does not exist. Please provide a valid file.`);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
logger.log(`Authentication header file ${authHeadersFile} loaded successfully. Using authentication headers to connect to the Kinotic Server.`);
|
|
81
|
+
}
|
|
82
|
+
const authHeaders = JSON.parse(fs.readFileSync(authHeadersFile, 'utf8'));
|
|
83
|
+
connectionInfo.connectHeaders = authHeaders;
|
|
84
|
+
const connectedInfo = await pTimeout(Kinotic.connect(connectionInfo), {
|
|
85
|
+
milliseconds: 60000,
|
|
86
|
+
message: 'Connection timeout trying to connect to the Kinotic Server'
|
|
87
|
+
});
|
|
88
|
+
if (connectedInfo) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
connectionInfo.connectHeaders = {
|
|
97
|
+
login: ParticipantConstants.CLI_PARTICIPANT_ID
|
|
98
|
+
};
|
|
99
|
+
const connectedInfo = await pTimeout(Kinotic.connect(connectionInfo), {
|
|
100
|
+
milliseconds: 60000,
|
|
101
|
+
message: 'Connection timeout trying to connect to the Kinotic Server'
|
|
102
|
+
});
|
|
103
|
+
if (connectedInfo) {
|
|
104
|
+
// This works because any client can subscribe to a destination that is scoped to the connectedInfo.replyToId
|
|
105
|
+
const scope = connectedInfo.replyToId + ':' + uuidv4();
|
|
106
|
+
const url = server + (server.endsWith('/') ? '' : '/') + '#/sessionUpgrade/' + encodeURIComponent(scope);
|
|
107
|
+
logger.log('Authenticate your account at:');
|
|
108
|
+
logger.log(url);
|
|
109
|
+
const answer = await confirm({
|
|
110
|
+
message: 'Open in browser?',
|
|
111
|
+
default: true,
|
|
112
|
+
});
|
|
113
|
+
if (answer) {
|
|
114
|
+
await open(url);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
logger.log('Browser will not be opened. You must authenticate your account before continuing.');
|
|
118
|
+
}
|
|
119
|
+
const sessionMetadata = await receiveSessionId(scope);
|
|
120
|
+
await Kinotic.disconnect();
|
|
121
|
+
connectionInfo.connectHeaders = {
|
|
122
|
+
session: sessionMetadata.sessionId
|
|
123
|
+
};
|
|
124
|
+
// Provide this so the continuum client will use the same replyToId as the session
|
|
125
|
+
connectionInfo.connectHeaders[EventConstants.REPLY_TO_ID_HEADER] = sessionMetadata.replyToId;
|
|
126
|
+
await Kinotic.connect(connectionInfo);
|
|
127
|
+
logger.log('Authenticated successfully\n');
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
logger.log("Could not connect to the Kinotic Server. Please check the server is running and the URL is correct.");
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
logger.log("Could not connect to the Kinotic Server. Please check the server is running and the URL is correct.", e);
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function receiveSessionId(scope) {
|
|
142
|
+
const subscribeCRI = EventConstants.SERVICE_DESTINATION_PREFIX + scope + '@continuum.cli.SessionUpgradeService';
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const subscription = Kinotic.eventBus.observe(subscribeCRI).subscribe((value) => {
|
|
145
|
+
// send reply to user
|
|
146
|
+
const replyTo = value.getHeader(EventConstants.REPLY_TO_HEADER);
|
|
147
|
+
if (replyTo) {
|
|
148
|
+
const replyEvent = new Event(replyTo);
|
|
149
|
+
const correlationId = value.getHeader(EventConstants.CORRELATION_ID_HEADER);
|
|
150
|
+
if (correlationId) {
|
|
151
|
+
replyEvent.setHeader(EventConstants.CORRELATION_ID_HEADER, correlationId);
|
|
152
|
+
}
|
|
153
|
+
replyEvent.setHeader(EventConstants.CONTENT_TYPE_HEADER, EventConstants.CONTENT_JSON);
|
|
154
|
+
Kinotic.eventBus.send(replyEvent);
|
|
155
|
+
}
|
|
156
|
+
subscription.unsubscribe();
|
|
157
|
+
const jsonObj = JSON.parse(value.getDataString());
|
|
158
|
+
if (jsonObj?.length > 0) {
|
|
159
|
+
resolve(jsonObj[0]);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
reject('No Session Id found in data');
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function getEntityDecoratorIfExists(node) {
|
|
168
|
+
if (Node.isClassDeclaration(node)) {
|
|
169
|
+
return node.getDecorator('Entity');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export function pathToTsGlobPath(path) {
|
|
173
|
+
return path.endsWith('.ts') ? path : (path.endsWith('/') ? path + '*.ts' : path + '/*.ts');
|
|
174
|
+
}
|
|
175
|
+
export function createTsMorphProject() {
|
|
176
|
+
const tsConfigFilePath = path.resolve('tsconfig.json');
|
|
177
|
+
if (!fs.existsSync(tsConfigFilePath)) {
|
|
178
|
+
throw new Error(`No tsconfig.json found in working directory: ${process.cwd()}`);
|
|
179
|
+
}
|
|
180
|
+
return new Project({
|
|
181
|
+
tsConfigFilePath: tsConfigFilePath,
|
|
182
|
+
manipulationSettings: {
|
|
183
|
+
indentationText: IndentationText.TwoSpaces
|
|
184
|
+
}
|
|
185
|
+
// compilerOptions: {
|
|
186
|
+
// target: ScriptTarget.ES2020,
|
|
187
|
+
// useDefineForClassFields: true,
|
|
188
|
+
// module: ModuleKind.ES2020,
|
|
189
|
+
// lib: ["ES2020"],
|
|
190
|
+
// skipLibCheck: true,
|
|
191
|
+
// downlevelIteration: true,
|
|
192
|
+
// emitDecoratorMetadata: true,
|
|
193
|
+
// experimentalDecorators: true,
|
|
194
|
+
// esModuleInterop: true,
|
|
195
|
+
// moduleResolution: ModuleResolutionKind.NodeNext,
|
|
196
|
+
// resolveJsonModule: true,
|
|
197
|
+
// isolatedModules: true,
|
|
198
|
+
// noEmit: true,
|
|
199
|
+
// }
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
export function convertAllEntities(config) {
|
|
203
|
+
const entities = [];
|
|
204
|
+
const project = createTsMorphProject();
|
|
205
|
+
if (config.verbose) {
|
|
206
|
+
project.enableLogging(true);
|
|
207
|
+
}
|
|
208
|
+
let absEntitiesPath = path.resolve(config.entitiesPath);
|
|
209
|
+
if (!absEntitiesPath.endsWith('.ts') && !absEntitiesPath.endsWith(path.sep)) {
|
|
210
|
+
absEntitiesPath = absEntitiesPath + path.sep;
|
|
211
|
+
}
|
|
212
|
+
project.addSourceFilesAtPaths(pathToTsGlobPath(config.entitiesPath));
|
|
213
|
+
const sourceFiles = project.getSourceFiles();
|
|
214
|
+
for (const sourceFile of sourceFiles) {
|
|
215
|
+
const absSourcePath = path.resolve(sourceFile.getFilePath());
|
|
216
|
+
// make sure this file is in our configured paths and not just introduced by the ts-config
|
|
217
|
+
if (absSourcePath.startsWith(absEntitiesPath)) {
|
|
218
|
+
const conversionContext = createConversionContext(new TypescriptConverterStrategy(new TypescriptConversionState(config.application), config.logger));
|
|
219
|
+
const exportedDeclarations = sourceFile.getExportedDeclarations();
|
|
220
|
+
exportedDeclarations.forEach((exportedDeclarationEntries, name) => {
|
|
221
|
+
exportedDeclarationEntries.forEach((exportedDeclaration) => {
|
|
222
|
+
if (Node.isClassDeclaration(exportedDeclaration) || Node.isInterfaceDeclaration(exportedDeclaration)) {
|
|
223
|
+
const declaration = exportedDeclaration;
|
|
224
|
+
// If the Entity is decorated with @Entity or has an EntityConfiguration we convert it
|
|
225
|
+
const decorator = getEntityDecoratorIfExists(exportedDeclaration);
|
|
226
|
+
if (decorator) {
|
|
227
|
+
let c3Type = null;
|
|
228
|
+
try {
|
|
229
|
+
conversionContext.state().multiTenantSelectionEnabled = false;
|
|
230
|
+
c3Type = conversionContext.convert(declaration.getType());
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
} // We ignore this error since the converter will print any errors
|
|
234
|
+
if (c3Type != null) {
|
|
235
|
+
if (c3Type instanceof ObjectC3Type) {
|
|
236
|
+
entities.push({
|
|
237
|
+
exportedFromFile: declaration.getSourceFile().getFilePath(),
|
|
238
|
+
defaultExport: declaration.isDefaultExport(),
|
|
239
|
+
entity: c3Type,
|
|
240
|
+
multiTenantSelectionEnabled: conversionContext.state().multiTenantSelectionEnabled
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
throw new Error(`Could not convert ${name} to a C3Type`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
throw new Error(`Could not convert ${name} to a C3Type`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return entities;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Will return the relative path from the 'from' path to the 'to' path
|
|
260
|
+
* @param from path to start from
|
|
261
|
+
* @param to path to end at
|
|
262
|
+
* @param fileExtensionForImports this is the file extension to append to the end of the relative path
|
|
263
|
+
*/
|
|
264
|
+
export function getRelativeImportPath(from, to, fileExtensionForImports = '') {
|
|
265
|
+
if (!from) {
|
|
266
|
+
throw new Error('from path is required');
|
|
267
|
+
}
|
|
268
|
+
if (!to) {
|
|
269
|
+
throw new Error('to path is required');
|
|
270
|
+
}
|
|
271
|
+
const fromDir = path.dirname(from);
|
|
272
|
+
let relativePath = path.relative(fromDir, to);
|
|
273
|
+
// Normalize path separators to forward slashes for import statements
|
|
274
|
+
// This is required because import statements always use forward slashes,
|
|
275
|
+
// even on Windows, but path.relative() returns platform-specific separators
|
|
276
|
+
relativePath = relativePath.replace(/\\/g, '/');
|
|
277
|
+
// Make sure path starts with './' or '../'
|
|
278
|
+
if (!relativePath.startsWith('../') && !relativePath.startsWith('./')) {
|
|
279
|
+
relativePath = `./${relativePath}`;
|
|
280
|
+
}
|
|
281
|
+
// Remove '.ts' extension
|
|
282
|
+
relativePath = relativePath.replace(/\.ts$/, '');
|
|
283
|
+
return relativePath + fileExtensionForImports;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Will return the name of the node module if the path is within a node module or null if not
|
|
287
|
+
* @param nodeModulePath to check
|
|
288
|
+
*/
|
|
289
|
+
export function tryGetNodeModuleName(nodeModulePath) {
|
|
290
|
+
let ret = null;
|
|
291
|
+
if (nodeModulePath.includes('node_modules')) {
|
|
292
|
+
const nodeModuleIdx = nodeModulePath.indexOf('node_modules/');
|
|
293
|
+
const partBeforeNodeModules = nodeModulePath.slice(0, nodeModuleIdx + 13);
|
|
294
|
+
const partAfterNodeModules = nodeModulePath.slice(nodeModuleIdx + 13);
|
|
295
|
+
const parts = partAfterNodeModules.split('/');
|
|
296
|
+
let previousPartPath = '';
|
|
297
|
+
for (let part of parts) {
|
|
298
|
+
const packagePath = path.resolve(partBeforeNodeModules, previousPartPath, part, 'package.json');
|
|
299
|
+
if (fs.existsSync(packagePath)) {
|
|
300
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
301
|
+
ret = packageJson.name;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
previousPartPath = part + '/';
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return ret;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Saves the C3Type to the local filesystem
|
|
313
|
+
* @param savePath to save the entities to
|
|
314
|
+
* @param entity to save
|
|
315
|
+
* @param logger to log to if desired, if null nothing will be logged
|
|
316
|
+
*/
|
|
317
|
+
export async function writeEntityJsonToFilesystem(savePath, entity, logger) {
|
|
318
|
+
const json = JSON.stringify(entity, jsonStringifyReplacer, 2);
|
|
319
|
+
if (json && json.length > 0) {
|
|
320
|
+
const outputPath = path.resolve(savePath, 'generated', 'entity-definitions', `${entity.namespace}.${entity.name}.json`);
|
|
321
|
+
await fsPromises.mkdir(path.dirname(outputPath), { recursive: true });
|
|
322
|
+
await fsPromises.writeFile(outputPath, json);
|
|
323
|
+
if (logger) {
|
|
324
|
+
logger.log(`Wrote ${entity.namespace}.${entity.name} to ${outputPath}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Save the C3Type(s) to the local filesystem
|
|
330
|
+
* @param savePath to save the entities to
|
|
331
|
+
* @param entities to save
|
|
332
|
+
* @param logger to log to if desired, if null nothing will be logged
|
|
333
|
+
*/
|
|
334
|
+
export async function writeEntitiesJsonToFilesystem(savePath, entities, logger) {
|
|
335
|
+
for (const entity of entities) {
|
|
336
|
+
await writeEntityJsonToFilesystem(savePath, entity, logger);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
export async function writeGeneratedServiceInfoToFilesystem(savePath, info, logger) {
|
|
340
|
+
const json = JSON.stringify(info, jsonStringifyReplacer, 2);
|
|
341
|
+
if (json && json.length > 0) {
|
|
342
|
+
const outputPath = path.resolve(savePath, 'generated', 'query-definitions', `${info.entityServiceName}.json`);
|
|
343
|
+
await fsPromises.mkdir(path.dirname(outputPath), { recursive: true });
|
|
344
|
+
await fsPromises.writeFile(outputPath, json);
|
|
345
|
+
if (logger) {
|
|
346
|
+
logger.log(`Wrote ${info.entityServiceName} named queries to ${outputPath}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { IConversionContext } from './IConversionContext.js';
|
|
2
|
+
import { IConverterStrategy } from './IConverterStrategy.js';
|
|
3
|
+
/**
|
|
4
|
+
* Created by Navíd Mitchell 🤪 on 4/26/23.
|
|
5
|
+
*/
|
|
6
|
+
export declare class DefaultConversionContext<T, R, S> implements IConversionContext<T, R, S> {
|
|
7
|
+
private readonly strategy;
|
|
8
|
+
private readonly conversionDepthStack;
|
|
9
|
+
private readonly errorStack;
|
|
10
|
+
private readonly _state;
|
|
11
|
+
private readonly logger;
|
|
12
|
+
private _actualJsonPath;
|
|
13
|
+
private _actualPropertyStack;
|
|
14
|
+
propertyStack: string[];
|
|
15
|
+
currentJsonPath: string;
|
|
16
|
+
constructor(strategy: IConverterStrategy<T, R, S>);
|
|
17
|
+
beginProcessingProperty(name: string): void;
|
|
18
|
+
endProcessingProperty(): void;
|
|
19
|
+
get actualJsonPath(): string;
|
|
20
|
+
get actualPropertyStack(): string[];
|
|
21
|
+
convert(value: T): R;
|
|
22
|
+
state(): S;
|
|
23
|
+
private selectConverter;
|
|
24
|
+
/**
|
|
25
|
+
* Log an exception when appropriate dealing with only logging once even when recursion has occurred
|
|
26
|
+
* @param e to log
|
|
27
|
+
*/
|
|
28
|
+
private logException;
|
|
29
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Created by Navíd Mitchell 🤪 on 4/26/23.
|
|
3
|
+
*/
|
|
4
|
+
export class DefaultConversionContext {
|
|
5
|
+
strategy;
|
|
6
|
+
conversionDepthStack = [];
|
|
7
|
+
errorStack = [];
|
|
8
|
+
_state;
|
|
9
|
+
logger;
|
|
10
|
+
_actualJsonPath = '';
|
|
11
|
+
_actualPropertyStack = [];
|
|
12
|
+
propertyStack = [];
|
|
13
|
+
currentJsonPath = '';
|
|
14
|
+
constructor(strategy) {
|
|
15
|
+
this.strategy = strategy;
|
|
16
|
+
let state = strategy.initialState();
|
|
17
|
+
if (state instanceof Function) {
|
|
18
|
+
this._state = state();
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
this._state = state;
|
|
22
|
+
}
|
|
23
|
+
let logger = strategy.logger();
|
|
24
|
+
if (logger instanceof Function) {
|
|
25
|
+
this.logger = logger();
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
beginProcessingProperty(name) {
|
|
32
|
+
this.currentJsonPath = (this.propertyStack.length > 0 ? this.propertyStack[this.propertyStack.length - 1] + '.' : '') + name;
|
|
33
|
+
this.propertyStack.push(this.currentJsonPath);
|
|
34
|
+
this._actualJsonPath = (this._actualPropertyStack.length > 0 ? this._actualPropertyStack[this._actualPropertyStack.length - 1] + '.' : '') + name;
|
|
35
|
+
this._actualPropertyStack.push(this._actualJsonPath);
|
|
36
|
+
}
|
|
37
|
+
endProcessingProperty() {
|
|
38
|
+
this.propertyStack.pop();
|
|
39
|
+
this.currentJsonPath = this.propertyStack.length > 0 ? this.propertyStack[this.propertyStack.length - 1] : '';
|
|
40
|
+
this._actualPropertyStack.pop();
|
|
41
|
+
this._actualJsonPath = this._actualPropertyStack.length > 0 ? this._actualPropertyStack[this._actualPropertyStack.length - 1] : '';
|
|
42
|
+
}
|
|
43
|
+
get actualJsonPath() {
|
|
44
|
+
return this._actualJsonPath;
|
|
45
|
+
}
|
|
46
|
+
get actualPropertyStack() {
|
|
47
|
+
return this._actualPropertyStack;
|
|
48
|
+
}
|
|
49
|
+
convert(value) {
|
|
50
|
+
try {
|
|
51
|
+
this.conversionDepthStack.unshift(value);
|
|
52
|
+
let converter = this.selectConverter(value);
|
|
53
|
+
if (converter != null) {
|
|
54
|
+
return converter.convert(value, this);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// this causes the stack to unwind, so this is intentional
|
|
58
|
+
// noinspection ExceptionCaughtLocallyJS
|
|
59
|
+
throw new Error('No ITypeConverter can be found for ' + this.strategy.valueToString(value) + '\nWhen using strategy ' + this.strategy.constructor.name);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
this.logException(e);
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
this.conversionDepthStack.shift();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
state() {
|
|
71
|
+
return this._state;
|
|
72
|
+
}
|
|
73
|
+
selectConverter(value) {
|
|
74
|
+
let ret = null;
|
|
75
|
+
for (let converter of this.strategy.typeConverters()) {
|
|
76
|
+
if (converter.supports(value, this.state())) {
|
|
77
|
+
ret = converter;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return ret;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Log an exception when appropriate dealing with only logging once even when recursion has occurred
|
|
85
|
+
* @param e to log
|
|
86
|
+
*/
|
|
87
|
+
logException(e) {
|
|
88
|
+
// This indicates this is the first time logException has been called for this context.
|
|
89
|
+
// This would occur at the furthest call depth so at this point the conversionDepthStack has the complete stack
|
|
90
|
+
if (this.errorStack.length === 0) {
|
|
91
|
+
// We loop vs add all to keep stack intact
|
|
92
|
+
for (let value of this.conversionDepthStack) {
|
|
93
|
+
this.errorStack.unshift(value);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (this.conversionDepthStack.length === 1) { // we are at the top of the stack during recursion
|
|
97
|
+
let sb = `\nError occurred during conversion.\nException: ${e.message}\nJsonPath: ${this.currentJsonPath}\nConversion Stack:\n`;
|
|
98
|
+
let objectCount = 1;
|
|
99
|
+
for (let value of this.errorStack) {
|
|
100
|
+
const spacer = ' '.repeat(objectCount);
|
|
101
|
+
sb += spacer;
|
|
102
|
+
sb += '- ';
|
|
103
|
+
sb += this.strategy.valueToString(value)
|
|
104
|
+
.replaceAll('\n', '\n ' + spacer);
|
|
105
|
+
sb += '\n';
|
|
106
|
+
objectCount++;
|
|
107
|
+
}
|
|
108
|
+
this.logger.log(sb);
|
|
109
|
+
this.errorStack.length = 0; // we have printed reset
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|