File size: 2,490 Bytes
aa89cb5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import type { EntitySchema } from '../sql/core';
import type { EntityChecker } from './checker';
import type { UserContext } from './context';

export type BootstrapEntity = Readonly<{
  entity: string;
  id: string;
  values?: Readonly<Record<string, unknown>>;
}>;

export type RuntimeBootstrap = Readonly<{
  defaultDomainRoot?: BootstrapEntity;
  constants?: readonly BootstrapEntity[];
  /** Generated typed Mutation program. Application workspaces cannot replace it. */
  ensure?: (context: UserContext) => Promise<void>;
}>;

export function mergeRuntimeBootstrap(
  left: RuntimeBootstrap,
  right: RuntimeBootstrap,
): RuntimeBootstrap {
  const leftRoot = left.defaultDomainRoot;
  const rightRoot = right.defaultDomainRoot;
  if (leftRoot && rightRoot &&
      (leftRoot.entity !== rightRoot.entity || leftRoot.id !== rightRoot.id)) {
    throw new Error('Cannot compose Runtime Modules with different Default Domain Roots');
  }
  const constants = new Map<string, BootstrapEntity>();
  for (const value of [...(left.constants ?? []), ...(right.constants ?? [])]) {
    constants.set(`${value.entity}:${value.id}`, value);
  }
  return {
    defaultDomainRoot: rightRoot ?? leftRoot,
    constants: [...constants.values()],
    ensure: left.ensure && right.ensure
      ? async context => { await left.ensure!(context); await right.ensure!(context); }
      : right.ensure ?? left.ensure,
  };
}

export interface RuntimeModuleTarget {
  install(module: RuntimeModule): RuntimeModuleTarget;
}

/** Passive, immutable metadata manifest. It never creates or alters database tables. */
export class RuntimeModule {
  readonly schemas: Readonly<Record<string, EntitySchema>>;
  readonly checkers: Readonly<Record<string, EntityChecker>>;
  readonly bootstrap: RuntimeBootstrap;

  constructor(
    schemas: Record<string, EntitySchema> = {},
    checkers: Record<string, EntityChecker> = {},
    bootstrap: RuntimeBootstrap = {},
  ) {
    this.schemas = Object.freeze({ ...schemas });
    this.checkers = Object.freeze({ ...checkers });
    this.bootstrap = Object.freeze({
      defaultDomainRoot: bootstrap.defaultDomainRoot,
      constants: Object.freeze([...(bootstrap.constants ?? [])]),
      ensure: bootstrap.ensure,
    });
  }

  and(other: RuntimeModule): RuntimeModule {
    return new RuntimeModule(
      { ...this.schemas, ...other.schemas },
      { ...this.checkers, ...other.checkers },
      mergeRuntimeBootstrap(this.bootstrap, other.bootstrap),
    );
  }
}