foobara-typescript-remote-command-generator 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|