@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.
Files changed (82) hide show
  1. package/README.md +594 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +6 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +6 -0
  6. package/dist/commands/generate.d.ts +11 -0
  7. package/dist/commands/generate.js +36 -0
  8. package/dist/commands/initialize.d.ts +12 -0
  9. package/dist/commands/initialize.js +102 -0
  10. package/dist/commands/synchronize.d.ts +17 -0
  11. package/dist/commands/synchronize.js +154 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.js +1 -0
  14. package/dist/internal/CodeGenerationService.d.ts +24 -0
  15. package/dist/internal/CodeGenerationService.js +256 -0
  16. package/dist/internal/Logger.d.ts +27 -0
  17. package/dist/internal/Logger.js +35 -0
  18. package/dist/internal/ProjectMigrationService.d.ts +28 -0
  19. package/dist/internal/ProjectMigrationService.js +99 -0
  20. package/dist/internal/Utils.d.ts +66 -0
  21. package/dist/internal/Utils.js +349 -0
  22. package/dist/internal/converter/ConverterConstants.d.ts +3 -0
  23. package/dist/internal/converter/ConverterConstants.js +4 -0
  24. package/dist/internal/converter/DefaultConversionContext.d.ts +29 -0
  25. package/dist/internal/converter/DefaultConversionContext.js +112 -0
  26. package/dist/internal/converter/IConversionContext.d.ts +64 -0
  27. package/dist/internal/converter/IConversionContext.js +11 -0
  28. package/dist/internal/converter/IConverterStrategy.d.ts +35 -0
  29. package/dist/internal/converter/IConverterStrategy.js +1 -0
  30. package/dist/internal/converter/ITypeConverter.d.ts +28 -0
  31. package/dist/internal/converter/ITypeConverter.js +1 -0
  32. package/dist/internal/converter/SpecificTypesConverter.d.ts +12 -0
  33. package/dist/internal/converter/SpecificTypesConverter.js +24 -0
  34. package/dist/internal/converter/codegen/ArrayC3TypeToStatementMapper.d.ts +9 -0
  35. package/dist/internal/converter/codegen/ArrayC3TypeToStatementMapper.js +46 -0
  36. package/dist/internal/converter/codegen/ObjectC3TypeToStatementMapper.d.ts +13 -0
  37. package/dist/internal/converter/codegen/ObjectC3TypeToStatementMapper.js +67 -0
  38. package/dist/internal/converter/codegen/PrimitiveC3TypeToStatementMapper.d.ts +9 -0
  39. package/dist/internal/converter/codegen/PrimitiveC3TypeToStatementMapper.js +24 -0
  40. package/dist/internal/converter/codegen/StatementMapper.d.ts +40 -0
  41. package/dist/internal/converter/codegen/StatementMapper.js +132 -0
  42. package/dist/internal/converter/codegen/StatementMapperConversionState.d.ts +9 -0
  43. package/dist/internal/converter/codegen/StatementMapperConversionState.js +17 -0
  44. package/dist/internal/converter/codegen/StatementMapperConverterStrategy.d.ts +16 -0
  45. package/dist/internal/converter/codegen/StatementMapperConverterStrategy.js +30 -0
  46. package/dist/internal/converter/codegen/UnionC3TypeToStatementMapper.d.ts +9 -0
  47. package/dist/internal/converter/codegen/UnionC3TypeToStatementMapper.js +46 -0
  48. package/dist/internal/converter/common/BaseConversionState.d.ts +9 -0
  49. package/dist/internal/converter/common/BaseConversionState.js +13 -0
  50. package/dist/internal/converter/typescript/ArrayToC3Type.d.ts +9 -0
  51. package/dist/internal/converter/typescript/ArrayToC3Type.js +17 -0
  52. package/dist/internal/converter/typescript/ConverterUtils.d.ts +4 -0
  53. package/dist/internal/converter/typescript/ConverterUtils.js +261 -0
  54. package/dist/internal/converter/typescript/EnumToC3Type.d.ts +12 -0
  55. package/dist/internal/converter/typescript/EnumToC3Type.js +26 -0
  56. package/dist/internal/converter/typescript/ObjectLikeToC3Type.d.ts +15 -0
  57. package/dist/internal/converter/typescript/ObjectLikeToC3Type.js +111 -0
  58. package/dist/internal/converter/typescript/PrimitiveToC3Type.d.ts +10 -0
  59. package/dist/internal/converter/typescript/PrimitiveToC3Type.js +33 -0
  60. package/dist/internal/converter/typescript/QueryOptionsToC3Type.d.ts +9 -0
  61. package/dist/internal/converter/typescript/QueryOptionsToC3Type.js +9 -0
  62. package/dist/internal/converter/typescript/TenantSelectionToC3Type.d.ts +9 -0
  63. package/dist/internal/converter/typescript/TenantSelectionToC3Type.js +9 -0
  64. package/dist/internal/converter/typescript/TypescriptConversionState.d.ts +24 -0
  65. package/dist/internal/converter/typescript/TypescriptConversionState.js +26 -0
  66. package/dist/internal/converter/typescript/TypescriptConverterStrategy.d.ts +16 -0
  67. package/dist/internal/converter/typescript/TypescriptConverterStrategy.js +44 -0
  68. package/dist/internal/converter/typescript/UnionToC3Type.d.ts +15 -0
  69. package/dist/internal/converter/typescript/UnionToC3Type.js +184 -0
  70. package/dist/internal/state/Environment.d.ts +13 -0
  71. package/dist/internal/state/Environment.js +65 -0
  72. package/dist/internal/state/IStateManager.d.ts +19 -0
  73. package/dist/internal/state/IStateManager.js +41 -0
  74. package/dist/internal/state/KinoticProjectConfigUtil.d.ts +9 -0
  75. package/dist/internal/state/KinoticProjectConfigUtil.js +153 -0
  76. package/dist/templates/AdminEntityService.liquid +14 -0
  77. package/dist/templates/BaseAdminEntityService.liquid +19 -0
  78. package/dist/templates/BaseEntityService.liquid +48 -0
  79. package/dist/templates/EntityService.liquid +14 -0
  80. package/dist/templates/KinoticProjectConfig.ts.liquid +10 -0
  81. package/oclif.manifest.json +161 -0
  82. 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,3 @@
1
+ export declare enum ConverterConstants {
2
+ SOURCE_FILE_PATH = "sourceFilePath"
3
+ }
@@ -0,0 +1,4 @@
1
+ export var ConverterConstants;
2
+ (function (ConverterConstants) {
3
+ ConverterConstants["SOURCE_FILE_PATH"] = "sourceFilePath";
4
+ })(ConverterConstants || (ConverterConstants = {}));
@@ -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
+ }