Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
/vendor/
/.idea/
/.phpunit.result.cache
/tests/.phpunit.result.cache
/code/deno
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ RUN install-php-extensions \

WORKDIR /opt/project

# Change ownership to the local user
RUN chown -R ${user}:${group} /opt/project

# Switch to user
USER ${uid}
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,22 @@ docker compose run --rm app ./bin/generate bmm:split openehr_base_1.3.0
docker compose run --rm app ./bin/generate bmm:split all
```

Generate Deno/TypeScript/JavaScript library from BMM JSON files:
```bash
docker compose run --rm app ./bin/generate bmm:deno <filename>
```
Examples
```bash
# Specific files
docker compose run --rm app ./bin/generate bmm:deno openehr_base_1.3.0 openehr_rm_1.2.0
# Or generate for all schemas
docker compose run --rm app ./bin/generate bmm:deno all
```
TODO, things not yet fixed in bmm:deno
- Split JS and TS to separate subtrees under code/deno do that we get JS in code/deno/js/ and TS in code/deno/ts instead of having them mixed
- if the "all" parameter is used, tehen versions risk overwriting each other. add another level per version so it follows the patterns code/deno/{{version}}/js/ and code/deno/{{version}}/ts/
- Rename "Deno" to js-ts


## Testing

Expand Down
59 changes: 59 additions & 0 deletions bin/Command/BmmDenoCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace Console\Command;

use OpenEHR\Tools\CodeGen\Reader\BmmJsonReader;
use OpenEHR\Tools\CodeGen\CodeGenerator;
use OpenEHR\Tools\CodeGen\Writer\BmmDenoWriter;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Command to generate Javascript+TypeScript libraries
* (targeting runtime: Deno and browsers) based on indicated BMM schema(s)
*/
class BmmDenoCommand extends Command
{
protected function configure(): void
{
$this->setName('bmm:deno');
$this->setDescription('Generate Deno library based on indicated BMM schema(s).');
$this->addArgument(
'read',
InputArgument::IS_ARRAY,
'BMM schema(s) to read; multiple schemas are supported when given as multiple arguments. '
. 'Dependencies should be read first (i.e. first BASE then RM). '
. 'Example: <info>generate bmm:deno openehr_base_1.2.0 openehr_rm_1.1.0</info>.',
);
}


protected function execute(InputInterface $input, OutputInterface $output): int
{
$toRead = $input->getArgument('read');
if (empty($toRead)) {
$output->writeln('<error>Please specify which BMM schema should be read. See usage with --help.</error>');
return Command::INVALID;
}
if ($toRead[0] === 'all') {
$toRead = array_map(fn($filename) => basename($filename, '.bmm.json'), glob(BmmJsonReader::DIR . '*.bmm.json'));
}
try {
$reader = new BmmJsonReader();
foreach ($toRead as $schema) {
$reader->read($schema);
}
$writer = new CodeGenerator($reader);
$writer->addWriter(new BmmDenoWriter());
$writer->generate();
} catch (\Throwable $e) {
$output->writeln((string)$e);
return Command::FAILURE;
}

return Command::SUCCESS;
}

}
2 changes: 2 additions & 0 deletions bin/generate
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use Console\Command\BmmJsonToAsciiDoc;
use Console\Command\BmmJsonToClassJson;
use Console\Command\XmiToBmm;
use Console\Command\XmiToInternalModel;
use Console\Command\BmmDenoCommand;
use Symfony\Component\Console\Application;

try {
Expand All @@ -27,6 +28,7 @@ try {
$console->add(new BmmJsonToPlantUml());
$console->add(new BmmJsonToAsciiDoc());
$console->add(new BmmJsonToClassJson());
$console->add(new BmmDenoCommand());
exit($console->run());
} catch (\Throwable $exception) {
error_log((string)$exception);
Expand Down
44 changes: 44 additions & 0 deletions code/BMM-JSON/test_schema.bmm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"bmm_version": "2.4",
"rm_publisher": "test",
"schema_name": "test_schema",
"rm_release": "1.0.0",
"schema_revision": "1.0.0",
"schema_lifecycle_state": "test",
"schema_description": "test",
"schema_author": "test",
"packages": [
{
"name": "TestPackage",
"classes": [
"TestClass"
]
}
],
"class_definitions": {
"TestClass": {
"name": "TestClass",
"ancestors": [],
"properties": [
{
"_type": "P_BMM_SINGLE_PROPERTY",
"name": "test_property",
"type_def": {
"_type": "BMM_SIMPLE_TYPE",
"type": "STRING"
}
}
],
"functions": [
{
"name": "test_function",
"parameters": [],
"result": {
"_type": "BMM_SIMPLE_TYPE",
"type": "STRING"
}
}
]
}
}
}
54 changes: 54 additions & 0 deletions docs/prd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Product Requirements Document: TypeScript/JavaScript openEHR Library Generator

## 1. Introduction

This document outlines the requirements for a new feature to be added to the openEHR code generator. The feature will enable the simultaneous generation of two versions of openEHR libraries: one in TypeScript and another in JavaScript with extensive JSDoc annotations. The generated libraries will be designed for use with the Deno runtime.

## 2. Problem Statement

Currently, the code generator can produce BMM JSON, BMM YAML, XMI Internal Model, and PlantUML files. There is a need to extend its capabilities to generate code for TypeScript and JavaScript developers who are working with openEHR data. This will enable them to work more efficiently and with greater type safety.

## 3. Goals

* To create a new generator that can produce openEHR libraries in both TypeScript and JavaScript.
* The generated JavaScript should be accompanied by extensive JSDoc annotations to provide a good developer experience for JavaScript users.
* The generated code should be compatible with the Deno runtime.
* The generated code should follow the specified coding conventions.
* The generator should be easy to use and well-documented.

## 4. User Stories

* As a TypeScript developer, I want to be able to generate openEHR libraries from BMM files so that I can work with openEHR data in a type-safe way.
* As a JavaScript developer, I want to be able to generate openEHR libraries from BMM files with extensive JSDoc annotations so that I can get good autocompletion and type checking in my editor.
* As a developer using Deno, I want to be able to use the generated openEHR libraries in my projects without having to transpile them or use a compatibility layer.

## 5. Requirements

### 5.1. Functional Requirements

* The generator shall be implemented as a single writer that can produce both TypeScript and JavaScript output.
* The generator shall be triggered by a new command in the `bin/generate` script.
* The generated code shall be compatible with the Deno runtime and its module system.
* The generator shall produce JSDoc 3 annotations for the JavaScript output, including namepaths and tags for assertions, invariants, preconditions, and postconditions when available from the BMM source files.
* The generated code shall follow the specified coding conventions:
* `snake_case` for methods.
* `CAPITALIZED` class names for openEHR types.
* `CamelCase` for utility/helper classes and methods that are not part of the openEHR specifications.
* The generator shall not introduce any external dependencies other than those provided by the Deno runtime or modern browsers.

### 5.2. Non-Functional Requirements

* The generated code should be well-formatted and easy to read.
* The generator should be performant and be able to handle large BMM files.
* The generator should be well-tested to ensure the correctness of the generated code.

## 6. Future Enhancements

* Support for publishing the generated libraries to a Deno-friendly package registry.
* Support for generating code for other languages, such as Python or C#.
* Support for generating code from other input formats, such as Archetype Definition Language (ADL).

## 7. Out of Scope

* The implementation of a full openEHR validation library. The generated code will only represent the data structures and will not include any validation logic.
* The implementation of a user interface for the generator. The generator will only be available as a command-line tool.
74 changes: 32 additions & 42 deletions src/Model/Bmm/BmmSingleFunctionParameter.php
Original file line number Diff line number Diff line change
@@ -1,73 +1,63 @@
<?php /** @noinspection PhpMissingParentConstructorInspection */
<?php
// Paste this code into src/Model/Bmm/BmmSingleFunctionParameter.php

namespace OpenEHR\Tools\CodeGen\Model\Bmm;

use JsonSerializable;
use OpenEHR\Tools\CodeGen\Model\YamlSerializable;
use Symfony\Component\Yaml\Tag\TaggedValue;

/**
* Class representing a BMM single function parameter
*/
readonly class BmmSingleFunctionParameter extends AbstractBmmFunctionParameter implements JsonSerializable, YamlSerializable
{

/**
* @param string $name
* @param string $type
* @param string|null $documentation
* @param bool|null $isNullable
*/
public function __construct(
public string $name,
public string $type,
public ?string $documentation = null,
public ?bool $isNullable = false,
string $name,
public BmmSimpleType|BmmContainerType|BmmGenericType $typeDef,
)
{
parent::__construct($name);
}

/**
* @return array<string, mixed>
*/
public function jsonSerialize(): array
{

return array_filter([
'_type' => 'P_BMM_SINGLE_FUNCTION_PARAMETER',
'name' => $this->name,
'documentation' => $this->documentation,
'is_nullable' => $this->isNullable,
'type' => $this->type,
'type_def' => $this->typeDef,
]);
}

/**
* @return TaggedValue
*/
public function yamlSerialize(): TaggedValue
public function yamlSerialize(): array
{
return new TaggedValue('P_BMM_SINGLE_FUNCTION_PARAMETER', array_filter([
return array_filter([
'name' => $this->name,
'documentation' => $this->documentation,
'is_nullable' => $this->isNullable,
'type' => $this->type,
]));
'type_def' => $this->typeDef->yamlSerialize(),
]);
}

/**
* Create a BMMSingleFunctionParameter from a JSON array
*
* @param array<string, mixed> $data
* @return self
*/
public static function fromArray(array $data): self
{
$typeDefData = null;

// Case 1: The modern 'type_def' object exists.
if (isset($data['type_def']) && is_array($data['type_def'])) {
$typeDefData = $data['type_def'];
}
// Case 2: The legacy 'type' string exists.
elseif (isset($data['type'])) {
$typeDefData = [
'_type' => 'BMM_SIMPLE_TYPE',
'type' => $data['type'],
];
}
// Case 3: Neither exists, so we must default to 'ANY' to prevent a crash.
else {
$typeDefData = [
'_type' => 'BMM_SIMPLE_TYPE',
'type' => 'ANY',
];
}

return new self(
name: $data['name'],
type: $data['type'] ?? 'Any',
documentation: $data['documentation'] ?? null,
isNullable: $data['is_nullable'] ?? false,
typeDef: AbstractBmmType::fromArray($typeDefData)
);
}
}
Loading