Skip to content

fix: refactoring process import#70

Open
Kronprinz03 wants to merge 7 commits intomainfrom
refactor-process-import
Open

fix: refactoring process import#70
Kronprinz03 wants to merge 7 commits intomainfrom
refactor-process-import

Conversation

@Kronprinz03
Copy link
Copy Markdown
Contributor

Have you...

  • Added relevant entry to the change log?

Copy link
Copy Markdown
Contributor

@hyperspace-insights hyperspace-insights bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactoring correctly splits the monolithic processImport.ts into focused modules (csnBuilder, schemaMapper, rawWorkflowConverter, utils). However, a few correctness issues were introduced during the split: dead code (options.file assignment with no subsequent use), a shared mutable EMPTY_OBJECT_SCHEMA constant that could be silently corrupted if any caller mutates its properties, and two for...in loops that dropped the original Object.hasOwn guards present in the removed file. These should be addressed before merging.

PR Bot Information

Version: 1.19.9 | 📖 Documentation | 🚨 Create Incident | 💬 Feedback

  • File Content Strategy: Full file content
  • Event Trigger: pull_request.opened
  • LLM: anthropic--claude-4.6-sonnet
  • Correlation ID: d927e7b0-2d0a-11f1-8545-5a299948cb7c


if (options.name) {
const { filePath, processHeader } = await fetchAndSaveProcessDefinition(options.name);
options.file = filePath;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: dataTypeCache is populated after buildCsnModel is called in the options.name branch

When fetchAndSaveProcessDefinition returns, dataTypeCache has been populated with the fetched data types (line 64). However, buildCsnModel is called on line 35 with that same dataTypeCache reference — which is correct. But the old generateCsnModel helper in processImport.ts correctly called loadProcessHeader first (which populates the cache) then buildCsnModel. Here the two branches are consistent.

Actually the real bug is on line 34: options.file = filePath is assigned but never used — the function returns immediately on line 35 without using options.file. This is dead code left over from the refactoring (the original code set options.file and then fell through to a separate generateCsnModel call).

Suggested change
options.file = filePath;
return buildCsnModel(processHeader, dataTypeCache);

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

@@ -0,0 +1,31 @@
import { JsonSchema } from '../api';

export const EMPTY_OBJECT_SCHEMA: JsonSchema = { type: 'object', properties: {}, required: [] };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: EMPTY_OBJECT_SCHEMA is a shared mutable object

The constant is exported and passed around as a schema object. restoreRefs in rawWorkflowConverter.ts (line 147-148) calls restoreRefs(inputs, dt) where inputs may be this exact object reference (when processSchemaContent.definitions?.out is undefined). restoreRefs mutates schema.properties by reassigning keys, and other callers such as ensureObjectSchema return this same reference. If properties were ever mutated, all subsequent uses would be corrupted.

Consider freezing the object or returning a fresh copy each time it is needed.

Suggested change
export const EMPTY_OBJECT_SCHEMA: JsonSchema = { type: 'object', properties: {}, required: [] };
export const EMPTY_OBJECT_SCHEMA: JsonSchema = Object.freeze({ type: 'object', properties: {}, required: [] }) as JsonSchema;

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Comment on lines +27 to +30
for (const propName in properties) {
const propSchema = properties[propName];
const safeName = sanitizeName(propName);
elements[safeName] = mapSchemaPropertyToElement(safeName, propSchema, required.has(propName), {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic Error: buildTypeFromSchema iterates with for...in without an hasOwnProperty guard

The original code in the removed file explicitly used Object.hasOwn(properties, propName) to guard against inherited enumerable properties. The refactored version drops this guard. If a properties object ever has inherited enumerable properties, extra spurious elements would be emitted into the CSN type.

Should add the same guard back to match the original defensive behavior.

Suggested change
for (const propName in properties) {
const propSchema = properties[propName];
const safeName = sanitizeName(propName);
elements[safeName] = mapSchemaPropertyToElement(safeName, propSchema, required.has(propName), {
for (const propName in properties) {
if (!Object.hasOwn(properties, propName)) continue;
const propSchema = properties[propName];
const safeName = sanitizeName(propName);

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Comment on lines +132 to +136
for (const name in properties) {
const schema = properties[name];
const safeName = sanitizeName(name);
elements[safeName] = mapSchemaPropertyToElement(safeName, schema, required.has(name), {
parentTypeName: fqn(ctx.serviceName, `${baseName(ctx.parentTypeName)}_Item`),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic Error: buildArrayItemsSpec inline-object branch also iterates with for...in without an hasOwnProperty guard

The original code in the removed file used Object.hasOwn(properties, name) here too. The refactored version drops this guard, creating the same inherited-property risk.

Suggested change
for (const name in properties) {
const schema = properties[name];
const safeName = sanitizeName(name);
elements[safeName] = mapSchemaPropertyToElement(safeName, schema, required.has(name), {
parentTypeName: fqn(ctx.serviceName, `${baseName(ctx.parentTypeName)}_Item`),
for (const name in properties) {
if (!Object.hasOwn(properties, name)) continue;
const schema = properties[name];
const safeName = sanitizeName(name);
elements[safeName] = mapSchemaPropertyToElement(safeName, schema, required.has(name), {

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

@Kronprinz03 Kronprinz03 marked this pull request as ready for review April 1, 2026 14:48
@Kronprinz03 Kronprinz03 requested a review from a team as a code owner April 1, 2026 14:48
@hyperspace-insights
Copy link
Copy Markdown
Contributor

Summary

The following content is AI-generated and provides a summary of the pull request:


Refactor: Split processImport Module into Focused Sub-Modules

♻️ Refactor: The monolithic lib/processImport.ts file has been replaced with a structured lib/processImport/ directory, splitting responsibilities across dedicated modules for better maintainability and separation of concerns.

Changes

  • lib/processImport.ts: Removed — replaced by the new lib/processImport/ directory.
  • lib/processImport/index.ts: Added — main entry point for the process import flow; handles both remote fetch (via --name) and local file loading, with module-level dataTypeCache management.
  • lib/processImport/csnBuilder.ts: Added — builds the CSN model from a ProcessHeader and dataTypeCache, generating service definitions, typed input/output types, and process actions (start, getAttributes, getOutputs, getInstancesByBusinessKey, suspend, resume, cancel).
  • lib/processImport/schemaMapper.ts: Added — maps JSON Schema properties to CDS type elements, handling primitives, nested objects, arrays, and $ref resolution.
  • lib/processImport/rawWorkflowConverter.ts: Added — detects and converts raw SBPA workflow JSON (downloaded models) into the unified ProcessHeader format expected by the CSN builder, including ref restoration for inlined complex types.
  • lib/processImport/utils.ts: Added — shared utility functions (fqn, baseName, splitAtLastDot, capitalize, sanitizeName) and the EMPTY_OBJECT_SCHEMA constant.
  • lib/processImportRegistration.ts: Modified — the CLI import handler now always sets saveProcessHeader = true when invoked via CLI, ensuring the converted ProcessHeader JSON is persisted.
  • README.md: Updated — fixed a typo in the download path (~./~/), improved documentation for what gets generated (listing both the .cds service file and the .json ProcessHeader), and clarified re-import instructions.
  • tests/hybrid/processImport.test.ts: Updated — raw workflow JSON test fixtures moved from srv/workflows/ to a dedicated tests/hybrid/downloadedModels/ directory; import paths updated accordingly.
  • tests/hybrid/downloadedModels/: Added — three raw SBPA workflow JSON test fixtures (ImportProcess_Simple_Inputs.json, ImportProcess_Complex_Inputs.json, ImportProcess_Attributes_And_Outputs.json).
  • tests/bookshop/srv/workflows/*.json: Updated — cleaned up test ProcessHeader fixtures (removed description, createdAt, updatedAt, valid fields; normalized field ordering; replaced $ref date/password properties with explicit type/format equivalents).

  • 🔄 Regenerate and Update Summary
  • ✏️ Insert as PR Description (deletes this comment)
  • 🗑️ Delete comment
PR Bot Information

Version: 1.19.15 | 📖 Documentation | 🚨 Create Incident | 💬 Feedback

  • File Content Strategy: Full file content
  • Event Trigger: pull_request.ready_for_review
  • Output Template: Default Template
  • Correlation ID: d9e28410-2dd9-11f1-8523-7656c3a11098
  • Summary Prompt: Default Prompt
  • LLM: anthropic--claude-4.6-sonnet

💌 Have ideas or want to contribute? Create an issue and share your thoughts with us!
📑 Check out the documentation for more information.
📬 Subscribe to the Hyperspace PR Bot DL to get the latest announcements and pilot features!

Made with ❤️ by Hyperspace.

Copy link
Copy Markdown
Contributor

@hyperspace-insights hyperspace-insights bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactoring cleanly decomposes the monolithic processImport.ts into well-scoped modules (csnBuilder, schemaMapper, rawWorkflowConverter, utils), and the test fixture reorganisation is a clear improvement. However, there are a few correctness issues introduced during the refactor that should be addressed: the shared mutable EMPTY_OBJECT_SCHEMA singleton risks cross-call corruption, restoreRefs mutates the live parsed-JSON objects in place, the module-level dataTypeCache is not concurrency-safe, and a defensive hasOwnProperty guard was accidentally dropped from the property iteration loop.

PR Bot Information

Version: 1.19.15 | 📖 Documentation | 🚨 Create Incident | 💬 Feedback

  • File Content Strategy: Full file content
  • LLM: anthropic--claude-4.6-sonnet
  • Event Trigger: pull_request.ready_for_review
  • Correlation ID: d9e28410-2dd9-11f1-8523-7656c3a11098

@@ -0,0 +1,31 @@
import { JsonSchema } from '../api';

export const EMPTY_OBJECT_SCHEMA: JsonSchema = { type: 'object', properties: {}, required: [] };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: EMPTY_OBJECT_SCHEMA is a shared mutable singleton

EMPTY_OBJECT_SCHEMA is exported as a plain object literal and used as a fallback in rawWorkflowConverter.ts (lines 140–141 and 159) and in schemaMapper.ts (line 45). Because it is the same object reference every time, any caller that mutates it (e.g. restoreRefs mutates schema.properties) will corrupt the shared instance for all subsequent calls.

Consider freezing it or always returning a fresh copy where it is used as a default:

Suggested change
export const EMPTY_OBJECT_SCHEMA: JsonSchema = { type: 'object', properties: {}, required: [] };
export const EMPTY_OBJECT_SCHEMA: JsonSchema = Object.freeze({ type: 'object', properties: {}, required: [] }) as JsonSchema;
export function emptyObjectSchema(): JsonSchema {
return { type: 'object', properties: {}, required: [] };
}

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Comment on lines +140 to +147
const inputs = processSchemaContent.definitions?.out ?? EMPTY_OBJECT_SCHEMA;
const outputs = processSchemaContent.definitions?.in ?? EMPTY_OBJECT_SCHEMA;

// Reconstruct $ref patterns in inputs/outputs for complex type properties.
// In the raw format, complex types are inlined. We replace them with
// $ref: "$.{uid}" references so buildCsnModel can resolve them via dataTypeCache.
for (const dt of dataTypes) {
restoreRefs(inputs, dt);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: restoreRefs mutates the inputs/outputs schemas returned directly from the parsed JSON

inputs and outputs are references into the original parsed JSON object (processSchemaContent.definitions?.out). restoreRefs writes new values back into schema.properties[propName], which modifies the caller's live data. If EMPTY_OBJECT_SCHEMA is used as the fallback (no definitions.out/in), this also mutates the shared constant.

Additionally, only the top-level properties are walked — complex types nested inside array items (e.g. the complexe array whose items have refName) are never visited, so $ref references inside array items are not restored.

Consider deep-cloning inputs/outputs before mutating them, and recursively walking into array items:

Suggested change
const inputs = processSchemaContent.definitions?.out ?? EMPTY_OBJECT_SCHEMA;
const outputs = processSchemaContent.definitions?.in ?? EMPTY_OBJECT_SCHEMA;
// Reconstruct $ref patterns in inputs/outputs for complex type properties.
// In the raw format, complex types are inlined. We replace them with
// $ref: "$.{uid}" references so buildCsnModel can resolve them via dataTypeCache.
for (const dt of dataTypes) {
restoreRefs(inputs, dt);
const inputs: JsonSchema = processSchemaContent.definitions?.out
? JSON.parse(JSON.stringify(processSchemaContent.definitions.out))
: { type: 'object', properties: {}, required: [] };
const outputs: JsonSchema = processSchemaContent.definitions?.in
? JSON.parse(JSON.stringify(processSchemaContent.definitions.in))
: { type: 'object', properties: {}, required: [] };
// Reconstruct $ref patterns in inputs/outputs for complex type properties.
// In the raw format, complex types are inlined. We replace them with
// $ref: "$.{uid}" references so buildCsnModel can resolve them via dataTypeCache.
for (const dt of dataTypes) {
restoreRefs(inputs, dt);
restoreRefs(outputs, dt);
}

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Comment on lines +27 to +36
export async function importProcess(
jsonFile: string,
options: ImportOptions = {},
): Promise<csn.CsnModel> {
dataTypeCache = new Map();

if (options.name) {
const { filePath, processHeader } = await fetchAndSaveProcessDefinition(options.name);
options.file = filePath;
return buildCsnModel(processHeader, dataTypeCache);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Module-level dataTypeCache makes importProcess non-reentrant

dataTypeCache is a module-level let variable that is reset at the start of each importProcess call. If two importProcess calls run concurrently (both are async), the second call will reset the cache while the first is still awaiting fetchAndSaveProcessDefinition, causing the first call to build its CSN model with an empty or partially-populated cache belonging to the second call.

Consider scoping the cache to each invocation and passing it explicitly (the new buildCsnModel signature already accepts it as a parameter, so the plumbing is almost complete):

Suggested change
export async function importProcess(
jsonFile: string,
options: ImportOptions = {},
): Promise<csn.CsnModel> {
dataTypeCache = new Map();
if (options.name) {
const { filePath, processHeader } = await fetchAndSaveProcessDefinition(options.name);
options.file = filePath;
return buildCsnModel(processHeader, dataTypeCache);
export async function importProcess(
jsonFile: string,
options: ImportOptions = {},
): Promise<csn.CsnModel> {
const dataTypeCache = new Map<string, DataType>();
if (options.name) {
const { filePath, processHeader } = await fetchAndSaveProcessDefinition(options.name, dataTypeCache);
options.file = filePath;
return buildCsnModel(processHeader, dataTypeCache);
}
const { processHeader, targetFilePath } = await loadProcessHeader(
jsonFile,
options.saveProcessHeader ?? false,
dataTypeCache,
);
if (targetFilePath) {
options.file = targetFilePath;
}
return buildCsnModel(processHeader, dataTypeCache);
}

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Comment on lines +27 to +36
for (const propName in properties) {
const propSchema = properties[propName];
const safeName = sanitizeName(propName);
elements[safeName] = mapSchemaPropertyToElement(safeName, propSchema, required.has(propName), {
parentTypeName: typeName,
serviceName,
definitions,
dataTypeCache,
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic Error: buildTypeFromSchema iterates properties with for...in without an hasOwnProperty guard

The for (const propName in properties) loop at line 27 will also iterate inherited enumerable properties if the properties object's prototype chain has been extended. The deleted file (processImport.ts) used Object.hasOwn explicitly. The refactored version dropped this guard.

Should add an own-property check to be consistent with the original behaviour:

Suggested change
for (const propName in properties) {
const propSchema = properties[propName];
const safeName = sanitizeName(propName);
elements[safeName] = mapSchemaPropertyToElement(safeName, propSchema, required.has(propName), {
parentTypeName: typeName,
serviceName,
definitions,
dataTypeCache,
});
}
const properties = schema.properties ?? {};
for (const propName in properties) {
if (!Object.hasOwn(properties, propName)) continue;
const propSchema = properties[propName];
const safeName = sanitizeName(propName);
elements[safeName] = mapSchemaPropertyToElement(safeName, propSchema, required.has(propName), {
parentTypeName: typeName,
serviceName,
definitions,
dataTypeCache,
});
}

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants