diff --git a/.eslintrc.json b/.eslintrc.json index ee289cb4..305d593b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,6 +8,7 @@ "plugin:@typescript-eslint/eslint-recommended" ], "ignorePatterns": [ + "package.json", "node_modules", "dist", "webpack.config.mjs" diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 86dc8c2e..723c94c4 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -41,7 +41,7 @@ jobs: - name: Install dependencies run: npm install - name: Build - run: npm run build + run: npm run build:prod - name: Test run: npm run test - name: Upload artifact diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 656e001d..df63f035 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,6 @@ jobs: - name: Install dependencies run: npm install - name: Build - run: npm run build + run: npm run build:dev - name: Test run: npm run test \ No newline at end of file diff --git a/package.json b/package.json index 1c93484c..d9268900 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,11 @@ "author": "Michael L Haufe (https://final-hill.com)", "license": "AGPL-3.0-only", "scripts": { - "build": "webpack", + "build:dev": "webpack --mode development", + "build:prod": "webpack --mode production", "lint": "eslint src/ --ext .mts --fix", - "serve": "webpack serve", - "test": "globstar -- node --experimental-test-coverage --import tsx --test \"src/**/*.test.mts\"" + "serve": "webpack serve --mode development", + "test": "globstar -- node --import tsx --test \"src/**/*.test.mts\"" }, "devDependencies": { "@types/dom-navigation": "^1.0.3", @@ -49,4 +50,4 @@ "mermaid": "^10.6.1", "style-loader": "^3.3.3" } -} +} \ No newline at end of file diff --git a/src/data/BehaviorRepository.mts b/src/data/BehaviorRepository.mts index b5380bee..e96616c3 100644 --- a/src/data/BehaviorRepository.mts +++ b/src/data/BehaviorRepository.mts @@ -1,6 +1,8 @@ -import { Behavior } from '~/domain/Behavior.mjs'; +import Behavior from '~/domain/Behavior.mjs'; import { LocalStorageRepository } from './LocalStorageRepository.mjs'; +import BehaviorToJsonMapper from '~/mappers/BehaviorToJsonMapper.mjs'; +import pkg from '~/../package.json' with { type: 'json' }; export class BehaviorRepository extends LocalStorageRepository { - constructor() { super('behavior', Behavior); } + constructor() { super('behavior', new BehaviorToJsonMapper(pkg.version)); } } \ No newline at end of file diff --git a/src/data/EnvironmentRepository.mts b/src/data/EnvironmentRepository.mts index acf3c0e2..cdb25ebb 100644 --- a/src/data/EnvironmentRepository.mts +++ b/src/data/EnvironmentRepository.mts @@ -1,6 +1,8 @@ -import { Environment } from '~/domain/Environment.mjs'; +import Environment from '~/domain/Environment.mjs'; import { PEGSRepository } from './PEGSRepository.mjs'; +import EnvironmentToJsonMapper from '~/mappers/EnvironmentToJsonMapper.mjs'; +import pkg from '~/../package.json' with { type: 'json' }; export class EnvironmentRepository extends PEGSRepository { - constructor() { super('environments', Environment); } + constructor() { super('environments', new EnvironmentToJsonMapper(pkg.version)); } } \ No newline at end of file diff --git a/src/data/GlossaryRepository.mts b/src/data/GlossaryRepository.mts index 4309450d..0c5309f0 100644 --- a/src/data/GlossaryRepository.mts +++ b/src/data/GlossaryRepository.mts @@ -1,8 +1,10 @@ -import { GlossaryTerm } from '~/domain/GlossaryTerm.mjs'; +import GlossaryTerm from '~/domain/GlossaryTerm.mjs'; import { LocalStorageRepository } from './LocalStorageRepository.mjs'; +import GlossaryTermToJsonMapper from '~/mappers/GlossaryTermToJsonMapper.mjs'; +import pkg from '~/../package.json' with { type: 'json' }; export class GlossaryRepository extends LocalStorageRepository { constructor() { - super('glossary', GlossaryTerm); + super('glossary', new GlossaryTermToJsonMapper(pkg.version)); } } \ No newline at end of file diff --git a/src/data/GoalsRepository.mts b/src/data/GoalsRepository.mts index 90b7b19d..54b9a299 100644 --- a/src/data/GoalsRepository.mts +++ b/src/data/GoalsRepository.mts @@ -1,6 +1,8 @@ -import { Goals } from '~/domain/Goals.mjs'; +import Goals from '~/domain/Goals.mjs'; import { PEGSRepository } from './PEGSRepository.mjs'; +import GoalsToJsonMapper from '~/mappers/GoalsToJsonMapper.mjs'; +import pkg from '~/../package.json' with { type: 'json' }; export class GoalsRepository extends PEGSRepository { - constructor() { super('goals', Goals); } + constructor() { super('goals', new GoalsToJsonMapper(pkg.version)); } } \ No newline at end of file diff --git a/src/data/LocalStorageRepository.mts b/src/data/LocalStorageRepository.mts index d3e9bf71..a00d83ae 100644 --- a/src/data/LocalStorageRepository.mts +++ b/src/data/LocalStorageRepository.mts @@ -1,15 +1,11 @@ -import { type Entity } from '~/domain/Entity.mjs'; +import type Entity from '~/domain/Entity.mjs'; +import type { EntityJson } from '~/mappers/EntityToJsonMapper.mjs'; +import type Mapper from '~/usecases/Mapper.mjs'; import Repository from '~/usecases/Repository.mjs'; export class LocalStorageRepository extends Repository { - private _storageKey; - private _fromJSON; - - constructor(storageKey: string, EntityConstructor: typeof Entity) { - super(EntityConstructor); - - this._storageKey = storageKey; - this._fromJSON = EntityConstructor.fromJSON as (json: any) => E; + constructor(readonly storageKey: string, mapper: Mapper) { + super(mapper); } get storage(): Storage { @@ -17,28 +13,28 @@ export class LocalStorageRepository extends Repository { } get(id: E['id']): Promise { - const data = this.storage.getItem(this._storageKey), - json: E[] = data ? JSON.parse(data) : [], + const data = this.storage.getItem(this.storageKey), + json: EntityJson[] = data ? JSON.parse(data) : [], result = json.find(item => item.id === id); return Promise.resolve( - result ? this._fromJSON(result) : undefined + result ? this.mapper.mapFrom(result) : undefined ); } getAll(filter: (entity: E) => boolean = _ => true): Promise { - const data = this.storage.getItem(this._storageKey), - json: E[] = data ? JSON.parse(data) : [], - result = json.filter(filter).map(this._fromJSON); + const data = this.storage.getItem(this.storageKey), + json: EntityJson[] = data ? JSON.parse(data) : [], + result = json.map(this.mapper.mapFrom).filter(filter); return Promise.resolve(result); } add(item: E): Promise { - const data = this.storage.getItem(this._storageKey), + const data = this.storage.getItem(this.storageKey), json: E[] = data ? JSON.parse(data) : []; - json.push(item.toJSON() as E); - this.storage.setItem(this._storageKey, JSON.stringify(json)); + json.push(this.mapper.mapTo(item)); + this.storage.setItem(this.storageKey, JSON.stringify(json)); this.dispatchEvent(new CustomEvent('update')); @@ -46,22 +42,22 @@ export class LocalStorageRepository extends Repository { } clear(): Promise { - this.storage.removeItem(this._storageKey); + this.storage.removeItem(this.storageKey); this.dispatchEvent(new CustomEvent('update')); return Promise.resolve(); } update(item: E): Promise { - const data = this.storage.getItem(this._storageKey), - json: E[] = data ? JSON.parse(data) : [], + const data = this.storage.getItem(this.storageKey), + json: EntityJson[] = data ? JSON.parse(data) : [], index = json.findIndex(e => e.id === item.id); if (index === -1) throw new Error('Not found'); - json[index] = item.toJSON() as E; - this.storage.setItem(this._storageKey, JSON.stringify(json)); + json[index] = this.mapper.mapTo(item); + this.storage.setItem(this.storageKey, JSON.stringify(json)); this.dispatchEvent(new CustomEvent('update')); @@ -69,15 +65,15 @@ export class LocalStorageRepository extends Repository { } delete(id: E['id']): Promise { - const data = this.storage.getItem(this._storageKey), - json: E[] = data ? JSON.parse(data) : [], + const data = this.storage.getItem(this.storageKey), + json: EntityJson[] = data ? JSON.parse(data) : [], index = json.findIndex(item => item.id === id); if (index === -1) throw new Error('Not found'); json.splice(index, 1); - this.storage.setItem(this._storageKey, JSON.stringify(json)); + this.storage.setItem(this.storageKey, JSON.stringify(json)); this.dispatchEvent(new CustomEvent('update')); diff --git a/src/data/LocalStorageRepository.test.mts b/src/data/LocalStorageRepository.test.mts index e579c350..1f4f0469 100644 --- a/src/data/LocalStorageRepository.test.mts +++ b/src/data/LocalStorageRepository.test.mts @@ -1,21 +1,23 @@ import { describe, test } from 'node:test'; import assert from 'node:assert/strict'; -import { Behavior } from '~/domain/Behavior.mjs'; import { LocalStorageRepository } from './LocalStorageRepository.mjs'; -import type { Entity } from '~/domain/Entity.mjs'; +import Behavior from '~/domain/Behavior.mjs'; // @ts-expect-error: No typings available import DomStorage from 'dom-storage'; +import BehaviorToJsonMapper from '~/mappers/BehaviorToJsonMapper.mjs'; const fakeStorage: Storage = new DomStorage(null, { strict: true }); -class TestLocalStorageRepository extends LocalStorageRepository { +class TestLocalStorageRepository extends LocalStorageRepository { override get storage() { return fakeStorage; } } describe('LocalStorageRepository', () => { - const repository = new TestLocalStorageRepository('localstorage-test', Behavior); + const repository = new TestLocalStorageRepository( + 'localstorage-test', new BehaviorToJsonMapper('0.3.0') + ); test('storage property', () => { assert(repository.storage instanceof DomStorage); diff --git a/src/data/PEGSRepository.mts b/src/data/PEGSRepository.mts index 3a7da98a..5c5f1b4f 100644 --- a/src/data/PEGSRepository.mts +++ b/src/data/PEGSRepository.mts @@ -1,9 +1,11 @@ -import type { PEGS } from '~/domain/PEGS.mjs'; +import type PEGS from '~/domain/PEGS.mjs'; import { LocalStorageRepository } from './LocalStorageRepository.mjs'; +import type Mapper from '~/usecases/Mapper.mjs'; +import type { EntityJson } from '~/mappers/EntityToJsonMapper.mjs'; export abstract class PEGSRepository extends LocalStorageRepository { - constructor(storageKey: string, EntityConstructor: typeof PEGS) { - super(storageKey, EntityConstructor); + constructor(storageKey: string, mapper: Mapper) { + super(storageKey, mapper); } async getBySlug(slug: string): Promise { diff --git a/src/data/PEGSRepository.test.mts b/src/data/PEGSRepository.test.mts deleted file mode 100644 index 1188bf83..00000000 --- a/src/data/PEGSRepository.test.mts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, test } from 'node:test'; -import assert from 'node:assert/strict'; -import { PEGS } from '~/domain/PEGS.mjs'; -// @ts-expect-error: No typings available -import DomStorage from 'dom-storage'; -import { PEGSRepository } from './PEGSRepository.mjs'; - -const fakeStorage: Storage = new DomStorage(null, { strict: true }); - -class TestPEGSRepository extends PEGSRepository { - constructor() { super('pegs', PEGS); } - - override get storage() { - return fakeStorage; - } -} - -describe('PEGSRepository', () => { - const repository = new TestPEGSRepository(); - - test('getBySlug', async () => { - const pegs = new PEGS({ - id: crypto.randomUUID(), - name: 'Sample PEGS', - description: 'This is a sample PEGS.' - }); - - await repository.add(pegs); - - const result = await repository.getBySlug(pegs.slug()); - assert(result instanceof PEGS); - assert(pegs.equals(result)); - }); -}); \ No newline at end of file diff --git a/src/data/ProjectRepository.mts b/src/data/ProjectRepository.mts index 87280c2b..0495c747 100644 --- a/src/data/ProjectRepository.mts +++ b/src/data/ProjectRepository.mts @@ -1,6 +1,8 @@ -import { Project } from '~/domain/Project.mjs'; +import Project from '~/domain/Project.mjs'; import { PEGSRepository } from './PEGSRepository.mjs'; +import ProjectToJsonMapper from '~/mappers/ProjectToJsonMapper.mjs'; +import pkg from '~/../package.json' with { type: 'json' }; export class ProjectRepository extends PEGSRepository { - constructor() { super('projects', Project); } + constructor() { super('projects', new ProjectToJsonMapper(pkg.version)); } } \ No newline at end of file diff --git a/src/data/StakeholderRepository.mts b/src/data/StakeholderRepository.mts index bc11aa91..abdb9127 100644 --- a/src/data/StakeholderRepository.mts +++ b/src/data/StakeholderRepository.mts @@ -1,6 +1,8 @@ -import { Stakeholder } from '~/domain/Stakeholder.mjs'; +import Stakeholder from '~/domain/Stakeholder.mjs'; import { LocalStorageRepository } from './LocalStorageRepository.mjs'; +import StakeholderToJsonMapper from '~/mappers/StakeholderToJsonMapper.mjs'; +import pkg from '~/../package.json' with { type: 'json' }; export class StakeholderRepository extends LocalStorageRepository { - constructor() { super('stakeholder', Stakeholder); } + constructor() { super('stakeholder', new StakeholderToJsonMapper(pkg.version)); } } \ No newline at end of file diff --git a/src/domain/Behavior.mts b/src/domain/Behavior.mts index d93b5dba..e84fd656 100644 --- a/src/domain/Behavior.mts +++ b/src/domain/Behavior.mts @@ -1,13 +1,7 @@ import type { Properties } from '~/types/Properties.mjs'; -import { Requirement, type RequirementJson } from './Requirement.mjs'; - -export interface BehaviorJson extends RequirementJson { } - -export class Behavior extends Requirement { - static override fromJSON({ id, statement }: BehaviorJson): Behavior { - return new Behavior({ id, statement }); - } +import Requirement from './Requirement.mjs'; +export default class Behavior extends Requirement { constructor(options: Properties) { super(options); } diff --git a/src/domain/Behavior.test.mts b/src/domain/Behavior.test.mts deleted file mode 100644 index d72b9502..00000000 --- a/src/domain/Behavior.test.mts +++ /dev/null @@ -1,22 +0,0 @@ -import { describe, test } from 'node:test'; -import assert from 'node:assert/strict'; -import { Behavior } from './Behavior.mjs'; - -describe('Behavior', () => { - test('fromJSON', () => { - const uuid = crypto.randomUUID(), - behavior = Behavior.fromJSON({ - id: uuid, - statement: 'test' - }); - assert.strictEqual(behavior.id, uuid); - assert.strictEqual(behavior.statement, 'test'); - }); - - test('toJSON', () => { - const uuid = crypto.randomUUID(), - behavior = new Behavior({ id: uuid, statement: 'test' }); - assert.strictEqual(behavior.toJSON().id, uuid); - assert.strictEqual(behavior.toJSON().statement, 'test'); - }); -}); \ No newline at end of file diff --git a/src/domain/Entity.mts b/src/domain/Entity.mts index 22ea0420..f724ccaa 100644 --- a/src/domain/Entity.mts +++ b/src/domain/Entity.mts @@ -1,41 +1,20 @@ import type { Properties } from '~/types/Properties.mjs'; import type { Uuid } from '~/types/Uuid.mjs'; -export interface EntityJson { - id: Uuid; -} - /** * An entity is an object that is not defined by its attributes, * but rather by a thread of continuity represented by its identity (id). */ -export class Entity { +export default class Entity { static emptyId: Uuid = '00000000-0000-0000-0000-000000000000'; - /** - * Creates an instance of the object from a JSON representation. - * @param json - The JSON representation of the object. - * @returns The object. - */ - static fromJSON({ id }: EntityJson): Entity { - return new Entity({ id }); - } - - #id: Uuid; - - constructor({ id }: Properties) { - this.#id = id; - } - /** * The unique identifier of the entity. */ - get id() { - return this.#id; - } + id: Uuid; - set id(value: Uuid) { - this.#id = value; + constructor({ id }: Properties) { + this.id = id; } /** @@ -46,14 +25,4 @@ export class Entity { equals(other: Entity): boolean { return this.id === other.id; } - - /** - * Converts the entity to a JSON representation. - * @returns The JSON representation of the entity. - */ - toJSON(): EntityJson { - return { - id: this.#id - }; - } } \ No newline at end of file diff --git a/src/domain/Entity.test.mts b/src/domain/Entity.test.mts deleted file mode 100644 index e97fb88e..00000000 --- a/src/domain/Entity.test.mts +++ /dev/null @@ -1,49 +0,0 @@ -import { describe, test } from 'node:test'; -import assert from 'node:assert/strict'; -import { Entity } from './Entity.mjs'; - -describe('Entity', () => { - test('static properties', () => { - assert.strictEqual(Entity.emptyId, '00000000-0000-0000-0000-000000000000'); - }); - - test('fromJSON', () => { - const uuid = crypto.randomUUID(), - entity = Entity.fromJSON({ id: uuid }); - assert.strictEqual(entity.id, uuid); - }); - - test('toJSON', () => { - const uuid = crypto.randomUUID(), - entity = new Entity({ id: uuid }), - json = entity.toJSON(); - assert.strictEqual(json.id, uuid); - }); - - test('equals', () => { - const uuid = crypto.randomUUID(), - entity1 = new Entity({ id: uuid }), - entity2 = new Entity({ id: uuid }), - entity3 = new Entity({ id: uuid }); - - // reflexive - assert.ok(entity1.equals(entity1)); - - // symmetric - assert.ok(entity1.equals(entity2)); - assert.ok(entity2.equals(entity1)); - - // transitive - assert.ok(entity1.equals(entity2)); - assert.ok(entity2.equals(entity3)); - assert.ok(entity1.equals(entity3)); - }); - - test('not equals', () => { - const entity1 = new Entity({ id: crypto.randomUUID() }), - entity2 = new Entity({ id: crypto.randomUUID() }); - - assert.ok(!entity1.equals(entity2)); - assert.ok(!entity2.equals(entity1)); - }); -}); \ No newline at end of file diff --git a/src/domain/Environment.mts b/src/domain/Environment.mts index d8014d84..9a5499e2 100644 --- a/src/domain/Environment.mts +++ b/src/domain/Environment.mts @@ -1,11 +1,7 @@ import type { Uuid } from '~/types/Uuid.mjs'; -import { PEGS, type PEGSJson } from './PEGS.mjs'; +import PEGS from './PEGS.mjs'; import type { Properties } from '~/types/Properties.mjs'; -export interface EnvironmentJson extends PEGSJson { - glossary: Uuid[]; -} - /** * The set of entities (people, organizations, regulations, devices and other material objects, other systems) * external to the project or system but with the potential to affect it or be affected by it. @@ -13,24 +9,11 @@ export interface EnvironmentJson extends PEGSJson { * An environment describes the application domain and external context in which a * system operates. */ -class Environment extends PEGS { - static override fromJSON({ description, id, name, glossary }: EnvironmentJson): Environment { - return new Environment({ description, id, name, glossary }); - } - +export default class Environment extends PEGS { glossary: Uuid[]; constructor(options: Properties) { super(options); this.glossary = options.glossary; } - - override toJSON(): EnvironmentJson { - return { - ...super.toJSON(), - glossary: this.glossary - }; - } -} - -export { Environment }; \ No newline at end of file +} \ No newline at end of file diff --git a/src/domain/Environment.test.mts b/src/domain/Environment.test.mts deleted file mode 100644 index c4d9c49e..00000000 --- a/src/domain/Environment.test.mts +++ /dev/null @@ -1,42 +0,0 @@ -import { describe, test } from 'node:test'; -import assert from 'node:assert/strict'; -import { Environment } from './Environment.mjs'; - -describe('Environment', () => { - test('fromJSON', () => { - const envId = crypto.randomUUID(), - glossaryIds = Array.from({ length: 3 }, () => crypto.randomUUID()), - environment = Environment.fromJSON({ - id: envId, - name: 'test', - description: 'test description', - glossary: glossaryIds, - }); - assert.strictEqual(environment.id, envId); - assert.strictEqual(environment.name, 'test'); - assert.strictEqual(environment.description, 'test description'); - assert.strictEqual(environment.glossary.length, 3); - assert.strictEqual(environment.glossary[0], glossaryIds[0]); - assert.strictEqual(environment.glossary[1], glossaryIds[1]); - assert.strictEqual(environment.glossary[2], glossaryIds[2]); - }); - - test('toJSON', () => { - const envId = crypto.randomUUID(), - glossaryIds = Array.from({ length: 3 }, () => crypto.randomUUID()), - environment = new Environment({ - id: envId, - name: 'test', - description: 'test description', - glossary: glossaryIds, - }), - json = environment.toJSON(); - assert.strictEqual(json.id, envId); - assert.strictEqual(json.name, 'test'); - assert.strictEqual(json.description, 'test description'); - assert.strictEqual(json.glossary.length, 3); - assert.strictEqual(json.glossary[0], glossaryIds[0]); - assert.strictEqual(json.glossary[1], glossaryIds[1]); - assert.strictEqual(json.glossary[2], glossaryIds[2]); - }); -}); \ No newline at end of file diff --git a/src/domain/GlossaryTerm.mts b/src/domain/GlossaryTerm.mts index 87934f7b..ca857ae4 100644 --- a/src/domain/GlossaryTerm.mts +++ b/src/domain/GlossaryTerm.mts @@ -1,20 +1,7 @@ import type { Properties } from '~/types/Properties.mjs'; -import { Entity, type EntityJson } from './Entity.mjs'; - -export interface GlossaryTermJson extends EntityJson { - term: string; - definition: string; -} - -export class GlossaryTerm extends Entity { - static override fromJSON(json: GlossaryTermJson): GlossaryTerm { - return new GlossaryTerm({ - id: json.id, - term: json.term, - definition: json.definition - }); - } +import Entity from './Entity.mjs'; +export default class GlossaryTerm extends Entity { definition: string; term: string; @@ -23,14 +10,4 @@ export class GlossaryTerm extends Entity { this.term = term; this.definition = definition; } - - override toJSON(): GlossaryTermJson { - return { - ...super.toJSON(), - term: this.term, - definition: this.definition - }; - } -} - -export default GlossaryTerm; \ No newline at end of file +} \ No newline at end of file diff --git a/src/domain/GlossaryTerm.test.mts b/src/domain/GlossaryTerm.test.mts deleted file mode 100644 index 592985ba..00000000 --- a/src/domain/GlossaryTerm.test.mts +++ /dev/null @@ -1,30 +0,0 @@ -import { describe, test } from 'node:test'; -import assert from 'node:assert/strict'; -import { GlossaryTerm } from './GlossaryTerm.mjs'; - -describe('GlossaryTerm', () => { - test('fromJSON', () => { - const uuid = crypto.randomUUID(), - glossaryTerm = GlossaryTerm.fromJSON({ - id: uuid, - term: 'test', - definition: 'test description', - }); - assert.strictEqual(glossaryTerm.id, uuid); - assert.strictEqual(glossaryTerm.term, 'test'); - assert.strictEqual(glossaryTerm.definition, 'test description'); - }); - - test('toJSON', () => { - const uuid = crypto.randomUUID(), - glossaryTerm = new GlossaryTerm({ - id: uuid, - term: 'test', - definition: 'test description', - }), - json = glossaryTerm.toJSON(); - assert.strictEqual(json.id, uuid); - assert.strictEqual(json.term, 'test'); - assert.strictEqual(json.definition, 'test description'); - }); -}); \ No newline at end of file diff --git a/src/domain/Goal.mts b/src/domain/Goal.mts index 89d68a19..96197764 100644 --- a/src/domain/Goal.mts +++ b/src/domain/Goal.mts @@ -1,20 +1,8 @@ -import { Requirement, type RequirementJson } from './Requirement.mjs'; - -export interface GoalJson extends RequirementJson { } +import Requirement from './Requirement.mjs'; /** * A result desired by an organization. * an objective of the project or system, in terms * of their desired effect on the environment */ -export class Goal extends Requirement { - static override fromJSON({ id, statement }: GoalJson): Goal { - return new Goal({ id, statement }); - } - - override toJSON(): GoalJson { - return { - ...super.toJSON() - }; - } -} \ No newline at end of file +export class Goal extends Requirement { } \ No newline at end of file diff --git a/src/domain/Goal.test.mts b/src/domain/Goal.test.mts deleted file mode 100644 index 16a0e8b7..00000000 --- a/src/domain/Goal.test.mts +++ /dev/null @@ -1,22 +0,0 @@ -import { describe, test } from 'node:test'; -import assert from 'node:assert/strict'; -import { Goal } from './Goal.mjs'; - -describe('Goal', () => { - test('fromJSON', () => { - const uuid = crypto.randomUUID(), - goal = Goal.fromJSON({ - id: uuid, - statement: 'test' - }); - assert.strictEqual(goal.id, uuid); - assert.strictEqual(goal.statement, 'test'); - }); - - test('toJSON', () => { - const uuid = crypto.randomUUID(), - goal = new Goal({ id: uuid, statement: 'test' }); - assert.strictEqual(goal.toJSON().id, uuid); - assert.strictEqual(goal.toJSON().statement, 'test'); - }); -}); \ No newline at end of file diff --git a/src/domain/Goals.mts b/src/domain/Goals.mts index 0ab7a45a..23f3f642 100644 --- a/src/domain/Goals.mts +++ b/src/domain/Goals.mts @@ -1,33 +1,12 @@ import type { Properties } from '~/types/Properties.mjs'; import type { Uuid } from '~/types/Uuid.mjs'; -import { PEGS, type PEGSJson } from './PEGS.mjs'; - -export interface GoalsJson extends PEGSJson { - functionalBehaviors: Uuid[]; - objective: string; - outcomes: string; - situation: string; - stakeholders: Uuid[]; -} +import PEGS from './PEGS.mjs'; /** * Goals are the needs and wants of an organization. * They are the things that the organization wants to achieve. */ -export class Goals extends PEGS { - static override fromJSON(json: GoalsJson): Goals { - return new Goals({ - functionalBehaviors: json.functionalBehaviors, - id: json.id, - description: json.description, - name: json.name, - objective: json.objective, - outcomes: json.outcomes, - situation: json.situation, - stakeholders: json.stakeholders - }); - } - +export default class Goals extends PEGS { /** * Functional behaviors specify what results or effects are expected from the system. * They specify "what" the system should do, not "how" it should do it. @@ -46,15 +25,4 @@ export class Goals extends PEGS { this.stakeholders = options.stakeholders; this.situation = options.situation; } - - override toJSON(): GoalsJson { - return { - ...super.toJSON(), - functionalBehaviors: this.functionalBehaviors, - objective: this.objective, - outcomes: this.outcomes, - situation: this.situation, - stakeholders: this.stakeholders - }; - } } \ No newline at end of file diff --git a/src/domain/Goals.test.mts b/src/domain/Goals.test.mts deleted file mode 100644 index dc8f4320..00000000 --- a/src/domain/Goals.test.mts +++ /dev/null @@ -1,66 +0,0 @@ -import { describe, test } from 'node:test'; -import assert from 'node:assert/strict'; -import { Goals } from './Goals.mjs'; - -describe('Goals', () => { - test('fromJSON', () => { - const id = crypto.randomUUID(), - fids = Array.from({ length: 3 }, () => crypto.randomUUID()), - sids = Array.from({ length: 3 }, () => crypto.randomUUID()), - goals = Goals.fromJSON({ - functionalBehaviors: fids, - id, - description: 'test', - name: 'test', - objective: 'test', - outcomes: 'test', - situation: 'test', - stakeholders: sids - }); - assert.strictEqual(goals.id, id); - assert.strictEqual(goals.description, 'test'); - assert.strictEqual(goals.name, 'test'); - assert.strictEqual(goals.objective, 'test'); - assert.strictEqual(goals.outcomes, 'test'); - assert.strictEqual(goals.situation, 'test'); - assert.strictEqual(goals.functionalBehaviors.length, 3); - goals.functionalBehaviors.forEach((fid, index) => { - assert.strictEqual(fid, fids[index]); - }); - assert.strictEqual(goals.stakeholders.length, 3); - goals.stakeholders.forEach((sid, index) => { - assert.strictEqual(sid, sids[index]); - }); - }); - - test('toJSON', () => { - const id = crypto.randomUUID(), - fids = Array.from({ length: 3 }, () => crypto.randomUUID()), - sids = Array.from({ length: 3 }, () => crypto.randomUUID()), - goals = new Goals({ - functionalBehaviors: fids, - id, - description: 'test', - name: 'test', - objective: 'test', - outcomes: 'test', - situation: 'test', - stakeholders: sids - }), - json = goals.toJSON(); - assert.strictEqual(json.id, id); - assert.strictEqual(json.description, 'test'); - assert.strictEqual(json.name, 'test'); - assert.strictEqual(json.objective, 'test'); - assert.strictEqual(json.outcomes, 'test'); - assert.strictEqual(json.situation, 'test'); - assert.strictEqual(json.functionalBehaviors.length, 3); - json.functionalBehaviors.forEach((fid, index) => { - assert.strictEqual(fid, fids[index]); - }); - assert.strictEqual(json.stakeholders.length, 3); - json.stakeholders.forEach((sid, index) => { - assert.strictEqual(sid, sids[index]); - }); - }); -}); \ No newline at end of file diff --git a/src/domain/PEGS.mts b/src/domain/PEGS.mts index 28b5c12c..2b0db28f 100644 --- a/src/domain/PEGS.mts +++ b/src/domain/PEGS.mts @@ -1,15 +1,10 @@ import type { Properties } from '~/types/Properties.mjs'; -import { Entity, type EntityJson } from './Entity.mjs'; - -export interface PEGSJson extends EntityJson { - name: string; - description: string; -} +import Entity from './Entity.mjs'; /** * The base class for all PEGS (Project, Environment, Goal, System) */ -export class PEGS extends Entity { +export default class PEGS extends Entity { static readonly maxNameLength = 60; static readonly maxDescriptionLength = 200; @@ -20,14 +15,6 @@ export class PEGS extends Entity { .replace(/--+/g, '-'); } - static override fromJSON(json: PEGSJson): PEGS { - return new PEGS({ - id: json.id, - description: json.description, - name: json.name - }); - } - private _name!: string; private _description!: string; @@ -64,12 +51,4 @@ export class PEGS extends Entity { slug(): string { return PEGS.slugify(this.name); } - - override toJSON(): PEGSJson { - return { - ...super.toJSON(), - name: this.name, - description: this.description - }; - } } \ No newline at end of file diff --git a/src/domain/PEGS.test.mts b/src/domain/PEGS.test.mts deleted file mode 100644 index 13ed97db..00000000 --- a/src/domain/PEGS.test.mts +++ /dev/null @@ -1,61 +0,0 @@ -import { describe, test } from 'node:test'; -import assert from 'node:assert/strict'; -import { PEGS } from './PEGS.mjs'; - -describe('PEGS', () => { - test('static properties', () => { - assert.strictEqual(PEGS.maxNameLength, 60); - assert.strictEqual(PEGS.maxDescriptionLength, 200); - assert.strictEqual(typeof PEGS.slugify, 'function'); - }); - - test('slugify', () => { - assert.strictEqual(PEGS.slugify('test'), 'test'); - assert.strictEqual(PEGS.slugify('test test'), 'test-test'); - assert.strictEqual(PEGS.slugify('test test test'), 'test-test-test'); - }); - - test('long name', () => { - const longName = 'a'.repeat(PEGS.maxNameLength + 1); - assert.throws(() => new PEGS({ - id: crypto.randomUUID(), - description: '', - name: longName - }), { - message: `Project name cannot be longer than ${PEGS.maxNameLength} characters` - }); - }); - - test('long description', () => { - const longDescription = 'a'.repeat(PEGS.maxDescriptionLength + 1); - assert.throws(() => new PEGS({ - id: crypto.randomUUID(), - description: longDescription, - name: '' - }), { - message: `Project description cannot be longer than ${PEGS.maxDescriptionLength} characters` - }); - }); - - test('fromJSON', () => { - const uuid = crypto.randomUUID(), - pegs = PEGS.fromJSON({ - id: uuid, - name: 'Sample PEGS', - description: 'test' - }); - assert.strictEqual(pegs.id, uuid); - assert.strictEqual(pegs.name, 'Sample PEGS'); - assert.strictEqual(pegs.description, 'test'); - assert.strictEqual(pegs.slug(), 'sample-pegs'); - }); - - test('toJSON', () => { - const uuid = crypto.randomUUID(), - pegs = new PEGS({ id: uuid, description: 'test', name: 'test' }), - json = pegs.toJSON(); - assert.strictEqual(json.id, uuid); - assert.strictEqual(json.description, 'test'); - assert.strictEqual(json.name, 'test'); - }); -}); \ No newline at end of file diff --git a/src/domain/Project.mts b/src/domain/Project.mts index e5c6c0cd..8d0844be 100644 --- a/src/domain/Project.mts +++ b/src/domain/Project.mts @@ -1,20 +1,8 @@ -import { PEGS, type PEGSJson } from './PEGS.mjs'; - -export interface ProjectJson extends PEGSJson { } +import PEGS from './PEGS.mjs'; /** * A Project is the set of human processes involved in the planning, * construction, revision, and operation of an associated system. * @extends PEGS */ -export class Project extends PEGS { - static override fromJSON({ id, description, name }: ProjectJson): Project { - return new Project({ id, description, name }); - } - - override toJSON(): ProjectJson { - return { - ...super.toJSON() - }; - } -} \ No newline at end of file +export default class Project extends PEGS { } \ No newline at end of file diff --git a/src/domain/Project.test.mts b/src/domain/Project.test.mts deleted file mode 100644 index 26637f24..00000000 --- a/src/domain/Project.test.mts +++ /dev/null @@ -1,27 +0,0 @@ -import { describe, test } from 'node:test'; -import assert from 'node:assert/strict'; -import { Project } from './Project.mjs'; - -describe('Project', () => { - test('fromJSON', () => { - const uuid = crypto.randomUUID(), - project = Project.fromJSON({ - id: uuid, - name: 'Sample Project', - description: 'test' - }); - assert.strictEqual(project.id, uuid); - assert.strictEqual(project.name, 'Sample Project'); - assert.strictEqual(project.description, 'test'); - assert.strictEqual(project.slug(), 'sample-project'); - }); - - test('toJSON', () => { - const uuid = crypto.randomUUID(), - project = new Project({ id: uuid, description: 'test', name: 'test' }), - json = project.toJSON(); - assert.strictEqual(json.id, uuid); - assert.strictEqual(json.description, 'test'); - assert.strictEqual(json.name, 'test'); - }); -}); \ No newline at end of file diff --git a/src/domain/Requirement.mts b/src/domain/Requirement.mts index 864dad1c..49a3cdda 100644 --- a/src/domain/Requirement.mts +++ b/src/domain/Requirement.mts @@ -1,14 +1,10 @@ import { type Properties } from '~/types/Properties.mjs'; -import { Entity, type EntityJson } from './Entity.mjs'; - -export interface RequirementJson extends EntityJson { - statement: string; -} +import Entity from './Entity.mjs'; /** * A Requirement is a statement that specifies a property. */ -export abstract class Requirement extends Entity { +export default class Requirement extends Entity { /** * A statement is a human-readable description of a requirement. */ @@ -28,11 +24,4 @@ export abstract class Requirement extends Entity { property(..._args: any[]): boolean { return false; } - - override toJSON(): RequirementJson { - return { - ...super.toJSON(), - statement: this.statement - }; - } } \ No newline at end of file diff --git a/src/domain/Stakeholder.mts b/src/domain/Stakeholder.mts index ed776ea8..5334da54 100644 --- a/src/domain/Stakeholder.mts +++ b/src/domain/Stakeholder.mts @@ -1,12 +1,5 @@ import type { Properties } from '~/types/Properties.mjs'; -import { Entity, type EntityJson } from './Entity.mjs'; - -export interface StakeholderJson extends EntityJson { - category: string; - description: string; - name: string; - segmentation: string; -} +import Entity from './Entity.mjs'; export enum StakeholderSegmentation { Client = 'Client', @@ -20,7 +13,7 @@ export enum StakeholderCategory { Observer = 'Observer' } -export class Stakeholder extends Entity { +export default class Stakeholder extends Entity { category: StakeholderCategory; description: string; name: string; @@ -33,24 +26,4 @@ export class Stakeholder extends Entity { this.name = name; this.segmentation = segmentation; } - - static override fromJSON({ id, category, description, name, segmentation }: StakeholderJson): Stakeholder { - return new Stakeholder({ - category: category as StakeholderCategory, - description, - id, - name, - segmentation: segmentation as StakeholderSegmentation - }); - } - - override toJSON(): StakeholderJson { - return { - ...super.toJSON(), - category: this.category, - description: this.description, - name: this.name, - segmentation: this.segmentation - }; - } } \ No newline at end of file diff --git a/src/domain/Stakeholder.test.mts b/src/domain/Stakeholder.test.mts deleted file mode 100644 index 16ced05a..00000000 --- a/src/domain/Stakeholder.test.mts +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, test } from 'node:test'; -import assert from 'node:assert/strict'; -import { Stakeholder, StakeholderCategory, StakeholderSegmentation } from './Stakeholder.mjs'; - -describe('Stakeholder', () => { - test('fromJSON', () => { - const uuid = crypto.randomUUID(), - stakeholder = Stakeholder.fromJSON({ - id: uuid, - category: StakeholderCategory.KeyStakeholder, - description: 'test', - name: 'Sample Stakeholder', - segmentation: StakeholderSegmentation.Client - }); - assert.strictEqual(stakeholder.id, uuid); - assert.strictEqual(stakeholder.category, StakeholderCategory.KeyStakeholder); - assert.strictEqual(stakeholder.description, 'test'); - assert.strictEqual(stakeholder.name, 'Sample Stakeholder'); - assert.strictEqual(stakeholder.segmentation, StakeholderSegmentation.Client); - }); - - test('toJSON', () => { - const uuid = crypto.randomUUID(), - stakeholder = new Stakeholder({ - id: uuid, - category: StakeholderCategory.KeyStakeholder, - description: 'test', - name: 'Sample Stakeholder', - segmentation: StakeholderSegmentation.Client - }), - json = stakeholder.toJSON(); - assert.strictEqual(json.id, uuid); - assert.strictEqual(json.category, StakeholderCategory.KeyStakeholder); - assert.strictEqual(json.description, 'test'); - assert.strictEqual(json.name, 'Sample Stakeholder'); - assert.strictEqual(json.segmentation, StakeholderSegmentation.Client); - }); -}); \ No newline at end of file diff --git a/src/mappers/BehaviorToJsonMapper.mts b/src/mappers/BehaviorToJsonMapper.mts new file mode 100644 index 00000000..927f890c --- /dev/null +++ b/src/mappers/BehaviorToJsonMapper.mts @@ -0,0 +1,18 @@ +import Behavior from '~/domain/Behavior.mjs'; +import RequirementToJsonMapper, { type RequirementJson } from './RequirementToJsonMapper.mjs'; + +export interface BehaviorJson extends RequirementJson { } + +export default class BehaviorToJsonMapper extends RequirementToJsonMapper { + override mapFrom(target: BehaviorJson): Behavior { + const version = target.serializationVersion ?? '{undefined}'; + + if (version.startsWith('0.3.')) + return new Behavior(target); + + throw new Error(`Unsupported serialization version: ${version}`); + } + override mapTo(source: Behavior): BehaviorJson { + return super.mapTo(source); + } +} \ No newline at end of file diff --git a/src/mappers/EntityToJsonMapper.mts b/src/mappers/EntityToJsonMapper.mts new file mode 100644 index 00000000..c8d7a02f --- /dev/null +++ b/src/mappers/EntityToJsonMapper.mts @@ -0,0 +1,36 @@ +import Entity from '~/domain/Entity.mjs'; +import type { Uuid } from '~/types/Uuid.mjs'; +import Mapper from '~/usecases/Mapper.mjs'; + +export interface EntityJson { + id: Uuid; + serializationVersion: string; +} + +export default class EntityToJsonMapper extends Mapper { + /** + * Converts the JSON representation of an entity to an entity. + * @param target The JSON representation of the entity. + * @returns The entity. + */ + mapFrom(target: EntityJson): Entity { + const version = target.serializationVersion ?? '{undefined}'; + + if (version.startsWith('0.3.')) + return new Entity(target); + + throw new Error(`Unsupported serialization version: ${version}`); + } + + /** + * Converts the entity to a JSON representation. + * @param source The entity to convert. + * @returns The JSON representation of the entity. + */ + mapTo({ id }: Entity): EntityJson { + return { + serializationVersion: this.serializationVersion, + id + }; + } +} \ No newline at end of file diff --git a/src/mappers/EnvironmentToJsonMapper.mts b/src/mappers/EnvironmentToJsonMapper.mts new file mode 100644 index 00000000..a65617ea --- /dev/null +++ b/src/mappers/EnvironmentToJsonMapper.mts @@ -0,0 +1,25 @@ +import type { Uuid } from '~/types/Uuid.mjs'; +import PEGSToJsonMapper, { type PEGSJson } from './PEGSToJsonMapper.mjs'; +import Environment from '~/domain/Environment.mjs'; + +export interface EnvironmentJson extends PEGSJson { + glossary: Uuid[]; +} + +export default class EnvironmentToJsonMapper extends PEGSToJsonMapper { + override mapFrom(target: EnvironmentJson): Environment { + const version = target.serializationVersion ?? '{undefined}'; + + if (version.startsWith('0.3.')) + return new Environment(target); + + throw new Error(`Unsupported serialization version: ${version}`); + } + + override mapTo(source: Environment): EnvironmentJson { + return { + ...super.mapTo(source), + glossary: source.glossary + }; + } +} \ No newline at end of file diff --git a/src/mappers/GlossaryTermToJsonMapper.mts b/src/mappers/GlossaryTermToJsonMapper.mts new file mode 100644 index 00000000..f742080b --- /dev/null +++ b/src/mappers/GlossaryTermToJsonMapper.mts @@ -0,0 +1,26 @@ +import EntityToJsonMapper, { type EntityJson } from './EntityToJsonMapper.mjs'; +import GlossaryTerm from '~/domain/GlossaryTerm.mjs'; + +export interface GlossaryTermJson extends EntityJson { + term: string; + definition: string; +} + +export default class GlossaryTermToJsonMapper extends EntityToJsonMapper { + override mapFrom(target: GlossaryTermJson): GlossaryTerm { + const version = target.serializationVersion ?? '{undefined}'; + + if (version.startsWith('0.3.')) + return new GlossaryTerm(target); + + throw new Error(`Unsupported serialization version: ${version}`); + } + + override mapTo(source: GlossaryTerm): GlossaryTermJson { + return { + ...super.mapTo(source), + term: source.term, + definition: source.definition + }; + } +} \ No newline at end of file diff --git a/src/mappers/GoalToJsonMapper.mts b/src/mappers/GoalToJsonMapper.mts new file mode 100644 index 00000000..c5c52220 --- /dev/null +++ b/src/mappers/GoalToJsonMapper.mts @@ -0,0 +1,18 @@ +import RequirementToJsonMapper, { type RequirementJson } from './RequirementToJsonMapper.mjs'; +import { Goal } from '~/domain/Goal.mjs'; + +export interface GoalJson extends RequirementJson { } + +export default class GoalToJsonMapper extends RequirementToJsonMapper { + override mapFrom(target: GoalJson): Goal { + const version = target.serializationVersion ?? '{undefined}'; + + if (version.startsWith('0.3.')) + return new Goal(target); + + throw new Error(`Unsupported serialization version: ${version}`); + } + override mapTo(source: Goal): GoalJson { + return super.mapTo(source); + } +} \ No newline at end of file diff --git a/src/mappers/GoalsToJsonMapper.mts b/src/mappers/GoalsToJsonMapper.mts new file mode 100644 index 00000000..f71e4683 --- /dev/null +++ b/src/mappers/GoalsToJsonMapper.mts @@ -0,0 +1,32 @@ +import type { Uuid } from '~/types/Uuid.mjs'; +import PEGSToJsonMapper, { type PEGSJson } from './PEGSToJsonMapper.mjs'; +import Goals from '~/domain/Goals.mjs'; + +export interface GoalsJson extends PEGSJson { + functionalBehaviors: Uuid[]; + objective: string; + outcomes: string; + situation: string; + stakeholders: Uuid[]; +} + +export default class GoalsToJsonMapper extends PEGSToJsonMapper { + override mapFrom(target: GoalsJson): Goals { + const version = target.serializationVersion ?? '{undefined}'; + + if (version.startsWith('0.3.')) + return new Goals(target); + + throw new Error(`Unsupported serialization version: ${version}`); + } + override mapTo(source: Goals): GoalsJson { + return { + ...super.mapTo(source), + functionalBehaviors: source.functionalBehaviors, + objective: source.objective, + outcomes: source.outcomes, + situation: source.situation, + stakeholders: source.stakeholders + }; + } +} \ No newline at end of file diff --git a/src/mappers/PEGSToJsonMapper.mts b/src/mappers/PEGSToJsonMapper.mts new file mode 100644 index 00000000..80be06a6 --- /dev/null +++ b/src/mappers/PEGSToJsonMapper.mts @@ -0,0 +1,25 @@ +import PEGS from '~/domain/PEGS.mjs'; +import EntityToJsonMapper, { type EntityJson } from './EntityToJsonMapper.mjs'; + +export interface PEGSJson extends EntityJson { + name: string; + description: string; +} + +export default class PEGSToJsonMapper extends EntityToJsonMapper { + override mapFrom(target: PEGSJson): PEGS { + const version = target.serializationVersion ?? '{undefined}'; + + if (version.startsWith('0.3.')) + return new PEGS(target); + + throw new Error(`Unsupported serialization version: ${version}`); + } + override mapTo(source: PEGS): PEGSJson { + return { + ...super.mapTo(source), + name: source.name, + description: source.description + }; + } +} \ No newline at end of file diff --git a/src/mappers/ProjectToJsonMapper.mts b/src/mappers/ProjectToJsonMapper.mts new file mode 100644 index 00000000..ffe557f7 --- /dev/null +++ b/src/mappers/ProjectToJsonMapper.mts @@ -0,0 +1,19 @@ +import Project from '~/domain/Project.mjs'; +import PEGSToJsonMapper, { type PEGSJson } from './PEGSToJsonMapper.mjs'; + +export interface ProjectJson extends PEGSJson { } + +export default class ProjectToJsonMapper extends PEGSToJsonMapper { + override mapFrom(target: ProjectJson): Project { + const version = target.serializationVersion ?? '{undefined}'; + + if (version.startsWith('0.3.')) + return new Project(target); + + throw new Error(`Unsupported serialization version: ${version}`); + } + + override mapTo(source: Project): ProjectJson { + return super.mapTo(source); + } +} \ No newline at end of file diff --git a/src/mappers/RequirementToJsonMapper.mts b/src/mappers/RequirementToJsonMapper.mts new file mode 100644 index 00000000..f2150f6e --- /dev/null +++ b/src/mappers/RequirementToJsonMapper.mts @@ -0,0 +1,18 @@ +import Requirement from '~/domain/Requirement.mjs'; +import EntityToJsonMapper, { type EntityJson } from './EntityToJsonMapper.mjs'; + +export interface RequirementJson extends EntityJson { + statement: string; +} + +export default class RequirementToJsonMapper extends EntityToJsonMapper { + override mapFrom(target: RequirementJson): Requirement { + return new Requirement(target); + } + override mapTo(source: Requirement): RequirementJson { + return { + ...super.mapTo(source), + statement: source.statement + }; + } +} \ No newline at end of file diff --git a/src/mappers/StakeholderToJsonMapper.mts b/src/mappers/StakeholderToJsonMapper.mts new file mode 100644 index 00000000..a312017c --- /dev/null +++ b/src/mappers/StakeholderToJsonMapper.mts @@ -0,0 +1,31 @@ +import Stakeholder, { StakeholderCategory, StakeholderSegmentation } from '~/domain/Stakeholder.mjs'; +import EntityToJsonMapper, { type EntityJson } from './EntityToJsonMapper.mjs'; + +export interface StakeholderJson extends EntityJson { + category: string; + description: string; + name: string; + segmentation: string; +} + +export default class StakeholderToJsonMapper extends EntityToJsonMapper { + override mapFrom(target: StakeholderJson): Stakeholder { + return new Stakeholder({ + category: target.category as StakeholderCategory, + description: target.description, + id: target.id, + name: target.name, + segmentation: target.segmentation as StakeholderSegmentation + }); + } + + override mapTo(source: Stakeholder): StakeholderJson { + return { + ...super.mapTo(source), + category: source.category, + description: source.description, + name: source.name, + segmentation: source.segmentation + }; + } +} \ No newline at end of file diff --git a/src/presentation/Router.mts b/src/presentation/Router.mts index 21b06197..2f8368a7 100644 --- a/src/presentation/Router.mts +++ b/src/presentation/Router.mts @@ -26,7 +26,8 @@ export default class Router extends HandleEvent(EventTarget) { if (!event.canIntercept || event.hashChange) return; - const url = new URL(event.destination.url), + const origin = document.location.origin, + url = new URL(event.destination.url, origin), // Page = this.#routeTable.get(url.pathname) ?? NotFound; // Search for a matching route pattern in the route table. Patterns diff --git a/src/presentation/components/BreadCrumb.mts b/src/presentation/components/BreadCrumb.mts index 3fd402ab..d72075b1 100644 --- a/src/presentation/components/BreadCrumb.mts +++ b/src/presentation/components/BreadCrumb.mts @@ -84,10 +84,11 @@ export class Breadcrumb extends Component { } protected override _initShadowHtml() { - const { nav, ul, template } = html; + const origin = document.location.origin, + { nav, ul, template } = html; return template(nav({ className: 'bread-crumb' }, ul({ className: 'crumbs' }, - this._makeCrumbs(new URL(document.location.href)) + this._makeCrumbs(new URL(document.location.href, origin)) ))); } @@ -95,7 +96,8 @@ export class Breadcrumb extends Component { if (!event.canIntercept || event.hashChange) return; - const url = new URL(event.destination.url), + const origin = document.location.origin, + url = new URL(event.destination.url, origin), ul = this.shadowRoot.querySelector('.crumbs')!; ul.replaceChildren(...this._makeCrumbs(url)); } diff --git a/src/presentation/components/DataTable.mts b/src/presentation/components/DataTable.mts index f88fdee1..dd5924f3 100644 --- a/src/presentation/components/DataTable.mts +++ b/src/presentation/components/DataTable.mts @@ -1,5 +1,5 @@ import type { Properties } from '~/types/Properties.mjs'; -import type { Entity } from '~/domain/Entity.mjs'; +import type Entity from '~/domain/Entity.mjs'; import html, { renderIf } from '../lib/html.mjs'; import { Component } from './Component.mjs'; import buttonTheme from '../theme/buttonTheme.mjs'; diff --git a/src/presentation/components/GlobalNav.mts b/src/presentation/components/GlobalNav.mts index 35d073d9..a2ada540 100644 --- a/src/presentation/components/GlobalNav.mts +++ b/src/presentation/components/GlobalNav.mts @@ -101,11 +101,12 @@ export class GlobalNav extends Component { if (!event.canIntercept || event.hashChange) return; - const url = new URL(event.destination.url), + const origin = document.location.origin, + url = new URL(event.destination.url, origin), as = this.shadowRoot.querySelectorAll('a'); as.forEach(a => { - if (isActive(new URL(a.href).pathname, url.pathname)) + if (isActive(new URL(a.href, origin).pathname, url.pathname)) a.classList.add('link-active'); else a.classList.remove('link-active'); diff --git a/src/presentation/components/PegsCards.mts b/src/presentation/components/PegsCards.mts index 0c75e285..fe8ebce6 100644 --- a/src/presentation/components/PegsCards.mts +++ b/src/presentation/components/PegsCards.mts @@ -1,5 +1,5 @@ -import { PEGS } from '~/domain/PEGS.mjs'; -import { Entity } from '~/domain/Entity.mjs'; +import PEGS from '~/domain/PEGS.mjs'; +import Entity from '~/domain/Entity.mjs'; import Repository from '~/usecases/Repository.mjs'; import { Container, PegsCard } from './index.mjs'; import html from '../lib/html.mjs'; diff --git a/src/presentation/pages/SlugPage.mts b/src/presentation/pages/SlugPage.mts index ec02edb9..6bd73cbc 100644 --- a/src/presentation/pages/SlugPage.mts +++ b/src/presentation/pages/SlugPage.mts @@ -9,7 +9,7 @@ const { p } = html; */ export abstract class SlugPage extends Page { // /parent/:slug/foo - #slug = new URL(location.href).pathname.split('/')[2]; + #slug = new URL(location.href, document.location.origin).pathname.split('/')[2]; constructor(properties: Properties, children: (string | Element)[]) { super(properties, children); diff --git a/src/presentation/pages/environments/Glossary.mts b/src/presentation/pages/environments/Glossary.mts index 7147a12d..fa8f0081 100644 --- a/src/presentation/pages/environments/Glossary.mts +++ b/src/presentation/pages/environments/Glossary.mts @@ -1,10 +1,10 @@ import html from '~/presentation/lib/html.mjs'; import { DataTable } from '~/presentation/components/DataTable.mjs'; import { SlugPage } from '../SlugPage.mjs'; -import { GlossaryTerm } from '~/domain/GlossaryTerm.mjs'; +import GlossaryTerm from '~/domain/GlossaryTerm.mjs'; import { EnvironmentRepository } from '~/data/EnvironmentRepository.mjs'; import { GlossaryRepository } from '~/data/GlossaryRepository.mjs'; -import type { Environment } from '~/domain/Environment.mjs'; +import type Environment from '~/domain/Environment.mjs'; const { p } = html; diff --git a/src/presentation/pages/environments/NewEnvironment.mts b/src/presentation/pages/environments/NewEnvironment.mts index 03ec00e5..b071067c 100644 --- a/src/presentation/pages/environments/NewEnvironment.mts +++ b/src/presentation/pages/environments/NewEnvironment.mts @@ -1,8 +1,9 @@ -import { Environment } from '~/domain/Environment.mjs'; +import Environment from '~/domain/Environment.mjs'; import { EnvironmentRepository } from '~/data/EnvironmentRepository.mjs'; import Page from '../Page.mjs'; import html from '~/presentation/lib/html.mjs'; -import { PEGS } from '~/domain/PEGS.mjs'; +import PEGS from '~/domain/PEGS.mjs'; +import formTheme from '~/presentation/theme/formTheme.mjs'; import requiredTheme from '~/presentation/theme/requiredTheme.mjs'; const { form, label, input, span, button } = html; @@ -53,6 +54,7 @@ export class NewEnvironment extends Page { return { ...super._initPageStyle(), ...requiredTheme, + ...formTheme, '.environment-form': { display: 'grid', gridTemplateColumns: '20% 1fr', diff --git a/src/presentation/pages/goals/Functionality.mts b/src/presentation/pages/goals/Functionality.mts index fc1b895e..6ef51043 100644 --- a/src/presentation/pages/goals/Functionality.mts +++ b/src/presentation/pages/goals/Functionality.mts @@ -1,5 +1,5 @@ -import { Behavior } from '~/domain/Behavior.mjs'; -import type { Goals } from '~/domain/Goals.mjs'; +import Behavior from '~/domain/Behavior.mjs'; +import type Goals from '~/domain/Goals.mjs'; import { GoalsRepository } from '~/data/GoalsRepository.mjs'; import { BehaviorRepository } from '~/data/BehaviorRepository.mjs'; import html from '~/presentation/lib/html.mjs'; diff --git a/src/presentation/pages/goals/NewGoals.mts b/src/presentation/pages/goals/NewGoals.mts index ab779c2a..abe09e25 100644 --- a/src/presentation/pages/goals/NewGoals.mts +++ b/src/presentation/pages/goals/NewGoals.mts @@ -1,8 +1,9 @@ -import { Goals } from '~/domain/Goals.mjs'; -import { PEGS } from '~/domain/PEGS.mjs'; +import Goals from '~/domain/Goals.mjs'; +import PEGS from '~/domain/PEGS.mjs'; import { GoalsRepository } from '~/data/GoalsRepository.mjs'; import html from '~/presentation/lib/html.mjs'; import requiredTheme from '~/presentation/theme/requiredTheme.mjs'; +import formTheme from '~/presentation/theme/formTheme.mjs'; import Page from '../Page.mjs'; const { form, label, input, span, button } = html; @@ -53,6 +54,7 @@ export class NewGoals extends Page { return { ...super._initPageStyle(), ...requiredTheme, + ...formTheme, '.goals-form': { display: 'grid', gridTemplateColumns: '20% 1fr', diff --git a/src/presentation/pages/goals/Rationale.mts b/src/presentation/pages/goals/Rationale.mts index 6fd4cd0f..deb0b508 100644 --- a/src/presentation/pages/goals/Rationale.mts +++ b/src/presentation/pages/goals/Rationale.mts @@ -1,4 +1,4 @@ -import { Goals } from '~/domain/Goals.mjs'; +import Goals from '~/domain/Goals.mjs'; import { GoalsRepository } from '~/data/GoalsRepository.mjs'; import html from '~/presentation/lib/html.mjs'; import { SlugPage } from '../SlugPage.mjs'; diff --git a/src/presentation/pages/goals/Stakeholders.mts b/src/presentation/pages/goals/Stakeholders.mts index 1478401a..aef4244c 100644 --- a/src/presentation/pages/goals/Stakeholders.mts +++ b/src/presentation/pages/goals/Stakeholders.mts @@ -1,5 +1,5 @@ -import type { Goals } from '~/domain/Goals.mjs'; -import { Stakeholder, StakeholderCategory, StakeholderSegmentation } from '~/domain/Stakeholder.mjs'; +import type Goals from '~/domain/Goals.mjs'; +import Stakeholder, { StakeholderCategory, StakeholderSegmentation } from '~/domain/Stakeholder.mjs'; import { GoalsRepository } from '~/data/GoalsRepository.mjs'; import { StakeholderRepository } from '~/data/StakeholderRepository.mjs'; import html from '~/presentation/lib/html.mjs'; diff --git a/src/usecases/Mapper.mts b/src/usecases/Mapper.mts index a3c76e60..97eeb3eb 100644 --- a/src/usecases/Mapper.mts +++ b/src/usecases/Mapper.mts @@ -1,4 +1,5 @@ -export interface Mapper { - mapFrom(from: From): To; - mapTo(to: To): From; +export default abstract class Mapper { + constructor(readonly serializationVersion: string) { } + abstract mapTo(source: Source): Target; + abstract mapFrom(target: Target): Source; } \ No newline at end of file diff --git a/src/usecases/Repository.mts b/src/usecases/Repository.mts index 864d3105..b4b74d2a 100644 --- a/src/usecases/Repository.mts +++ b/src/usecases/Repository.mts @@ -1,12 +1,10 @@ -import { type Entity } from '~/domain/Entity.mjs'; +import type Entity from '~/domain/Entity.mjs'; +import type Mapper from './Mapper.mjs'; export default abstract class Repository extends EventTarget { - readonly EntityConstructor; - - constructor(EntityConstructor: typeof Entity) { - super(); - this.EntityConstructor = EntityConstructor; - } + constructor( + readonly mapper: Mapper + ) { super(); } abstract getAll(): Promise; diff --git a/webpack.config.mjs b/webpack.config.mjs index 738f4a27..05155954 100644 --- a/webpack.config.mjs +++ b/webpack.config.mjs @@ -10,7 +10,7 @@ const __filename = url.fileURLToPath(import.meta.url), __dirname = path.dirname(__filename); export default { - mode: 'production', + mode: process.env.NODE_ENV, entry: { main: './src/main.mts' },