@genesislcap/event-type-codegen 15.17.0 → 15.18.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.
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderEventTypesFromMetadataContracts = exports.parseEventMetadataDump = exports.eventNameToDetailsInterfaceName = exports.quoteTsString = void 0;
4
+ const naming_1 = require("./naming");
5
+ const isRecord = (value) => typeof value === 'object' && value != null && !Array.isArray(value);
6
+ /** Quote a string for safe emission as a TS string literal / object key. */
7
+ const quoteTsString = (s) => `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
8
+ exports.quoteTsString = quoteTsString;
9
+ /** Bare identifier when legal; otherwise a quoted string key. */
10
+ const tsPropertyKey = (name) => /^[A-Za-z_$][\w$]*$/.test(name) ? name : (0, exports.quoteTsString)(name);
11
+ /** EVENT_BROKER_INSERT → BrokerInsertDetails */
12
+ const eventNameToDetailsInterfaceName = (eventName) => {
13
+ const base = eventName.startsWith('EVENT_') ? eventName.slice('EVENT_'.length) : eventName;
14
+ const pascal = base
15
+ .toLowerCase()
16
+ .split('_')
17
+ .filter(Boolean)
18
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
19
+ .join('');
20
+ return `${pascal}Details`;
21
+ };
22
+ exports.eventNameToDetailsInterfaceName = eventNameToDetailsInterfaceName;
23
+ /** Build a valid PascalCase TypeScript identifier from an enum name hint. */
24
+ const toSafeEnumIdentifier = (enumNameHint) => {
25
+ const enumName = enumNameHint
26
+ .split(/[^A-Za-z0-9]+/)
27
+ .filter(Boolean)
28
+ .map((p) => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase())
29
+ .join('');
30
+ if (!enumName)
31
+ return 'GeneratedEnum';
32
+ return /^[A-Za-z]/.test(enumName) ? enumName : `E${enumName}`;
33
+ };
34
+ const valuesKey = (values) => values.join('\0');
35
+ /**
36
+ * Register an enum under a collision-safe name. Same values → reuse; different values →
37
+ * disambiguate (`Foo`, `Foo2`, …) and report via `unmapped`.
38
+ */
39
+ const registerEnum = (enums, preferredName, values, unmapped, context) => {
40
+ const existing = enums.get(preferredName);
41
+ if (!existing) {
42
+ enums.set(preferredName, values);
43
+ return preferredName;
44
+ }
45
+ if (valuesKey(existing) === valuesKey(values)) {
46
+ return preferredName;
47
+ }
48
+ let n = 2;
49
+ let candidate = `${preferredName}${n}`;
50
+ while (enums.has(candidate) && valuesKey(enums.get(candidate)) !== valuesKey(values)) {
51
+ n += 1;
52
+ candidate = `${preferredName}${n}`;
53
+ }
54
+ if (!enums.has(candidate)) {
55
+ enums.set(candidate, values);
56
+ unmapped.push(`${context}: enum name collision for ${preferredName}; emitted as ${candidate}`);
57
+ }
58
+ return candidate;
59
+ };
60
+ const jsonSchemaTypeToTs = (propSchema, enums, enumNameHint, unmapped, context) => {
61
+ if (Array.isArray(propSchema.enum) && propSchema.enum.every((v) => typeof v === 'string')) {
62
+ const values = propSchema.enum;
63
+ const safeName = toSafeEnumIdentifier(enumNameHint);
64
+ return registerEnum(enums, safeName, values, unmapped, context);
65
+ }
66
+ if (Array.isArray(propSchema.oneOf)) {
67
+ const nonNull = propSchema.oneOf.find((branch) => isRecord(branch) && branch.type !== 'null');
68
+ if (isRecord(nonNull)) {
69
+ return jsonSchemaTypeToTs(nonNull, enums, enumNameHint, unmapped, context);
70
+ }
71
+ }
72
+ const type = propSchema.type;
73
+ if (type === 'integer' || type === 'number')
74
+ return 'number';
75
+ if (type === 'boolean')
76
+ return 'boolean';
77
+ if (type === 'array') {
78
+ const items = isRecord(propSchema.items) ? propSchema.items : {};
79
+ return `${jsonSchemaTypeToTs(items, enums, enumNameHint, unmapped, context)}[]`;
80
+ }
81
+ if (type === 'object')
82
+ return 'Record<string, unknown>';
83
+ return 'string';
84
+ };
85
+ const metadataJsonTypeToTs = (jsonType, fieldType) => {
86
+ const jt = (jsonType !== null && jsonType !== void 0 ? jsonType : '').toLowerCase();
87
+ if (jt === 'integer' || jt === 'number')
88
+ return 'number';
89
+ if (jt === 'boolean')
90
+ return 'boolean';
91
+ if ((fieldType !== null && fieldType !== void 0 ? fieldType : '').toUpperCase() === 'DATETIME' ||
92
+ (fieldType !== null && fieldType !== void 0 ? fieldType : '').toUpperCase() === 'DATE') {
93
+ return 'number';
94
+ }
95
+ return 'string';
96
+ };
97
+ const extractDetailsSchema = (inbound) => {
98
+ if (!inbound)
99
+ return null;
100
+ const rootProps = isRecord(inbound.properties) ? inbound.properties : undefined;
101
+ if (!rootProps)
102
+ return null;
103
+ // Event inbound is usually { DETAILS: { type: object, properties: {...}, required: [...] } }
104
+ const details = rootProps.DETAILS;
105
+ if (isRecord(details) && isRecord(details.properties)) {
106
+ const required = Array.isArray(details.required)
107
+ ? new Set(details.required.filter((r) => typeof r === 'string'))
108
+ : new Set();
109
+ return { properties: details.properties, required };
110
+ }
111
+ // Some schemas flatten DETAILS properties at the inbound root.
112
+ const required = Array.isArray(inbound.required)
113
+ ? new Set(inbound.required.filter((r) => typeof r === 'string'))
114
+ : new Set();
115
+ return { properties: rootProps, required };
116
+ };
117
+ /**
118
+ * Parse a header-captured event-metadata.json dump into per-event contracts.
119
+ */
120
+ const parseEventMetadataDump = (dump) => {
121
+ var _a, _b, _c, _d;
122
+ const contracts = [];
123
+ const enums = new Map();
124
+ const unmapped = [];
125
+ const eventNames = Object.keys((_a = dump.events) !== null && _a !== void 0 ? _a : {}).sort((a, b) => a.localeCompare(b));
126
+ for (const eventName of eventNames) {
127
+ if (!eventName.startsWith('EVENT_')) {
128
+ unmapped.push(`${eventName}: not an EVENT_* handler`);
129
+ continue;
130
+ }
131
+ const entry = dump.events[eventName];
132
+ const inbound = isRecord((_b = entry === null || entry === void 0 ? void 0 : entry.schema) === null || _b === void 0 ? void 0 : _b.INBOUND)
133
+ ? entry.schema.INBOUND
134
+ : undefined;
135
+ const detailsSchema = extractDetailsSchema(inbound);
136
+ const fields = [];
137
+ if (detailsSchema) {
138
+ for (const [fieldName, rawProp] of Object.entries(detailsSchema.properties)) {
139
+ if (fieldName === 'MESSAGE_TYPE' || fieldName === 'SOURCE_REF')
140
+ continue;
141
+ if (!isRecord(rawProp))
142
+ continue;
143
+ const optional = !detailsSchema.required.has(fieldName);
144
+ const enumNameHint = eventName.startsWith('EVENT_')
145
+ ? `${eventName.slice('EVENT_'.length)}_${fieldName}`
146
+ : `${eventName}_${fieldName}`;
147
+ fields.push({
148
+ name: fieldName,
149
+ tsType: jsonSchemaTypeToTs(rawProp, enums, enumNameHint, unmapped, `${eventName}.${fieldName}`),
150
+ optional,
151
+ });
152
+ }
153
+ }
154
+ else if ((_d = (_c = entry === null || entry === void 0 ? void 0 : entry.metadata) === null || _c === void 0 ? void 0 : _c.FIELD) === null || _d === void 0 ? void 0 : _d.length) {
155
+ for (const field of entry.metadata.FIELD) {
156
+ fields.push({
157
+ name: field.NAME,
158
+ tsType: metadataJsonTypeToTs(field.JSON_TYPE, field.TYPE),
159
+ optional: field.OPTIONAL !== false,
160
+ });
161
+ }
162
+ }
163
+ else {
164
+ unmapped.push(`${eventName}: no INBOUND DETAILS schema or metadata.FIELD`);
165
+ continue;
166
+ }
167
+ contracts.push({
168
+ eventName,
169
+ detailsInterface: (0, exports.eventNameToDetailsInterfaceName)(eventName),
170
+ fields,
171
+ });
172
+ }
173
+ const enumDefs = [...enums.entries()]
174
+ .map(([name, values]) => ({ name, values }))
175
+ .sort((a, b) => a.name.localeCompare(b.name));
176
+ return { contracts, enums: enumDefs, unmapped };
177
+ };
178
+ exports.parseEventMetadataDump = parseEventMetadataDump;
179
+ const renderEventTypesFromMetadataContracts = (contracts, enums, unmapped) => {
180
+ const unmappedComment = unmapped.length > 0
181
+ ? `/**\n * Unmapped handlers (not included in EventDetailsMap):\n${unmapped
182
+ .map((line) => ` * - ${line}`)
183
+ .join('\n')}\n */\n\n`
184
+ : '';
185
+ const enumExports = enums
186
+ .map((e) => {
187
+ const entries = e.values
188
+ .map((value) => ` ${(0, exports.quoteTsString)(value)}: ${(0, exports.quoteTsString)(value)},`)
189
+ .join('\n');
190
+ return `export const ${e.name} = {\n${entries}\n} as const;\n\nexport type ${e.name} = (typeof ${e.name})[keyof typeof ${e.name}];`;
191
+ })
192
+ .join('\n\n');
193
+ const interfaces = contracts
194
+ .map((c) => {
195
+ const lines = c.fields.map((f) => {
196
+ const optional = f.optional ? '?' : '';
197
+ return ` ${tsPropertyKey(f.name)}${optional}: ${f.tsType};`;
198
+ });
199
+ return `export interface ${c.detailsInterface} {\n${lines.join('\n')}\n}`;
200
+ })
201
+ .join('\n\n');
202
+ const mapEntries = contracts
203
+ .map((c) => ` ${tsPropertyKey(c.eventName)}: ${c.detailsInterface};`)
204
+ .join('\n');
205
+ const eventClasses = contracts
206
+ .map((c) => {
207
+ const className = (0, naming_1.eventNameToEventClassName)(c.eventName);
208
+ return `export class ${className} extends GenesisEvent<'${c.eventName}', ${c.detailsInterface}> {
209
+ readonly MESSAGE_TYPE = '${c.eventName}' as const;
210
+
211
+ constructor(details: ${c.detailsInterface}) {
212
+ super(details);
213
+ }
214
+ }`;
215
+ })
216
+ .join('\n\n');
217
+ const foundationCommsImport = contracts.length > 0 ? `import { GenesisEvent } from '@genesislcap/foundation-comms';\n\n` : '';
218
+ return `// AUTO-GENERATED — do not edit
219
+ // Source: event metadata dump (genx generate event-types --from-metadata)
220
+
221
+ ${unmappedComment}${foundationCommsImport}${enumExports ? `${enumExports}\n\n` : ''}${interfaces}
222
+
223
+ export interface EventDetailsMap {
224
+ ${mapEntries}
225
+ }
226
+
227
+ export type GenesisEventName = keyof EventDetailsMap;
228
+ ${eventClasses ? `\n${eventClasses}\n` : ''}`;
229
+ };
230
+ exports.renderEventTypesFromMetadataContracts = renderEventTypesFromMetadataContracts;
231
+ //# sourceMappingURL=parse-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-metadata.js","sourceRoot":"","sources":["../src/parse-metadata.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/event-type-codegen",
3
3
  "description": "Build-time TypeScript codegen from Kotlin event handler DTOs",
4
- "version": "15.17.0",
4
+ "version": "15.18.0",
5
5
  "license": "SEE LICENSE IN license.txt",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "consola": "^3.0.2"
22
22
  },
23
23
  "devDependencies": {
24
- "@genesislcap/foundation-testing": "15.17.0",
24
+ "@genesislcap/foundation-testing": "15.18.0",
25
25
  "@types/node": "^22.10.2",
26
26
  "tsx": "^4.7.0",
27
27
  "uvu": "0.5.4"
@@ -34,5 +34,5 @@
34
34
  "publishConfig": {
35
35
  "access": "public"
36
36
  },
37
- "gitHead": "2e040c4861f6acfde7afef48b60555548fac8708"
37
+ "gitHead": "a92013873774dbc7f67a32d37994f10777f86383"
38
38
  }
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, globSync } from 'node:fs';
1
+ import { existsSync, globSync, readFileSync } from 'node:fs';
2
2
  import { mkdir, writeFile } from 'node:fs/promises';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
  import { consola } from 'consola';
@@ -7,6 +7,11 @@ import { resolveEventTypesConfig } from './config';
7
7
  import { parseGeneratedDaosFromSources } from './parse-dao';
8
8
  import { parseGeneratedEnumsFromSources } from './parse-enums';
9
9
  import { parseHandlersFromSources, parseDtosFromSources } from './parse-kotlin';
10
+ import {
11
+ parseEventMetadataDump,
12
+ renderEventTypesFromMetadataContracts,
13
+ type EventMetadataDump,
14
+ } from './parse-metadata';
10
15
  import { renderEventTypesFile } from './render';
11
16
  import type { EventTypesResolvedConfig, GenerateResult, KotlinDto } from './types';
12
17
 
@@ -56,8 +61,50 @@ export type GenerateEventTypesOptions = {
56
61
  clientRoot: string;
57
62
  config?: EventTypesResolvedConfig;
58
63
  packageGenesis?: { eventTypes?: unknown };
64
+ /** When set, generate from a header-captured metadata dump instead of Kotlin sources. */
65
+ fromMetadataPath?: string;
59
66
  };
60
67
 
68
+ export async function generateEventTypesFromMetadata(options: {
69
+ clientRoot: string;
70
+ metadataPath: string;
71
+ output?: string;
72
+ }): Promise<GenerateResult> {
73
+ const metadataPath = resolve(options.clientRoot, options.metadataPath);
74
+ if (!existsSync(metadataPath)) {
75
+ throw new Error(`eventTypes: metadata dump not found at ${metadataPath}`);
76
+ }
77
+
78
+ let dump: EventMetadataDump;
79
+ try {
80
+ dump = JSON.parse(readFileSync(metadataPath, 'utf8')) as EventMetadataDump;
81
+ } catch (err) {
82
+ throw new Error(
83
+ `eventTypes: failed to parse metadata JSON dump at ${metadataPath}: ${
84
+ err instanceof Error ? err.message : String(err)
85
+ }`,
86
+ );
87
+ }
88
+ const { contracts, enums, unmapped } = parseEventMetadataDump(dump);
89
+ const contents = renderEventTypesFromMetadataContracts(contracts, enums, unmapped);
90
+
91
+ const outputRel = options.output ?? 'src/generated/genesis-event-types.ts';
92
+ const outputPath = resolve(options.clientRoot, outputRel);
93
+ await mkdir(dirname(outputPath), { recursive: true });
94
+ await writeFile(outputPath, contents, 'utf8');
95
+
96
+ consola.info(
97
+ `eventTypes: wrote ${outputPath} from metadata (${contracts.length} events, ${unmapped.length} unmapped)`,
98
+ );
99
+
100
+ return {
101
+ outputPath,
102
+ eventCount: contracts.length,
103
+ dtoCount: contracts.length,
104
+ unmappedCount: unmapped.length,
105
+ };
106
+ }
107
+
61
108
  export async function generateEventTypes(
62
109
  options: GenerateEventTypesOptions,
63
110
  ): Promise<GenerateResult | null> {
@@ -65,6 +112,14 @@ export async function generateEventTypes(
65
112
  options.config ??
66
113
  resolveEventTypesConfig(options.clientRoot, undefined, options.packageGenesis);
67
114
 
115
+ if (options.fromMetadataPath) {
116
+ return generateEventTypesFromMetadata({
117
+ clientRoot: options.clientRoot,
118
+ metadataPath: options.fromMetadataPath,
119
+ output: resolved.output,
120
+ });
121
+ }
122
+
68
123
  if (!resolved.enabled) {
69
124
  return null;
70
125
  }
@@ -118,3 +173,12 @@ export async function generateEventTypes(
118
173
 
119
174
  export * from './types';
120
175
  export { resolveEventTypesConfig } from './config';
176
+ export {
177
+ parseEventMetadataDump,
178
+ renderEventTypesFromMetadataContracts,
179
+ eventNameToDetailsInterfaceName,
180
+ type EventMetadataDump,
181
+ type MetadataEventContract,
182
+ type MetadataField,
183
+ } from './parse-metadata';
184
+ export { toUpperUnderscoreWithAcronyms, kotlinPropertyToWireField } from './naming';
@@ -38,25 +38,35 @@ export type ParseKotlinPropertyOptions = {
38
38
  allowBareParams?: boolean;
39
39
  };
40
40
 
41
+ /** Plain or backtick-escaped Kotlin identifier (`value` for reserved words). */
42
+ const KOTLIN_IDENT = '(?:\\w+|`[^`]+`)';
43
+
44
+ const PROPERTY_REGEX = new RegExp(
45
+ `^(?:public\\s+)?(?:val|var)\\s+(${KOTLIN_IDENT})\\s*:\\s*([^=]+?)(?:\\s*=\\s*.+)?$`,
46
+ );
47
+
48
+ const BARE_PROPERTY_REGEX = new RegExp(`^(${KOTLIN_IDENT})\\s*:\\s*([^=]+?)(?:\\s*=\\s*.+)?$`);
49
+
50
+ const stripBackticks = (ident: string): string =>
51
+ ident.startsWith('`') && ident.endsWith('`') ? ident.slice(1, -1) : ident;
52
+
41
53
  /**
42
54
  * Parse a Kotlin constructor / data-class property segment into a wire-facing property.
43
- * Accepts optional public modifier and use-site-stripped annotations.
55
+ * Accepts optional public modifier, use-site-stripped annotations, and backtick-escaped
56
+ * reserved identifiers (e.g. `value` → value → wire VALUE).
44
57
  */
