jinaga-server 1.1.4 → 2.0.0

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.
Files changed (84) hide show
  1. package/README.md +12 -0
  2. package/dist/authentication/authentication-device.d.ts +3 -2
  3. package/dist/authentication/authentication-device.js +6 -0
  4. package/dist/authentication/authentication-device.js.map +1 -1
  5. package/dist/authentication/authentication-impl.d.ts +3 -2
  6. package/dist/authentication/authentication-impl.js +6 -0
  7. package/dist/authentication/authentication-impl.js.map +1 -1
  8. package/dist/authentication/authentication-noop.d.ts +20 -0
  9. package/dist/authentication/authentication-noop.js +52 -0
  10. package/dist/authentication/authentication-noop.js.map +1 -0
  11. package/dist/authentication/authentication-session.d.ts +3 -2
  12. package/dist/authentication/authentication-session.js +6 -0
  13. package/dist/authentication/authentication-session.js.map +1 -1
  14. package/dist/authentication.d.ts +3 -2
  15. package/dist/authentication.js +6 -0
  16. package/dist/authentication.js.map +1 -1
  17. package/dist/authorization/authorization-keystore.d.ts +3 -4
  18. package/dist/authorization/authorization-keystore.js +9 -4
  19. package/dist/authorization/authorization-keystore.js.map +1 -1
  20. package/dist/debug.d.ts +1 -0
  21. package/dist/debug.js +30 -0
  22. package/dist/debug.js.map +1 -0
  23. package/dist/http/router.js.map +1 -1
  24. package/dist/jinaga-server.js +15 -7
  25. package/dist/jinaga-server.js.map +1 -1
  26. package/dist/memory/memory-keystore.js +0 -6
  27. package/dist/memory/memory-keystore.js.map +1 -1
  28. package/dist/postgres/connection.js +1 -0
  29. package/dist/postgres/connection.js.map +1 -1
  30. package/dist/postgres/maps.d.ts +7 -6
  31. package/dist/postgres/maps.js +9 -1
  32. package/dist/postgres/maps.js.map +1 -1
  33. package/dist/postgres/postgres-keystore.js +0 -6
  34. package/dist/postgres/postgres-keystore.js.map +1 -1
  35. package/dist/postgres/postgres-store.d.ts +1 -2
  36. package/dist/postgres/postgres-store.js +29 -24
  37. package/dist/postgres/postgres-store.js.map +1 -1
  38. package/dist/postgres/specification-result-sql.d.ts +4 -4
  39. package/dist/postgres/specification-result-sql.js +34 -24
  40. package/dist/postgres/specification-result-sql.js.map +1 -1
  41. package/dist/postgres/specification-sql.js.map +1 -1
  42. package/dist/postgres/sql.d.ts +1 -1
  43. package/dist/postgres/sql.js +15 -7
  44. package/dist/postgres/sql.js.map +1 -1
  45. package/dist/util/maps.d.ts +0 -0
  46. package/dist/util/maps.js +2 -0
  47. package/dist/util/maps.js.map +1 -0
  48. package/docs/sql/SQL Generator (projection).png +0 -0
  49. package/docs/sql/SQLFromSpecification.md +2 -0
  50. package/integration-test/jinaga-test/Dockerfile +2 -2
  51. package/integration-test/jinaga-test/jinaga.test.js +3 -3
  52. package/integration-test/jinaga-test/package-lock.json +745 -3484
  53. package/integration-test/jinaga-test/package.json +2 -0
  54. package/integration-test/run.sh +3 -3
  55. package/package.json +5 -6
  56. package/replicator/index.ts +31 -0
  57. package/replicator/package-lock.json +855 -0
  58. package/replicator/package.json +27 -0
  59. package/replicator/tsconfig.json +15 -0
  60. package/src/authentication/authentication-device.ts +11 -1
  61. package/src/authentication/authentication-impl.ts +9 -2
  62. package/src/authentication/authentication-noop.ts +65 -0
  63. package/src/authentication/authentication-session.ts +11 -1
  64. package/src/authentication.ts +12 -2
  65. package/src/authorization/authorization-keystore.ts +12 -7
  66. package/src/debug.ts +30 -0
  67. package/src/http/router.ts +2 -2
  68. package/src/jinaga-server.ts +18 -8
  69. package/src/memory/memory-keystore.ts +0 -6
  70. package/src/postgres/connection.ts +2 -1
  71. package/src/postgres/maps.ts +8 -0
  72. package/src/postgres/postgres-keystore.ts +2 -8
  73. package/src/postgres/postgres-store.ts +37 -30
  74. package/src/postgres/specification-result-sql.ts +56 -45
  75. package/src/postgres/specification-sql.ts +1 -1
  76. package/src/postgres/sql.ts +23 -12
  77. package/src/util/maps.ts +0 -0
  78. package/test/postgres/postgresReadSpec.ts +4 -1
  79. package/test/postgres/postgresSpec.ts +31 -31
  80. package/test/postgres/postgresSpecificationSpec.ts +1 -1
  81. package/tsconfig.json +1 -0
  82. package/tsconfig.test.json +26 -0
  83. package/docs/specification.txt +0 -41
  84. package/interactive/index.js +0 -56
