foobara-typescript-remote-command-generator 0.0.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE-APACHE.txt +202 -0
- data/LICENSE-MIT.txt +21 -0
- data/LICENSE.txt +8 -0
- data/README.md +55 -0
- data/lib/foobara/typescript_remote_command_generator.rb +13 -0
- data/src/remote_generator/association_depth.rb +11 -0
- data/src/remote_generator/generate_typescript.rb +85 -0
- data/src/remote_generator/services/aggregate_entity_generator.rb +49 -0
- data/src/remote_generator/services/aggregate_model_generator.rb +53 -0
- data/src/remote_generator/services/atom_entity_generator.rb +47 -0
- data/src/remote_generator/services/atom_model_generator.rb +49 -0
- data/src/remote_generator/services/command_errors_generator.rb +36 -0
- data/src/remote_generator/services/command_errors_index_generator.rb +31 -0
- data/src/remote_generator/services/command_generator.rb +41 -0
- data/src/remote_generator/services/command_inputs_generator.rb +33 -0
- data/src/remote_generator/services/command_manifest_generator.rb +13 -0
- data/src/remote_generator/services/command_result_generator.rb +80 -0
- data/src/remote_generator/services/dependency_group.rb +137 -0
- data/src/remote_generator/services/domain_config_generator.rb +21 -0
- data/src/remote_generator/services/domain_generator.rb +63 -0
- data/src/remote_generator/services/domain_manifest_generator.rb +17 -0
- data/src/remote_generator/services/entity_generator.rb +43 -0
- data/src/remote_generator/services/entity_manifest_generator.rb +10 -0
- data/src/remote_generator/services/entity_variants_generator.rb +47 -0
- data/src/remote_generator/services/error_generator.rb +68 -0
- data/src/remote_generator/services/loaded_entity_generator.rb +27 -0
- data/src/remote_generator/services/manifest_generator.rb +13 -0
- data/src/remote_generator/services/model_generator.rb +84 -0
- data/src/remote_generator/services/model_manifest_generator.rb +11 -0
- data/src/remote_generator/services/model_variants_generator.rb +35 -0
- data/src/remote_generator/services/organization_config_generator.rb +21 -0
- data/src/remote_generator/services/organization_generator.rb +41 -0
- data/src/remote_generator/services/organization_manifest_generator.rb +17 -0
- data/src/remote_generator/services/processor_class_generator.rb +18 -0
- data/src/remote_generator/services/root_manifest_generator.rb +13 -0
- data/src/remote_generator/services/typescript_from_manifest_base_generator.rb +293 -0
- data/src/remote_generator/services/unloaded_entity_generator.rb +25 -0
- data/src/remote_generator/write_typescript_to_disk.rb +78 -0
- data/src/remote_generator.rb +5 -0
- data/templates/Command/Errors.ts.erb +11 -0
- data/templates/Command/Inputs.ts.erb +5 -0
- data/templates/Command/Result.ts.erb +7 -0
- data/templates/Command/errors/index.ts.erb +3 -0
- data/templates/Command.ts.erb +11 -0
- data/templates/Domain/config.ts.erb +16 -0
- data/templates/Domain.ts.erb +17 -0
- data/templates/Entity/Aggregate.ts.erb +18 -0
- data/templates/Entity/Ambiguous.ts.erb +30 -0
- data/templates/Entity/Atom.ts.erb +18 -0
- data/templates/Entity/Loaded.ts.erb +14 -0
- data/templates/Entity/Unloaded.ts.erb +18 -0
- data/templates/EntityVariants.ts.erb +40 -0
- data/templates/Error.ts.erb +8 -0
- data/templates/Model/Aggregate.ts.erb +12 -0
- data/templates/Model/Atom.ts.erb +12 -0
- data/templates/Model/Model.ts.erb +18 -0
- data/templates/ModelVariants.ts.erb +26 -0
- data/templates/Organization/config.ts.erb +16 -0
- data/templates/Organization.ts.erb +8 -0
- data/templates/base/DataPath.ts +49 -0
- data/templates/base/Entity.ts +87 -0
- data/templates/base/Error.ts +29 -0
- data/templates/base/Model.ts +24 -0
- data/templates/base/Outcome.ts +42 -0
- data/templates/base/RemoteCommand.ts +87 -0
- metadata +142 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
import RemoteCommand from "<%= path_to_root %>base/RemoteCommand"
|
2
|
+
|
3
|
+
import Inputs from "./Inputs"
|
4
|
+
import Result from "./Result"
|
5
|
+
import { Error } from "./Errors"
|
6
|
+
|
7
|
+
export class <%= command_name %> extends RemoteCommand<Inputs, Result, Error> {
|
8
|
+
static readonly organizationName = "<%= organization_name %>"
|
9
|
+
static readonly domainName = "<%= domain_name %>"
|
10
|
+
static readonly commandName = "<%= command_name %>"
|
11
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
let _urlBase = process.env.REACT_APP_FOOBARA_GLOBAL_URL_BASE
|
2
|
+
|
3
|
+
const config = {
|
4
|
+
get urlBase(): string {
|
5
|
+
if (_urlBase === undefined) {
|
6
|
+
throw new Error("urlBase is not set and REACT_APP_FOOBARA_GLOBAL_URL_BASE is undefined")
|
7
|
+
}
|
8
|
+
|
9
|
+
return _urlBase
|
10
|
+
},
|
11
|
+
set urlBase(urlBase: string) {
|
12
|
+
_urlBase = urlBase
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
export default config
|
@@ -0,0 +1,17 @@
|
|
1
|
+
export * as config from "./config"
|
2
|
+
|
3
|
+
export const isGlobal = <%= global? %>
|
4
|
+
export const organizationName = "<%= organization_name %>"
|
5
|
+
export const domainName = "<%= domain_name %>"
|
6
|
+
|
7
|
+
<% command_generators.each do |command| %>
|
8
|
+
export <%= command.import_destructure %> from "<%= path_to_root %><%= command.import_path %>"
|
9
|
+
<% if command.command_errors_index_generator.applicable? %>
|
10
|
+
export <%= command.command_errors_index_generator.import_destructure %> from "<%= path_to_root %><%= command.command_errors_index_generator.import_path %>"
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
// TODO: put these on an entities module so that commands can be the only top-level interface.
|
15
|
+
<% entity_generators.each do |entity| %>
|
16
|
+
export <%= entity.import_destructure %> from "<%= path_to_root %><%= entity.import_path %>"
|
17
|
+
<% end %>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import {
|
2
|
+
<%= entity_name %>AttributesType
|
3
|
+
} from "./Ambiguous"
|
4
|
+
import { Loaded<%= entity_name %> } from "./Loaded"
|
5
|
+
<% dependency_roots.each do |dependency_root| %>
|
6
|
+
import { <%= dependency_root.ts_instance_name %> } from "<%= path_to_root %><%= dependency_root.import_path %>"
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
export interface <%= entity_name %>AggregateAttributesType extends <%= entity_name %>AttributesType <%= attributes_type_ts_type %>
|
10
|
+
|
11
|
+
export class <%= entity_name %>Aggregate extends Loaded<%= entity_name %><<%= entity_name %>AggregateAttributesType> {
|
12
|
+
<% if has_associations? %>
|
13
|
+
/* eslint-disable @typescript-eslint/class-literal-property-style */
|
14
|
+
get isAtom (): false { return false }
|
15
|
+
get isAggregate (): true { return true }
|
16
|
+
/* eslint-enable @typescript-eslint/class-literal-property-style */
|
17
|
+
<% end %>
|
18
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { Entity } from "<%= path_to_root %>base/Entity"
|
2
|
+
<% dependency_roots.each do |dependency_root| %>
|
3
|
+
import { <%= dependency_root.scoped_name %> } from "<%= path_to_root %><%= dependency_root.import_path %>"
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
export type <%= entity_name %>PrimaryKeyType = <%= primary_key_ts_type %>
|
7
|
+
export const <%= entity_name_downcase %>PrimaryKeyAttributeName: "<%= primary_key_name %>" = "<%= primary_key_name %>"
|
8
|
+
export interface <%= entity_name %>AttributesType <%= attributes_type_ts_type %>
|
9
|
+
|
10
|
+
export class <%= entity_name %><
|
11
|
+
AttributesType extends <%= entity_name %>AttributesType = <%= entity_name %>AttributesType
|
12
|
+
> extends Entity<<%= entity_name %>PrimaryKeyType, AttributesType> {
|
13
|
+
static readonly modelName: string = "<%= entity_name %>"
|
14
|
+
static readonly entityName: string = "<%= entity_name %>"
|
15
|
+
static readonly primaryKeyAttributeName: "<%= primary_key_name %>" = "<%= primary_key_name %>"
|
16
|
+
|
17
|
+
get <%= primary_key_name %> (): <%= entity_name %>PrimaryKeyType {
|
18
|
+
return this.primaryKey
|
19
|
+
}
|
20
|
+
|
21
|
+
get associationPropertyPaths (): string[][] { return <%= association_property_paths_ts %> }
|
22
|
+
readonly hasAssociations: <%= has_associations? %> = <%= has_associations? %>
|
23
|
+
|
24
|
+
<% attribute_names.each do |attribute_name| %>
|
25
|
+
get <%= attribute_name %> (): AttributesType["<%= attribute_name %>"] {
|
26
|
+
return this.readAttribute("<%= attribute_name %>")
|
27
|
+
}
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import {
|
2
|
+
<%= entity_name %>AttributesType
|
3
|
+
} from "./Ambiguous"
|
4
|
+
import { Loaded<%= entity_name %> } from "./Loaded"
|
5
|
+
<% dependency_roots.each do |dependency_root| %>
|
6
|
+
import { <%= dependency_root.ts_instance_name %> } from "<%= path_to_root %><%= dependency_root.import_path %>"
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
export interface <%= entity_name %>AtomAttributesType extends <%= entity_name %>AttributesType <%= atom_attributes_ts_type %>
|
10
|
+
|
11
|
+
export class <%= entity_name %>Atom extends Loaded<%= entity_name %><<%= entity_name %>AtomAttributesType> {
|
12
|
+
<% if has_associations? %>
|
13
|
+
/* eslint-disable @typescript-eslint/class-literal-property-style */
|
14
|
+
get isAtom (): true { return true }
|
15
|
+
get isAggregate (): false { return false }
|
16
|
+
/* eslint-enable @typescript-eslint/class-literal-property-style */
|
17
|
+
<% end %>
|
18
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import {
|
2
|
+
<%= entity_name %>,
|
3
|
+
<%= entity_name %>AttributesType
|
4
|
+
} from "./Ambiguous"
|
5
|
+
|
6
|
+
export class Loaded<%= entity_name %><T extends <%= entity_name %>AttributesType = <%= entity_name %>AttributesType> extends <%= entity_name %><T> {
|
7
|
+
readonly isLoaded: true = true
|
8
|
+
<% unless has_associations? %>
|
9
|
+
/* eslint-disable @typescript-eslint/class-literal-property-style */
|
10
|
+
get isAtom (): true { return true }
|
11
|
+
get isAggregate (): true { return true }
|
12
|
+
/* eslint-enable @typescript-eslint/class-literal-property-style */
|
13
|
+
<% end %>
|
14
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { Never } from "<%= path_to_root %>base/Entity"
|
2
|
+
import {
|
3
|
+
<%= entity_name %>,
|
4
|
+
//<%= entity_name %>PrimaryKeyType
|
5
|
+
<%= entity_name %>AttributesType
|
6
|
+
} from "./Ambiguous"
|
7
|
+
|
8
|
+
export type Unloaded<%= entity_name %>AttributesType = Never<<%= entity_name %>AttributesType>
|
9
|
+
|
10
|
+
export class Unloaded<%= entity_name %> extends <%= entity_name %><Unloaded<%= entity_name %>AttributesType> {
|
11
|
+
/*
|
12
|
+
constructor(id: <%= entity_name %>PrimaryKeyType) {
|
13
|
+
super(id, {})
|
14
|
+
}
|
15
|
+
*/
|
16
|
+
|
17
|
+
readonly isLoaded: false = false
|
18
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import {
|
2
|
+
<%= entity_name %>,
|
3
|
+
<%= entity_name %>PrimaryKeyType,
|
4
|
+
<%= entity_name_downcase %>PrimaryKeyAttributeName,
|
5
|
+
<%= entity_name %>AttributesType
|
6
|
+
} from "./<%= entity_name %>/Ambiguous"
|
7
|
+
import {
|
8
|
+
Unloaded<%= entity_name %>,
|
9
|
+
Unloaded<%= entity_name %>AttributesType
|
10
|
+
} from "./<%= entity_name %>/Unloaded"
|
11
|
+
import {
|
12
|
+
Loaded<%= entity_name %>
|
13
|
+
} from "./<%= entity_name %>/Loaded"
|
14
|
+
|
15
|
+
<% if has_associations? %>
|
16
|
+
import {
|
17
|
+
<%= entity_name %>Atom,
|
18
|
+
<%= entity_name %>AtomAttributesType
|
19
|
+
} from "./<%= entity_name %>/Atom"
|
20
|
+
import {
|
21
|
+
<%= entity_name %>Aggregate,
|
22
|
+
<%= entity_name %>AggregateAttributesType
|
23
|
+
} from "./<%= entity_name %>/Aggregate"
|
24
|
+
<% end %>
|
25
|
+
|
26
|
+
export {
|
27
|
+
<%= entity_name %>,
|
28
|
+
type <%= entity_name %>AttributesType,
|
29
|
+
Unloaded<%= entity_name %>,
|
30
|
+
type Unloaded<%= entity_name %>AttributesType,
|
31
|
+
Loaded<%= entity_name %>,
|
32
|
+
<% if has_associations? %>
|
33
|
+
<%= entity_name %>Atom,
|
34
|
+
type <%= entity_name %>AtomAttributesType,
|
35
|
+
<%= entity_name %>Aggregate,
|
36
|
+
type <%= entity_name %>AggregateAttributesType,
|
37
|
+
<% end %>
|
38
|
+
type <%= entity_name %>PrimaryKeyType,
|
39
|
+
<%= entity_name_downcase %>PrimaryKeyAttributeName
|
40
|
+
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<% dependency_roots.each do |dependency_root| %>
|
2
|
+
import { <%= dependency_root.scoped_name %> } from "<%= path_to_root %><%= dependency_root.import_path %>"
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
import { <%= error_base_class %> } from "<%= path_to_root %>base/Error"
|
6
|
+
|
7
|
+
export class <%= error_name %> extends <%= error_base_class %><<%= context_ts_type %>> {
|
8
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import {
|
2
|
+
<%= model_name %>,
|
3
|
+
<%= model_name %>AttributesType
|
4
|
+
} from "./<%= model_name %>"
|
5
|
+
<% dependency_roots.each do |dependency_root| %>
|
6
|
+
import { <%= dependency_root.ts_instance_name %> } from "<%= path_to_root %><%= dependency_root.import_path %>"
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
export interface <%= model_name %>AggregateAttributesType extends <%= model_name %>AttributesType <%= attributes_type_ts_type %>
|
10
|
+
|
11
|
+
export class <%= model_name %>Aggregate extends <%= model_name %><<%= model_name %>AggregateAttributesType> {
|
12
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import {
|
2
|
+
<%= model_name %>,
|
3
|
+
<%= model_name %>AttributesType
|
4
|
+
} from "./<%= model_name %>"
|
5
|
+
<% dependency_roots.each do |dependency_root| %>
|
6
|
+
import { <%= dependency_root.ts_instance_name %> } from "<%= path_to_root %><%= dependency_root.import_path %>"
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
export interface <%= model_name %>AtomAttributesType extends <%= model_name %>AttributesType <%= atom_attributes_ts_type %>
|
10
|
+
|
11
|
+
export class <%= model_name %>Atom extends <%= model_name %><<%= model_name %>AtomAttributesType> {
|
12
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { Model } from "<%= path_to_root %>base/Model"
|
2
|
+
<% dependency_roots.each do |dependency_root| %>
|
3
|
+
import { <%= dependency_root.scoped_name %> } from "<%= path_to_root %><%= dependency_root.import_path %>"
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
export interface <%= model_name %>AttributesType <%= attributes_type_ts_type %>
|
7
|
+
|
8
|
+
export class <%= model_name %><
|
9
|
+
AttributesType extends <%= model_name %>AttributesType = <%= model_name %>AttributesType
|
10
|
+
> extends Model<AttributesType> {
|
11
|
+
static readonly modelName: string = "<%= model_name %>"
|
12
|
+
|
13
|
+
<% attribute_names.each do |attribute_name| %>
|
14
|
+
get <%= attribute_name %> (): AttributesType["<%= attribute_name %>"] {
|
15
|
+
return this.readAttribute("<%= attribute_name %>")
|
16
|
+
}
|
17
|
+
<% end %>
|
18
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import {
|
2
|
+
<%= model_name %>,
|
3
|
+
<%= model_name %>AttributesType
|
4
|
+
} from "./<%= model_name %>/<%= model_name %>"
|
5
|
+
|
6
|
+
<% if has_associations? %>
|
7
|
+
import {
|
8
|
+
<%= model_name %>Atom,
|
9
|
+
<%= model_name %>AtomAttributesType
|
10
|
+
} from "./<%= model_name %>/Atom"
|
11
|
+
import {
|
12
|
+
<%= model_name %>Aggregate,
|
13
|
+
<%= model_name %>AggregateAttributesType
|
14
|
+
} from "./<%= model_name %>/Aggregate"
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
export {
|
18
|
+
<%= model_name %>,
|
19
|
+
type <%= model_name %>AttributesType,
|
20
|
+
<% if has_associations? %>
|
21
|
+
<%= model_name %>Atom,
|
22
|
+
type <%= model_name %>AtomAttributesType,
|
23
|
+
<%= model_name %>Aggregate,
|
24
|
+
type <%= model_name %>AggregateAttributesType,
|
25
|
+
<% end %>
|
26
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
let _urlBase = process.env.REACT_APP_FOOBARA_GLOBAL_URL_BASE
|
2
|
+
|
3
|
+
const config = {
|
4
|
+
get urlBase(): string {
|
5
|
+
if (_urlBase === undefined) {
|
6
|
+
throw new Error("urlBase is not set and REACT_APP_FOOBARA_GLOBAL_URL_BASE is undefined")
|
7
|
+
}
|
8
|
+
|
9
|
+
return _urlBase
|
10
|
+
},
|
11
|
+
set urlBase(urlBase: string) {
|
12
|
+
_urlBase = urlBase
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
export default config
|
@@ -0,0 +1,8 @@
|
|
1
|
+
export * as config from "./config"
|
2
|
+
|
3
|
+
<% domain_generators.each do |domain| %>
|
4
|
+
export <%= domain.import_destructure %> from "<%= path_to_root %><%= domain.import_path %>"
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
export const isGlobal = <%= global? %>
|
8
|
+
export const organizationName = "<%= organization_name %>"
|
@@ -0,0 +1,49 @@
|
|
1
|
+
function flatten (array: any[][]): any[] {
|
2
|
+
return array.reduce((acc, val) => acc.concat(val), [])
|
3
|
+
}
|
4
|
+
|
5
|
+
function uniq<T> (array: T[]): T[] {
|
6
|
+
return Array.from(new Set(array));
|
7
|
+
}
|
8
|
+
|
9
|
+
function compact (array: any[]): any[] {
|
10
|
+
return array.filter(item => item !== null && item !== undefined)
|
11
|
+
}
|
12
|
+
|
13
|
+
function _valuesAt<T extends (Record<string, any> | any[])> (objects: T[], path: Array<string | number>): any[] {
|
14
|
+
if (path.length === 0) return objects
|
15
|
+
|
16
|
+
const [pathPart, ...remainingParts] = path
|
17
|
+
|
18
|
+
let newObjects: any[]
|
19
|
+
|
20
|
+
if (pathPart === '#') {
|
21
|
+
const flat = flatten(objects as any[][])
|
22
|
+
newObjects = uniq(flat)
|
23
|
+
} else if (typeof pathPart === 'number') {
|
24
|
+
newObjects = compact(objects.map((object: T) => {
|
25
|
+
if (Array.isArray(object)) {
|
26
|
+
return object[pathPart]
|
27
|
+
} else {
|
28
|
+
throw new Error(`Cannot access index ${pathPart} of object because it's not an array`)
|
29
|
+
}
|
30
|
+
}))
|
31
|
+
} else if (typeof pathPart === 'string') {
|
32
|
+
newObjects = compact(objects.map((object: T) => {
|
33
|
+
if (typeof object === 'object' && object !== null) {
|
34
|
+
const record: Record<string, any> = object
|
35
|
+
return record[pathPart]
|
36
|
+
} else {
|
37
|
+
throw new Error(`Bad object and part: ${pathPart}`)
|
38
|
+
}
|
39
|
+
}))
|
40
|
+
} else {
|
41
|
+
throw new Error(`Bad path part: ${typeof pathPart}`)
|
42
|
+
}
|
43
|
+
|
44
|
+
return _valuesAt(newObjects, remainingParts)
|
45
|
+
}
|
46
|
+
|
47
|
+
export function valuesAt<T extends Record<string, any> | any[]> (object: T, path: string[]): any[] {
|
48
|
+
return _valuesAt([object], path)
|
49
|
+
}
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import { Model } from './Model';
|
2
|
+
import { valuesAt } from './DataPath';
|
3
|
+
|
4
|
+
export type Never<T> = {[P in keyof T]: never};
|
5
|
+
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
7
|
+
export type EntityPrimaryKeyType = number | string
|
8
|
+
|
9
|
+
export abstract class Entity<PrimaryKeyType extends EntityPrimaryKeyType, AttributesType>
|
10
|
+
extends Model<AttributesType> {
|
11
|
+
static readonly entityName: string
|
12
|
+
static readonly primaryKeyAttributeName: string
|
13
|
+
|
14
|
+
readonly primaryKey: PrimaryKeyType
|
15
|
+
readonly isLoaded: boolean
|
16
|
+
|
17
|
+
abstract get hasAssociations(): boolean
|
18
|
+
abstract get associationPropertyPaths (): string[][]
|
19
|
+
|
20
|
+
constructor(primaryKey: PrimaryKeyType, attributes: AttributesType) {
|
21
|
+
super(attributes)
|
22
|
+
this.primaryKey = primaryKey
|
23
|
+
this.isLoaded = attributes !== undefined
|
24
|
+
}
|
25
|
+
|
26
|
+
/* Can we make this work or not?
|
27
|
+
getConstructor(): EntityConstructor<PrimaryKeyType, AttributesType> {
|
28
|
+
return this.constructor as EntityConstructor<PrimaryKeyType, AttributesType>;
|
29
|
+
}
|
30
|
+
*/
|
31
|
+
entitiesAt(path: string[]): Entity<EntityPrimaryKeyType, Record<string, any>>[] {
|
32
|
+
return valuesAt(this, path).filter(item => item !== undefined) as Entity<EntityPrimaryKeyType, Record<string, any>>[]
|
33
|
+
}
|
34
|
+
|
35
|
+
get isAtom(): boolean {
|
36
|
+
if (!this.isLoaded) {
|
37
|
+
throw new Error("Record is not loaded so can't check if it's an atom")
|
38
|
+
}
|
39
|
+
|
40
|
+
if (!this.hasAssociations) {
|
41
|
+
return true
|
42
|
+
}
|
43
|
+
|
44
|
+
for (const path of this.associationPropertyPaths) {
|
45
|
+
for (const record of this.entitiesAt(path)) {
|
46
|
+
if (record.isLoaded) {
|
47
|
+
return false
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
return true
|
53
|
+
}
|
54
|
+
|
55
|
+
get isAggregate(): boolean {
|
56
|
+
if (!this.isLoaded) {
|
57
|
+
throw new Error("Record is not loaded so can't check if it's an aggregate")
|
58
|
+
}
|
59
|
+
|
60
|
+
if (!this.hasAssociations) {
|
61
|
+
return true
|
62
|
+
}
|
63
|
+
|
64
|
+
for (const path of this.associationPropertyPaths) {
|
65
|
+
for (const record of this.entitiesAt(path)) {
|
66
|
+
if (!record.isLoaded) {
|
67
|
+
return false
|
68
|
+
}
|
69
|
+
|
70
|
+
if (!record.isAggregate) {
|
71
|
+
return false
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
return true
|
77
|
+
}
|
78
|
+
get attributes(): AttributesType {
|
79
|
+
if (!this.isLoaded) {
|
80
|
+
throw new Error(
|
81
|
+
`Cannot read attributes because :${this.primaryKey} is not a loaded record`
|
82
|
+
)
|
83
|
+
}
|
84
|
+
|
85
|
+
return this._attributes
|
86
|
+
}
|
87
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
2
|
+
|
3
|
+
export abstract class FoobaraError<contextT = any> {
|
4
|
+
static readonly symbol: string
|
5
|
+
static readonly category: 'data' | 'runtime'
|
6
|
+
|
7
|
+
readonly key: string
|
8
|
+
readonly path: string[]
|
9
|
+
readonly runtime_path: string[]
|
10
|
+
readonly message: string
|
11
|
+
readonly context: contextT
|
12
|
+
|
13
|
+
constructor ({ key, path, runtime_path, message, context }:
|
14
|
+
{ key: string, path?: string[], runtime_path?: string[], message: string, context: contextT }) {
|
15
|
+
this.key = key
|
16
|
+
this.path = path ?? []
|
17
|
+
this.runtime_path = runtime_path ?? []
|
18
|
+
this.message = message
|
19
|
+
this.context = context
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
export class DataError<contextT extends Record<string, any>> extends FoobaraError<contextT> {
|
24
|
+
static readonly category: 'data' = 'data'
|
25
|
+
}
|
26
|
+
|
27
|
+
export class RuntimeError<contextT extends Record<string, any>> extends FoobaraError<contextT> {
|
28
|
+
static readonly category: 'runtime' = 'runtime'
|
29
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
export abstract class Model<AttributesType> {
|
2
|
+
static readonly modelName: string
|
3
|
+
readonly _attributes: AttributesType
|
4
|
+
|
5
|
+
|
6
|
+
constructor(attributes: AttributesType) {
|
7
|
+
this._attributes = attributes
|
8
|
+
}
|
9
|
+
|
10
|
+
|
11
|
+
/* Can we make this work or not?
|
12
|
+
getConstructor(): EntityConstructor<PrimaryKeyType, AttributesType> {
|
13
|
+
return this.constructor as EntityConstructor<PrimaryKeyType, AttributesType>;
|
14
|
+
}
|
15
|
+
*/
|
16
|
+
|
17
|
+
get attributes(): AttributesType {
|
18
|
+
return this._attributes
|
19
|
+
}
|
20
|
+
|
21
|
+
readAttribute<T extends keyof this["_attributes"]>(attributeName: T): this["_attributes"][T] {
|
22
|
+
return (this.attributes as unknown as this["_attributes"])[attributeName]
|
23
|
+
}
|
24
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import { type FoobaraError } from './Error'
|
2
|
+
|
3
|
+
export class Outcome<Result, OutcomeError extends FoobaraError> {
|
4
|
+
readonly result?: Result
|
5
|
+
readonly errors: OutcomeError[] = []
|
6
|
+
readonly _isSuccess: boolean
|
7
|
+
|
8
|
+
constructor (result?: Result, errors?: OutcomeError[]) {
|
9
|
+
this.result = result
|
10
|
+
if (errors != null) {
|
11
|
+
this.errors = errors
|
12
|
+
}
|
13
|
+
this._isSuccess = errors == null || errors.length === 0
|
14
|
+
}
|
15
|
+
|
16
|
+
isSuccess (): this is SuccessfulOutcome<Result, OutcomeError> {
|
17
|
+
return this._isSuccess
|
18
|
+
}
|
19
|
+
|
20
|
+
get errorMessage (): string {
|
21
|
+
return this.errors.map(e => e.message).join(', ')
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
export class SuccessfulOutcome<Result, OutcomeError extends FoobaraError> extends Outcome<Result, OutcomeError> {
|
26
|
+
readonly _isSuccess: true = true
|
27
|
+
readonly errors: OutcomeError[] = []
|
28
|
+
readonly result: Result
|
29
|
+
|
30
|
+
constructor (result: Result) {
|
31
|
+
super(result, [])
|
32
|
+
this.result = result
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
export class ErrorOutcome<Result, OutcomeError extends FoobaraError> extends Outcome<Result, OutcomeError> {
|
37
|
+
readonly _isSuccess: false = false
|
38
|
+
|
39
|
+
constructor (errors: OutcomeError[]) {
|
40
|
+
super(undefined, errors)
|
41
|
+
}
|
42
|
+
}
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import { type Outcome, SuccessfulOutcome, ErrorOutcome } from './Outcome'
|
2
|
+
import { type FoobaraError } from './Error'
|
3
|
+
|
4
|
+
export default abstract class RemoteCommand<Inputs, Result, CommandError extends FoobaraError> {
|
5
|
+
static _urlBase: string | undefined
|
6
|
+
static commandName: string
|
7
|
+
static organizationName: string
|
8
|
+
static domainName: string
|
9
|
+
|
10
|
+
// TODO: make use of domain's config instead of process.env directly.
|
11
|
+
static get urlBase (): string {
|
12
|
+
let base = this._urlBase
|
13
|
+
|
14
|
+
if (base == null) {
|
15
|
+
base = process.env.REACT_APP_FOOBARA_GLOBAL_URL_BASE
|
16
|
+
}
|
17
|
+
|
18
|
+
if (base == null) {
|
19
|
+
throw new Error("urlBase is not set and REACT_APP_FOOBARA_GLOBAL_URL_BASE is undefined")
|
20
|
+
}
|
21
|
+
|
22
|
+
return base
|
23
|
+
}
|
24
|
+
|
25
|
+
static set urlBase (urlBase: string) {
|
26
|
+
this._urlBase = urlBase
|
27
|
+
}
|
28
|
+
|
29
|
+
get organizationName (): string {
|
30
|
+
return (this.constructor as typeof RemoteCommand<Inputs, Result, CommandError>).organizationName
|
31
|
+
}
|
32
|
+
|
33
|
+
get domainName (): string {
|
34
|
+
return (this.constructor as typeof RemoteCommand<Inputs, Result, CommandError>).domainName
|
35
|
+
}
|
36
|
+
|
37
|
+
inputs: Inputs
|
38
|
+
|
39
|
+
constructor (inputs: Inputs) {
|
40
|
+
this.inputs = inputs
|
41
|
+
}
|
42
|
+
|
43
|
+
get commandName (): string {
|
44
|
+
return (this.constructor as typeof RemoteCommand<Inputs, Result, CommandError>).commandName
|
45
|
+
}
|
46
|
+
|
47
|
+
get urlBase (): string {
|
48
|
+
return (this.constructor as typeof RemoteCommand<Inputs, Result, CommandError>).urlBase
|
49
|
+
}
|
50
|
+
|
51
|
+
static get fullCommandName (): string {
|
52
|
+
const path = []
|
53
|
+
|
54
|
+
if (this.organizationName != null && this.organizationName !== "GlobalOrganization") {
|
55
|
+
path.push(this.organizationName)
|
56
|
+
}
|
57
|
+
if (this.domainName != null && this.domainName !== "GlobalDomain") {
|
58
|
+
path.push(this.domainName)
|
59
|
+
}
|
60
|
+
if (this.commandName != null) {
|
61
|
+
path.push(this.commandName)
|
62
|
+
}
|
63
|
+
return path.join('::')
|
64
|
+
}
|
65
|
+
|
66
|
+
get fullCommandName (): string {
|
67
|
+
return (this.constructor as typeof RemoteCommand<Inputs, Result, CommandError>).fullCommandName
|
68
|
+
}
|
69
|
+
|
70
|
+
async run (): Promise<Outcome<Result, CommandError>> {
|
71
|
+
const url = `${this.urlBase}/run/${this.fullCommandName}`
|
72
|
+
|
73
|
+
const response = await fetch(url, {
|
74
|
+
method: 'POST',
|
75
|
+
headers: { 'Content-Type': 'application/json' },
|
76
|
+
body: JSON.stringify(this.inputs)
|
77
|
+
})
|
78
|
+
|
79
|
+
if (response.ok) {
|
80
|
+
return new SuccessfulOutcome<Result, CommandError>(await response.json())
|
81
|
+
} else if (response.status === 422) {
|
82
|
+
return new ErrorOutcome<Result, CommandError>(await response.json())
|
83
|
+
} else {
|
84
|
+
throw new Error(`not sure how to handle ${await response.text()}`)
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|