45
58
  export const parseKotlinProperty = (
46
59
  segment: string,
47
60
  options: ParseKotlinPropertyOptions = {},
48
61
  ): KotlinProperty | null => {
49
62
  const cleaned = stripKotlinAnnotations(segment);
50
- const propertyMatch = cleaned.match(
51
- /^(?:public\s+)?(?:val|var)\s+(\w+)\s*:\s*([^=]+?)(?:\s*=\s*.+)?$/,
52
- );
63
+ const propertyMatch = cleaned.match(PROPERTY_REGEX);
53
64
  const match =
54
- propertyMatch ??
55
- (options.allowBareParams ? cleaned.match(/^(\w+)\s*:\s*([^=]+?)(?:\s*=\s*.+)?$/) : null);
65
+ propertyMatch ?? (options.allowBareParams ? cleaned.match(BARE_PROPERTY_REGEX) : null);
56
66
 
57
67
  if (!match) return null;
58
68
 
59
- const name = match[1];
69
+ const name = stripBackticks(match[1]);
60
70
  let kotlinType = match[2].trim();
61
71
  const optional = kotlinType.endsWith('?') || cleaned.includes('=');
62
72
  kotlinType = kotlinType.replace(/\?$/, '').trim();
package/src/naming.ts CHANGED
@@ -1,8 +1,64 @@
1
- import { constantCase, pascalCase } from 'change-case';
1
+ import { pascalCase } from 'change-case';
2
+
3
+ /**
4
+ * Character classification used by {@link toUpperUnderscoreWithAcronyms}.
5
+ * Mirrors genesis-server `CharType` in GenesisMessage.kt.
6
+ */
7
+ type CharType = 'UPPERCASE' | 'LOWERCASE' | 'NUMBER';
8
+
9
+ /**
10
+ * Convert camelCase / PascalCase to UPPER_UNDERSCORE, inserting `_` before digits and
11
+ * splitting acronyms the same way as genesis-server `toUpperUnderscoreWithAcronyms`.
12
+ *
13
+ * Examples: `addressLine1` → `ADDRESS_LINE_1`, `orderID` → `ORDER_ID`.
14
+ *
15
+ * @public
16
+ */
17
+ export const toUpperUnderscoreWithAcronyms = (name: string): string => {
18
+ if (!name) return name;
19
+
20
+ // Already wire-formatted — genesis-server passes these through unchanged.
21
+ if (/^[A-Z0-9]+(?:_[A-Z0-9]+)*$/.test(name)) return name;
22
+
23
+ const length = name.length;
24
+ const result: string[] = [];
25
+ // Genesis seeds non-upper (including digits) as NUMBER so the first digit after
26
+ // letters still gets a leading underscore on subsequent chars.
27
+ let lastCharacterType: CharType = name[0] >= 'A' && name[0] <= 'Z' ? 'UPPERCASE' : 'NUMBER';
28
+
29
+ result.push(name[0].toUpperCase());
30
+
31
+ const isUpperOrDigit = (c: string): boolean => (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
32
+
33
+ for (let i = 1; i < length; i += 1) {
34
+ const ch = name[i];
35
+ if (ch >= 'A' && ch <= 'Z') {
36
+ // Continue an acronym run when the next char is also upper/digit; otherwise split.
37
+ const continueAcronym =
38
+ lastCharacterType === 'UPPERCASE' && (i + 1 >= length || isUpperOrDigit(name[i + 1]));
39
+ if (!continueAcronym) {
40
+ result.push('_');
41
+ }
42
+ result.push(ch);
43
+ lastCharacterType = 'UPPERCASE';
44
+ } else if (ch >= '0' && ch <= '9') {
45
+ if (lastCharacterType !== 'NUMBER') {
46
+ result.push('_');
47
+ }
48
+ result.push(ch);
49
+ lastCharacterType = 'NUMBER';
50
+ } else {
51
+ result.push(ch.toUpperCase());
52
+ lastCharacterType = 'LOWERCASE';
53
+ }
54
+ }
55
+
56
+ return result.join('');
57
+ };
2
58
 
3
59
  /** Wire field name for a Kotlin property (DETAILS uses UPPER_SNAKE_CASE). */
4
60
  export const kotlinPropertyToWireField = (propertyName: string): string =>
5
- constantCase(propertyName);
61
+ toUpperUnderscoreWithAcronyms(propertyName);
6
62
 
7
63
  /** Generated interface name for a Kotlin DTO or handler input type. */
8
64
  export const dtoToDetailsInterfaceName = (dtoName: string): string =>
@@ -16,10 +16,11 @@ const PRIMITIVE_TS: Record<string, string> = {
16
16
  Float: 'number',
17
17
  Boolean: 'boolean',
18
18
  BigDecimal: 'string',
19
- DateTime: 'string',
20
- Instant: 'string',
21
- LocalDate: 'string',
22
- LocalDateTime: 'string',
19
+ // Genesis wire JSON_TYPE for DATE/DATETIME is integer (epoch millis).
20
+ DateTime: 'number',
21
+ Instant: 'number',
22
+ LocalDate: 'number',
23
+ LocalDateTime: 'number',
23
24
  };
24
25
 
25
26
  const DATA_CLASS_START = /^\s*(?:data\s+)?class\s+(\w+)\s*(?:\([^)]*\))?\s*(?:\([^)]*\)|\{)/;