@@ -3,6 +3,7 @@ import {
3
3
  Direction,
4
4
  ExistentialCondition,
5
5
  FactEnvelope,
6
+ FactFeed,
6
7
  FactPath,
7
8
  FactRecord,
8
9
  FactReference,
@@ -20,9 +21,8 @@ import {
20
21
  Specification,
21
22
  Step,
22
23
  Storage,
23
- TopologicalSorter,
24
+ TopologicalSorter
24
25
  } from "jinaga";
25
- import { FactFeed } from "jinaga/dist/storage";
26
26
  import { PoolClient } from "pg";
27
27
 
28
28
  import { distinct, flatten } from "../util/fn";
@@ -37,6 +37,7 @@ import {
37
37
  emptyFactTypeMap,
38
38
  emptyPublicKeyMap,
39
39
  emptyRoleMap,
40
+ ensureGetFactTypeId,
40
41
  FactMap,
41
42
  FactTypeMap,
42
43
  getFactId,
@@ -48,7 +49,7 @@ import {
48
49
  mergeFactTypes,
49
50
  mergeRoleMaps,
50
51
  PublicKeyMap,
51
- RoleMap,
52
+ RoleMap
52
53
  } from "./maps";
53
54
  import { ResultSetTree, resultSqlFromSpecification, SqlQueryTree } from "./specification-result-sql";
54
55
  import { sqlFromFeed } from "./specification-sql";
@@ -167,7 +168,7 @@ export class PostgresStore implements Storage {
167
168
  } = await this.connectionFactory.withTransaction(async (connection) => {
168
169
  const factTypes = await storeFactTypes(facts, this.factTypeMap, connection);
169
170
  const existingFacts = await findExistingFacts(facts, factTypes, connection);
170
- const newFacts = facts.filter(f => !hasFact(existingFacts, f.hash, factTypes.get(f.type)));
171
+ const newFacts = facts.filter(f => !hasFact(existingFacts, f.hash, ensureGetFactTypeId(factTypes, f.type)));
171
172
  if (newFacts.length === 0) {
172
173
  return {
173
174
  newEnvelopes: [],
@@ -235,6 +236,11 @@ export class PostgresStore implements Storage {
235
236
  const factTypes = await this.loadFactTypesFromSpecification(specification);
236
237
  const roleMap = await this.loadRolesFromSpecification(specification, factTypes);
237
238
 
239
+ // If any of the start facts are not known types, the specification cannot be satisfied.
240
+ if (start.filter(f => getFactTypeId(factTypes, f.type) === undefined).length > 0) {
241
+ return [];
242
+ }
243
+
238
244
  const composer = resultSqlFromSpecification(start, specification, factTypes, roleMap);
239
245
  if (composer === null) {
240
246
  return [];
@@ -317,7 +323,7 @@ export class PostgresStore implements Storage {
317
323
  private async loadRolesFromSteps(steps: Step[], factTypes: FactTypeMap, initialType: string) {
318
324
  const roleMap = this.roleMap;
319
325
  const unknownRoles = allRoles(steps, factTypes, getFactTypeId(factTypes, initialType))
320
- .filter(r => !hasRole(roleMap, r.defining_fact_type_id, r.role));
326
+ .filter(r => !hasRole(roleMap, r.successor_type_id, r.role));
321
327
  if (unknownRoles.length > 0) {
322
328
  const loadedRoles = await this.connectionFactory.with(async (connection) => {
323
329
  return await loadRoles(unknownRoles, roleMap, connection);
@@ -332,11 +338,12 @@ export class PostgresStore implements Storage {
332
338
  async loadRolesFromSpecification(specification: Specification, factTypes: FactTypeMap): Promise<RoleMap> {
333
339
  const roleMap = this.roleMap;
334
340
  const unknownRoles = getAllRoles(specification)
341
+ .filter(r => getFactTypeId(factTypes, r.successorType))
335
342
  .map(r => ({
336
- defining_fact_type_id: getFactTypeId(factTypes, r.definingFactType),
343
+ successor_type_id: ensureGetFactTypeId(factTypes, r.successorType),
337
344
  role: r.name
338
345
  }))
339
- .filter(r => !hasRole(roleMap, r.defining_fact_type_id, r.role));
346
+ .filter(r => !hasRole(roleMap, r.successor_type_id, r.role));
340
347
  if (unknownRoles.length > 0) {
341
348
  const loadedRoles = await this.connectionFactory.with(async (connection) => {
342
349
  return await loadRoles(unknownRoles, roleMap, connection);
@@ -352,10 +359,10 @@ export class PostgresStore implements Storage {
352
359
  const roleMap = this.roleMap;
353
360
  const unknownRoles = getAllRolesFromFeed(feed)
354
361
  .map(r => ({
355
- defining_fact_type_id: getFactTypeId(factTypes, r.definingFactType),
362
+ successor_type_id: ensureGetFactTypeId(factTypes, r.successorType),
356
363
  role: r.name
357
364
  }))
358
- .filter(r => !hasRole(roleMap, r.defining_fact_type_id, r.role));
365
+ .filter(r => !hasRole(roleMap, r.successor_type_id, r.role));
359
366
  if (unknownRoles.length > 0) {
360
367
  const loadedRoles = await this.connectionFactory.with(async (connection) => {
361
368
  return await loadRoles(unknownRoles, roleMap, connection);
@@ -535,21 +542,21 @@ async function loadFactTypes(factTypeNames: string[], connection: PoolClient) {
535
542
  async function storeRoles(facts: FactRecord[], factTypes: FactTypeMap, roleMap: RoleMap, connection: PoolClient) {
536
543
  // Find distinct roles
537
544
  const roles = flatten(facts, fact => {
538
- const defining_fact_type_id = factTypes.get(fact.type);
545
+ const successor_type_id = ensureGetFactTypeId(factTypes, fact.type);
539
546
  return Object.keys(fact.predecessors).map(role => ({
540
547
  role,
541
- defining_fact_type_id
548
+ successor_type_id
542
549
  }));
543
550
  }).filter((role, index, array) => array.findIndex(r =>
544
551
  r.role === role.role &&
545
- r.defining_fact_type_id === role.defining_fact_type_id
552
+ r.successor_type_id === role.successor_type_id
546
553
  ) === index);
547
554
 
548
555
  if (roles.length > 0) {
549
556
  // Look up existing roles
550
557
  const roleIds = await loadRoles(roles, roleMap, connection);
551
558
  const remainingRoles = roles.filter(role => !hasRole(
552
- roleIds, role.defining_fact_type_id, role.role));
559
+ roleIds, role.successor_type_id, role.role));
553
560
  if (remainingRoles.length === 0) {
554
561
  return roleIds;
555
562
  }
@@ -562,7 +569,7 @@ async function storeRoles(facts: FactRecord[], factTypes: FactTypeMap, roleMap:
562
569
  ' RETURNING role_id, name, defining_fact_type_id;';
563
570
  const remainingRoleParameters = flatten(remainingRoles, (role) => [
564
571
  role.role,
565
- role.defining_fact_type_id
572
+ role.successor_type_id
566
573
  ]);
567
574
  const { rows: newRows }: RoleResult = await connection.query(insertSql, remainingRoleParameters);
568
575
  if (newRows.length !== remainingRoles.length) {
@@ -579,12 +586,12 @@ async function storeRoles(facts: FactRecord[], factTypes: FactTypeMap, roleMap:
579
586
  }
580
587
  }
581
588
 
582
- async function loadRoles(roles: { role: string; defining_fact_type_id: number; }[], roleMap: RoleMap, connection: PoolClient) {
589
+ async function loadRoles(roles: { role: string; successor_type_id: number; }[], roleMap: RoleMap, connection: PoolClient) {
583
590
  const roleValues = roles.map((role, index) =>
584
591
  `($${index * 2 + 1}, $${index * 2 + 2}::integer)`);
585
592
  const roleParameters = flatten(roles, (role) => [
586
593
  role.role,
587
- role.defining_fact_type_id
594
+ role.successor_type_id
588
595
  ]);
589
596
 
590
597
  const lookUpSql = 'SELECT role.name, role.defining_fact_type_id, role.role_id' +
@@ -646,7 +653,7 @@ async function insertFactsEdgesAndAncestors(facts: FactRecord[], factTypes: Fact
646
653
 
647
654
  function sqlInsertFacts(facts: FactRecord[], roles: RoleMap, factTypes: FactTypeMap) {
648
655
  const factValues = facts.map((f, i) => `(\$${i * 3 + 1}, \$${i * 3 + 2}::integer, \$${i * 3 + 3}::jsonb)`);
649
- const factParameters = flatten(facts, (f) => [f.hash, factTypes.get(f.type), {
656
+ const factParameters = flatten(facts, (f) => [f.hash, ensureGetFactTypeId(factTypes, f.type), {
650
657
  fields: f.fields,
651
658
  predecessors: canonicalPredecessors(f.predecessors)
652
659
  }]);
@@ -669,8 +676,8 @@ function sqlInsertFactsEdgesAndAncestors(facts: FactRecord[], factParameters: (s
669
676
  let parameterOffset = factParameters.length;
670
677
  const edgeValues = edgeRecords.map((e, i) => `(\$${i * 5 + 1 + parameterOffset}, \$${i * 5 + 2 + parameterOffset}::integer, \$${i * 5 + 3 + parameterOffset}, \$${i * 5 + 4 + parameterOffset}::integer, \$${i * 5 + 5 + parameterOffset}::integer)`);
671
678
  const edgeParameters = flatten(edgeRecords, (e) => {
672
- const successor_fact_type_id = getFactTypeId(factTypes, e.successor_type);
673
- const predecessor_fact_type_id = getFactTypeId(factTypes, e.predecessor_type);
679
+ const successor_fact_type_id = ensureGetFactTypeId(factTypes, e.successor_type);
680
+ const predecessor_fact_type_id = ensureGetFactTypeId(factTypes, e.predecessor_type);
674
681
  return [
675
682
  e.successor_hash,
676
683
  successor_fact_type_id,
@@ -833,7 +840,7 @@ async function storePublicKeys(envelopes: FactEnvelope[], connection: PoolClient
833
840
 
834
841
  async function insertSignatures(envelopes: FactEnvelope[], allFacts: FactMap, factTypes: FactTypeMap, publicKey: PublicKeyMap, connection: PoolClient) {
835
842
  const signatureRecords = flatten(envelopes, envelope => envelope.signatures.map(signature => ({
836
- factId: getFactId(allFacts, envelope.fact.hash, getFactTypeId(factTypes, envelope.fact.type)),
843
+ factId: getFactId(allFacts, envelope.fact.hash, ensureGetFactTypeId(factTypes, envelope.fact.type)),
837
844
  publicKeyId: getPublicKeyId(publicKey, signature.publicKey),
838
845
  signature: signature.signature
839
846
  })));
@@ -862,35 +869,35 @@ function allFactTypes(steps: Step[]): string[] {
862
869
  return [...factTypes, ...childFactTypes].filter(distinct);
863
870
  }
864
871
 
865
- function allRoles(steps: Step[], factTypes: FactTypeMap, initialTypeId: number) {
866
- let roles: { defining_fact_type_id: number; role: string }[] = [];
867
- let defining_fact_type_id = initialTypeId;
872
+ function allRoles(steps: Step[], factTypes: FactTypeMap, initialTypeId: number | undefined) {
873
+ let roles: { successor_type_id: number; role: string }[] = [];
874
+ let successor_type_id = initialTypeId;
868
875
  let role: string | undefined = undefined;
869
876
 
870
877
  for (const step of steps) {
871
878
  if (step instanceof PropertyCondition) {
872
879
  if (step.name === 'type') {
873
- defining_fact_type_id = getFactTypeId(factTypes, step.value);
874
- if (defining_fact_type_id && role) {
875
- roles.push({ defining_fact_type_id, role });
880
+ successor_type_id = getFactTypeId(factTypes, step.value);
881
+ if (successor_type_id && role) {
882
+ roles.push({ successor_type_id: successor_type_id, role });
876
883
  }
877
884
  role = undefined;
878
885
  }
879
886
  }
880
887
  else if (step instanceof Join) {
881
888
  if (step.direction === Direction.Predecessor) {
882
- if (defining_fact_type_id) {
883
- roles.push({ defining_fact_type_id, role: step.role });
889
+ if (successor_type_id) {
890
+ roles.push({ successor_type_id: successor_type_id, role: step.role });
884
891
  }
885
892
  role = undefined;
886
893
  }
887
894
  else {
888
895
  role = step.role;
889
896
  }
890
- defining_fact_type_id = undefined;
897
+ successor_type_id = undefined;
891
898
  }
892
899
  else if (step instanceof ExistentialCondition) {
893
- roles = roles.concat(allRoles(step.steps, factTypes, defining_fact_type_id));
900
+ roles = roles.concat(allRoles(step.steps, factTypes, successor_type_id));
894
901
  }
895
902
  }
896
903
 
@@ -1,17 +1,17 @@
1
1
  import {
2
- ChildProjections,
3
- ElementProjection,
2
+ ComponentProjection,
4
3
  FactReference,
4
+ FieldProjection,
5
5
  Label,
6
6
  Match,
7
7
  PathCondition,
8
- ResultProjection,
8
+ Projection,
9
9
  SingularProjection,
10
10
  Specification,
11
- SpecificationProjection,
11
+ SpecificationProjection
12
12
  } from "jinaga";
13
13
 
14
- import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
14
+ import { ensureGetFactTypeId, FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
15
15
 
16
16
  interface SpecificationLabel {
17
17
  name: string;
@@ -222,7 +222,7 @@ class QueryDescription {
222
222
  const firstEdge = this.edges[0];
223
223
  const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
224
224
  const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
225
- const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact.factIndex;
225
+ const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact!.factIndex;
226
226
  const writtenFactIndexes = new Set<number>().add(firstFactIndex);
227
227
  const joins: string[] = generateJoins(this.edges, writtenFactIndexes);
228
228
  const inputWhereClauses = this.inputs
@@ -410,13 +410,13 @@ function generateExistentialWhereClause(existentialCondition: ExistentialConditi
410
410
  return `SELECT 1 FROM public.edge e${firstEdge.edgeIndex}${joins.join("")} WHERE ${whereClause.join(" AND ")}${inputWhereClauses}${existentialWhereClauses}`;
411
411
  }
412
412
 
413
- type FactByIdentifier = {
414
- [identifier: string]: FactDescription;
413
+ type FactByLabel = {
414
+ [label: string]: FactDescription;
415
415
  };
416
416
 
417
417
  interface ResultDescription {
418
418
  queryDescription: QueryDescription;
419
- resultProjection: ResultProjection;
419
+ resultProjection: Projection;
420
420
  childResultDescriptions: NamedResultDescription[];
421
421
  }
422
422
 
@@ -455,7 +455,7 @@ interface NamedResultSetTree extends ResultSetTree {
455
455
  export class ResultComposer {
456
456
  constructor(
457
457
  private readonly sqlQuery: SpecificationSqlQuery,
458
- private readonly resultProjection: ResultProjection,
458
+ private readonly resultProjection: Projection,
459
459
  private readonly parentFactIdLength: number,
460
460
  private readonly childResultComposers: NamedResultComposer[]
461
461
  ) { }
@@ -555,36 +555,41 @@ export class ResultComposer {
555
555
  }
556
556
 
557
557
  private projectionOf(row: any): any {
558
- if (!Array.isArray(this.resultProjection)) {
559
- return this.singularValue(this.resultProjection, row);
558
+ if (this.resultProjection.type === "field") {
559
+ return this.fieldValue(this.resultProjection, row);
560
560
  }
561
- else if (this.resultProjection.length === 0 && this.childResultComposers.length === 0) {
562
- return this.sqlQuery.labels
563
- .slice(this.parentFactIdLength)
564
- .reduce((acc, label) => ({
561
+ else if (this.resultProjection.type === "composite") {
562
+ if (
563
+ this.resultProjection.components.length === 0 &&
564
+ this.childResultComposers.length === 0) {
565
+ return this.sqlQuery.labels
566
+ .slice(this.parentFactIdLength)
567
+ .reduce((acc, label) => ({
568
+ ...acc,
569
+ [label.name]: row[`data${label.index}`].fields
570
+ }), {})
571
+ }
572
+ else {
573
+ return this.resultProjection.components.reduce((acc, component) => ({
565
574
  ...acc,
566
- [label.name]: row[`data${label.index}`].fields
567
- }), {})
568
- }
569
- else {
570
- return this.resultProjection.reduce((acc, elementProjection) => ({
571
- ...acc,
572
- [elementProjection.name]: this.elementValue(elementProjection, row)
573
- }), {});
575
+ [component.name]: this.elementValue(component, row)
576
+ }), {});
577
+ }
574
578
  }
575
579
  }
576
580
 
577
- private elementValue(projection: ElementProjection, row: any): any {
578
- const label = this.getLabel(projection.label);
581
+ private elementValue(projection: ComponentProjection, row: any): any {
579
582
  if (projection.type === "field") {
583
+ const label = this.getLabel(projection.label);
580
584
  return row[`data${label.index}`].fields[projection.field];
581
585
  }
582
586
  else if (projection.type === "hash") {
587
+ const label = this.getLabel(projection.label);
583
588
  return row[`hash${label.index}`];
584
589
  }
585
590
  }
586
591
 
587
- private singularValue(projection: SingularProjection, row: any): any {
592
+ private fieldValue(projection: FieldProjection, row: any): any {
588
593
  const label = this.getLabel(projection.label);
589
594
  return row[`data${label.index}`].fields[projection.field];
590
595
  }
@@ -626,27 +631,30 @@ class ResultDescriptionBuilder {
626
631
  // The DescriptionBuilder will branch at various points, and
627
632
  // build on the current query description along each branch.
628
633
  const initialQueryDescription = new QueryDescription([], [], [], [], [], []);
629
- return this.createResultDescription(initialQueryDescription, specification.given, start, specification.matches, specification.childProjections, {}, []);
634
+ return this.createResultDescription(initialQueryDescription, specification.given, start, specification.matches, specification.projection, {}, []);
630
635
  }
631
636
 
632
- private createResultDescription(queryDescription: QueryDescription, given: Label[], start: FactReference[], matches: Match[], childProjections: ChildProjections, knownFacts: FactByIdentifier, path: number[]): ResultDescription {
637
+ private createResultDescription(queryDescription: QueryDescription, given: Label[], start: FactReference[], matches: Match[], projection: Projection, knownFacts: FactByLabel, path: number[]): ResultDescription {
633
638
  ({ queryDescription, knownFacts } = this.addEdges(queryDescription, given, start, knownFacts, path, matches));
634
639
  if (!queryDescription.isSatisfiable()) {
635
640
  // Abort the branch if the query is not satisfiable
636
641
  return {
637
642
  queryDescription,
638
- resultProjection: [],
643
+ resultProjection: {
644
+ type: "composite",
645
+ components: []
646
+ },
639
647
  childResultDescriptions: []
640
648
  }
641
649
  }
642
650
  const childResultDescriptions: NamedResultDescription[] = [];
643
- if (Array.isArray(childProjections)) {
644
- const specificationProjections = childProjections
645
- .filter(projection => projection.type === "specification") as SpecificationProjection[];
646
- const elementProjections = childProjections
647
- .filter(projection => projection.type === "field" || projection.type === "hash") as ElementProjection[];
651
+ if (projection.type === "composite") {
652
+ const specificationProjections = projection.components
653
+ .filter(projection => projection.type === "specification") as ({ name: string } & SpecificationProjection)[];
654
+ const singularProjections = projection.components
655
+ .filter(projection => projection.type === "field" || projection.type === "hash") as ({ name: string } & SingularProjection)[];
648
656
  for (const child of specificationProjections) {
649
- const childResultDescription = this.createResultDescription(queryDescription, given, start, child.matches, child.childProjections, knownFacts, []);
657
+ const childResultDescription = this.createResultDescription(queryDescription, given, start, child.matches, child.projection, knownFacts, []);
650
658
  childResultDescriptions.push({
651
659
  name: child.name,
652
660
  ...childResultDescription
@@ -654,20 +662,23 @@ class ResultDescriptionBuilder {
654
662
  }
655
663
  return {
656
664
  queryDescription,
657
- resultProjection: elementProjections,
665
+ resultProjection: {
666
+ type: "composite",
667
+ components: singularProjections
668
+ },
658
669
  childResultDescriptions
659
670
  };
660
671
  }
661
672
  else {
662
673
  return {
663
674
  queryDescription,
664
- resultProjection: childProjections,
675
+ resultProjection: projection,
665
676
  childResultDescriptions: []
666
677
  }
667
678
  }
668
679
  }
669
680
 
670
- private addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByIdentifier, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByIdentifier } {
681
+ private addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
671
682
  for (const match of matches) {
672
683
  for (const condition of match.conditions) {
673
684
  if (condition.type === "path") {
@@ -701,7 +712,7 @@ class ResultDescriptionBuilder {
701
712
  };
702
713
  }
703
714
 
704
- private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByIdentifier, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByIdentifier } {
715
+ private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
705
716
  // If no input parameter has been allocated, allocate one now.
706
717
  if (!knownFacts.hasOwnProperty(condition.labelRight)) {
707
718
  const givenIndex = given.findIndex(given => given.name === condition.labelRight);
@@ -710,7 +721,7 @@ class ResultDescriptionBuilder {
710
721
  }
711
722
  const { queryDescription: newQueryDescription, factDescription } = queryDescription.withInputParameter(
712
723
  given[givenIndex],
713
- getFactTypeId(this.factTypes, start[givenIndex].type),
724
+ ensureGetFactTypeId(this.factTypes, start[givenIndex].type),
714
725
  start[givenIndex].hash,
715
726
  path
716
727
  );
@@ -753,11 +764,11 @@ class ResultDescriptionBuilder {
753
764
  }
754
765
  else {
755
766
  // If we have not written the fact, we need to write it now.
756
- const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.targetType);
767
+ const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.predecessorType);
757
768
  queryDescription = query.withEdge(predecessorFactIndex, factIndex, roleParameter, path);
758
769
  factIndex = predecessorFactIndex;
759
770
  }
760
- type = role.targetType;
771
+ type = role.predecessorType;
761
772
  }
762
773
 
763
774
  const rightType = type;
@@ -785,7 +796,7 @@ class ResultDescriptionBuilder {
785
796
  roleId,
786
797
  declaringType: type
787
798
  });
788
- type = role.targetType;
799
+ type = role.predecessorType;
789
800
  }
790
801
 
791
802
  if (type !== rightType) {
@@ -823,7 +834,7 @@ function idsEqual(a: number[], b: number[]) {
823
834
  return a.every((value, index) => value === b[index]);
824
835
  }
825
836
 
826
- export function resultSqlFromSpecification(start: FactReference[], specification: Specification, factTypes: FactTypeMap, roleMap: RoleMap): ResultComposer {
837
+ export function resultSqlFromSpecification(start: FactReference[], specification: Specification, factTypes: FactTypeMap, roleMap: RoleMap): ResultComposer | null {
827
838
  const descriptionBuilder = new ResultDescriptionBuilder(factTypes, roleMap);
828
839
  const description = descriptionBuilder.buildDescription(start, specification);
829
840
 
@@ -63,7 +63,7 @@ class QueryDescription {
63
63
  const firstEdge = this.edges[0];
64
64
  const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
65
65
  const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
66
- const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact.factIndex;
66
+ const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact!.factIndex;
67
67
  const writtenFactIndexes = new Set<number>().add(firstFactIndex);
68
68
  const joins: string[] = generateJoins(this.edges, writtenFactIndexes);
69
69
  const inputWhereClauses = this.inputs
@@ -1,6 +1,6 @@
1
1
  import { Direction, ExistentialCondition, FactReference, Join, PropertyCondition, Quantifier, Step } from "jinaga";
2
2
 
3
- import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
3
+ import { ensureGetFactTypeId, FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
4
4
 
5
5
  export type SqlQuery = {
6
6
  empty: boolean,
@@ -10,7 +10,7 @@ export type SqlQuery = {
10
10
  factTypeNames: string[]
11
11
  };
12
12
 
13
- export function sqlFromSteps(start: FactReference, steps: Step[], factTypes: FactTypeMap, roleMap: RoleMap) : SqlQuery {
13
+ export function sqlFromSteps(start: FactReference, steps: Step[], factTypes: FactTypeMap, roleMap: RoleMap) : SqlQuery | null {
14
14
  const builder = new QueryBuilder(factTypes, roleMap);
15
15
 
16
16
  return builder.buildQuery(start, steps);
@@ -90,18 +90,18 @@ class QueryBuilder {
90
90
  constructor(private factTypes: FactTypeMap, private roleMap: RoleMap) {
91
91
  }
92
92
 
93
- buildQuery(start: FactReference, steps: Step[]): SqlQuery {
93
+ buildQuery(start: FactReference, steps: Step[]): SqlQuery | null {
94
94
  if (steps.length === 0) {
95
95
  return null;
96
96
  }
97
97
 
98
- const startTypeId = getFactTypeId(this.factTypes, start.type);
98
+ const startTypeId = ensureGetFactTypeId(this.factTypes, start.type);
99
99
  const startState: QueryBuilderState = {
100
100
  state: 'start',
101
101
  typeId: startTypeId,
102
102
  typeName: start.type
103
103
  };
104
- const finalState = steps.reduce((state, step) => {
104
+ const finalState = steps.reduce((state: QueryBuilderState, step) => {
105
105
  return this.matchStep(state, step);
106
106
  }, startState);
107
107
  this.end(finalState);
@@ -134,6 +134,14 @@ class QueryBuilder {
134
134
  }
135
135
 
136
136
  private buildJoins(joins: QueryJoin[], priorFactId: string) {
137
+ interface Joins {
138
+ priorFactId: string,
139
+ clauses: string[]
140
+ };
141
+ const initialJoins: Joins = {
142
+ priorFactId,
143
+ clauses: []
144
+ };
137
145
  const clauses = joins.reduce((joins, join) => {
138
146
  if (join.table === 'edge') {
139
147
  if (join.direction === 'successor') {
@@ -161,10 +169,7 @@ class QueryBuilder {
161
169
  clauses: [...joins.clauses, clause]
162
170
  };
163
171
  }
164
- }, {
165
- priorFactId,
166
- clauses: []
167
- }).clauses;
172
+ }, initialJoins).clauses;
168
173
  return clauses.join('');
169
174
  }
170
175
 
@@ -209,7 +214,7 @@ class QueryBuilder {
209
214
  typeId: typeId,
210
215
  typeName: typeName
211
216
  };
212
- const finalState = steps.reduce((state, step) => {
217
+ const finalState = steps.reduce((state: QueryBuilderState, step) => {
213
218
  return this.matchStep(state, step);
214
219
  }, startState);
215
220
  this.end(finalState);
@@ -270,12 +275,15 @@ class QueryBuilder {
270
275
  position: `${state.typeName}.${step.role}`
271
276
  }
272
277
  }
273
- if (step.direction === Direction.Successor) {
278
+ else if (step.direction === Direction.Successor) {
274
279
  return {
275
280
  state: 'successor-join',
276
281
  role: step.role
277
282
  }
278
283
  }
284
+ else {
285
+ throw new Error(`Unknown direction ${step.direction}`);
286
+ }
279
287
  }
280
288
  else if (step instanceof ExistentialCondition) {
281
289
  return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
@@ -311,13 +319,16 @@ class QueryBuilder {
311
319
  position: `${state.typeName}.${step.role}`
312
320
  }
313
321
  }
314
- if (step.direction === Direction.Successor) {
322
+ else if (step.direction === Direction.Successor) {
315
323
  this.emitFact(state.typeName);
316
324
  return {
317
325
  state: 'successor-join',
318
326
  role: step.role
319
327
  }
320
328
  }
329
+ else {
330
+ throw new Error(`Unknown direction ${step.direction}`);
331
+ }
321
332
  }
322
333
  else if (step instanceof ExistentialCondition) {
323
334
  return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
File without changes
@@ -27,7 +27,7 @@ function sqlFor(descriptiveString: string, bookmarks: string[] = []) {
27
27
  // Filter out the roles named "unknown", and those of unknown fact types.
28
28
  let roleMap = getAllRoles(specification).filter(r => r.name !== 'unknown').reduce(
29
29
  (r, role, i) => {
30
- const factTypeId = getFactTypeId(factTypes, role.definingFactType);
30
+ const factTypeId = getFactTypeId(factTypes, role.successorType);
31
31
  if (!factTypeId) {
32
32
  return r;
33
33
  }
@@ -44,6 +44,9 @@ function sqlFor(descriptiveString: string, bookmarks: string[] = []) {
44
44
  throw new Error(`Unknown input type ${input.type}`);
45
45
  });
46
46
  const composer = resultSqlFromSpecification(start, specification, factTypes, roleMap);
47
+ if (!composer) {
48
+ throw new Error("The specification is not satisfiable.");
49
+ }
47
50
  return { composer, factTypes, roleMap };
48
51
  }
49
52